@smithers-orchestrator/agents 0.22.0 → 0.23.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smithers-orchestrator/agents",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "AI SDK and CLI agent adapters for Smithers",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -47,12 +47,13 @@
47
47
  "dependencies": {
48
48
  "@ai-sdk/anthropic": "^3.0.71",
49
49
  "@ai-sdk/openai": "^3.0.53",
50
+ "@modelcontextprotocol/sdk": "^1.29.0",
50
51
  "ai": "^6.0.168",
51
52
  "effect": "^3.21.1",
52
53
  "zod": "^4.3.6",
53
- "@smithers-orchestrator/driver": "0.22.0",
54
- "@smithers-orchestrator/errors": "0.22.0",
55
- "@smithers-orchestrator/observability": "0.22.0"
54
+ "@smithers-orchestrator/errors": "0.23.0",
55
+ "@smithers-orchestrator/driver": "0.23.0",
56
+ "@smithers-orchestrator/observability": "0.23.0"
56
57
  },
57
58
  "devDependencies": {
58
59
  "@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}
@@ -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
- if (this.opts.debug)
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
- pushList(args, "--extensions", this.opts.extensions);
236
- if (this.opts.listExtensions)
237
- args.push("--list-extensions");
238
- pushFlag(args, "--resume", resumeSession);
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("--prompt", fullPrompt);
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. Passed as `--gemini_dir` so
26
- * Antigravity reads/writes `<configDir>/antigravity-cli/...` instead of the
27
- * user's default `~/.gemini/antigravity-cli/...`.
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 */
@@ -628,7 +629,11 @@ export class BaseCliAgent {
628
629
  idleMs: this.idleTimeoutMs,
629
630
  });
630
631
  const cwd = this.cwd ?? options?.rootDir ?? process.cwd();
