@aiwg/cli 2026.10.0 → 2026.10.3
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/agentic/code/providers/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3091 -24
- package/bin/aiwg.mjs +86 -21
- package/dist/src/a2a/codecs.js +9 -0
- package/dist/src/artifacts/corpus-tools/vision-extract.js +105 -15
- package/dist/src/artifacts/repair.js +9 -4
- package/dist/src/channel/manager.mjs +59 -10
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- package/dist/src/cli/handlers/decision.js +297 -2
- package/dist/src/cli/handlers/diagnose.js +9 -2
- package/dist/src/cli/handlers/feedback.js +14 -5
- package/dist/src/cli/handlers/init.js +245 -30
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +69 -6
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +16 -0
- package/dist/src/cli/handlers/regenerate.js +14 -0
- package/dist/src/cli/handlers/runtime-info.js +24 -9
- package/dist/src/cli/handlers/script-runner.js +16 -6
- package/dist/src/cli/handlers/session.js +396 -62
- package/dist/src/cli/handlers/sessions.js +146 -40
- package/dist/src/cli/handlers/setup-manifest.js +439 -39
- package/dist/src/cli/handlers/setup.js +139 -39
- package/dist/src/cli/handlers/subcommands.js +3 -1
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/use.js +13 -0
- package/dist/src/cli/handlers/utilities.js +106 -23
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -0
- package/dist/src/config/aiwg-config.js +268 -4
- package/dist/src/config/cli.js +146 -19
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
- package/dist/src/config/project-artifacts-health.mjs +14 -3
- package/dist/src/config/user-config.js +36 -10
- package/dist/src/decision/adapters/jev.js +80 -28
- package/dist/src/decision/ask.js +352 -0
- package/dist/src/decision/driver.js +4 -0
- package/dist/src/decision/entry.js +38 -6
- package/dist/src/decision/heldout/collector.js +10 -1
- package/dist/src/decision/index.js +3 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/jev-credentials.js +121 -0
- package/dist/src/decision/ledger.js +839 -0
- package/dist/src/decision/validate.js +6 -3
- package/dist/src/effects/dispatch-verifier.js +252 -0
- package/dist/src/effects/index.js +1 -0
- package/dist/src/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +3 -1
- package/dist/src/installation/manager-command.mjs +1 -1
- package/dist/src/installation/manager.mjs +17 -2
- package/dist/src/lint/runner.js +2 -2
- package/dist/src/mcp/adapters/codex-runtime.js +360 -56
- package/dist/src/mcp/cli.mjs +587 -77
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.mjs +16 -26
- package/dist/src/mcp/helpers.mjs +106 -2
- package/dist/src/mcp/omp-config.mjs +50 -29
- package/dist/src/mcp/profiles.js +97 -23
- package/dist/src/mcp/profiles.mjs +99 -27
- package/dist/src/mcp/registry.js +264 -57
- package/dist/src/mcp/registry.mjs +198 -45
- package/dist/src/mcp/server.mjs +403 -20
- package/dist/src/mcp/toml-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.mjs +357 -0
- package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
- package/dist/src/mcp/tools/decision.mjs +58 -0
- package/dist/src/mcp/tools/orchestration.mjs +38 -0
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
- package/dist/src/memory/project-registry.js +2 -2
- package/dist/src/mission-protocol/codecs.js +639 -1
- package/dist/src/mission-protocol/types.js +110 -0
- package/dist/src/mission-protocol/worker-status.js +2 -0
- package/dist/src/models/config-loader.js +70 -13
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3091 -24
- package/dist/src/models/model-discovery.js +862 -63
- package/dist/src/models/provider-policy.js +625 -11
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +259 -0
- package/dist/src/models/worker-direct-readiness.js +529 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +131 -0
- package/dist/src/models/worker-installation.mjs +879 -0
- package/dist/src/models/wrapper-route.js +18 -0
- package/dist/src/ops/registry.js +9 -14
- package/dist/src/plugins/standalone-packager.js +4 -3
- package/dist/src/process/bounded-child-process.mjs +387 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -0
- package/dist/src/providers/grok-build-headless.js +43 -21
- package/dist/src/providers/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- package/dist/src/serve/executor-registry.js +16 -24
- package/dist/src/serve/fleet-mission-conductor.js +9 -0
- package/dist/src/serve/mission-conductor.js +9 -0
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +538 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +45 -0
- package/dist/src/sessions/adapters/generic.js +37 -3
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +423 -24
- package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
- package/dist/src/sessions/adapters/muse.js +68 -29
- package/dist/src/sessions/adapters/pi.js +1 -0
- package/dist/src/sessions/batch-contracts.js +13 -1
- package/dist/src/sessions/batch-import.js +23 -42
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +35 -0
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/grok-build-cli.js +67 -5
- package/dist/src/sessions/importer.js +24 -8
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/repository.js +54 -13
- package/dist/src/sessions/workspace-discovery.js +99 -37
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
- package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
- package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
- package/dist/src/smiths/mcpsmith/example.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- package/dist/src/writing/fidelity.js +18 -1
- package/package.json +18 -1
- package/schemas/mission-v1.schema.json +2429 -0
- package/schemas/models/model-catalog.v1.schema.json +301 -0
- package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
- package/schemas/models/model-policy.v1.schema.json +14 -0
- package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
- package/tools/_resolve-impl.mjs +39 -0
- package/tools/agents/deploy-agents.mjs +15 -2
- package/tools/agents/providers/base.mjs +63 -43
- package/tools/agents/providers/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +6 -3
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- package/tools/providers/deepseek-harness-transport.mjs +358 -0
- package/tools/providers/omp-transport.mjs +192 -0
- package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
- package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
- package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
- package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
- package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
- package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
- package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
- package/tools/ralph-external/lib/worker-health.mjs +439 -0
- package/vscode-extension/schemas/aiwg.config.v1.json +1953 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory (Droid) CLI Provider Adapter for External Ralph Loop
|
|
3
|
+
*
|
|
4
|
+
* Provides support for Factory's Droid CLI as a provider for
|
|
5
|
+
* autonomous task execution in Ralph loops.
|
|
6
|
+
*
|
|
7
|
+
* Droid CLI differences from Claude:
|
|
8
|
+
* - Binary: `droid` instead of `claude`
|
|
9
|
+
* - Headless mode: `droid exec` subcommand
|
|
10
|
+
* - Permission bypass: `--skip-permissions-unsafe` instead of `--dangerously-skip-permissions`
|
|
11
|
+
* - Output format: `--output-format text|stream-json` (supports stream-json!)
|
|
12
|
+
* - Session resume: `-s/--session-id <id>` with prompt required
|
|
13
|
+
* - Model selection: `-m/--model <id>` (direct model IDs, not generic)
|
|
14
|
+
* - No --print flag (exec is already non-interactive)
|
|
15
|
+
* - No --append-system-prompt (inject into main prompt)
|
|
16
|
+
* - No --max-budget-usd flag
|
|
17
|
+
* - No --max-turns flag
|
|
18
|
+
* - No --mcp-config flag (but has MCP subcommand)
|
|
19
|
+
* - No --agent flag
|
|
20
|
+
* - Autonomy levels: --auto low|medium|high (alternative to skip-permissions)
|
|
21
|
+
*
|
|
22
|
+
* @implements Plan: Multi-Provider Support for External Ralph Loop
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { ProviderAdapter, registerProvider } from './provider-adapter.mjs';
|
|
26
|
+
import { classifyWorkerHealthFailure, healthEventMatchesBinding } from './worker-health.mjs';
|
|
27
|
+
|
|
28
|
+
/** Model mapping from generic names to Factory/Droid model IDs */
|
|
29
|
+
const MODEL_MAP = {
|
|
30
|
+
'opus': 'claude-opus-5',
|
|
31
|
+
'sonnet': 'claude-sonnet-5',
|
|
32
|
+
'haiku': 'claude-haiku-4-5-20251001',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Version-bound direct-worker surface (roctinam/aiwg#2902, B-OG-37).
|
|
37
|
+
*
|
|
38
|
+
* Qualified against droid-cli 0.238.0 `droid exec --help` (help/version
|
|
39
|
+
* syntax only, captured on host 'noodle' 2026-10-10,
|
|
40
|
+
* .aiwg/working/s1-scratch/cli-help/droid-0.238.0.txt; never live
|
|
41
|
+
* qualification):
|
|
42
|
+
*
|
|
43
|
+
* droid exec [--auto low] -m M -r E --cwd W --output-format stream-json < briefPath
|
|
44
|
+
*
|
|
45
|
+
* `exec` is Droid's non-interactive mode ("Run non-interactively (for
|
|
46
|
+
* scripts/automation)"); `-m/--model`, `-r/--reasoning-effort`, `--auto`,
|
|
47
|
+
* `--cwd` and `-o/--output-format` are all `exec`-level flags on 0.238.0
|
|
48
|
+
* `exec --help`. There is no file-output flag (`-o` selects the *format*,
|
|
49
|
+
* not a destination path): the final is read from the captured stdout
|
|
50
|
+
* stream instead, never a separate file. There is no positional `prompt`
|
|
51
|
+
* requirement either — `exec --help`'s own Examples show
|
|
52
|
+
* `echo "analyze code" | droid exec` and `cat prompt.txt | droid exec --auto
|
|
53
|
+
* medium` with no `prompt` argument at all, so omitting the positional and
|
|
54
|
+
* piping the brief to stdin (`stdin: 'brief-text'`) is help-evidenced, not
|
|
55
|
+
* invented; the brief never enters argv. `--skip-permissions-unsafe`
|
|
56
|
+
* ("Bypass all checks - DANGEROUS!") is Ralph-loop legacy
|
|
57
|
+
* (`buildSessionArgs` below) and is never emitted by the direct profile.
|
|
58
|
+
* The generic cross-provider sandbox contract this profile accepts is
|
|
59
|
+
* `read-only`/`workspace-write`; `workspace-write` maps to `--auto low`
|
|
60
|
+
* ("File creation/modification in non-system directories") and
|
|
61
|
+
* `read-only` maps to no `--auto` flag at all — `exec --help`'s own
|
|
62
|
+
* "Autonomy Levels" section documents the no-flags default as read-only,
|
|
63
|
+
* so omitting it is the exact, help-evidenced way to request it, not an
|
|
64
|
+
* inherited default. `medium`/`high` are out of this contract's scope and
|
|
65
|
+
* are never emitted. `exec --help`'s "Autonomy Levels" section is also the
|
|
66
|
+
* sole permission gate `exec` exposes — there is no separate
|
|
67
|
+
* approval-prompt flag, because `exec` is non-interactive by construction
|
|
68
|
+
* regardless of autonomy level. Reasoning-effort levels are model-dependent
|
|
69
|
+
* (`exec --help`'s "Model details" lists the per-model valid sets); an
|
|
70
|
+
* explicit value drawn from the observed union of every model's levels is
|
|
71
|
+
* required and the CLI's per-model default is never inherited.
|
|
72
|
+
*
|
|
73
|
+
* `--output-format stream-json` and the terminal `json` object shape are
|
|
74
|
+
* doc-sourced, not help-verified (the installed CLI was never run with a
|
|
75
|
+
* prompt): https://docs.factory.com/droid-exec/overview documents
|
|
76
|
+
* `--output-format json` producing exactly one terminal object,
|
|
77
|
+
* `{"type":"result","subtype":"success","is_error":false,"duration_ms":N,
|
|
78
|
+
* "num_turns":N,"result":"...","session_id":"..."}`, and
|
|
79
|
+
* https://docs.factory.com/sdk/typescript documents the same message
|
|
80
|
+
* vocabulary the CLI's JSON-RPC/stream protocol shares across surfaces:
|
|
81
|
+
* default types `assistant`, `user`, `tool_call`, `tool_result`, `hook`,
|
|
82
|
+
* `error` and the terminal `result` (subtypes `success`, `interrupted`,
|
|
83
|
+
* `error_during_execution`, `error_structured_output`). This grammar
|
|
84
|
+
* drives `observeHealthEvent`/`parseDirectFinal` below but is explicitly
|
|
85
|
+
* doc-sourced evidence (grade D), never live qualification, and the
|
|
86
|
+
* registry `qualification.status` stays `unqualified`.
|
|
87
|
+
*
|
|
88
|
+
* Authentication travels via `FACTORY_API_KEY` in the environment
|
|
89
|
+
* (`exec --help`'s "Authentication" section); `HOME` stays on the env
|
|
90
|
+
* allowlist because `--worktree-dir`'s deprecation notice points at
|
|
91
|
+
* `~/.factory/settings.json` for persistent settings.
|
|
92
|
+
*
|
|
93
|
+
* Installed-module shape: static `interfaceVersion` and `register` exports
|
|
94
|
+
* so the #2885 installed loader can approve this file by digest under
|
|
95
|
+
* `installed:factory` (mirroring #2897 codex-adapter.mjs / #2911
|
|
96
|
+
* openhuman-adapter.mjs). The bottom self-registration targets the legacy
|
|
97
|
+
* Ralph-loop registry only; the direct worker profile loads through the
|
|
98
|
+
* installed loader.
|
|
99
|
+
*/
|
|
100
|
+
export const FACTORY_SUPPORTED_VERSIONS = Object.freeze(['0.238.0']);
|
|
101
|
+
/** Union of every reasoning-effort label observed across models in
|
|
102
|
+
* `exec --help`'s "Model details" section (including `none`, the sole
|
|
103
|
+
* level for "Auto Model" and one of the levels for GPT-6 Sol/Luna and
|
|
104
|
+
* GPT-5.6 Sol/Sol Fast/Terra/Luna); the valid subset is model-dependent
|
|
105
|
+
* and unqualified until live evidence. */
|
|
106
|
+
export const FACTORY_DIRECT_EFFORT_VALUES = Object.freeze(['none', 'off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
|
|
107
|
+
/** Generic cross-provider sandbox contract this profile accepts
|
|
108
|
+
* (`WorkerExecution.sandbox`). `--skip-permissions-unsafe` and the
|
|
109
|
+
* `medium`/`high` `--auto` tiers are legacy/unsafe or out of this
|
|
110
|
+
* contract's scope and are never emitted by the direct profile. */
|
|
111
|
+
export const FACTORY_DIRECT_SANDBOX_MODES = Object.freeze(['read-only', 'workspace-write']);
|
|
112
|
+
/** `--auto` is droid's only scoped autonomy flag: `workspace-write` maps to
|
|
113
|
+
* the least-privileged writable tier (`low`, "File creation/modification
|
|
114
|
+
* in non-system directories"); `read-only` maps to no `--auto` flag at
|
|
115
|
+
* all, because omitting it is help-evidenced as the CLI's own read-only
|
|
116
|
+
* mode (`exec --help`'s "Autonomy Levels": "DEFAULT (no flags) -
|
|
117
|
+
* Read-only mode") and there is no more-explicit way to request it. */
|
|
118
|
+
const FACTORY_SANDBOX_TO_AUTO = Object.freeze({ 'read-only': null, 'workspace-write': 'low' });
|
|
119
|
+
/** `exec` has no separate approval-prompt flag: non-interactive by
|
|
120
|
+
* construction, so only the no-prompt policy is ever requested. */
|
|
121
|
+
export const FACTORY_DIRECT_APPROVAL_POLICIES = Object.freeze(['never']);
|
|
122
|
+
/** Brief transport for the direct profile: piped stdin, never argv. */
|
|
123
|
+
export const FACTORY_DIRECT_STDIN = 'brief-text';
|
|
124
|
+
/** Generic Ralph-loop aliases resolve through MODEL_MAP above; the direct
|
|
125
|
+
* profile requires an exact observed catalog model instead. */
|
|
126
|
+
const FACTORY_GENERIC_ALIASES = Object.freeze(['opus', 'sonnet', 'haiku']);
|
|
127
|
+
/** `exec --help`'s "Available Models" section lists `auto` as "Auto
|
|
128
|
+
* Model" with "supports reasoning: No; supported: [none]" in "Model
|
|
129
|
+
* details" — the CLI itself, not the resolved selection, then picks the
|
|
130
|
+
* real model. `-m auto` would silently violate the explicit-model
|
|
131
|
+
* invariant exactly as an omitted `-m` would (the CLI's own documented
|
|
132
|
+
* default-by-omission is `gpt-6-sol`, per `exec --help`'s
|
|
133
|
+
* `-m, --model <id> ... (default: gpt-6-sol)`); buildWorkerArgs already
|
|
134
|
+
* refuses omission by requiring a non-empty string, and this refuses the
|
|
135
|
+
* equivalent `auto` escape hatch case-insensitively. */
|
|
136
|
+
const FACTORY_RESERVED_MODEL_VALUES = Object.freeze(['auto']);
|
|
137
|
+
|
|
138
|
+
export class FactoryAdapter extends ProviderAdapter {
|
|
139
|
+
/**
|
|
140
|
+
* Doc-sourced `assistant`/`user`/`tool_call`/`tool_result`/`hook`/`error`/
|
|
141
|
+
* `result` vocabulary (module doc above). No session-start event is
|
|
142
|
+
* evidenced, so the handshake stays `process-only`; a standalone `error`
|
|
143
|
+
* event is fatal exactly like a `result` with `is_error: true`, so a later
|
|
144
|
+
* clean terminal can never paper over it. Session exports and another
|
|
145
|
+
* provider's event names cannot qualify this grammar.
|
|
146
|
+
*
|
|
147
|
+
* Terminal `result` success requires AFFIRMATIVE markers, never an
|
|
148
|
+
* absence of negative ones: this doc-sourced grammar (grade D, never
|
|
149
|
+
* live-observed) is not trusted to omit fields consistently, so a bare
|
|
150
|
+
* `{"type":"result"}` or any other field combination that is not
|
|
151
|
+
* exactly the documented success shape — `subtype === 'success'`,
|
|
152
|
+
* `is_error === false`, and a non-empty `result` string — fails closed
|
|
153
|
+
* rather than defaulting to success. An `interrupted`/
|
|
154
|
+
* `error_during_execution`/`error_structured_output`/unknown `subtype`,
|
|
155
|
+
* a missing or non-`false` `is_error`, or an empty/missing `result` are
|
|
156
|
+
* all failures even if the other fields look clean.
|
|
157
|
+
* @returns {import('./worker-health.mjs').HealthObservation | null}
|
|
158
|
+
*/
|
|
159
|
+
observeHealthEvent(event, source, binding) {
|
|
160
|
+
if (source?.kind !== 'stdout' || !event || typeof event !== 'object' || typeof event.type !== 'string') return null;
|
|
161
|
+
if (!healthEventMatchesBinding({ sessionId: event.session_id }, binding)) return null;
|
|
162
|
+
if (event.type === 'result') {
|
|
163
|
+
const hasResultContent = typeof event.result === 'string' && event.result.trim().length > 0;
|
|
164
|
+
const success = event.subtype === 'success' && event.is_error === false && hasResultContent;
|
|
165
|
+
return { kind: 'terminal', success, key: event.subtype ?? 'result' };
|
|
166
|
+
}
|
|
167
|
+
if (event.type === 'error') {
|
|
168
|
+
return { ...classifyWorkerHealthFailure({ type: 'error', message: event.message ?? event.error?.message }, { source, provider: 'factory' }),
|
|
169
|
+
fatal: true };
|
|
170
|
+
}
|
|
171
|
+
if (event.type === 'tool_result') {
|
|
172
|
+
const id = event.tool_call_id ?? event.id;
|
|
173
|
+
return (typeof id === 'string' || typeof id === 'number') ? { kind: 'progress', key: String(id) } : { kind: 'activity' };
|
|
174
|
+
}
|
|
175
|
+
if (event.type === 'tool_call' || event.type === 'assistant' || event.type === 'user' || event.type === 'hook') {
|
|
176
|
+
return { kind: 'activity' };
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Driver-owned argv after the binary for the version-bound direct-shell
|
|
183
|
+
* profile. Every control is explicit: exact observed model (never an
|
|
184
|
+
* alias or the CLI's `gpt-6-sol` default), explicit reasoning effort
|
|
185
|
+
* (never a per-model default), the scoped `--auto` autonomy level (never
|
|
186
|
+
* `--skip-permissions-unsafe`), the conductor workdir as `--cwd` and the
|
|
187
|
+
* `stream-json` event stream. No prompt argument is emitted: the brief
|
|
188
|
+
* arrives on stdin (`stdin: 'brief-text'`), so it never enters argv.
|
|
189
|
+
*
|
|
190
|
+
* @param {{ selected?: { model?: unknown, effort?: { value?: unknown } }, cwd?: unknown, brief?: unknown }} invocation
|
|
191
|
+
* @param {unknown} _profile
|
|
192
|
+
* @param {{ sandbox?: unknown, approval?: unknown }} [permissions]
|
|
193
|
+
* @returns {string[]}
|
|
194
|
+
*/
|
|
195
|
+
buildWorkerArgs(invocation, _profile, permissions) {
|
|
196
|
+
const model = invocation?.selected?.model;
|
|
197
|
+
if (typeof model !== 'string' || !model || model.includes('\0')) {
|
|
198
|
+
throw new Error('Factory worker requires an explicit model');
|
|
199
|
+
}
|
|
200
|
+
if (FACTORY_GENERIC_ALIASES.includes(model.toLowerCase())) {
|
|
201
|
+
throw new Error(`Factory worker requires an exact observed model, not the generic alias '${model}'`);
|
|
202
|
+
}
|
|
203
|
+
if (FACTORY_RESERVED_MODEL_VALUES.includes(model.toLowerCase())) {
|
|
204
|
+
throw new Error(`Factory worker requires an explicit observed model, not the CLI's own auto-selection '${model}'`);
|
|
205
|
+
}
|
|
206
|
+
const effort = invocation?.selected?.effort?.value ?? null;
|
|
207
|
+
if (effort === null || !FACTORY_DIRECT_EFFORT_VALUES.includes(effort)) {
|
|
208
|
+
throw new Error('Factory worker requires an explicit qualified reasoning effort (never a CLI default)');
|
|
209
|
+
}
|
|
210
|
+
const sandbox = permissions?.sandbox ?? 'workspace-write';
|
|
211
|
+
if (!FACTORY_DIRECT_SANDBOX_MODES.includes(sandbox)) {
|
|
212
|
+
throw new Error(`Factory worker sandbox is not a qualified direct mode: ${String(sandbox)}`);
|
|
213
|
+
}
|
|
214
|
+
const approval = permissions?.approval ?? 'never';
|
|
215
|
+
if (!FACTORY_DIRECT_APPROVAL_POLICIES.includes(approval)) {
|
|
216
|
+
throw new Error(`Factory worker approval is not a qualified direct policy: ${String(approval)}`);
|
|
217
|
+
}
|
|
218
|
+
const cwd = invocation?.cwd;
|
|
219
|
+
if (typeof cwd !== 'string' || !cwd.startsWith('/') || cwd.includes('\0')) {
|
|
220
|
+
throw new Error('Factory worker requires an absolute workspace');
|
|
221
|
+
}
|
|
222
|
+
const brief = invocation?.brief;
|
|
223
|
+
if (typeof brief !== 'string' || !brief || brief.includes('\0')) {
|
|
224
|
+
throw new Error('Factory worker requires a non-empty brief');
|
|
225
|
+
}
|
|
226
|
+
const auto = FACTORY_SANDBOX_TO_AUTO[sandbox];
|
|
227
|
+
const args = ['exec'];
|
|
228
|
+
if (auto) args.push('--auto', auto);
|
|
229
|
+
args.push('-m', model, '-r', effort, '--cwd', cwd, '--output-format', 'stream-json');
|
|
230
|
+
return args;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* `exec` reads the brief from stdin to EOF and has no documented stdin
|
|
235
|
+
* abort channel, so cancellation stays with owned process control.
|
|
236
|
+
* @returns {null}
|
|
237
|
+
*/
|
|
238
|
+
getAbortInput() {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Direct-profile final extraction from the captured `--output-format
|
|
244
|
+
* stream-json` stdout stream (module doc above). Unlike the legacy
|
|
245
|
+
* `parseOutput()` below (pinned regex-based shape, never changed here),
|
|
246
|
+
* this is JSONL-aware and fails closed on its own, without relying on the
|
|
247
|
+
* health observer: a malformed line, a standalone `error` event, or ANY
|
|
248
|
+
* `result` line that is not the documented success shape (`subtype ===
|
|
249
|
+
* 'success'`, `is_error === false`, non-empty `result`) rejects the whole
|
|
250
|
+
* stream. A later success never replaces an earlier failure, and an error
|
|
251
|
+
* after a success is never ignored. Returns the last successful `result`.
|
|
252
|
+
* @param {string} stdout
|
|
253
|
+
* @returns {Object|null}
|
|
254
|
+
*/
|
|
255
|
+
parseDirectFinal(stdout) {
|
|
256
|
+
if (typeof stdout !== 'string' || !stdout.trim()) return null;
|
|
257
|
+
let final = null;
|
|
258
|
+
for (const line of stdout.split('\n')) {
|
|
259
|
+
const trimmed = line.trim();
|
|
260
|
+
if (!trimmed) continue;
|
|
261
|
+
let event;
|
|
262
|
+
try {
|
|
263
|
+
event = JSON.parse(trimmed);
|
|
264
|
+
} catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
if (!event || typeof event !== 'object' || Array.isArray(event)) return null;
|
|
268
|
+
if (event.type === 'error') return null;
|
|
269
|
+
if (event.type !== 'result') continue;
|
|
270
|
+
const hasResultContent = typeof event.result === 'string' && event.result.trim().length > 0;
|
|
271
|
+
if (!(event.subtype === 'success' && event.is_error === false && hasResultContent)) return null;
|
|
272
|
+
final = event;
|
|
273
|
+
}
|
|
274
|
+
return final;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
/** @returns {string} */
|
|
279
|
+
getBinary() {
|
|
280
|
+
return 'droid';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** @returns {string} */
|
|
284
|
+
getName() {
|
|
285
|
+
return 'factory';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Factory supports stream-json output and session resume.
|
|
290
|
+
* @returns {import('./provider-adapter.mjs').ProviderCapabilities}
|
|
291
|
+
*/
|
|
292
|
+
getCapabilities() {
|
|
293
|
+
return {
|
|
294
|
+
streamJson: true,
|
|
295
|
+
sessionResume: true,
|
|
296
|
+
budgetControl: false,
|
|
297
|
+
systemPrompt: false,
|
|
298
|
+
agentMode: false,
|
|
299
|
+
mcpConfig: false,
|
|
300
|
+
maxTurns: false,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Build args for the main headless session.
|
|
306
|
+
*
|
|
307
|
+
* Factory uses `droid exec --skip-permissions-unsafe` for headless operation.
|
|
308
|
+
*
|
|
309
|
+
* @param {import('./provider-adapter.mjs').SessionArgs} options
|
|
310
|
+
* @returns {string[]}
|
|
311
|
+
*/
|
|
312
|
+
buildSessionArgs(options) {
|
|
313
|
+
const args = [
|
|
314
|
+
'exec',
|
|
315
|
+
// SECURITY: --skip-permissions-unsafe bypasses ALL permission prompts
|
|
316
|
+
// Equivalent to Claude's --dangerously-skip-permissions
|
|
317
|
+
'--skip-permissions-unsafe',
|
|
318
|
+
'--output-format', 'stream-json',
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
// Model selection (map generic to Factory model IDs)
|
|
322
|
+
if (options.model) {
|
|
323
|
+
args.push('-m', this.mapModel(options.model));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Session resume
|
|
327
|
+
if (options.sessionId) {
|
|
328
|
+
args.push('-s', options.sessionId);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Budget control not supported
|
|
332
|
+
if (options.budget) {
|
|
333
|
+
this.warnUnsupported('budgetControl', 'Budget control (--max-budget-usd)');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Max turns not supported
|
|
337
|
+
if (options.maxTurns) {
|
|
338
|
+
this.warnUnsupported('maxTurns', 'Max turns (--max-turns)');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// MCP configuration not supported via flag
|
|
342
|
+
if (options.mcpConfig) {
|
|
343
|
+
this.warnUnsupported('mcpConfig', 'MCP configuration (--mcp-config)');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// System prompt: Factory doesn't support --append-system-prompt,
|
|
347
|
+
// so we prepend it to the main prompt
|
|
348
|
+
let prompt = options.prompt;
|
|
349
|
+
if (options.systemPrompt) {
|
|
350
|
+
prompt = `[System Context]\n${options.systemPrompt}\n\n[Task]\n${prompt}`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// The prompt itself (must be last)
|
|
354
|
+
args.push(prompt);
|
|
355
|
+
|
|
356
|
+
return args;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Build args for short analysis calls (spawnSync).
|
|
361
|
+
*
|
|
362
|
+
* Uses read-only mode (no --skip-permissions-unsafe) since analysis
|
|
363
|
+
* calls only need to read and reason, not modify files.
|
|
364
|
+
*
|
|
365
|
+
* @param {import('./provider-adapter.mjs').AnalysisArgs} options
|
|
366
|
+
* @returns {string[]}
|
|
367
|
+
*/
|
|
368
|
+
buildAnalysisArgs(options) {
|
|
369
|
+
const args = [
|
|
370
|
+
'exec',
|
|
371
|
+
'--output-format', 'text',
|
|
372
|
+
];
|
|
373
|
+
|
|
374
|
+
// Model selection
|
|
375
|
+
if (options.model) {
|
|
376
|
+
args.push('-m', this.mapModel(options.model));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Agent flag not supported by Factory
|
|
380
|
+
if (options.agent) {
|
|
381
|
+
// Silently skip - agent context will be in the prompt itself
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// The analysis prompt (must be last)
|
|
385
|
+
args.push(options.prompt);
|
|
386
|
+
|
|
387
|
+
return args;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Map generic model names to Factory/Droid model IDs.
|
|
392
|
+
*
|
|
393
|
+
* @param {string} genericModel
|
|
394
|
+
* @returns {string}
|
|
395
|
+
*/
|
|
396
|
+
mapModel(genericModel) {
|
|
397
|
+
const mapped = MODEL_MAP[genericModel.toLowerCase()];
|
|
398
|
+
if (mapped) return mapped;
|
|
399
|
+
// Pass through if already a Factory model name or unknown
|
|
400
|
+
return genericModel;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Environment overrides for headless Factory sessions.
|
|
405
|
+
* @returns {Object<string, string>}
|
|
406
|
+
*/
|
|
407
|
+
getEnvOverrides() {
|
|
408
|
+
return {
|
|
409
|
+
CI: 'true',
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Factory does not expose session transcripts in a known file path.
|
|
415
|
+
* @returns {null}
|
|
416
|
+
*/
|
|
417
|
+
getTranscriptPath() {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Parse Factory output — supports stream-json or text.
|
|
423
|
+
*
|
|
424
|
+
* @param {string} stdout
|
|
425
|
+
* @returns {Object|null}
|
|
426
|
+
*/
|
|
427
|
+
parseOutput(stdout) {
|
|
428
|
+
try {
|
|
429
|
+
const jsonMatch = stdout.match(/\{[\s\S]*\}/);
|
|
430
|
+
if (!jsonMatch) return null;
|
|
431
|
+
return JSON.parse(jsonMatch[0]);
|
|
432
|
+
} catch {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Self-register on import for the legacy Ralph loop only. The direct worker
|
|
439
|
+
// profile loads through the #2885 installed loader instead (see below).
|
|
440
|
+
registerProvider('factory', () => new FactoryAdapter());
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* #2995: `FactoryAdapter.parseOutput` above is the legacy-pinned,
|
|
444
|
+
* loose-regex candidate parser (`test/unit/ralph/factory-adapter.test.mjs`
|
|
445
|
+
* and the legacy Ralph loop depend on its exact shape) — it extracts the
|
|
446
|
+
* FIRST `{...}` to the LAST `}` across the whole stdout, so it fails
|
|
447
|
+
* (returns null) on any realistic multi-event `stream-json` run (assistant/
|
|
448
|
+
* tool_call/tool_result lines before the final `result` line), and it is
|
|
449
|
+
* loose in the other direction too (a non-affirmative or empty-text result
|
|
450
|
+
* can still parse as a truthy object). The installed-module loader's
|
|
451
|
+
* `extractFinal` hook IS `instance.parseOutput` (src/models/
|
|
452
|
+
* worker-installation.mjs ~l.730) and session-launcher.mjs's gate
|
|
453
|
+
* (~l.462) throws unless that hook returns a non-null, non-`success:false`
|
|
454
|
+
* result — so a real direct-shell run could never reach `parseWorkerFinal`
|
|
455
|
+
* at all with the legacy parser wired to it.
|
|
456
|
+
*
|
|
457
|
+
* `FactoryDirectAdapter` is the Claude/Hermes `-direct`-subclass pattern:
|
|
458
|
+
* only `parseOutput` changes, to the ALREADY-correct `parseDirectFinal`
|
|
459
|
+
* (above — one rule, never a second one), which returns null unless the
|
|
460
|
+
* whole stream is a single droid-shaped success terminal with non-empty
|
|
461
|
+
* text. `parseWorkerFinal` only needs the strict version here (no
|
|
462
|
+
* generic-envelope fallback): this class is never reachable through the
|
|
463
|
+
* legacy bare `registerProvider('factory', ...)` id above, only through
|
|
464
|
+
* the installed-module `register(factory)` export below, so the legacy
|
|
465
|
+
* group-mode contract (test/unit/ralph-external/worker-finals.test.ts)
|
|
466
|
+
* never sees it.
|
|
467
|
+
*/
|
|
468
|
+
export class FactoryDirectAdapter extends FactoryAdapter {
|
|
469
|
+
// #2995 round 4 (item 3): production resolves the installed adapter by
|
|
470
|
+
// `adapter.id` (now `factory-direct`, item B) and
|
|
471
|
+
// src/models/worker-installation.mjs's `inspectWorkerInstallation`
|
|
472
|
+
// requires `adapter.id === instance.getName()`. `getBinary()` (the real
|
|
473
|
+
// `droid` executable) and the `provider: 'factory'` literals inside
|
|
474
|
+
// `observeHealthEvent` are untouched -- only this identity label changes.
|
|
475
|
+
getName() { return 'factory-direct'; }
|
|
476
|
+
|
|
477
|
+
parseOutput(stdout) {
|
|
478
|
+
return this.parseDirectFinal(stdout);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* @param {string|Uint8Array} output
|
|
483
|
+
* @param {Object} [options]
|
|
484
|
+
* @returns {Promise<import('../../../src/mission-protocol/types.js').WorkerClaims>}
|
|
485
|
+
*/
|
|
486
|
+
async parseWorkerFinal(output, options = {}) {
|
|
487
|
+
if (options.format === 'mcp') return super.parseWorkerFinal(output, options);
|
|
488
|
+
const text = typeof output === 'string' ? output : Buffer.from(output).toString('utf8');
|
|
489
|
+
const final = this.parseDirectFinal(text);
|
|
490
|
+
if (!final) throw new Error('Factory worker stream did not produce a successful final result');
|
|
491
|
+
return super.parseWorkerFinal(final.result, { ...options, format: 'json' });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Installed-module interface version (mirrors #2897 codex-adapter.mjs). */
|
|
496
|
+
export const interfaceVersion = 1;
|
|
497
|
+
|
|
498
|
+
/** Installed-module registration: exactly the one approved adapter ID. */
|
|
499
|
+
export function register(factory) {
|
|
500
|
+
factory('factory-direct', () => new FactoryDirectAdapter());
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export default FactoryAdapter;
|