@smithers-orchestrator/agents 0.22.0 → 0.24.0
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/package.json +10 -4
- package/src/AmpAgent.js +26 -19
- package/src/AntigravityAgent.js +53 -18
- package/src/AntigravityAgentOptions.ts +45 -4
- package/src/BaseCliAgent/AgentGenerateOptions.ts +12 -0
- package/src/BaseCliAgent/BaseCliAgent.js +78 -8
- package/src/BaseCliAgent/RunCommandResult.ts +4 -0
- package/src/BaseCliAgent/runCommandEffect.js +3 -2
- package/src/BaseCliAgent/taskContextEnv.js +31 -0
- package/src/ClaudeCodeAgent.js +19 -1
- package/src/ForgeAgent.js +26 -19
- package/src/HermesAgent.js +1 -1
- package/src/VibeAgent.js +214 -0
- package/src/VibeAgentOptions.ts +11 -0
- package/src/agent-contract/createSmithersAgentContract.js +1 -0
- package/src/agent-contract/renderSmithersAgentPromptGuidance.js +4 -0
- package/src/capability-registry/AgentCapabilityRegistry.ts +1 -1
- package/src/cli-capabilities/CliAgentCapabilityAdapterId.ts +4 -1
- package/src/cli-capabilities/CliAgentCapabilityReportEntry.ts +2 -0
- package/src/cli-capabilities/getCliAgentCapabilityDoctorReport.js +48 -1
- package/src/cli-capabilities/getCliAgentCapabilityReport.js +24 -0
- package/src/cli-capabilities/index.js +5 -0
- package/src/cli-surface/CliAgentSurfaceTypes.ts +34 -0
- package/src/cli-surface/cliAgentSurfaceManifest.js +490 -0
- package/src/cli-surface/index.js +5 -0
- package/src/diagnostics/getDiagnosticStrategy.js +1 -1
- package/src/index.d.ts +707 -386
- package/src/index.js +21 -0
- package/src/mcp/McpServerConfig.ts +19 -0
- package/src/mcp/McpToolset.ts +17 -0
- package/src/mcp/McpToolsetOptions.ts +16 -0
- package/src/mcp/createMcpToolset.d.ts +12 -0
- package/src/mcp/createMcpToolset.js +83 -0
- package/src/sanitizeForOpenAI.js +20 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smithers-orchestrator/agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "AI SDK and CLI agent adapters for Smithers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,6 +35,11 @@
|
|
|
35
35
|
"import": "./src/cli-capabilities/index.js",
|
|
36
36
|
"default": "./src/cli-capabilities/index.js"
|
|
37
37
|
},
|
|
38
|
+
"./mcp/createMcpToolset": {
|
|
39
|
+
"types": "./src/mcp/createMcpToolset.d.ts",
|
|
40
|
+
"import": "./src/mcp/createMcpToolset.js",
|
|
41
|
+
"default": "./src/mcp/createMcpToolset.js"
|
|
42
|
+
},
|
|
38
43
|
"./*": {
|
|
39
44
|
"types": "./src/index.d.ts",
|
|
40
45
|
"import": "./src/*.js",
|
|
@@ -47,12 +52,13 @@
|
|
|
47
52
|
"dependencies": {
|
|
48
53
|
"@ai-sdk/anthropic": "^3.0.71",
|
|
49
54
|
"@ai-sdk/openai": "^3.0.53",
|
|
55
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
50
56
|
"ai": "^6.0.168",
|
|
51
57
|
"effect": "^3.21.1",
|
|
52
58
|
"zod": "^4.3.6",
|
|
53
|
-
"@smithers-orchestrator/driver": "0.
|
|
54
|
-
"@smithers-orchestrator/errors": "0.
|
|
55
|
-
"@smithers-orchestrator/observability": "0.
|
|
59
|
+
"@smithers-orchestrator/driver": "0.24.0",
|
|
60
|
+
"@smithers-orchestrator/errors": "0.24.0",
|
|
61
|
+
"@smithers-orchestrator/observability": "0.24.0"
|
|
56
62
|
},
|
|
57
63
|
"devDependencies": {
|
|
58
64
|
"@types/bun": "latest",
|
package/src/AmpAgent.js
CHANGED
|
@@ -6,6 +6,31 @@ import { BaseCliAgent, pushFlag, isRecord, asString, toolKindFromName, createSyn
|
|
|
6
6
|
/** @typedef {import("./capability-registry/AgentCapabilityRegistry.ts").AgentCapabilityRegistry} AgentCapabilityRegistry */
|
|
7
7
|
/** @typedef {import("./BaseCliAgent/CliOutputInterpreter.ts").CliOutputInterpreter} CliOutputInterpreter */
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @returns {AgentCapabilityRegistry}
|
|
11
|
+
*/
|
|
12
|
+
export function createAmpCapabilityRegistry() {
|
|
13
|
+
return {
|
|
14
|
+
version: 1,
|
|
15
|
+
engine: "amp",
|
|
16
|
+
runtimeTools: {},
|
|
17
|
+
mcp: {
|
|
18
|
+
bootstrap: "project-config",
|
|
19
|
+
supportsProjectScope: true,
|
|
20
|
+
supportsUserScope: false,
|
|
21
|
+
},
|
|
22
|
+
skills: {
|
|
23
|
+
supportsSkills: false,
|
|
24
|
+
smithersSkillIds: [],
|
|
25
|
+
},
|
|
26
|
+
humanInteraction: {
|
|
27
|
+
supportsUiRequests: false,
|
|
28
|
+
methods: [],
|
|
29
|
+
},
|
|
30
|
+
builtIns: ["default"],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
9
34
|
/**
|
|
10
35
|
* Agent implementation that wraps the 'amp' CLI executable.
|
|
11
36
|
* It translates generation requests into CLI arguments and executes the process.
|
|
@@ -23,25 +48,7 @@ export class AmpAgent extends BaseCliAgent {
|
|
|
23
48
|
constructor(opts = {}) {
|
|
24
49
|
super(opts);
|
|
25
50
|
this.opts = opts;
|
|
26
|
-
this.capabilities =
|
|
27
|
-
version: 1,
|
|
28
|
-
engine: "amp",
|
|
29
|
-
runtimeTools: {},
|
|
30
|
-
mcp: {
|
|
31
|
-
bootstrap: "project-config",
|
|
32
|
-
supportsProjectScope: true,
|
|
33
|
-
supportsUserScope: false,
|
|
34
|
-
},
|
|
35
|
-
skills: {
|
|
36
|
-
supportsSkills: false,
|
|
37
|
-
smithersSkillIds: [],
|
|
38
|
-
},
|
|
39
|
-
humanInteraction: {
|
|
40
|
-
supportsUiRequests: false,
|
|
41
|
-
methods: [],
|
|
42
|
-
},
|
|
43
|
-
builtIns: ["default"],
|
|
44
|
-
};
|
|
51
|
+
this.capabilities = createAmpCapabilityRegistry();
|
|
45
52
|
}
|
|
46
53
|
/**
|
|
47
54
|
* @returns {CliOutputInterpreter}
|
package/src/AntigravityAgent.js
CHANGED
|
@@ -1,9 +1,51 @@
|
|
|
1
1
|
import { BaseCliAgent, pushFlag, pushList, isRecord, asString, truncate, toolKindFromName, createSyntheticIdGenerator, } from "./BaseCliAgent/index.js";
|
|
2
|
+
import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
|
|
2
3
|
import { normalizeCapabilityStringList, } from "./capability-registry/index.js";
|
|
4
|
+
import { getCliAgentSurfaceManifestEntry } from "./cli-surface/index.js";
|
|
3
5
|
/** @typedef {import("./capability-registry/AgentCapabilityRegistry.ts").AgentCapabilityRegistry} AgentCapabilityRegistry */
|
|
4
6
|
/** @typedef {import("./BaseCliAgent/CliOutputInterpreter.ts").CliOutputInterpreter} CliOutputInterpreter */
|
|
5
7
|
/** @typedef {import("./AntigravityAgentOptions.ts").AntigravityAgentOptions} AntigravityAgentOptions */
|
|
6
8
|
|
|
9
|
+
const ANTIGRAVITY_SURFACE = getCliAgentSurfaceManifestEntry("antigravity");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} option
|
|
13
|
+
* @param {string} flag
|
|
14
|
+
* @returns {SmithersError}
|
|
15
|
+
*/
|
|
16
|
+
function unsupportedAntigravityOption(option, flag) {
|
|
17
|
+
const rule = ANTIGRAVITY_SURFACE?.unsupportedFlags.find((entry) => entry.flag === flag);
|
|
18
|
+
const replacement = rule?.replacement ? ` Use ${rule.replacement} instead.` : "";
|
|
19
|
+
const reason = rule?.reason ? ` ${rule.reason}` : "";
|
|
20
|
+
return new SmithersError("AGENT_CONFIG_INVALID", `AntigravityAgent option "${option}" maps to unsupported agy flag ${flag}.${reason}${replacement}`, {
|
|
21
|
+
agentEngine: "antigravity",
|
|
22
|
+
option,
|
|
23
|
+
flag,
|
|
24
|
+
replacement: rule?.replacement,
|
|
25
|
+
failureRetryable: false,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {AntigravityAgentOptions} opts
|
|
31
|
+
*/
|
|
32
|
+
function assertSupportedAntigravityOptions(opts) {
|
|
33
|
+
if (opts.debug)
|
|
34
|
+
throw unsupportedAntigravityOption("debug", "--debug");
|
|
35
|
+
if (opts.screenReader)
|
|
36
|
+
throw unsupportedAntigravityOption("screenReader", "--screen-reader");
|
|
37
|
+
if (opts.outputFormat !== undefined)
|
|
38
|
+
throw unsupportedAntigravityOption("outputFormat", "--output-format");
|
|
39
|
+
if (opts.listSessions)
|
|
40
|
+
throw unsupportedAntigravityOption("listSessions", "--list-sessions");
|
|
41
|
+
if (opts.deleteSession !== undefined)
|
|
42
|
+
throw unsupportedAntigravityOption("deleteSession", "--delete-session");
|
|
43
|
+
if (opts.extensions?.length)
|
|
44
|
+
throw unsupportedAntigravityOption("extensions", "--extensions");
|
|
45
|
+
if (opts.listExtensions)
|
|
46
|
+
throw unsupportedAntigravityOption("listExtensions", "--list-extensions");
|
|
47
|
+
}
|
|
48
|
+
|
|
7
49
|
/**
|
|
8
50
|
* @param {AntigravityAgentOptions} opts
|
|
9
51
|
*/
|
|
@@ -209,15 +251,13 @@ export class AntigravityAgent extends BaseCliAgent {
|
|
|
209
251
|
* @param {{ prompt: string; systemPrompt?: string; cwd: string; options: any; }} params
|
|
210
252
|
*/
|
|
211
253
|
async buildCommand(params) {
|
|
254
|
+
assertSupportedAntigravityOptions(this.opts);
|
|
212
255
|
const args = [];
|
|
213
256
|
const yoloEnabled = this.opts.dangerouslySkipPermissions ?? this.opts.yolo ?? this.yolo;
|
|
214
|
-
const outputFormat = this.opts.outputFormat ??
|
|
215
|
-
(params.options?.onEvent ? "stream-json" : "json");
|
|
216
257
|
const resumeSession = typeof params.options?.resumeSession === "string"
|
|
217
258
|
? params.options.resumeSession
|
|
218
|
-
: this.opts.resume;
|
|
219
|
-
|
|
220
|
-
args.push("--debug");
|
|
259
|
+
: this.opts.conversation ?? this.opts.resume;
|
|
260
|
+
args.push("--cwd", params.cwd);
|
|
221
261
|
pushFlag(args, "--model", this.opts.model ?? this.model);
|
|
222
262
|
if (this.opts.sandbox)
|
|
223
263
|
args.push("--sandbox");
|
|
@@ -232,18 +272,11 @@ export class AntigravityAgent extends BaseCliAgent {
|
|
|
232
272
|
pushList(args, "--allowed-tools", this.opts.allowedTools);
|
|
233
273
|
}
|
|
234
274
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (this.opts.listSessions)
|
|
240
|
-
args.push("--list-sessions");
|
|
241
|
-
pushFlag(args, "--delete-session", this.opts.deleteSession);
|
|
242
|
-
pushList(args, "--include-directories", this.opts.includeDirectories);
|
|
243
|
-
if (this.opts.screenReader)
|
|
244
|
-
args.push("--screen-reader");
|
|
275
|
+
if (this.opts.continue)
|
|
276
|
+
args.push("--continue");
|
|
277
|
+
pushFlag(args, "--conversation", resumeSession);
|
|
278
|
+
pushList(args, "--add-dir", this.opts.includeDirectories);
|
|
245
279
|
pushFlag(args, "--gemini_dir", this.opts.geminiDir ?? this.opts.configDir);
|
|
246
|
-
pushFlag(args, "--output-format", outputFormat);
|
|
247
280
|
if (this.extraArgs?.length)
|
|
248
281
|
args.push(...this.extraArgs);
|
|
249
282
|
const systemPrefix = params.systemPrompt
|
|
@@ -253,14 +286,16 @@ export class AntigravityAgent extends BaseCliAgent {
|
|
|
253
286
|
? "\n\nREMINDER: Your response MUST be ONLY the required raw JSON object. Do not include prose, markdown, or code fences. The first character must be `{` and the last character must be `}`.\n"
|
|
254
287
|
: "";
|
|
255
288
|
const fullPrompt = `${systemPrefix}${params.prompt ?? ""}${jsonReminder}`;
|
|
256
|
-
args.push("
|
|
289
|
+
args.push("-p", fullPrompt);
|
|
257
290
|
const accountEnv = {};
|
|
291
|
+
if (this.opts.geminiDir ?? this.opts.configDir)
|
|
292
|
+
accountEnv.GEMINI_DIR = this.opts.geminiDir ?? this.opts.configDir;
|
|
258
293
|
if (this.opts.apiKey)
|
|
259
294
|
accountEnv.GEMINI_API_KEY = this.opts.apiKey;
|
|
260
295
|
return {
|
|
261
296
|
command: this.opts.binary ?? "agy",
|
|
262
297
|
args,
|
|
263
|
-
outputFormat,
|
|
298
|
+
outputFormat: "text",
|
|
264
299
|
env: Object.keys(accountEnv).length > 0 ? accountEnv : undefined,
|
|
265
300
|
};
|
|
266
301
|
}
|
|
@@ -1,30 +1,71 @@
|
|
|
1
1
|
import type { BaseCliAgentOptions } from "./BaseCliAgent/BaseCliAgentOptions";
|
|
2
2
|
|
|
3
3
|
export type AntigravityAgentOptions = BaseCliAgentOptions & {
|
|
4
|
-
debug?: boolean;
|
|
5
4
|
model?: string;
|
|
6
5
|
sandbox?: boolean;
|
|
7
6
|
yolo?: boolean;
|
|
8
7
|
dangerouslySkipPermissions?: boolean;
|
|
9
8
|
allowedMcpServerNames?: string[];
|
|
10
9
|
allowedTools?: string[];
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated Antigravity renamed extensions to plugins and manages them via
|
|
12
|
+
* `agy plugin`; launch-time extension flags are rejected at runtime.
|
|
13
|
+
*/
|
|
11
14
|
extensions?: string[];
|
|
15
|
+
/**
|
|
16
|
+
* @deprecated Use `agy plugin list` outside Smithers. This option is rejected
|
|
17
|
+
* at runtime because current `agy` builds no longer accept it during launch.
|
|
18
|
+
*/
|
|
12
19
|
listExtensions?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Native Antigravity conversation id. Smithers emits `--conversation`.
|
|
22
|
+
*/
|
|
23
|
+
conversation?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Continue the latest Antigravity conversation. Smithers emits `--continue`.
|
|
26
|
+
*/
|
|
27
|
+
continue?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* @deprecated Use `conversation`; Smithers still maps this to
|
|
30
|
+
* `--conversation` for compatibility.
|
|
31
|
+
*/
|
|
13
32
|
resume?: string;
|
|
33
|
+
/**
|
|
34
|
+
* @deprecated Conversation listing is interactive via `/resume`; this option
|
|
35
|
+
* is rejected at runtime.
|
|
36
|
+
*/
|
|
14
37
|
listSessions?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* @deprecated Conversation deletion is not a supported non-interactive
|
|
40
|
+
* launch flag; this option is rejected at runtime.
|
|
41
|
+
*/
|
|
15
42
|
deleteSession?: string;
|
|
16
43
|
includeDirectories?: string[];
|
|
44
|
+
/**
|
|
45
|
+
* @deprecated Current `agy` builds do not expose `--screen-reader`; this
|
|
46
|
+
* option is rejected at runtime.
|
|
47
|
+
*/
|
|
17
48
|
screenReader?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* @deprecated Current `agy` builds do not expose `--output-format`; Smithers
|
|
51
|
+
* reads Antigravity stdout as text.
|
|
52
|
+
*/
|
|
18
53
|
outputFormat?: "text" | "json" | "stream-json";
|
|
54
|
+
/**
|
|
55
|
+
* @deprecated Current `agy` builds do not expose `--debug`; this option is
|
|
56
|
+
* rejected at runtime.
|
|
57
|
+
*/
|
|
58
|
+
debug?: boolean;
|
|
19
59
|
/**
|
|
20
60
|
* Antigravity CLI binary to execute. The official CLI currently installs
|
|
21
61
|
* `agy`; this exists for test harnesses and future binary renames.
|
|
22
62
|
*/
|
|
23
63
|
binary?: string;
|
|
24
64
|
/**
|
|
25
|
-
* Path to an isolated Google CLI config root.
|
|
26
|
-
* Antigravity reads/writes
|
|
27
|
-
* user's default
|
|
65
|
+
* Path to an isolated Google CLI config root. Smithers passes it as
|
|
66
|
+
* `--gemini_dir` and `GEMINI_DIR` so Antigravity reads/writes
|
|
67
|
+
* `<configDir>/antigravity-cli/...` instead of the user's default
|
|
68
|
+
* `~/.gemini/antigravity-cli/...`.
|
|
28
69
|
*/
|
|
29
70
|
configDir?: string;
|
|
30
71
|
/**
|
|
@@ -20,5 +20,17 @@ export type AgentGenerateOptions = {
|
|
|
20
20
|
isRetry?: unknown;
|
|
21
21
|
retryAttempt?: unknown;
|
|
22
22
|
schemaRetry?: unknown;
|
|
23
|
+
/**
|
|
24
|
+
* Run context for the task this agent invocation belongs to. Surfaced to the
|
|
25
|
+
* spawned agent process (and its subprocesses) as SMITHERS_RUN_ID / NODE_ID /
|
|
26
|
+
* ITERATION / ATTEMPT so the agent can address its own run — e.g. to raise a
|
|
27
|
+
* blocking `smithers ask-human` request.
|
|
28
|
+
*/
|
|
29
|
+
taskContext?: {
|
|
30
|
+
runId?: string;
|
|
31
|
+
nodeId?: string;
|
|
32
|
+
iteration?: number;
|
|
33
|
+
attempt?: number;
|
|
34
|
+
};
|
|
23
35
|
[key: string]: unknown;
|
|
24
36
|
};
|
|
@@ -14,6 +14,7 @@ import { extractTextFromJsonValue } from "./extractTextFromJsonValue.js";
|
|
|
14
14
|
import { createAgentStdoutTextEmitter } from "./createAgentStdoutTextEmitter.js";
|
|
15
15
|
import { buildGenerateResult } from "./buildGenerateResult.js";
|
|
16
16
|
import { runCommandEffect } from "./runCommandEffect.js";
|
|
17
|
+
import { taskContextEnv } from "./taskContextEnv.js";
|
|
17
18
|
/** @typedef {import("./AgentCliEvent.ts").AgentCliEvent} AgentCliEvent */
|
|
18
19
|
|
|
19
20
|
/** @typedef {import("./AgentGenerateOptions.ts").AgentGenerateOptions} AgentGenerateOptions */
|
|
@@ -450,6 +451,27 @@ function buildStreamResult(result) {
|
|
|
450
451
|
fullStream: fullStream,
|
|
451
452
|
};
|
|
452
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Fallback when truncated stdout lost the per-message usage events: the
|
|
456
|
+
* interpreter's completed event carries the harness usage summary (#277).
|
|
457
|
+
* @param {{ usage?: unknown } | null} completedEvent
|
|
458
|
+
* @returns {CliUsageInfo | undefined}
|
|
459
|
+
*/
|
|
460
|
+
function usageFromCompletedEvent(completedEvent) {
|
|
461
|
+
const u = completedEvent?.usage;
|
|
462
|
+
if (!u || typeof u !== "object" || Array.isArray(u))
|
|
463
|
+
return undefined;
|
|
464
|
+
const num = (value) => (typeof value === "number" && Number.isFinite(value) ? value : undefined);
|
|
465
|
+
const usage = {
|
|
466
|
+
inputTokens: num(u.input_tokens) ?? num(u.inputTokens),
|
|
467
|
+
outputTokens: num(u.output_tokens) ?? num(u.outputTokens),
|
|
468
|
+
cacheReadTokens: num(u.cache_read_input_tokens) ?? num(u.cacheReadTokens),
|
|
469
|
+
cacheWriteTokens: num(u.cache_creation_input_tokens) ?? num(u.cacheWriteTokens),
|
|
470
|
+
reasoningTokens: num(u.reasoning_tokens) ?? num(u.reasoningTokens),
|
|
471
|
+
totalTokens: num(u.total_tokens) ?? num(u.totalTokens),
|
|
472
|
+
};
|
|
473
|
+
return Object.values(usage).some((value) => value !== undefined) ? usage : undefined;
|
|
474
|
+
}
|
|
453
475
|
/**
|
|
454
476
|
* @param {string} raw
|
|
455
477
|
* @returns {CliUsageInfo | undefined}
|
|
@@ -628,7 +650,11 @@ export class BaseCliAgent {
|
|
|
628
650
|
idleMs: this.idleTimeoutMs,
|
|
629
651
|
});
|
|
630
652
|
const cwd = this.cwd ?? options?.rootDir ?? process.cwd();
|
|
631
|
-
const env = {
|
|
653
|
+
const env = {
|
|
654
|
+
...process.env,
|
|
655
|
+
...this.env,
|
|
656
|
+
...taskContextEnv(options?.taskContext),
|
|
657
|
+
};
|
|
632
658
|
const combinedSystem = combineNonEmpty([
|
|
633
659
|
this.systemPrompt,
|
|
634
660
|
systemFromMessages,
|
|
@@ -846,6 +872,10 @@ export class BaseCliAgent {
|
|
|
846
872
|
idleTimeoutMs: callTimeouts.idleMs,
|
|
847
873
|
signal: options?.abortSignal,
|
|
848
874
|
maxOutputBytes: this.maxOutputBytes ?? options?.maxOutputBytes,
|
|
875
|
+
// CLI harnesses emit their final result event at the END of
|
|
876
|
+
// the stream; if the capture cap trips, the tail is the part
|
|
877
|
+
// that must survive (#277).
|
|
878
|
+
truncateKeep: "tail",
|
|
849
879
|
onStdout: (chunk) => {
|
|
850
880
|
stdoutEmitter?.push(chunk);
|
|
851
881
|
handleInterpreterChunk("stdout", chunk);
|
|
@@ -858,12 +888,30 @@ export class BaseCliAgent {
|
|
|
858
888
|
flushBufferedLines("stdout", true);
|
|
859
889
|
flushBufferedLines("stderr", true);
|
|
860
890
|
emitEvents(interpreter?.onExit?.(result));
|
|
861
|
-
|
|
891
|
+
if (result.stdoutTruncated) {
|
|
892
|
+
emitEvents({
|
|
893
|
+
type: "action",
|
|
894
|
+
engine: commandSpec.command,
|
|
895
|
+
phase: "completed",
|
|
896
|
+
entryType: "thought",
|
|
897
|
+
action: {
|
|
898
|
+
id: `stdout-truncated-${randomUUID()}`,
|
|
899
|
+
kind: "warning",
|
|
900
|
+
title: "captured stdout truncated",
|
|
901
|
+
detail: {},
|
|
902
|
+
},
|
|
903
|
+
message: "Captured stdout exceeded maxOutputBytes; kept the stream tail. The streamed interpreter answer is used as the result text.",
|
|
904
|
+
ok: true,
|
|
905
|
+
level: "warning",
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
const outputFileText = commandSpec.outputFile
|
|
862
909
|
? yield* Effect.tryPromise({
|
|
863
910
|
try: () => fs.readFile(commandSpec.outputFile, "utf8"),
|
|
864
911
|
catch: (cause) => toSmithersError(cause, "read output file"),
|
|
865
|
-
}).pipe(Effect.catchAll(() => Effect.succeed(
|
|
866
|
-
:
|
|
912
|
+
}).pipe(Effect.catchAll(() => Effect.succeed(null)))
|
|
913
|
+
: null;
|
|
914
|
+
const stdout = typeof outputFileText === "string" ? outputFileText : result.stdout;
|
|
867
915
|
if (result.exitCode && result.exitCode !== 0) {
|
|
868
916
|
const filteredStderr = filterBenignStderr(result.stderr, commandSpec.benignStderrPatterns);
|
|
869
917
|
if (!(commandSpec.command === "codex" && filteredStderr.length === 0)) {
|
|
@@ -929,13 +977,35 @@ export class BaseCliAgent {
|
|
|
929
977
|
}
|
|
930
978
|
}
|
|
931
979
|
}
|
|
932
|
-
const
|
|
933
|
-
?
|
|
980
|
+
const extractedFromStdout = outputFormat === "json" || outputFormat === "stream-json"
|
|
981
|
+
? extractTextFromJsonPayload(rawText)
|
|
934
982
|
: rawText;
|
|
935
|
-
|
|
983
|
+
// The interpreter parses the live stream line-by-line BEFORE the
|
|
984
|
+
// capture cap applies, so its completed answer survives stdout
|
|
985
|
+
// truncation. Prefer it whenever the captured stdout was
|
|
986
|
+
// truncated or yields no final message; otherwise keep the
|
|
987
|
+
// historical extraction so intact runs are unchanged (#277).
|
|
988
|
+
const streamedAnswer = typeof completedEvent?.answer === "string" && completedEvent.answer.trim().length > 0
|
|
989
|
+
? completedEvent.answer
|
|
990
|
+
: undefined;
|
|
991
|
+
// A dedicated final-message file (e.g. codex --output-last-message)
|
|
992
|
+
// is the CLI's authoritative output channel: it holds the complete
|
|
993
|
+
// final message and is immune to the stdout byte cap and to
|
|
994
|
+
// line-by-line stream interpretation. When it parsed as JSON, trust
|
|
995
|
+
// it over the truncation/stream fallbacks, which otherwise surface a
|
|
996
|
+
// short `message` field instead of the full structured object.
|
|
997
|
+
const outputFileJson = typeof outputFileText === "string" && outputFileText.trim() !== ""
|
|
998
|
+
? tryParseJson(outputFileText)
|
|
999
|
+
: null;
|
|
1000
|
+
const extractedText = outputFileJson != null
|
|
1001
|
+
? outputFileText
|
|
1002
|
+
: result.stdoutTruncated || extractedFromStdout == null || extractedFromStdout.trim() === ""
|
|
1003
|
+
? (streamedAnswer ?? extractedFromStdout ?? rawText)
|
|
1004
|
+
: extractedFromStdout;
|
|
1005
|
+
const output = outputFileJson ?? tryParseJson(extractedText);
|
|
936
1006
|
// Extract token usage from raw stdout before text extraction strips it.
|
|
937
1007
|
// Each CLI harness embeds usage differently (NDJSON events, JSON stats, etc.)
|
|
938
|
-
const cliUsage = extractUsageFromOutput(stdout);
|
|
1008
|
+
const cliUsage = extractUsageFromOutput(stdout) ?? usageFromCompletedEvent(completedEvent);
|
|
939
1009
|
const usage = cliUsage ? {
|
|
940
1010
|
inputTokens: cliUsage.inputTokens,
|
|
941
1011
|
inputTokenDetails: {
|
|
@@ -2,4 +2,8 @@ export type RunCommandResult = {
|
|
|
2
2
|
stdout: string;
|
|
3
3
|
stderr: string;
|
|
4
4
|
exitCode: number | null;
|
|
5
|
+
/** True when captured stdout exceeded maxOutputBytes and was truncated. */
|
|
6
|
+
stdoutTruncated?: boolean;
|
|
7
|
+
/** True when captured stderr exceeded maxOutputBytes and was truncated. */
|
|
8
|
+
stderrTruncated?: boolean;
|
|
5
9
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { spawnCaptureEffect } from "@smithers-orchestrator/driver/child-process";
|
|
3
3
|
/**
|
|
4
|
-
* @typedef {{ cwd: string; env: Record<string, string>; input?: string; timeoutMs?: number; idleTimeoutMs?: number; signal?: AbortSignal; maxOutputBytes?: number; onStdout?: (chunk: string) => void; onStderr?: (chunk: string) => void; }} RunCommandOptions
|
|
4
|
+
* @typedef {{ cwd: string; env: Record<string, string>; input?: string; timeoutMs?: number; idleTimeoutMs?: number; signal?: AbortSignal; maxOutputBytes?: number; truncateKeep?: "head" | "tail"; onStdout?: (chunk: string) => void; onStderr?: (chunk: string) => void; }} RunCommandOptions
|
|
5
5
|
*/
|
|
6
6
|
/** @typedef {import("./RunCommandResult.ts").RunCommandResult} RunCommandResult */
|
|
7
7
|
/** @typedef {import("@smithers-orchestrator/errors/SmithersError").SmithersError} SmithersError */
|
|
@@ -13,7 +13,7 @@ import { spawnCaptureEffect } from "@smithers-orchestrator/driver/child-process"
|
|
|
13
13
|
* @returns {Effect.Effect<RunCommandResult, SmithersError>}
|
|
14
14
|
*/
|
|
15
15
|
export function runCommandEffect(command, args, options) {
|
|
16
|
-
const { cwd, env, input, timeoutMs, idleTimeoutMs, signal, maxOutputBytes, onStdout, onStderr, } = options;
|
|
16
|
+
const { cwd, env, input, timeoutMs, idleTimeoutMs, signal, maxOutputBytes, truncateKeep, onStdout, onStderr, } = options;
|
|
17
17
|
return spawnCaptureEffect(command, args, {
|
|
18
18
|
cwd,
|
|
19
19
|
env,
|
|
@@ -22,6 +22,7 @@ export function runCommandEffect(command, args, options) {
|
|
|
22
22
|
timeoutMs,
|
|
23
23
|
idleTimeoutMs,
|
|
24
24
|
maxOutputBytes,
|
|
25
|
+
truncateKeep,
|
|
25
26
|
onStdout,
|
|
26
27
|
onStderr,
|
|
27
28
|
}).pipe(Effect.annotateLogs({
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map a task's run context into the `SMITHERS_*` environment variables that a
|
|
3
|
+
* Smithers-spawned agent — and any subprocess it runs, e.g. `smithers ask-human` —
|
|
4
|
+
* uses to identify the run/node it belongs to. Undefined/blank fields are omitted
|
|
5
|
+
* so we never clobber an inherited value with `"undefined"`.
|
|
6
|
+
*
|
|
7
|
+
* @param {{ runId?: string, nodeId?: string, iteration?: number, attempt?: number } | null | undefined} taskContext
|
|
8
|
+
* @returns {Record<string, string>}
|
|
9
|
+
*/
|
|
10
|
+
export function taskContextEnv(taskContext) {
|
|
11
|
+
if (!taskContext) {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
/** @type {Record<string, string>} */
|
|
15
|
+
const env = {};
|
|
16
|
+
if (typeof taskContext.runId === "string" && taskContext.runId.length > 0) {
|
|
17
|
+
env.SMITHERS_RUN_ID = taskContext.runId;
|
|
18
|
+
}
|
|
19
|
+
if (typeof taskContext.nodeId === "string" && taskContext.nodeId.length > 0) {
|
|
20
|
+
env.SMITHERS_NODE_ID = taskContext.nodeId;
|
|
21
|
+
}
|
|
22
|
+
if (typeof taskContext.iteration === "number" &&
|
|
23
|
+
Number.isInteger(taskContext.iteration)) {
|
|
24
|
+
env.SMITHERS_ITERATION = String(taskContext.iteration);
|
|
25
|
+
}
|
|
26
|
+
if (typeof taskContext.attempt === "number" &&
|
|
27
|
+
Number.isInteger(taskContext.attempt)) {
|
|
28
|
+
env.SMITHERS_ATTEMPT = String(taskContext.attempt);
|
|
29
|
+
}
|
|
30
|
+
return env;
|
|
31
|
+
}
|
package/src/ClaudeCodeAgent.js
CHANGED
|
@@ -383,7 +383,7 @@ export class ClaudeCodeAgent extends BaseCliAgent {
|
|
|
383
383
|
args.push("--chrome");
|
|
384
384
|
if (this.opts.noChrome)
|
|
385
385
|
args.push("--no-chrome");
|
|
386
|
-
if (this.opts.continue)
|
|
386
|
+
if (this.opts.continue || params.options?.continueSession)
|
|
387
387
|
args.push("--continue");
|
|
388
388
|
if (this.opts.debug === true) {
|
|
389
389
|
args.push("--debug");
|
|
@@ -445,9 +445,27 @@ export class ClaudeCodeAgent extends BaseCliAgent {
|
|
|
445
445
|
args.push("--verbose");
|
|
446
446
|
if (this.extraArgs?.length)
|
|
447
447
|
args.push(...this.extraArgs);
|
|
448
|
+
// Durability: inject a PostToolUse hook that calls back into smithers for a
|
|
449
|
+
// strict Tier 1 snapshot at each file-edit / Bash boundary. Only when the
|
|
450
|
+
// engine passes a socket path (durability enabled); additive --settings.
|
|
451
|
+
const durabilitySocket = typeof params.options?.durabilitySocket === "string"
|
|
452
|
+
? params.options.durabilitySocket
|
|
453
|
+
: undefined;
|
|
454
|
+
if (durabilitySocket) {
|
|
455
|
+
args.push("--settings", JSON.stringify({
|
|
456
|
+
hooks: {
|
|
457
|
+
PostToolUse: [{
|
|
458
|
+
matcher: "Write|Edit|MultiEdit|NotebookEdit|Bash",
|
|
459
|
+
hooks: [{ type: "command", command: "smithers snapshot-hook" }],
|
|
460
|
+
}],
|
|
461
|
+
},
|
|
462
|
+
}));
|
|
463
|
+
}
|
|
448
464
|
if (params.prompt)
|
|
449
465
|
args.push(params.prompt);
|
|
450
466
|
const accountEnv = {};
|
|
467
|
+
if (durabilitySocket)
|
|
468
|
+
accountEnv.SMITHERS_SNAPSHOT_SOCK = durabilitySocket;
|
|
451
469
|
if (this.opts.configDir)
|
|
452
470
|
accountEnv.CLAUDE_CONFIG_DIR = this.opts.configDir;
|
|
453
471
|
if (this.opts.apiKey)
|
package/src/ForgeAgent.js
CHANGED
|
@@ -5,6 +5,31 @@ import { randomUUID } from "node:crypto";
|
|
|
5
5
|
/** @typedef {import("./BaseCliAgent/CliOutputInterpreter.ts").CliOutputInterpreter} CliOutputInterpreter */
|
|
6
6
|
/** @typedef {import("./ForgeAgentOptions.ts").ForgeAgentOptions} ForgeAgentOptions */
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @returns {AgentCapabilityRegistry}
|
|
10
|
+
*/
|
|
11
|
+
export function createForgeCapabilityRegistry() {
|
|
12
|
+
return {
|
|
13
|
+
version: 1,
|
|
14
|
+
engine: "forge",
|
|
15
|
+
runtimeTools: {},
|
|
16
|
+
mcp: {
|
|
17
|
+
bootstrap: "unsupported",
|
|
18
|
+
supportsProjectScope: false,
|
|
19
|
+
supportsUserScope: false,
|
|
20
|
+
},
|
|
21
|
+
skills: {
|
|
22
|
+
supportsSkills: false,
|
|
23
|
+
smithersSkillIds: [],
|
|
24
|
+
},
|
|
25
|
+
humanInteraction: {
|
|
26
|
+
supportsUiRequests: false,
|
|
27
|
+
methods: [],
|
|
28
|
+
},
|
|
29
|
+
builtIns: ["default"],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
8
33
|
export class ForgeAgent extends BaseCliAgent {
|
|
9
34
|
opts;
|
|
10
35
|
/** @type {AgentCapabilityRegistry} */
|
|
@@ -17,25 +42,7 @@ export class ForgeAgent extends BaseCliAgent {
|
|
|
17
42
|
constructor(opts = {}) {
|
|
18
43
|
super(opts);
|
|
19
44
|
this.opts = opts;
|
|
20
|
-
this.capabilities =
|
|
21
|
-
version: 1,
|
|
22
|
-
engine: "forge",
|
|
23
|
-
runtimeTools: {},
|
|
24
|
-
mcp: {
|
|
25
|
-
bootstrap: "unsupported",
|
|
26
|
-
supportsProjectScope: false,
|
|
27
|
-
supportsUserScope: false,
|
|
28
|
-
},
|
|
29
|
-
skills: {
|
|
30
|
-
supportsSkills: false,
|
|
31
|
-
smithersSkillIds: [],
|
|
32
|
-
},
|
|
33
|
-
humanInteraction: {
|
|
34
|
-
supportsUiRequests: false,
|
|
35
|
-
methods: [],
|
|
36
|
-
},
|
|
37
|
-
builtIns: ["default"],
|
|
38
|
-
};
|
|
45
|
+
this.capabilities = createForgeCapabilityRegistry();
|
|
39
46
|
}
|
|
40
47
|
/**
|
|
41
48
|
* @returns {CliOutputInterpreter}
|
package/src/HermesAgent.js
CHANGED
|
@@ -29,7 +29,7 @@ export class HermesAgent extends OpenAIAgent {
|
|
|
29
29
|
nativeStructuredOutput = false,
|
|
30
30
|
...rest
|
|
31
31
|
} = opts;
|
|
32
|
-
if (baseURL === undefined) {
|
|
32
|
+
if (baseURL === undefined || baseURL.trim() === "") {
|
|
33
33
|
throw new SmithersError(
|
|
34
34
|
"AGENT_CONFIG_INVALID",
|
|
35
35
|
"HermesAgent requires a baseURL (or the HERMES_BASE_URL env var) pointing at the Hermes OpenAI-compatible API, e.g. http://127.0.0.1:5123/v1.",
|