feat(FN-4912): complete Step 6 — wire env writes into acquisition

Fusion-Task-Id: FN-4912
Fusion-Task-Lineage: 943d0651-052a-41b5-8069-4c60f4db1ba7
This commit is contained in:
Fusion (runfusion.ai)
2026-05-17 22:49:06 -07:00
committed by gsxdsm
parent a8615ecf6c
commit c8d01ca80d
3 changed files with 38 additions and 3 deletions

View File

@@ -111,6 +111,7 @@ export interface HeartbeatMonitorOptions {
reflectionService?: AgentReflectionService;
/** Optional self-improvement service for periodic self-improve injection */
selfImproveService?: SelfImproveServiceLike;
secretsStore?: Pick<import("@fusion/core").SecretsStore, "listEnvExportable">;
}
/** Options for waking up an agent */
@@ -2397,6 +2398,7 @@ export class HeartbeatMonitor {
audit,
runContext,
runInitCommand: false,
secretsStore: this.options.secretsStore,
});
sessionCwd = acquisition.worktreePath;
} catch (worktreeErr) {

View File

@@ -902,6 +902,7 @@ export interface TaskExecutorOptions {
/** MessageStore for sending messages to other agents. When provided, executor agents gain fn_send_message capability. */
messageStore?: import("@fusion/core").MessageStore;
missionStore?: MissionStore;
secretsStore?: Pick<import("@fusion/core").SecretsStore, "listEnvExportable">;
onSliceComplete?: (slice: Slice) => void;
onStart?: (task: Task, worktreePath: string) => void;
onComplete?: (task: Task) => void;
@@ -2860,6 +2861,7 @@ export class TaskExecutor {
createWorktree: this.createWorktree.bind(this),
runConfiguredCommand,
taskEnv,
secretsStore: this.options.secretsStore,
});
worktreePath = acquisition.worktreePath;

View File

@@ -1,7 +1,7 @@
import { existsSync } from "node:fs";
import { exec } from "node:child_process";
import { promisify } from "node:util";
import type { RunMutationContext, Settings, Task, TaskStore } from "@fusion/core";
import type { RunMutationContext, Settings, Task, TaskStore, SecretsStore } from "@fusion/core";
import { generateWorktreeName, slugify } from "./worktree-names.js";
import { resolveTaskWorktreePathForBackend } from "./worktree-paths.js";
import { hydrateWorktreeDb } from "./worktree-db-hydrate.js";
@@ -32,6 +32,7 @@ import {
type WorktrunkOpName,
} from "./worktrunk-failure-handler.js";
import type { RunAuditor } from "./run-audit.js";
import { writeSecretsEnvFile } from "./secrets-env-writer.js";
const execAsync = promisify(exec);
@@ -48,9 +49,10 @@ export interface AcquireTaskWorktreeOptions {
settings: Partial<Settings>;
pool?: WorktreePool;
logger?: { log: (m: string) => void; warn: (m: string) => void; error?: (m: string) => void };
audit?: RunAuditor;
audit?: Pick<RunAuditor, "git" | "filesystem">;
runContext?: RunMutationContext;
runInitCommand?: boolean;
secretsStore?: Pick<SecretsStore, "listEnvExportable">;
createWorktree?: (
branch: string,
path: string,
@@ -153,7 +155,7 @@ async function maybeWarnForeignTaskStartPoint(
}
export async function acquireTaskWorktree(opts: AcquireTaskWorktreeOptions): Promise<AcquireTaskWorktreeResult> {
const { task, rootDir, store, settings, pool, logger, audit, runContext, createWorktree, runConfiguredCommand, runInitCommand, taskEnv } = opts;
const { task, rootDir, store, settings, pool, logger, audit, runContext, createWorktree, runConfiguredCommand, runInitCommand, taskEnv, secretsStore } = opts;
const notifyFallback = async (op: WorktrunkOpName, stderr?: string) => {
await store.logEntry(task.id, `Worktrunk ${op} failed; continuing with native worktree backend (${stderr ?? "no stderr"})`, undefined, runContext);
};
@@ -251,6 +253,7 @@ export async function acquireTaskWorktree(opts: AcquireTaskWorktreeOptions): Pro
if (task.worktree && isResume) {
logger?.log(`Reusing existing worktree: ${worktreePath}`);
const hydrated = await hydrate(worktreePath);
// FN-4912: resume path reuses the prior on-disk .env (and its fingerprint sidecar). Rewrite is owned by the next fresh acquisition.
return { worktreePath, branch: task.branch ?? branchName, source: "existing", hydrated, isResume: true };
}
@@ -338,6 +341,20 @@ export async function acquireTaskWorktree(opts: AcquireTaskWorktreeOptions): Pro
runContext,
});
const hydrated = await hydrate(worktreePath);
try {
await writeSecretsEnvFile({
rootDir,
worktreePath,
taskId: task.id,
settings,
worktreeSource: "pool",
secretsStore,
audit,
logger,
});
} catch (err) {
logger?.warn?.(`${task.id}: secrets-env write failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
}
return {
worktreePath,
branch,
@@ -449,5 +466,19 @@ export async function acquireTaskWorktree(opts: AcquireTaskWorktreeOptions): Pro
runContext,
});
const hydrated = await hydrate(worktreePath);
try {
await writeSecretsEnvFile({
rootDir,
worktreePath,
taskId: task.id,
settings,
worktreeSource: "fresh",
secretsStore,
audit,
logger,
});
} catch (err) {
logger?.warn?.(`${task.id}: secrets-env write failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
}
return { worktreePath, branch, source: acquiredFromPool ? "pool" : "fresh", hydrated, isResume: false };
}