631
- const env = { ...process.env, ...this.env };
632
+ const env = {
633
+ ...process.env,
634
+ ...this.env,
635
+ ...taskContextEnv(options?.taskContext),
636
+ };
632
637
  const combinedSystem = combineNonEmpty([
633
638
  this.systemPrompt,
634
639
  systemFromMessages,
@@ -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
+ }
@@ -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}
@@ -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.",
@@ -0,0 +1,214 @@
1
+ import {
2
+ BaseCliAgent,
3
+ pushFlag,
4
+ isRecord,
5
+ asString,
6
+ createSyntheticIdGenerator,
7
+ } from "./BaseCliAgent/index.js";
8
+
9
+ /** @typedef {import("./BaseCliAgent/index.ts").BaseCliAgentOptions} BaseCliAgentOptions */
10
+ /** @typedef {import("./capability-registry/index.ts").AgentCapabilityRegistry} AgentCapabilityRegistry */
11
+ /** @typedef {import("./BaseCliAgent/index.ts").CliOutputInterpreter} CliOutputInterpreter */
12
+
13
+ /**
14
+ * @param {VibeAgentOptions} [opts]
15
+ * @returns {AgentCapabilityRegistry}
16
+ */
17
+ export function createVibeCapabilityRegistry(opts = {}) {
18
+ return {
19
+ version: 1,
20
+ engine: "vibe",
21
+ runtimeTools: {},
22
+ mcp: {
23
+ bootstrap: "project-config",
24
+ supportsProjectScope: true,
25
+ supportsUserScope: true,
26
+ },
27
+ skills: {
28
+ supportsSkills: true,
29
+ installMode: "plugin",
30
+ smithersSkillIds: [],
31
+ },
32
+ humanInteraction: {
33
+ supportsUiRequests: false,
34
+ methods: [],
35
+ },
36
+ builtIns: ["default"],
37
+ };
38
+ }
39
+
40
+ /**
41
+ * @typedef {BaseCliAgentOptions & {
42
+ * agent?: string;
43
+ * maxTurns?: number;
44
+ * maxPrice?: number;
45
+ * maxTokens?: number;
46
+ * enabledTools?: string[];
47
+ * sessionId?: string;
48
+ * continueSession?: boolean;
49
+ * }} VibeAgentOptions
50
+ */
51
+
52
+ export class VibeAgent extends BaseCliAgent {
53
+ /** @type {VibeAgentOptions} */
54
+ opts;
55
+ /** @type {AgentCapabilityRegistry} */
56
+ capabilities;
57
+ /** @type {"vibe"} */
58
+ cliEngine = "vibe";
59
+ /** @type {string | undefined} */
60
+ issuedSessionId;
61
+
62
+ /**
63
+ * @param {VibeAgentOptions} [opts]
64
+ */
65
+ constructor(opts = {}) {
66
+ super({ ...opts, yolo: opts.yolo ?? false });
67
+ this.opts = opts;
68
+ this.capabilities = createVibeCapabilityRegistry(opts);
69
+ }
70
+
71
+ /**
72
+ * @returns {CliOutputInterpreter}
73
+ */
74
+ createOutputInterpreter() {
75
+ let finalAnswer = "";
76
+ let didEmitStarted = false;
77
+ let didEmitCompleted = false;
78
+ const nextSyntheticId = createSyntheticIdGenerator();
79
+
80
+ /**
81
+ * @param {string} line
82
+ * @returns {import("./BaseCliAgent/index.ts").AgentCliEvent[]}
83
+ */
84
+ const parseLine = (line) => {
85
+ const trimmed = line.trim();
86
+ if (!trimmed) return [];
87
+
88
+ /** @type {Record<string, unknown>} */
89
+ let payload;
90
+ try {
91
+ payload = JSON.parse(trimmed);
92
+ } catch {
93
+ return [];
94
+ }
95
+
96
+ if (!isRecord(payload)) return [];
97
+ const role = asString(payload.role);
98
+ const content = asString(payload.content);
99
+ if (role !== "assistant" || !content) return [];
100
+
101
+ const events = [];
102
+
103
+ if (!didEmitStarted) {
104
+ didEmitStarted = true;
105
+ events.push({
106
+ type: "started",
107
+ engine: this.cliEngine,
108
+ title: "Vibe",
109
+ resume: this.issuedSessionId,
110
+ });
111
+ }
112
+
113
+ const id = nextSyntheticId("vibe-message");
114
+ finalAnswer += content;
115
+
116
+ events.push({
117
+ type: "action",
118
+ engine: this.cliEngine,
119
+ phase: "updated",
120
+ entryType: "message",
121
+ action: { id, kind: "turn", title: "assistant" },
122
+ message: content,
123
+ });
124
+
125
+ return events;
126
+ };
127
+
128
+ return {
129
+ onStdoutLine: parseLine,
130
+
131
+ onExit: (result) => {
132
+ if (didEmitCompleted) return [];
133
+ didEmitCompleted = true;
134
+
135
+ const ok = (result.exitCode ?? 0) === 0;
136
+
137
+ const events = [];
138
+ if (!didEmitStarted) {
139
+ events.push({
140
+ type: "started",
141
+ engine: this.cliEngine,
142
+ title: "Vibe",
143
+ resume: this.issuedSessionId,
144
+ });
145
+ }
146
+
147
+ events.push({
148
+ type: "completed",
149
+ engine: this.cliEngine,
150
+ ok,
151
+ answer: ok ? finalAnswer || undefined : undefined,
152
+ error: ok
153
+ ? undefined
154
+ : result.stderr?.trim() ||
155
+ `vibe exited with code ${result.exitCode ?? -1}`,
156
+ resume: this.issuedSessionId,
157
+ });
158
+
159
+ return events;
160
+ },
161
+ };
162
+ }
163
+
164
+ /**
165
+ * @param {{ prompt: string; systemPrompt?: string; cwd: string; options: any }} params
166
+ */
167
+ async buildCommand(params) {
168
+ const args = [];
169
+
170
+ const resumeSession = typeof params.options?.resumeSession === "string"
171
+ ? params.options.resumeSession
172
+ : undefined;
173
+ const effectiveSession = resumeSession ?? this.opts.sessionId;
174
+ this.issuedSessionId = effectiveSession;
175
+ if (this.opts.continueSession && !effectiveSession) {
176
+ args.push("-c");
177
+ }
178
+ pushFlag(args, "--resume", effectiveSession);
179
+
180
+ pushFlag(args, "--agent", this.opts.agent);
181
+ if (this.opts.maxTurns !== undefined) {
182
+ args.push("--max-turns", String(this.opts.maxTurns));
183
+ }
184
+ if (this.opts.maxPrice !== undefined) {
185
+ args.push("--max-price", String(this.opts.maxPrice));
186
+ }
187
+ if (this.opts.maxTokens !== undefined) {
188
+ args.push("--max-tokens", String(this.opts.maxTokens));
189
+ }
190
+ if (this.opts.enabledTools?.length) {
191
+ for (const tool of this.opts.enabledTools) {
192
+ args.push("--enabled-tools", tool);
193
+ }
194
+ }
195
+
196
+ args.push("--trust");
197
+ args.push("--output", "streaming");
198
+ pushFlag(args, "--workdir", params.cwd);
199
+
200
+ if (this.extraArgs?.length) args.push(...this.extraArgs);
201
+
202
+ const systemPrefix = params.systemPrompt
203
+ ? `${params.systemPrompt}\n\n`
204
+ : "";
205
+ const fullPrompt = `${systemPrefix}${params.prompt ?? ""}`;
206
+ pushFlag(args, "--prompt", fullPrompt);
207
+
208
+ return {
209
+ command: "vibe",
210
+ args,
211
+ outputFormat: "stream-json",
212
+ };
213
+ }
214
+ }
@@ -0,0 +1,11 @@
1
+ import type { BaseCliAgentOptions } from "./BaseCliAgent/BaseCliAgentOptions";
2
+
3
+ export type VibeAgentOptions = BaseCliAgentOptions & {
4
+ agent?: string;
5
+ maxTurns?: number;
6
+ maxPrice?: number;
7
+ maxTokens?: number;
8
+ enabledTools?: string[];
9
+ sessionId?: string;
10
+ continueSession?: boolean;
11
+ };
@@ -30,6 +30,7 @@ const WORKFLOW_TOOL_NAMES = new Set([
30
30
  ]);
31
31
  const APPROVAL_TOOL_NAMES = new Set([
32
32
  "approve",
33
+ "ask_human",
33
34
  "deny",
34
35
  "list_pending_approvals",
35
36
  "resolve_approval",