actoviq-agent-sdk 0.4.2 → 0.4.5

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.
Files changed (100) hide show
  1. package/README-zh.md +58 -62
  2. package/README.md +72 -22
  3. package/assets/actoviq-icon.png +0 -0
  4. package/bin/actoviq-gui.js +24 -0
  5. package/dist/src/cli/actoviq-react.js +31 -14
  6. package/dist/src/cli/actoviq-react.js.map +1 -1
  7. package/dist/src/cli/actoviq-tui.js +9 -0
  8. package/dist/src/cli/actoviq-tui.js.map +1 -1
  9. package/dist/src/cli/bridge-interactive-agent.js +94 -15
  10. package/dist/src/cli/bridge-interactive-agent.js.map +1 -1
  11. package/dist/src/cli/version.d.ts +3 -0
  12. package/dist/src/cli/version.d.ts.map +1 -0
  13. package/dist/src/cli/version.js +28 -0
  14. package/dist/src/cli/version.js.map +1 -0
  15. package/dist/src/hooks/userHooks.d.ts +39 -0
  16. package/dist/src/hooks/userHooks.d.ts.map +1 -0
  17. package/dist/src/hooks/userHooks.js +169 -0
  18. package/dist/src/hooks/userHooks.js.map +1 -0
  19. package/dist/src/index.d.ts +2 -0
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/index.js +2 -0
  22. package/dist/src/index.js.map +1 -1
  23. package/dist/src/mcp/mcpServerConfig.d.ts +19 -0
  24. package/dist/src/mcp/mcpServerConfig.d.ts.map +1 -0
  25. package/dist/src/mcp/mcpServerConfig.js +51 -0
  26. package/dist/src/mcp/mcpServerConfig.js.map +1 -0
  27. package/dist/src/memory/projectContext.d.ts +29 -0
  28. package/dist/src/memory/projectContext.d.ts.map +1 -0
  29. package/dist/src/memory/projectContext.js +107 -0
  30. package/dist/src/memory/projectContext.js.map +1 -0
  31. package/dist/src/parity/actoviqBridgeSdk.d.ts +3 -0
  32. package/dist/src/parity/actoviqBridgeSdk.d.ts.map +1 -1
  33. package/dist/src/parity/actoviqBridgeSdk.js +93 -92
  34. package/dist/src/parity/actoviqBridgeSdk.js.map +1 -1
  35. package/dist/src/parity/bridgeConfigs.d.ts +20 -0
  36. package/dist/src/parity/bridgeConfigs.d.ts.map +1 -0
  37. package/dist/src/parity/bridgeConfigs.js +110 -0
  38. package/dist/src/parity/bridgeConfigs.js.map +1 -0
  39. package/dist/src/parity/bridgeEventAdapter.d.ts +20 -0
  40. package/dist/src/parity/bridgeEventAdapter.d.ts.map +1 -0
  41. package/dist/src/parity/bridgeEventAdapter.js +121 -0
  42. package/dist/src/parity/bridgeEventAdapter.js.map +1 -0
  43. package/dist/src/parity/bridgeExecResolver.d.ts +27 -0
  44. package/dist/src/parity/bridgeExecResolver.d.ts.map +1 -0
  45. package/dist/src/parity/bridgeExecResolver.js +95 -0
  46. package/dist/src/parity/bridgeExecResolver.js.map +1 -0
  47. package/dist/src/parity/bridgeProviders.d.ts +132 -0
  48. package/dist/src/parity/bridgeProviders.d.ts.map +1 -0
  49. package/dist/src/parity/bridgeProviders.js +651 -0
  50. package/dist/src/parity/bridgeProviders.js.map +1 -0
  51. package/dist/src/prompts/outputStyles.d.ts +20 -0
  52. package/dist/src/prompts/outputStyles.d.ts.map +1 -0
  53. package/dist/src/prompts/outputStyles.js +35 -0
  54. package/dist/src/prompts/outputStyles.js.map +1 -0
  55. package/dist/src/prompts/systemPrompt.d.ts.map +1 -1
  56. package/dist/src/prompts/systemPrompt.js +9 -1
  57. package/dist/src/prompts/systemPrompt.js.map +1 -1
  58. package/dist/src/router/modelRouter.d.ts +7 -1
  59. package/dist/src/router/modelRouter.d.ts.map +1 -1
  60. package/dist/src/router/modelRouter.js +50 -15
  61. package/dist/src/router/modelRouter.js.map +1 -1
  62. package/dist/src/runtime/agentSession.d.ts +6 -0
  63. package/dist/src/runtime/agentSession.d.ts.map +1 -1
  64. package/dist/src/runtime/agentSession.js +15 -0
  65. package/dist/src/runtime/agentSession.js.map +1 -1
  66. package/dist/src/runtime/bashClassification.d.ts +18 -0
  67. package/dist/src/runtime/bashClassification.d.ts.map +1 -0
  68. package/dist/src/runtime/bashClassification.js +83 -0
  69. package/dist/src/runtime/bashClassification.js.map +1 -0
  70. package/dist/src/team/modelTeam.d.ts +3 -5
  71. package/dist/src/team/modelTeam.d.ts.map +1 -1
  72. package/dist/src/team/modelTeam.js +98 -162
  73. package/dist/src/team/modelTeam.js.map +1 -1
  74. package/dist/src/team/teamRuntime.d.ts +60 -0
  75. package/dist/src/team/teamRuntime.d.ts.map +1 -0
  76. package/dist/src/team/teamRuntime.js +139 -0
  77. package/dist/src/team/teamRuntime.js.map +1 -0
  78. package/dist/src/tools/actoviqCoreTools.d.ts +1 -0
  79. package/dist/src/tools/actoviqCoreTools.d.ts.map +1 -1
  80. package/dist/src/tools/actoviqCoreTools.js +5 -0
  81. package/dist/src/tools/actoviqCoreTools.js.map +1 -1
  82. package/dist/src/tools/planMode/PlanModeTools.d.ts +18 -0
  83. package/dist/src/tools/planMode/PlanModeTools.d.ts.map +1 -0
  84. package/dist/src/tools/planMode/PlanModeTools.js +71 -0
  85. package/dist/src/tools/planMode/PlanModeTools.js.map +1 -0
  86. package/dist/src/tui/actoviqTui.d.ts +3 -0
  87. package/dist/src/tui/actoviqTui.d.ts.map +1 -1
  88. package/dist/src/tui/actoviqTui.js +1468 -91
  89. package/dist/src/tui/actoviqTui.js.map +1 -1
  90. package/dist/src/tui/transcript.d.ts +6 -0
  91. package/dist/src/tui/transcript.d.ts.map +1 -1
  92. package/dist/src/tui/transcript.js +27 -0
  93. package/dist/src/tui/transcript.js.map +1 -1
  94. package/dist/src/types.d.ts +128 -9
  95. package/dist/src/types.d.ts.map +1 -1
  96. package/dist/src/ui/commandSurface.d.ts +13 -0
  97. package/dist/src/ui/commandSurface.d.ts.map +1 -0
  98. package/dist/src/ui/commandSurface.js +100 -0
  99. package/dist/src/ui/commandSurface.js.map +1 -0
  100. package/package.json +9 -3
