@agentmeshhq/agent 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/__tests__/orphan-process.test.d.ts +11 -0
- package/dist/__tests__/orphan-process.test.js +286 -0
- package/dist/__tests__/orphan-process.test.js.map +1 -0
- package/dist/__tests__/runner.test.js +16 -0
- package/dist/__tests__/runner.test.js.map +1 -1
- package/dist/__tests__/watchdog.test.js +138 -12
- package/dist/__tests__/watchdog.test.js.map +1 -1
- package/dist/cli/index.js +0 -0
- package/dist/cli/status.js +11 -0
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/stop.js +7 -2
- package/dist/cli/stop.js.map +1 -1
- package/dist/config/schema.d.ts +4 -2
- package/dist/core/daemon/assignment-message.d.ts +12 -0
- package/dist/core/daemon/assignment-message.js +36 -0
- package/dist/core/daemon/assignment-message.js.map +1 -0
- package/dist/core/daemon/bootstrap.d.ts +35 -0
- package/dist/core/daemon/bootstrap.js +52 -0
- package/dist/core/daemon/bootstrap.js.map +1 -0
- package/dist/core/daemon/crash-log.d.ts +16 -0
- package/dist/core/daemon/crash-log.js +24 -0
- package/dist/core/daemon/crash-log.js.map +1 -0
- package/dist/core/daemon/health-policy.d.ts +21 -0
- package/dist/core/daemon/health-policy.js +32 -0
- package/dist/core/daemon/health-policy.js.map +1 -0
- package/dist/core/daemon/sandbox-config.d.ts +9 -0
- package/dist/core/daemon/sandbox-config.js +17 -0
- package/dist/core/daemon/sandbox-config.js.map +1 -0
- package/dist/core/daemon/state.d.ts +33 -0
- package/dist/core/daemon/state.js +77 -0
- package/dist/core/daemon/state.js.map +1 -0
- package/dist/core/daemon/tmux-session.d.ts +17 -0
- package/dist/core/daemon/tmux-session.js +34 -0
- package/dist/core/daemon/tmux-session.js.map +1 -0
- package/dist/core/daemon/workspace.d.ts +10 -0
- package/dist/core/daemon/workspace.js +51 -0
- package/dist/core/daemon/workspace.js.map +1 -0
- package/dist/core/daemon.d.ts +0 -6
- package/dist/core/daemon.js +123 -244
- package/dist/core/daemon.js.map +1 -1
- package/dist/core/injector.js +6 -0
- package/dist/core/injector.js.map +1 -1
- package/dist/core/runner/build.d.ts +9 -0
- package/dist/core/runner/build.js +53 -0
- package/dist/core/runner/build.js.map +1 -0
- package/dist/core/runner/detect.d.ts +5 -0
- package/dist/core/runner/detect.js +14 -0
- package/dist/core/runner/detect.js.map +1 -0
- package/dist/core/runner/index.d.ts +5 -0
- package/dist/core/runner/index.js +5 -0
- package/dist/core/runner/index.js.map +1 -0
- package/dist/core/runner/model.d.ts +5 -0
- package/dist/core/runner/model.js +7 -0
- package/dist/core/runner/model.js.map +1 -0
- package/dist/core/runner/opencode-models.d.ts +15 -0
- package/dist/core/runner/opencode-models.js +70 -0
- package/dist/core/runner/opencode-models.js.map +1 -0
- package/dist/core/runner/types.d.ts +19 -0
- package/dist/core/runner/types.js +8 -0
- package/dist/core/runner/types.js.map +1 -0
- package/dist/core/runner.d.ts +5 -47
- package/dist/core/runner.js +5 -167
- package/dist/core/runner.js.map +1 -1
- package/dist/core/tmux-runtime.d.ts +13 -0
- package/dist/core/tmux-runtime.js +72 -0
- package/dist/core/tmux-runtime.js.map +1 -0
- package/dist/core/tmux.d.ts +7 -1
- package/dist/core/tmux.js +75 -45
- package/dist/core/tmux.js.map +1 -1
- package/dist/core/watchdog.d.ts +18 -1
- package/dist/core/watchdog.js +78 -29
- package/dist/core/watchdog.js.map +1 -1
- package/package.json +30 -11
- package/dist/cli/inbox.d.ts +0 -5
- package/dist/cli/inbox.js +0 -123
- package/dist/cli/inbox.js.map +0 -1
- package/dist/cli/issue.d.ts +0 -42
- package/dist/cli/issue.js +0 -297
- package/dist/cli/issue.js.map +0 -1
- package/dist/cli/ready.d.ts +0 -5
- package/dist/cli/ready.js +0 -131
- package/dist/cli/ready.js.map +0 -1
- package/dist/cli/sync.d.ts +0 -8
- package/dist/cli/sync.js +0 -154
- package/dist/cli/sync.js.map +0 -1
- package/dist/core/issue-cache.d.ts +0 -44
- package/dist/core/issue-cache.js +0 -75
- package/dist/core/issue-cache.js.map +0 -1
- package/src/__tests__/context.test.ts +0 -464
- package/src/__tests__/injector.test.ts +0 -29
- package/src/__tests__/jwt.test.ts +0 -112
- package/src/__tests__/loader.test.ts +0 -239
- package/src/__tests__/runner.test.ts +0 -104
- package/src/__tests__/sandbox.test.ts +0 -435
- package/src/__tests__/watchdog.test.ts +0 -368
- package/src/cli/attach.ts +0 -22
- package/src/cli/build.ts +0 -145
- package/src/cli/config.ts +0 -148
- package/src/cli/context.ts +0 -231
- package/src/cli/deploy.ts +0 -155
- package/src/cli/index.ts +0 -376
- package/src/cli/init.ts +0 -75
- package/src/cli/list.ts +0 -70
- package/src/cli/local.ts +0 -183
- package/src/cli/logs.ts +0 -64
- package/src/cli/migrate.ts +0 -212
- package/src/cli/nudge.ts +0 -81
- package/src/cli/restart.ts +0 -59
- package/src/cli/slack.ts +0 -70
- package/src/cli/start.ts +0 -118
- package/src/cli/status.ts +0 -91
- package/src/cli/stop.ts +0 -48
- package/src/cli/test.ts +0 -143
- package/src/cli/token.ts +0 -188
- package/src/cli/whoami.ts +0 -142
- package/src/config/loader.ts +0 -121
- package/src/config/schema.ts +0 -68
- package/src/context/handoff.ts +0 -122
- package/src/context/index.ts +0 -8
- package/src/context/schema.ts +0 -111
- package/src/context/storage.ts +0 -197
- package/src/core/daemon.ts +0 -1317
- package/src/core/heartbeat.ts +0 -129
- package/src/core/injector.ts +0 -292
- package/src/core/registry.ts +0 -159
- package/src/core/runner.ts +0 -225
- package/src/core/sandbox.ts +0 -547
- package/src/core/session-id.ts +0 -111
- package/src/core/tmux.ts +0 -405
- package/src/core/watchdog.ts +0 -238
- package/src/core/websocket.ts +0 -94
- package/src/index.ts +0 -10
- package/src/utils/jwt.ts +0 -87
- package/tsconfig.json +0 -8
- package/vitest.config.ts +0 -12
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface WorkspaceSetupInput {
|
|
2
|
+
workspacePath: string;
|
|
3
|
+
repoUrl: string;
|
|
4
|
+
defaultBranch: string;
|
|
5
|
+
projectName: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Ensures a project workspace exists and is on the target default branch.
|
|
9
|
+
*/
|
|
10
|
+
export declare function setupWorkspace(input: WorkspaceSetupInput): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Ensures a project workspace exists and is on the target default branch.
|
|
6
|
+
*/
|
|
7
|
+
export function setupWorkspace(input) {
|
|
8
|
+
const { workspacePath, repoUrl, defaultBranch, projectName } = input;
|
|
9
|
+
console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
10
|
+
console.log(`🔧 AUTO-SETUP: Setting up workspace for ${projectName}`);
|
|
11
|
+
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
|
|
12
|
+
const gitDir = path.join(workspacePath, ".git");
|
|
13
|
+
if (fs.existsSync(gitDir)) {
|
|
14
|
+
console.log(`✓ Workspace already exists: ${workspacePath}`);
|
|
15
|
+
console.log(" Updating from remote...");
|
|
16
|
+
try {
|
|
17
|
+
execSync("git fetch origin", { cwd: workspacePath, stdio: "inherit" });
|
|
18
|
+
execSync(`git checkout ${defaultBranch}`, { cwd: workspacePath, stdio: "inherit" });
|
|
19
|
+
execSync(`git pull origin ${defaultBranch}`, { cwd: workspacePath, stdio: "inherit" });
|
|
20
|
+
console.log("✓ Workspace updated successfully\n");
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.warn(`⚠ Could not update workspace: ${error.message}`);
|
|
24
|
+
console.log(" Continuing with existing state...\n");
|
|
25
|
+
}
|
|
26
|
+
return workspacePath;
|
|
27
|
+
}
|
|
28
|
+
const parentDir = path.dirname(workspacePath);
|
|
29
|
+
if (!fs.existsSync(parentDir)) {
|
|
30
|
+
console.log(`Creating directory: ${parentDir}`);
|
|
31
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
console.log("Cloning repository...");
|
|
34
|
+
console.log(` URL: ${repoUrl}`);
|
|
35
|
+
console.log(` Path: ${workspacePath}`);
|
|
36
|
+
console.log(` Branch: ${defaultBranch}\n`);
|
|
37
|
+
try {
|
|
38
|
+
execSync(`git clone --branch ${defaultBranch} "${repoUrl}" "${workspacePath}"`, {
|
|
39
|
+
stdio: "inherit",
|
|
40
|
+
});
|
|
41
|
+
console.log("\n✓ Repository cloned successfully");
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error(`\n✗ Failed to clone repository: ${error.message}`);
|
|
45
|
+
console.error("\nMake sure you have access to the repository and SSH keys are configured.");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
console.log(`✓ Workspace ready: ${workspacePath}\n`);
|
|
49
|
+
return workspacePath;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../../../src/core/daemon/workspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAS7B;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,KAA0B;IACvD,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;IAErE,OAAO,CAAC,GAAG,CAAC,oFAAoF,CAAC,CAAC;IAClG,OAAO,CAAC,GAAG,CAAC,2CAA2C,WAAW,EAAE,CAAC,CAAC;IACtE,OAAO,CAAC,GAAG,CAAC,oFAAoF,CAAC,CAAC;IAElG,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAChD,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,+BAA+B,aAAa,EAAE,CAAC,CAAC;QAC5D,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC;QAEzC,IAAI,CAAC;YACH,QAAQ,CAAC,kBAAkB,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;YACvE,QAAQ,CAAC,gBAAgB,aAAa,EAAE,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;YACpF,QAAQ,CAAC,mBAAmB,aAAa,EAAE,EAAE,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;YACvF,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,iCAAkC,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;YAC1E,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;QACvD,CAAC;QAED,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC9C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,uBAAuB,SAAS,EAAE,CAAC,CAAC;QAChD,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,WAAW,aAAa,EAAE,CAAC,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,aAAa,aAAa,IAAI,CAAC,CAAC;IAE5C,IAAI,CAAC;QACH,QAAQ,CAAC,sBAAsB,aAAa,KAAK,OAAO,MAAM,aAAa,GAAG,EAAE;YAC9E,KAAK,EAAE,SAAS;SACjB,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IACpD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,mCAAoC,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7E,OAAO,CAAC,KAAK,CAAC,4EAA4E,CAAC,CAAC;QAC5F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,sBAAsB,aAAa,IAAI,CAAC,CAAC;IACrD,OAAO,aAAa,CAAC;AACvB,CAAC"}
|
package/dist/core/daemon.d.ts
CHANGED
|
@@ -46,7 +46,6 @@ export declare class AgentDaemon {
|
|
|
46
46
|
private sandboxMemory;
|
|
47
47
|
private sandbox;
|
|
48
48
|
private healthCheckInterval;
|
|
49
|
-
private serverContext;
|
|
50
49
|
private _preStartSessionId;
|
|
51
50
|
private _attemptedResumeSessionId;
|
|
52
51
|
private restartCount;
|
|
@@ -96,11 +95,6 @@ export declare class AgentDaemon {
|
|
|
96
95
|
* Uses project.workdir from HQ as source of truth, falls back to helpful instructions
|
|
97
96
|
*/
|
|
98
97
|
private checkAssignments;
|
|
99
|
-
/**
|
|
100
|
-
* Sets up workspace by cloning repository or using existing clone
|
|
101
|
-
* Returns the absolute path to the workspace
|
|
102
|
-
*/
|
|
103
|
-
private setupWorkspace;
|
|
104
98
|
/**
|
|
105
99
|
* Ensures the sandbox OpenCode config exists
|
|
106
100
|
* Creates ~/.agentmesh/opencode-sandbox.json with permissive permissions and model
|
package/dist/core/daemon.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { getAgentState, resetAgentRestartCount, updateAgentInState } from "../config/loader.js";
|
|
6
6
|
import { loadContext, loadOrCreateContext, saveContext } from "../context/index.js";
|
|
7
|
+
import { renderMissingWorkdirMessage } from "./daemon/assignment-message.js";
|
|
8
|
+
import { bootstrapDaemon } from "./daemon/bootstrap.js";
|
|
9
|
+
import { formatCrashLog } from "./daemon/crash-log.js";
|
|
10
|
+
import { getNudgeMessage, getStuckDetail, isWithinNudgeWaitWindow, shouldResetRestartCount, } from "./daemon/health-policy.js";
|
|
11
|
+
import { writeSandboxOpencodeConfig } from "./daemon/sandbox-config.js";
|
|
12
|
+
import { captureAgentChildPids, persistRunningState } from "./daemon/state.js";
|
|
13
|
+
import { startTmuxRuntimeSession } from "./daemon/tmux-session.js";
|
|
14
|
+
import { setupWorkspace } from "./daemon/workspace.js";
|
|
7
15
|
import { Heartbeat } from "./heartbeat.js";
|
|
8
16
|
import { handleWebSocketEvent, injectRestoredContext, injectStartupMessage } from "./injector.js";
|
|
9
17
|
import { checkInbox, fetchAssignments, registerAgent } from "./registry.js";
|
|
10
|
-
import {
|
|
18
|
+
import { getRunnerDisplayName } from "./runner.js";
|
|
11
19
|
import { DockerSandbox } from "./sandbox.js";
|
|
12
20
|
import { getLatestSessionId, snapshotSessionId, waitForNewSessionId } from "./session-id.js";
|
|
13
|
-
import { captureSessionContext, captureSessionOutput, createSession, destroySession,
|
|
21
|
+
import { captureSessionContext, captureSessionOutput, createSession, destroySession, isSessionHealthy, killProcessTree, updateSessionEnvironment, } from "./tmux.js";
|
|
22
|
+
import { prepareOpenCodeRuntime } from "./tmux-runtime.js";
|
|
14
23
|
import { checkAgentProgress, cleanupOrphanContainers, isProcessRunning, sendNudge, } from "./watchdog.js";
|
|
15
24
|
import { AgentWebSocket } from "./websocket.js";
|
|
16
25
|
// Maximum number of auto-restart attempts
|
|
@@ -49,7 +58,6 @@ export class AgentDaemon {
|
|
|
49
58
|
sandboxMemory;
|
|
50
59
|
sandbox = null;
|
|
51
60
|
healthCheckInterval = null;
|
|
52
|
-
serverContext;
|
|
53
61
|
// Session resume tracking
|
|
54
62
|
_preStartSessionId;
|
|
55
63
|
_attemptedResumeSessionId;
|
|
@@ -59,51 +67,20 @@ export class AgentDaemon {
|
|
|
59
67
|
stuckSince = null;
|
|
60
68
|
nudgeSentAt = null;
|
|
61
69
|
constructor(options) {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
throw new Error("No config found. Run 'agentmesh init' first.");
|
|
65
|
-
}
|
|
66
|
-
// Ensure config has required fields with defaults
|
|
67
|
-
if (!config.agents)
|
|
68
|
-
config.agents = [];
|
|
69
|
-
if (!config.defaults)
|
|
70
|
-
config.defaults = { command: "opencode", model: "claude-sonnet-4-5-20250929" };
|
|
71
|
-
this.config = config;
|
|
70
|
+
const boot = bootstrapDaemon(options);
|
|
71
|
+
this.config = boot.config;
|
|
72
72
|
this.agentName = options.name;
|
|
73
|
-
this.shouldRestoreContext =
|
|
74
|
-
this.isWorkerAgent =
|
|
75
|
-
this.autoSetup =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
// Override with provided options
|
|
87
|
-
if (options.command)
|
|
88
|
-
agentConfig.command = options.command;
|
|
89
|
-
if (options.workdir)
|
|
90
|
-
agentConfig.workdir = options.workdir;
|
|
91
|
-
if (options.model)
|
|
92
|
-
agentConfig.model = options.model;
|
|
93
|
-
this.agentConfig = agentConfig;
|
|
94
|
-
this.serveMode = options.serve === true;
|
|
95
|
-
this.servePort = options.servePort || 3001;
|
|
96
|
-
this.sandboxMode = options.sandbox === true;
|
|
97
|
-
this.sandboxImage = options.sandboxImage || "agentmesh/agent-sandbox:latest";
|
|
98
|
-
this.sandboxCpu = options.sandboxCpu || "1";
|
|
99
|
-
this.sandboxMemory = options.sandboxMemory || "2g";
|
|
100
|
-
// Build runner configuration with model resolution
|
|
101
|
-
this.runnerConfig = buildRunnerConfig({
|
|
102
|
-
cliModel: options.model,
|
|
103
|
-
agentModel: agentConfig.model,
|
|
104
|
-
defaultModel: config.defaults.model,
|
|
105
|
-
command: agentConfig.command,
|
|
106
|
-
});
|
|
73
|
+
this.shouldRestoreContext = boot.shouldRestoreContext;
|
|
74
|
+
this.isWorkerAgent = boot.isWorkerAgent;
|
|
75
|
+
this.autoSetup = boot.autoSetup;
|
|
76
|
+
this.agentConfig = boot.agentConfig;
|
|
77
|
+
this.serveMode = boot.serveMode;
|
|
78
|
+
this.servePort = boot.servePort;
|
|
79
|
+
this.sandboxMode = boot.sandboxMode;
|
|
80
|
+
this.sandboxImage = boot.sandboxImage;
|
|
81
|
+
this.sandboxCpu = boot.sandboxCpu;
|
|
82
|
+
this.sandboxMemory = boot.sandboxMemory;
|
|
83
|
+
this.runnerConfig = boot.runnerConfig;
|
|
107
84
|
const runnerName = getRunnerDisplayName(this.runnerConfig.type);
|
|
108
85
|
console.log(`Runner: ${runnerName}`);
|
|
109
86
|
console.log(`Effective model: ${this.runnerConfig.model}`);
|
|
@@ -121,8 +98,13 @@ export class AgentDaemon {
|
|
|
121
98
|
throw new Error(`Agent "${this.agentName}" is already running (PID: ${existingState.pid}). ` +
|
|
122
99
|
`Use 'agentmesh stop ${this.agentName}' first.`);
|
|
123
100
|
}
|
|
124
|
-
// Process not running
|
|
101
|
+
// Process not running — clean up stale state and any orphaned child processes
|
|
125
102
|
console.log(`Cleaning up stale state for PID ${existingState.pid}`);
|
|
103
|
+
const orphanPids = existingState.childPids ?? [];
|
|
104
|
+
if (orphanPids.length > 0) {
|
|
105
|
+
console.log(`[STARTUP] Found ${orphanPids.length} orphaned child PIDs from previous run — killing...`);
|
|
106
|
+
killProcessTree(orphanPids);
|
|
107
|
+
}
|
|
126
108
|
}
|
|
127
109
|
// Clean up orphan containers in sandbox mode
|
|
128
110
|
if (this.sandboxMode) {
|
|
@@ -151,7 +133,6 @@ export class AgentDaemon {
|
|
|
151
133
|
if (registration.status === "re-registered") {
|
|
152
134
|
console.log(`Re-registered as: ${this.agentId}`);
|
|
153
135
|
if (registration.context && Object.keys(registration.context).length > 0) {
|
|
154
|
-
this.serverContext = registration.context;
|
|
155
136
|
console.log(`Server context restored: ${Object.keys(registration.context).join(", ")}`);
|
|
156
137
|
}
|
|
157
138
|
}
|
|
@@ -168,39 +149,16 @@ export class AgentDaemon {
|
|
|
168
149
|
await this.startServeMode();
|
|
169
150
|
}
|
|
170
151
|
else {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (savedSessionId) {
|
|
182
|
-
console.log(`[CONTEXT] Found saved OpenCode session ID: ${savedSessionId}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// Snapshot the latest session ID in logs BEFORE starting OpenCode.
|
|
186
|
-
// This lets us detect whether OpenCode actually resumed vs created a new session.
|
|
187
|
-
const preStartSessionId = snapshotSessionId(this.agentName);
|
|
188
|
-
console.log(`Creating tmux session: ${sessionName}`);
|
|
189
|
-
// Include runner env vars (e.g., OPENCODE_MODEL) at session creation
|
|
190
|
-
const created = createSession(this.agentName, this.agentConfig.command, this.agentConfig.workdir, this.runnerConfig.env, // Apply model env at process start
|
|
191
|
-
savedSessionId);
|
|
192
|
-
if (!created) {
|
|
193
|
-
throw new Error("Failed to create tmux session");
|
|
194
|
-
}
|
|
195
|
-
// Store pre-start snapshot for fallback detection later
|
|
196
|
-
this._preStartSessionId = preStartSessionId;
|
|
197
|
-
this._attemptedResumeSessionId = savedSessionId;
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
console.log(`Reconnecting to existing session: ${sessionName}`);
|
|
201
|
-
// Update environment for existing session
|
|
202
|
-
updateSessionEnvironment(this.agentName, this.runnerConfig.env);
|
|
203
|
-
}
|
|
152
|
+
const sessionStart = startTmuxRuntimeSession({
|
|
153
|
+
agentName: this.agentName,
|
|
154
|
+
agentId: this.agentId,
|
|
155
|
+
command: this.agentConfig.command,
|
|
156
|
+
workdir: this.agentConfig.workdir,
|
|
157
|
+
runnerEnv: this.runnerConfig.env,
|
|
158
|
+
shouldRestoreContext: this.shouldRestoreContext,
|
|
159
|
+
});
|
|
160
|
+
this._preStartSessionId = sessionStart.preStartSessionId;
|
|
161
|
+
this._attemptedResumeSessionId = sessionStart.attemptedResumeSessionId;
|
|
204
162
|
// Inject environment variables into tmux session
|
|
205
163
|
console.log("Injecting environment variables...");
|
|
206
164
|
updateSessionEnvironment(this.agentName, {
|
|
@@ -209,20 +167,27 @@ export class AgentDaemon {
|
|
|
209
167
|
});
|
|
210
168
|
}
|
|
211
169
|
// Save state including runtime model info
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
name: this.agentName,
|
|
170
|
+
persistRunningState({
|
|
171
|
+
agentName: this.agentName,
|
|
215
172
|
agentId: this.agentId,
|
|
216
173
|
pid: process.pid,
|
|
217
|
-
tmuxSession: sessionName,
|
|
218
|
-
startedAt: new Date().toISOString(),
|
|
219
174
|
token: this.token,
|
|
220
175
|
workdir: this.agentConfig.workdir,
|
|
221
176
|
assignedProject: this.assignedProject,
|
|
222
177
|
runtimeModel: this.runnerConfig.model,
|
|
223
178
|
runnerType: this.runnerConfig.type,
|
|
224
179
|
sandboxContainer: this.sandbox?.getContainerName(),
|
|
180
|
+
serveMode: this.serveMode,
|
|
181
|
+
servePort: this.servePort,
|
|
225
182
|
});
|
|
183
|
+
// Track child PIDs for cleanup on restart/stop (tmux mode only — sandbox/serve manage their own)
|
|
184
|
+
if (!this.sandboxMode && !this.serveMode) {
|
|
185
|
+
const childPids = captureAgentChildPids(this.agentName);
|
|
186
|
+
if (childPids.length > 0) {
|
|
187
|
+
updateAgentInState(this.agentName, { childPids });
|
|
188
|
+
console.log(`[STARTUP] Tracking ${childPids.length} child PIDs: ${childPids.join(", ")}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
226
191
|
// Start heartbeat with auto-refresh
|
|
227
192
|
console.log("Starting heartbeat...");
|
|
228
193
|
this.heartbeat = new Heartbeat({
|
|
@@ -404,13 +369,10 @@ Nudge agent:
|
|
|
404
369
|
if (!this.isRunning)
|
|
405
370
|
return;
|
|
406
371
|
// Reset restart count after stable operation
|
|
407
|
-
if (this.
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
this.restartCount = 0;
|
|
412
|
-
resetAgentRestartCount(this.agentName);
|
|
413
|
-
}
|
|
372
|
+
if (shouldResetRestartCount(this.restartCount, this.lastStableTime, RESTART_COUNT_RESET_MS)) {
|
|
373
|
+
console.log(`[HEALTH] Agent stable for 30+ minutes, resetting restart count`);
|
|
374
|
+
this.restartCount = 0;
|
|
375
|
+
resetAgentRestartCount(this.agentName);
|
|
414
376
|
}
|
|
415
377
|
// For sandbox mode, pass container name so health check looks inside container
|
|
416
378
|
const containerName = this.sandboxMode ? this.sandbox?.getContainerName() : undefined;
|
|
@@ -422,7 +384,17 @@ Nudge agent:
|
|
|
422
384
|
}
|
|
423
385
|
// Session is alive - check progress watchdog
|
|
424
386
|
const progress = checkAgentProgress(this.agentName, containerName);
|
|
425
|
-
if (progress.status === "
|
|
387
|
+
if (progress.status === "waiting_for_human") {
|
|
388
|
+
// Agent is intentionally waiting for human input - do not classify as stuck
|
|
389
|
+
if (this.stuckSince) {
|
|
390
|
+
// Clear any prior stuck tracking since the agent signalled a legitimate wait
|
|
391
|
+
this.stuckSince = null;
|
|
392
|
+
this.nudgeSentAt = null;
|
|
393
|
+
updateAgentInState(this.agentName, { stuckSince: undefined, status: "waiting" });
|
|
394
|
+
}
|
|
395
|
+
console.log(`[HEALTH] Agent is waiting for human input: ${progress.details}`);
|
|
396
|
+
}
|
|
397
|
+
else if (progress.status === "permission_blocked" || progress.status === "stuck") {
|
|
426
398
|
await this.handleStuckAgent(progress);
|
|
427
399
|
}
|
|
428
400
|
else if (progress.status === "active") {
|
|
@@ -451,24 +423,18 @@ Nudge agent:
|
|
|
451
423
|
catch {
|
|
452
424
|
lastOutput = "Failed to capture session output";
|
|
453
425
|
}
|
|
454
|
-
const crashLog =
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
--- Last Session Output ---
|
|
468
|
-
${lastOutput}
|
|
469
|
-
================================================================================
|
|
470
|
-
|
|
471
|
-
`;
|
|
426
|
+
const crashLog = formatCrashLog({
|
|
427
|
+
timestamp,
|
|
428
|
+
agentName: this.agentName,
|
|
429
|
+
agentId: this.agentId,
|
|
430
|
+
reason,
|
|
431
|
+
restartCount: this.restartCount,
|
|
432
|
+
maxRestartAttempts: MAX_RESTART_ATTEMPTS,
|
|
433
|
+
sandboxLabel: this.sandboxMode ? this.sandbox?.getContainerName() || "sandbox" : "none",
|
|
434
|
+
workdir: this.agentConfig.workdir,
|
|
435
|
+
model: this.runnerConfig.model,
|
|
436
|
+
lastOutput,
|
|
437
|
+
});
|
|
472
438
|
fs.appendFileSync(logFile, crashLog);
|
|
473
439
|
// Save context (including session ID) before restart attempt
|
|
474
440
|
if (this.agentId) {
|
|
@@ -517,7 +483,7 @@ ${lastOutput}
|
|
|
517
483
|
if (!this.stuckSince) {
|
|
518
484
|
// First detection of stuck state
|
|
519
485
|
this.stuckSince = now;
|
|
520
|
-
console.log(`[HEALTH] Agent appears stuck: ${progress
|
|
486
|
+
console.log(`[HEALTH] Agent appears stuck: ${getStuckDetail(progress)}`);
|
|
521
487
|
updateAgentInState(this.agentName, {
|
|
522
488
|
stuckSince: now.toISOString(),
|
|
523
489
|
status: "stuck",
|
|
@@ -528,9 +494,7 @@ ${lastOutput}
|
|
|
528
494
|
// If we haven't sent a nudge yet, send one
|
|
529
495
|
if (!this.nudgeSentAt) {
|
|
530
496
|
console.log(`[HEALTH] Sending nudge to worker agent...`);
|
|
531
|
-
const nudgeMessage = progress
|
|
532
|
-
? "Please continue with your task. If you see a permission prompt, try an alternative approach that doesn't require that permission."
|
|
533
|
-
: "Please continue with your current task.";
|
|
497
|
+
const nudgeMessage = getNudgeMessage(progress);
|
|
534
498
|
const sent = sendNudge(this.agentName, nudgeMessage);
|
|
535
499
|
if (sent) {
|
|
536
500
|
this.nudgeSentAt = now;
|
|
@@ -542,8 +506,7 @@ ${lastOutput}
|
|
|
542
506
|
return;
|
|
543
507
|
}
|
|
544
508
|
// Check if enough time has passed since nudge
|
|
545
|
-
|
|
546
|
-
if (timeSinceNudge < NUDGE_WAIT_MS) {
|
|
509
|
+
if (isWithinNudgeWaitWindow(this.nudgeSentAt, NUDGE_WAIT_MS, now)) {
|
|
547
510
|
// Still waiting for agent to respond to nudge
|
|
548
511
|
return;
|
|
549
512
|
}
|
|
@@ -558,15 +521,19 @@ ${lastOutput}
|
|
|
558
521
|
* Restarts the agent session (sandbox or non-sandbox)
|
|
559
522
|
*/
|
|
560
523
|
async restartSession() {
|
|
561
|
-
//
|
|
562
|
-
|
|
524
|
+
// Retrieve tracked child PIDs before destroying the session
|
|
525
|
+
const currentState = getAgentState(this.agentName);
|
|
526
|
+
const childPids = currentState?.childPids ?? [];
|
|
527
|
+
// Destroy existing session AND kill all tracked child processes
|
|
528
|
+
destroySession(this.agentName, childPids);
|
|
529
|
+
// Allow cleanup to settle before spawning a new session
|
|
530
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
563
531
|
if (this.sandboxMode && this.sandbox) {
|
|
564
532
|
// Restart sandbox container
|
|
565
533
|
const newContainerId = await this.sandbox.restart();
|
|
566
534
|
console.log(`[RESTART] New container: ${newContainerId.substring(0, 12)}`);
|
|
567
535
|
// Recreate tmux session for sandbox
|
|
568
536
|
const containerName = this.sandbox.getContainerName();
|
|
569
|
-
const sessionName = getSessionName(this.agentName);
|
|
570
537
|
// Build environment args for docker exec
|
|
571
538
|
const envArgs = [];
|
|
572
539
|
const allEnv = {
|
|
@@ -588,10 +555,15 @@ ${lastOutput}
|
|
|
588
555
|
if (!created) {
|
|
589
556
|
throw new Error("Failed to create tmux session for restarted sandbox");
|
|
590
557
|
}
|
|
591
|
-
//
|
|
558
|
+
// Track new child PIDs and update state
|
|
559
|
+
const newChildPids = captureAgentChildPids(this.agentName);
|
|
592
560
|
updateAgentInState(this.agentName, {
|
|
593
561
|
sandboxContainer: containerName,
|
|
562
|
+
childPids: newChildPids,
|
|
594
563
|
});
|
|
564
|
+
if (newChildPids.length > 0) {
|
|
565
|
+
console.log(`[RESTART] Tracking ${newChildPids.length} child PIDs: ${newChildPids.join(", ")}`);
|
|
566
|
+
}
|
|
595
567
|
}
|
|
596
568
|
else {
|
|
597
569
|
// Non-sandbox restart — load saved session ID for native resume
|
|
@@ -615,6 +587,12 @@ ${lastOutput}
|
|
|
615
587
|
AGENTMESH_AGENT_ID: this.agentId,
|
|
616
588
|
...this.runnerConfig.env,
|
|
617
589
|
});
|
|
590
|
+
// Track new child PIDs
|
|
591
|
+
const newChildPids = captureAgentChildPids(this.agentName);
|
|
592
|
+
updateAgentInState(this.agentName, { childPids: newChildPids });
|
|
593
|
+
if (newChildPids.length > 0) {
|
|
594
|
+
console.log(`[RESTART] Tracking ${newChildPids.length} child PIDs: ${newChildPids.join(", ")}`);
|
|
595
|
+
}
|
|
618
596
|
// Verify native resume and fallback if needed
|
|
619
597
|
if (savedSessionId && savedContext) {
|
|
620
598
|
const newSessionId = await waitForNewSessionId(this.agentName, preRestartSessionId, 15000);
|
|
@@ -709,27 +687,7 @@ ${lastOutput}
|
|
|
709
687
|
async startServeMode() {
|
|
710
688
|
console.log(`Starting opencode serve mode on port ${this.servePort}...`);
|
|
711
689
|
const workdir = this.agentConfig.workdir || process.cwd();
|
|
712
|
-
|
|
713
|
-
// See docs/RCA-OPENCODE-SQLITE-CORRUPTION.md for details.
|
|
714
|
-
const agentDataDir = path.join(os.homedir(), ".agentmesh", "opencode-data", this.agentName);
|
|
715
|
-
const agentOpencodeDir = path.join(agentDataDir, "opencode");
|
|
716
|
-
if (!fs.existsSync(agentOpencodeDir)) {
|
|
717
|
-
fs.mkdirSync(agentOpencodeDir, { recursive: true });
|
|
718
|
-
}
|
|
719
|
-
// Copy auth.json from default OpenCode data dir so agents inherit API keys.
|
|
720
|
-
// Strips xAI provider to prevent OpenCode from defaulting to non-Anthropic models.
|
|
721
|
-
const agentAuthPath = path.join(agentOpencodeDir, "auth.json");
|
|
722
|
-
const sourceAuthPath = path.join(os.homedir(), ".local", "share", "opencode", "auth.json");
|
|
723
|
-
if (!fs.existsSync(agentAuthPath) && fs.existsSync(sourceAuthPath)) {
|
|
724
|
-
try {
|
|
725
|
-
const auth = JSON.parse(fs.readFileSync(sourceAuthPath, "utf-8"));
|
|
726
|
-
delete auth.xai;
|
|
727
|
-
fs.writeFileSync(agentAuthPath, JSON.stringify(auth, null, 2));
|
|
728
|
-
}
|
|
729
|
-
catch {
|
|
730
|
-
// Non-fatal — agent will just need manual auth
|
|
731
|
-
}
|
|
732
|
-
}
|
|
690
|
+
const agentDataDir = prepareOpenCodeRuntime(this.agentName);
|
|
733
691
|
// Build environment for opencode serve
|
|
734
692
|
const env = {
|
|
735
693
|
...process.env,
|
|
@@ -966,39 +924,22 @@ Logs: docker logs ${containerName}
|
|
|
966
924
|
const suggestedPath = `~/.agentmesh/workspaces/${this.config.workspace}/${repoAssignment.project.code.toLowerCase()}/${this.agentName}`;
|
|
967
925
|
// If --auto-setup is enabled, automatically clone the repo
|
|
968
926
|
if (this.autoSetup) {
|
|
969
|
-
this.agentConfig.workdir =
|
|
927
|
+
this.agentConfig.workdir = setupWorkspace({
|
|
928
|
+
workspacePath: expandedPath,
|
|
929
|
+
repoUrl: repo.url,
|
|
930
|
+
defaultBranch: repo.default_branch,
|
|
931
|
+
projectName: repoAssignment.project.name,
|
|
932
|
+
});
|
|
970
933
|
return;
|
|
971
934
|
}
|
|
972
|
-
console.error(
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
Branch: ${repo.default_branch}
|
|
981
|
-
|
|
982
|
-
Option 1: Set workdir in project settings (recommended)
|
|
983
|
-
- Go to AgentMesh HQ → Projects → ${repoAssignment.project.name} → Settings
|
|
984
|
-
- Set the workdir field to the local path
|
|
985
|
-
|
|
986
|
-
Option 2: Set up workspace manually and pass --workdir:
|
|
987
|
-
|
|
988
|
-
mkdir -p ${suggestedPath}
|
|
989
|
-
git clone ${repo.url} ${suggestedPath}
|
|
990
|
-
cd ${suggestedPath} && git checkout ${repo.default_branch}
|
|
991
|
-
|
|
992
|
-
Then start the agent with:
|
|
993
|
-
|
|
994
|
-
agentmesh start -n ${this.agentName} --workdir ${suggestedPath}
|
|
995
|
-
|
|
996
|
-
Option 3: Use --auto-setup to automatically clone the repository:
|
|
997
|
-
|
|
998
|
-
agentmesh start -n ${this.agentName} --auto-setup
|
|
999
|
-
|
|
1000
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1001
|
-
`);
|
|
935
|
+
console.error(renderMissingWorkdirMessage({
|
|
936
|
+
projectName: repoAssignment.project.name,
|
|
937
|
+
repoFullName: repo.full_name,
|
|
938
|
+
repoUrl: repo.url,
|
|
939
|
+
defaultBranch: repo.default_branch,
|
|
940
|
+
suggestedPath,
|
|
941
|
+
agentName: this.agentName,
|
|
942
|
+
}));
|
|
1002
943
|
// No session to clean up - we haven't created it yet
|
|
1003
944
|
process.exit(1);
|
|
1004
945
|
}
|
|
@@ -1009,78 +950,16 @@ Option 3: Use --auto-setup to automatically clone the repository:
|
|
|
1009
950
|
console.log("Could not fetch assignments:", error.message);
|
|
1010
951
|
}
|
|
1011
952
|
}
|
|
1012
|
-
/**
|
|
1013
|
-
* Sets up workspace by cloning repository or using existing clone
|
|
1014
|
-
* Returns the absolute path to the workspace
|
|
1015
|
-
*/
|
|
1016
|
-
setupWorkspace(workspacePath, repoUrl, defaultBranch, projectName) {
|
|
1017
|
-
console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
1018
|
-
console.log(`🔧 AUTO-SETUP: Setting up workspace for ${projectName}`);
|
|
1019
|
-
console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
|
|
1020
|
-
// Check if directory already exists and is a git repo
|
|
1021
|
-
const gitDir = path.join(workspacePath, ".git");
|
|
1022
|
-
if (fs.existsSync(gitDir)) {
|
|
1023
|
-
console.log(`✓ Workspace already exists: ${workspacePath}`);
|
|
1024
|
-
console.log(` Updating from remote...`);
|
|
1025
|
-
try {
|
|
1026
|
-
// Fetch and checkout the branch
|
|
1027
|
-
execSync(`git fetch origin`, { cwd: workspacePath, stdio: "inherit" });
|
|
1028
|
-
execSync(`git checkout ${defaultBranch}`, { cwd: workspacePath, stdio: "inherit" });
|
|
1029
|
-
execSync(`git pull origin ${defaultBranch}`, { cwd: workspacePath, stdio: "inherit" });
|
|
1030
|
-
console.log(`✓ Workspace updated successfully\n`);
|
|
1031
|
-
}
|
|
1032
|
-
catch (error) {
|
|
1033
|
-
console.warn(`⚠ Could not update workspace: ${error.message}`);
|
|
1034
|
-
console.log(` Continuing with existing state...\n`);
|
|
1035
|
-
}
|
|
1036
|
-
return workspacePath;
|
|
1037
|
-
}
|
|
1038
|
-
// Create parent directories
|
|
1039
|
-
const parentDir = path.dirname(workspacePath);
|
|
1040
|
-
if (!fs.existsSync(parentDir)) {
|
|
1041
|
-
console.log(`Creating directory: ${parentDir}`);
|
|
1042
|
-
fs.mkdirSync(parentDir, { recursive: true });
|
|
1043
|
-
}
|
|
1044
|
-
// Clone the repository
|
|
1045
|
-
console.log(`Cloning repository...`);
|
|
1046
|
-
console.log(` URL: ${repoUrl}`);
|
|
1047
|
-
console.log(` Path: ${workspacePath}`);
|
|
1048
|
-
console.log(` Branch: ${defaultBranch}\n`);
|
|
1049
|
-
try {
|
|
1050
|
-
execSync(`git clone --branch ${defaultBranch} "${repoUrl}" "${workspacePath}"`, {
|
|
1051
|
-
stdio: "inherit",
|
|
1052
|
-
});
|
|
1053
|
-
console.log(`\n✓ Repository cloned successfully`);
|
|
1054
|
-
}
|
|
1055
|
-
catch (error) {
|
|
1056
|
-
console.error(`\n✗ Failed to clone repository: ${error.message}`);
|
|
1057
|
-
console.error(`\nMake sure you have access to the repository and SSH keys are configured.`);
|
|
1058
|
-
// No session to clean up - we haven't created it yet
|
|
1059
|
-
process.exit(1);
|
|
1060
|
-
}
|
|
1061
|
-
console.log(`✓ Workspace ready: ${workspacePath}\n`);
|
|
1062
|
-
return workspacePath;
|
|
1063
|
-
}
|
|
1064
953
|
/**
|
|
1065
954
|
* Ensures the sandbox OpenCode config exists
|
|
1066
955
|
* Creates ~/.agentmesh/opencode-sandbox.json with permissive permissions and model
|
|
1067
956
|
*/
|
|
1068
957
|
ensureSandboxOpencodeConfig() {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
const config = {
|
|
1075
|
-
...SANDBOX_OPENCODE_CONFIG,
|
|
1076
|
-
};
|
|
1077
|
-
// Include model from runner config
|
|
1078
|
-
const model = this.runnerConfig.env?.OPENCODE_MODEL;
|
|
1079
|
-
if (model) {
|
|
1080
|
-
config.model = model;
|
|
1081
|
-
}
|
|
1082
|
-
// Always write to ensure model is up to date
|
|
1083
|
-
fs.writeFileSync(SANDBOX_OPENCODE_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
958
|
+
writeSandboxOpencodeConfig({
|
|
959
|
+
configPath: SANDBOX_OPENCODE_CONFIG_PATH,
|
|
960
|
+
baseConfig: SANDBOX_OPENCODE_CONFIG,
|
|
961
|
+
model: this.runnerConfig.env?.OPENCODE_MODEL,
|
|
962
|
+
});
|
|
1084
963
|
console.log(`Updated sandbox OpenCode config: ${SANDBOX_OPENCODE_CONFIG_PATH}`);
|
|
1085
964
|
}
|
|
1086
965
|
}
|