@@ -0,0 +1,651 @@
1
+ /**
2
+ * Multi-provider abstraction for the bridge SDK's directCli mode.
3
+ *
4
+ * The vendored-bundle path and the original Claude Code `-p` stream-json path
5
+ * are both the `claude` provider. `pi` and `codex` reuse the same spawn +
6
+ * line-by-line JSONL pipeline but speak their own wire protocols, so each
7
+ * provider supplies four pieces the rest of `actoviqBridgeSdk.ts` stays
8
+ * protocol-agnostic around:
9
+ *
10
+ * 1. executable resolution (which binary on PATH, default error message)
11
+ * 2. argv construction (the provider's CLI flags + prompt placement)
12
+ * 3. child-process env injection (provider-specific credential variables)
13
+ * 4. event normalization (provider JSONL → the `system/assistant/result`
14
+ * trio `execute()` already switches on — plus claude content-block shape)
15
+ *
16
+ * Normalization is stateful (pi's session id arrives once in a header line;
17
+ * codex's model is never in the stream; assistant text accumulates across
18
+ * deltas), so each provider hands back a fresh normalizer per run.
19
+ */
20
+ import { execFile } from 'node:child_process';
21
+ import { ActoviqBridgeProcessError } from '../errors.js';
22
+ import { mapActoviqEnvToAnthropicEnv } from '../config/anthropicEnvMapping.js';
23
+ import { getLoadedJsonConfig } from '../config/loadJsonConfigFile.js';
24
+ import { findExecutableOnPath, IS_WINDOWS, pathExists, } from './bridgeExecResolver.js';
25
+ function execFileAsync(file, args, options) {
26
+ return new Promise((resolve, reject) => {
27
+ execFile(file, args, options, (error, stdout, stderr) => {
28
+ if (error)
29
+ reject(error);
30
+ else
31
+ resolve({ stdout: stdout, stderr: stderr });
32
+ });
33
+ });
34
+ }
35
+ /** Shared base for executable resolution (PATH lookup + explicit-path check). */
36
+ export class BaseRuntimeProvider {
37
+ /**
38
+ * Resolve the executable for a run. Precedence (all in-memory; mirrors
39
+ * `buildChildEnvironment` in actoviqBridgeSdk.ts — no file I/O here):
40
+ * 1. `explicitPath` arg (caller-supplied `{ executable }`)
41
+ * 2. `ACTOVIQ_<ID>_PATH` env var (top-level or `env:` block — both are
42
+ * captured by `extractEnv` in loadJsonConfigFile.ts)
43
+ * 3. `raw.bridge.providers[id].path` from the loaded settings store
44
+ * 4. `findExecutableOnPath(this.pathBinary)` — the binary on PATH
45
+ *
46
+ * Mirrors the `ACTOVIQ_BASH_PATH` precedent in src/tools/bash/BashTool.ts.
47
+ */
48
+ async resolveExecutable(explicitPath) {
49
+ // For user-specified paths (explicit, env, settings block), just check
50
+ // existence — the user knows what they're pointing at, and scripts (.mjs,
51
+ // .js, …) often lack +x on Linux. Only PATH lookups need the stricter
52
+ // isExecutable check so we don't pick up non-runnable files.
53
+ if (explicitPath) {
54
+ if (!(await pathExists(explicitPath))) {
55
+ throw new ActoviqBridgeProcessError(`The configured executable was not found: ${explicitPath}`);
56
+ }
57
+ return explicitPath;
58
+ }
59
+ const envVar = `ACTOVIQ_${this.id.toUpperCase()}_PATH`;
60
+ const loaded = getLoadedJsonConfig();
61
+ const settingsEnvPath = typeof loaded?.env?.[envVar] === 'string' ? loaded.env[envVar] : undefined;
62
+ const processEnvPath = process.env[envVar];
63
+ const envPath = settingsEnvPath ?? processEnvPath;
64
+ if (envPath) {
65
+ if (!(await pathExists(envPath))) {
66
+ throw new ActoviqBridgeProcessError(`${envVar} (${envPath}) was not found.`);
67
+ }
68
+ return envPath;
69
+ }
70
+ const settingsBlockPath = readSettingsBlockPath(loaded?.raw, this.id);
71
+ if (settingsBlockPath) {
72
+ if (!(await pathExists(settingsBlockPath))) {
73
+ throw new ActoviqBridgeProcessError(`Configured ${this.id} bridge path (${settingsBlockPath}) was not found.`);
74
+ }
75
+ return settingsBlockPath;
76
+ }
77
+ const pathCandidate = await findExecutableOnPath(this.pathBinary);
78
+ if (pathCandidate) {
79
+ return pathCandidate;
80
+ }
81
+ throw new ActoviqBridgeProcessError(`No "${this.pathBinary}" executable was found on PATH. Install ${this.displayName}, set ${envVar}, or run \`/bridge\` to configure it.`);
82
+ }
83
+ async probeVersion(executablePath) {
84
+ // Windows npm shims are `.cmd`/`.bat` and need a shell to run. Mirror the
85
+ // spawn shape used by actoviqBridgeSdk.ts:1536.
86
+ try {
87
+ const { stdout } = await execFileAsync(executablePath, ['--version'], {
88
+ shell: IS_WINDOWS && /\.(?:cmd|bat)$/i.test(executablePath),
89
+ windowsHide: true,
90
+ timeout: 10_000,
91
+ maxBuffer: 1024 * 1024,
92
+ });
93
+ const trimmed = (stdout ?? '').trim();
94
+ return trimmed || undefined;
95
+ }
96
+ catch {
97
+ return undefined;
98
+ }
99
+ }
100
+ }
101
+ /**
102
+ * Build a single normalized event with common optional fields pre-stamped.
103
+ * Normalizers route every native event through this so the downstream
104
+ * `execute()` switch (system/assistant/result) and `runtimeInfoFromInitEvent`
105
+ * keep working untouched.
106
+ */
107
+ export function bridgeEvent(type, fields = {}) {
108
+ return { type, ...fields };
109
+ }
110
+ let currentProvider;
111
+ /**
112
+ * Read the per-provider path override from the `bridge.providers[id].path`
113
+ * settings block (in-memory only — the caller persists via the settings store).
114
+ */
115
+ function readSettingsBlockPath(raw, id) {
116
+ if (!raw)
117
+ return undefined;
118
+ const bridge = raw.bridge;
119
+ if (!bridge || typeof bridge !== 'object')
120
+ return undefined;
121
+ const providers = bridge.providers;
122
+ if (!providers || typeof providers !== 'object')
123
+ return undefined;
124
+ const entry = providers[id];
125
+ if (!entry || typeof entry !== 'object')
126
+ return undefined;
127
+ const p = entry.path;
128
+ return typeof p === 'string' && p ? p : undefined;
129
+ }
130
+ /**
131
+ * The configured default provider, read from `bridge.defaultProvider` in the
132
+ * loaded settings store. Falls back to `'claude'`. Explicit
133
+ * `directCliProvider` (passed to `resolveProvider`) always wins over this.
134
+ */
135
+ export function getDefaultProviderId() {
136
+ const raw = getLoadedJsonConfig()?.raw;
137
+ if (raw && typeof raw === 'object') {
138
+ const bridge = raw.bridge;
139
+ if (bridge && typeof bridge === 'object') {
140
+ const dp = bridge.defaultProvider;
141
+ if (dp === 'claude' || dp === 'pi' || dp === 'codex' || dp === 'codewhale' || dp === 'reasonix' || dp === 'crush')
142
+ return dp;
143
+ }
144
+ }
145
+ return 'claude';
146
+ }
147
+ export function resolveProvider(id) {
148
+ const resolved = id ?? getDefaultProviderId();
149
+ if (resolved === 'claude')
150
+ return claudeProvider;
151
+ if (resolved === 'pi')
152
+ return piProvider;
153
+ if (resolved === 'codex')
154
+ return codexProvider;
155
+ if (resolved === 'codewhale')
156
+ return codewhaleProvider;
157
+ if (resolved === 'reasonix')
158
+ return reasonixProvider;
159
+ if (resolved === 'crush')
160
+ return crushProvider;
161
+ throw new ActoviqBridgeProcessError(`Unknown bridge provider: ${String(resolved)}`);
162
+ }
163
+ /**
164
+ * Probe the locally installed agent CLIs. Resolves each provider via the
165
+ * env/settings/PATH chain (so env overrides are honored) and best-effort
166
+ * `--version`. Never throws — a missing provider is reported as
167
+ * `available: false` with `path: undefined`.
168
+ */
169
+ export async function detectBridgeProviders() {
170
+ const results = [];
171
+ for (const provider of [claudeProvider, piProvider, codexProvider, codewhaleProvider, reasonixProvider, crushProvider]) {
172
+ let path;
173
+ let available = false;
174
+ let version;
175
+ try {
176
+ path = await provider.resolveExecutable();
177
+ available = Boolean(path);
178
+ if (path) {
179
+ version = await provider.probeVersion(path);
180
+ }
181
+ }
182
+ catch {
183
+ // Not installed / not configured — report unavailable.
184
+ }
185
+ results.push({
186
+ id: provider.id,
187
+ displayName: provider.displayName,
188
+ path,
189
+ available,
190
+ version,
191
+ });
192
+ }
193
+ return results;
194
+ }
195
+ /** Package-private seam for tests that need the ambient provider. */
196
+ export function _setCurrentProvider(provider) {
197
+ currentProvider = provider;
198
+ }
199
+ export function _getCurrentProvider() {
200
+ return currentProvider;
201
+ }
202
+ // ---------------------------------------------------------------------------
203
+ // claude provider (stream-json — the original protocol)
204
+ // ---------------------------------------------------------------------------
205
+ class ClaudeProvider extends BaseRuntimeProvider {
206
+ id = 'claude';
207
+ pathBinary = 'claude';
208
+ displayName = 'Claude Code (@anthropic-ai/claude-code)';
209
+ buildArgs(prompt, _options) {
210
+ // The full flag set lives in actoviqBridgeSdk.buildCliArgs(); claude is the
211
+ // default and keeps using that builder. Other providers override buildArgs.
212
+ // (buildCliArgs is invoked directly from actoviqBridgeSdk.ts for the claude
213
+ // path; this indirection exists so the provider list is exhaustive.)
214
+ return ['-p', prompt];
215
+ }
216
+ buildChildEnv(baseEnv, settingsEnv, overrides) {
217
+ // Actoviq settings are the single source of model/credential config: derive
218
+ // ANTHROPIC_* equivalents so the Claude Code-based child process does not
219
+ // silently fall back to ~/.claude/settings.json or keychain credentials.
220
+ // Derived values override inherited process.env ANTHROPIC_* entries, while
221
+ // explicit ANTHROPIC_* keys in the settings env block and caller overrides win.
222
+ return {
223
+ ...baseEnv,
224
+ ...mapActoviqEnvToAnthropicEnv(settingsEnv),
225
+ ...settingsEnv,
226
+ ...(overrides ?? {}),
227
+ };
228
+ }
229
+ createNormalizer() {
230
+ // claude's stream-json is already the canonical system/assistant/result shape.
231
+ return { translate: raw => [raw] };
232
+ }
233
+ suggestedModels() {
234
+ return ['claude-sonnet-4-6', 'claude-opus-4-8', 'claude-haiku-4-5', 'claude-fable-5'];
235
+ }
236
+ }
237
+ // ---------------------------------------------------------------------------
238
+ // pi provider (pi -p --mode json)
239
+ // ---------------------------------------------------------------------------
240
+ class PiProvider extends BaseRuntimeProvider {
241
+ id = 'pi';
242
+ pathBinary = 'pi';
243
+ displayName = 'pi CLI (@earendil-works/pi-coding-agent)';
244
+ buildArgs(prompt, options) {
245
+ const args = ['-p', '--mode', 'json'];
246
+ // Non-interactive, no trust prompt — matches a headless bridge run.
247
+ // (Previously --no-session made every turn ephemeral; we now persist so
248
+ // multi-turn works: first turn --session-id <uuid> creates, later turns
249
+ // --session <uuid> resumes. The PiNormalizer surfaces the same id back.)
250
+ args.push('--no-approve');
251
+ if (options.model) {
252
+ // pi accepts "provider/id"; pass through unchanged.
253
+ args.push('--model', options.model);
254
+ }
255
+ if (options.appendSystemPrompt) {
256
+ args.push('--append-system-prompt', options.appendSystemPrompt);
257
+ }
258
+ else if (options.systemPrompt) {
259
+ args.push('--system-prompt', options.systemPrompt);
260
+ }
261
+ // Multi-turn session threading. ActoviqBridgeSession sets sessionId on
262
+ // the first turn and resume: this.id afterwards.
263
+ if (typeof options.resume === 'string') {
264
+ args.push('--session', options.resume);
265
+ }
266
+ else if (options.resume === true) {
267
+ args.push('--resume');
268
+ }
269
+ else if (options.sessionId) {
270
+ args.push('--session-id', options.sessionId);
271
+ }
272
+ else if (options.continueMostRecent) {
273
+ args.push('--continue');
274
+ }
275
+ // pi takes the prompt as the final positional argument.
276
+ args.push(prompt);
277
+ return args;
278
+ }
279
+ buildChildEnv(baseEnv, settingsEnv, overrides) {
280
+ // pi reads *_API_KEY by provider (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.).
281
+ // We pass the Actoviq settings env through unchanged; callers set the
282
+ // provider-specific key directly. No ANTHROPIC_* remapping for non-claude.
283
+ return { ...baseEnv, ...settingsEnv, ...(overrides ?? {}) };
284
+ }
285
+ createNormalizer() {
286
+ return new PiNormalizer();
287
+ }
288
+ suggestedModels() {
289
+ return ['gpt-5', 'gpt-5-mini', 'claude-sonnet-4-6', 'deepseek-v4-pro', 'gemini-2.5-pro'];
290
+ }
291
+ }
292
+ class PiNormalizer {
293
+ sessionId;
294
+ cwd;
295
+ model;
296
+ initEmitted = false;
297
+ pendingAssistantText = '';
298
+ translate(raw) {
299
+ const type = typeof raw.type === 'string' ? raw.type : '';
300
+ if (type === 'session') {
301
+ this.sessionId = typeof raw.id === 'string' ? raw.id : this.sessionId;
302
+ this.cwd = typeof raw.cwd === 'string' ? raw.cwd : this.cwd;
303
+ // No init emission yet — wait for agent_start so we mirror claude's
304
+ // "runtime ready" semantics. The session header carries no model.
305
+ return [];
306
+ }
307
+ if (type === 'agent_start' && !this.initEmitted) {
308
+ this.initEmitted = true;
309
+ const init = bridgeEvent('system', {
310
+ subtype: 'init',
311
+ session_id: this.sessionId ?? '',
312
+ cwd: this.cwd,
313
+ // pi exposes no tool/skill/agent catalog in its stream; introspection
314
+ // methods will return empty/limited data for this provider.
315
+ tools: [],
316
+ mcp_servers: [],
317
+ slash_commands: [],
318
+ agents: [],
319
+ skills: [],
320
+ plugins: [],
321
+ model: this.model,
322
+ });
323
+ return [init];
324
+ }
325
+ if (type === 'message_update') {
326
+ // Assistant streaming deltas. Accumulate text; emit assistant text deltas
327
+ // in the claude stream_event shape so existing delta consumers work.
328
+ const ame = raw.assistantMessageEvent;
329
+ if (isRecord(ame) && ame.type === 'text_delta' && typeof ame.delta === 'string') {
330
+ this.pendingAssistantText += ame.delta;
331
+ return [bridgeEvent('stream_event', {
332
+ session_id: this.sessionId ?? '',
333
+ event: {
334
+ type: 'content_block_delta',
335
+ index: 0,
336
+ delta: { type: 'text_delta', text: ame.delta },
337
+ },
338
+ })];
339
+ }
340
+ return [];
341
+ }
342
+ if (type === 'message_end') {
343
+ const message = raw.message;
344
+ if (isRecord(message) && message.role === 'assistant') {
345
+ // Capture model from the finalized assistant message (only place it appears).
346
+ if (typeof message.model === 'string') {
347
+ this.model = message.model;
348
+ }
349
+ const text = extractPiAssistantText(message);
350
+ return [bridgeEvent('assistant', {
351
+ session_id: this.sessionId ?? '',
352
+ message: {
353
+ role: 'assistant',
354
+ content: text ? [{ type: 'text', text }] : [],
355
+ },
356
+ })];
357
+ }
358
+ return [];
359
+ }
360
+ if (type === 'agent_end') {
361
+ return [bridgeEvent('result', {
362
+ subtype: 'success',
363
+ session_id: this.sessionId ?? '',
364
+ is_error: false,
365
+ result: this.pendingAssistantText,
366
+ stop_reason: 'end_turn',
367
+ num_turns: 1,
368
+ })];
369
+ }
370
+ return [];
371
+ }
372
+ }
373
+ function extractPiAssistantText(message) {
374
+ const content = message.content;
375
+ if (!Array.isArray(content)) {
376
+ return typeof content === 'string' ? content : '';
377
+ }
378
+ return content
379
+ .map(block => {
380
+ if (!isRecord(block))
381
+ return '';
382
+ return block.type === 'text' && typeof block.text === 'string' ? block.text : '';
383
+ })
384
+ .join('');
385
+ }
386
+ // ---------------------------------------------------------------------------
387
+ // codex provider (codex exec --json)
388
+ // ---------------------------------------------------------------------------
389
+ class CodexProvider extends BaseRuntimeProvider {
390
+ id = 'codex';
391
+ pathBinary = 'codex';
392
+ displayName = 'Codex CLI (@openai/codex)';
393
+ buildArgs(prompt, options) {
394
+ const args = ['exec', '--json', '--skip-git-repo-check', '--color', 'never', '--ephemeral'];
395
+ // Autonomous: no approval prompt can block a non-TTY run.
396
+ args.push('--dangerously-bypass-approvals-and-sandbox');
397
+ if (options.model) {
398
+ args.push('-m', options.model);
399
+ }
400
+ if (options.systemPrompt) {
401
+ args.push('-c', `system_prompt="${options.systemPrompt.replace(/"/g, '\\"')}"`);
402
+ }
403
+ if (typeof options.maxTurns === 'number') {
404
+ args.push('-c', `max_turns=${options.maxTurns}`);
405
+ }
406
+ args.push(prompt);
407
+ return args;
408
+ }
409
+ buildChildEnv(baseEnv, settingsEnv, overrides) {
410
+ // codex reads OPENAI_API_KEY / config.toml. Pass Actoviq env through.
411
+ return { ...baseEnv, ...settingsEnv, ...(overrides ?? {}) };
412
+ }
413
+ createNormalizer() {
414
+ return new CodexNormalizer();
415
+ }
416
+ suggestedModels() {
417
+ return ['gpt-5', 'gpt-5-mini', 'o3', 'o4-mini'];
418
+ }
419
+ }
420
+ class CodexNormalizer {
421
+ threadId;
422
+ initEmitted = false;
423
+ translate(raw) {
424
+ const type = typeof raw.type === 'string' ? raw.type : '';
425
+ if (type === 'thread.started') {
426
+ this.threadId = typeof raw.thread_id === 'string' ? raw.thread_id : this.threadId;
427
+ if (!this.initEmitted) {
428
+ this.initEmitted = true;
429
+ return [bridgeEvent('system', {
430
+ subtype: 'init',
431
+ session_id: this.threadId ?? '',
432
+ // codex exec JSONL carries no model/tools catalog.
433
+ tools: [],
434
+ mcp_servers: [],
435
+ slash_commands: [],
436
+ agents: [],
437
+ skills: [],
438
+ plugins: [],
439
+ model: undefined,
440
+ })];
441
+ }
442
+ return [];
443
+ }
444
+ if (type === 'item.completed') {
445
+ const item = raw.item;
446
+ if (isRecord(item) && item.type === 'agent_message' && typeof item.text === 'string') {
447
+ return [bridgeEvent('assistant', {
448
+ session_id: this.threadId ?? '',
449
+ message: {
450
+ role: 'assistant',
451
+ content: [{ type: 'text', text: item.text }],
452
+ },
453
+ })];
454
+ }
455
+ return [];
456
+ }
457
+ if (type === 'turn.completed') {
458
+ return [bridgeEvent('result', {
459
+ subtype: 'success',
460
+ session_id: this.threadId ?? '',
461
+ is_error: false,
462
+ stop_reason: 'end_turn',
463
+ num_turns: 1,
464
+ })];
465
+ }
466
+ if (type === 'turn.failed' || type === 'error') {
467
+ const message = typeof raw.message === 'string'
468
+ ? raw.message
469
+ : (isRecord(raw.error) && typeof raw.error.message === 'string' ? raw.error.message : 'codex run failed');
470
+ return [bridgeEvent('result', {
471
+ subtype: 'error',
472
+ session_id: this.threadId ?? '',
473
+ is_error: true,
474
+ result: message,
475
+ stop_reason: 'error',
476
+ num_turns: 1,
477
+ })];
478
+ }
479
+ return [];
480
+ }
481
+ }
482
+ // ---------------------------------------------------------------------------
483
+ // Plain-text normalizer — shared by providers whose headless mode emits
484
+ // plain text (no JSONL / stream-json). Captures the full stdout as the
485
+ // assistant text and closes with a result event. No streaming deltas, no
486
+ // tool cards — simple and adequate for reasonix / crush.
487
+ // ---------------------------------------------------------------------------
488
+ class PlainTextNormalizer {
489
+ rawText = true;
490
+ sessionId;
491
+ text = '';
492
+ translate(raw) {
493
+ // raw-text mode: `parseStdoutEvents` wraps each line as `{_raw: line}`.
494
+ const line = typeof raw._raw === 'string' ? raw._raw : '';
495
+ this.text += (this.text ? '\n' : '') + line;
496
+ return [];
497
+ }
498
+ flush() {
499
+ const sid = this.sessionId ?? '';
500
+ return [
501
+ bridgeEvent('system', {
502
+ subtype: 'init',
503
+ session_id: sid,
504
+ tools: [],
505
+ mcp_servers: [],
506
+ slash_commands: [],
507
+ agents: [],
508
+ skills: [],
509
+ plugins: [],
510
+ }),
511
+ bridgeEvent('assistant', {
512
+ session_id: sid,
513
+ message: {
514
+ role: 'assistant',
515
+ content: this.text ? [{ type: 'text', text: this.text }] : [],
516
+ },
517
+ }),
518
+ bridgeEvent('result', {
519
+ subtype: 'success',
520
+ session_id: sid,
521
+ is_error: false,
522
+ result: this.text,
523
+ stop_reason: 'end_turn',
524
+ num_turns: 1,
525
+ }),
526
+ ];
527
+ }
528
+ }
529
+ // ---------------------------------------------------------------------------
530
+ // codewhale provider (stream-json — compatible with Claude Code)
531
+ // ---------------------------------------------------------------------------
532
+ class CodewhaleProvider extends BaseRuntimeProvider {
533
+ id = 'codewhale';
534
+ pathBinary = 'codewhale';
535
+ displayName = 'CodeWhale CLI (codewhale)';
536
+ buildArgs(prompt, options) {
537
+ const args = ['exec', '--auto', '--output-format', 'stream-json'];
538
+ // codewhale persists sessions per workspace. Its --session-id RESUMES by
539
+ // id rather than creating, and we can't reliably feed its native id back
540
+ // through ActoviqBridgeSession's uuid, so use --continue (most-recent) for
541
+ // resumed turns — correct for sequential same-provider turns and survives
542
+ // switching away and back.
543
+ if (options.resume || options.continueMostRecent) {
544
+ args.push('--continue');
545
+ }
546
+ args.push(prompt);
547
+ return args;
548
+ }
549
+ buildChildEnv(baseEnv, settingsEnv, overrides) {
550
+ return { ...baseEnv, ...settingsEnv, ...(overrides ?? {}) };
551
+ }
552
+ createNormalizer() {
553
+ return { translate: raw => [raw] };
554
+ }
555
+ suggestedModels() { return []; }
556
+ }
557
+ // ---------------------------------------------------------------------------
558
+ // reasonix provider (plain-text, DeepSeek-native)
559
+ // ---------------------------------------------------------------------------
560
+ class ReasonixProvider extends BaseRuntimeProvider {
561
+ id = 'reasonix';
562
+ pathBinary = 'reasonix';
563
+ displayName = 'Reasonix CLI (reasonix)';
564
+ buildArgs(prompt, options) {
565
+ const args = ['run'];
566
+ if (options.model) {
567
+ args.push('-m', options.model);
568
+ }
569
+ if (options.systemPrompt) {
570
+ args.push('-s', options.systemPrompt);
571
+ }
572
+ if (options.effort) {
573
+ args.push('--effort', options.effort);
574
+ }
575
+ args.push(prompt);
576
+ return args;
577
+ }
578
+ buildChildEnv(baseEnv, settingsEnv, overrides) {
579
+ return { ...baseEnv, ...settingsEnv, ...(overrides ?? {}) };
580
+ }
581
+ createNormalizer() {
582
+ return new PlainTextNormalizer();
583
+ }
584
+ suggestedModels() {
585
+ return ['deepseek-v4-pro', 'deepseek-v4-flash'];
586
+ }
587
+ }
588
+ class CrushProvider extends BaseRuntimeProvider {
589
+ id = 'crush';
590
+ pathBinary = 'crush';
591
+ displayName = 'Crush CLI (crush)';
592
+ buildArgs(prompt, options) {
593
+ const args = ['run'];
594
+ if (options.model) {
595
+ args.push('-m', options.model);
596
+ }
597
+ // crush persists sessions per workspace and emits plain text (no session
598
+ // id to capture), so resumed turns use --continue (most-recent). Correct
599
+ // for sequential turns and survives switching away and back.
600
+ if (options.resume || options.continueMostRecent) {
601
+ args.push('--continue');
602
+ }
603
+ args.push(prompt);
604
+ return args;
605
+ }
606
+ buildChildEnv(baseEnv, settingsEnv, overrides) {
607
+ return { ...baseEnv, ...settingsEnv, ...(overrides ?? {}) };
608
+ }
609
+ createNormalizer() {
610
+ return new PlainTextNormalizer();
611
+ }
612
+ suggestedModels() {
613
+ return ['gpt-5', 'claude-sonnet-4-6', 'gemini-2.5-pro'];
614
+ }
615
+ }
616
+ function isRecord(value) {
617
+ return typeof value === 'object' && value !== null;
618
+ }
619
+ export const claudeProvider = new ClaudeProvider();
620
+ export const piProvider = new PiProvider();
621
+ export const codexProvider = new CodexProvider();
622
+ export const codewhaleProvider = new CodewhaleProvider();
623
+ export const reasonixProvider = new ReasonixProvider();
624
+ export const crushProvider = new CrushProvider();
625
+ export const BRIDGE_PROVIDERS = {
626
+ claude: claudeProvider,
627
+ pi: piProvider,
628
+ codex: codexProvider,
629
+ codewhale: codewhaleProvider,
630
+ reasonix: reasonixProvider,
631
+ crush: crushProvider,
632
+ };
633
+ /**
634
+ * Best-effort credential env-var names each provider's CLI reads. The TUI
635
+ * treats "any one of these set" (across the settings env block ∪ process.env)
636
+ * as "credentials likely configured". Empty arrays mean the provider's
637
+ * credential var is not known from its public surface (codewhale/crush are
638
+ * multi-backend); the UI shows an honest "(unknown)" rather than a wrong key.
639
+ * This is advisory display data only — it never gates a run.
640
+ */
641
+ export const BRIDGE_PROVIDER_CREDENTIALS = {
642
+ // claude maps ACTOVIQ_* → ANTHROPIC_* (see anthropicEnvMapping.ts); either form works.
643
+ claude: ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'ACTOVIQ_API_KEY', 'ACTOVIQ_AUTH_TOKEN'],
644
+ // pi supports OpenAI- and Anthropic-compatible backends.
645
+ pi: ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY'],
646
+ codex: ['OPENAI_API_KEY'],
647
+ reasonix: ['DEEPSEEK_API_KEY'],
648
+ codewhale: [],
649
+ crush: [],
650
+ };
651
+ //# sourceMappingURL=bridgeProviders.js.map