crewx-agent-cli 0.2.3 → 0.2.4
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/README.md +79 -4
- package/dist/adapters.d.ts +12 -0
- package/dist/adapters.d.ts.map +1 -1
- package/dist/adapters.js +112 -40
- package/dist/adapters.js.map +1 -1
- package/dist/api.d.ts +40 -17
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +187 -11
- package/dist/api.js.map +1 -1
- package/dist/bridge-watchdog.d.ts +10 -0
- package/dist/bridge-watchdog.d.ts.map +1 -0
- package/dist/bridge-watchdog.js +45 -0
- package/dist/bridge-watchdog.js.map +1 -0
- package/dist/config.d.ts +9 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +53 -2
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +6 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +11 -1
- package/dist/constants.js.map +1 -1
- package/dist/daemon.d.ts +32 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +479 -100
- package/dist/daemon.js.map +1 -1
- package/dist/errors.d.ts +4 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -6
- package/dist/index.js.map +1 -1
- package/dist/join.d.ts +1 -0
- package/dist/join.d.ts.map +1 -1
- package/dist/join.js +13 -0
- package/dist/join.js.map +1 -1
- package/dist/outbox.d.ts +22 -0
- package/dist/outbox.d.ts.map +1 -0
- package/dist/outbox.js +189 -0
- package/dist/outbox.js.map +1 -0
- package/dist/process-lock.d.ts +31 -0
- package/dist/process-lock.d.ts.map +1 -0
- package/dist/process-lock.js +272 -0
- package/dist/process-lock.js.map +1 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +7 -0
- package/dist/prompt.js.map +1 -1
- package/dist/runtime-proxy.d.ts +18 -0
- package/dist/runtime-proxy.d.ts.map +1 -0
- package/dist/runtime-proxy.js +221 -0
- package/dist/runtime-proxy.js.map +1 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# CrewX CLI
|
|
2
2
|
|
|
3
|
-
Connect an existing Codex, Claude Code, Pi, Hermes, or OpenClaw installation to
|
|
3
|
+
Connect an existing Codex, Claude Code, Pi, Hermes, or OpenClaw installation to
|
|
4
|
+
a CrewX workspace. The runner stays on your machine, so the agent uses its
|
|
5
|
+
existing login, configuration, tools, and access to the selected working
|
|
6
|
+
directory.
|
|
7
|
+
|
|
8
|
+
For a persistent agent, CrewX recommends a connected machine managed by
|
|
9
|
+
`crewx-bridge`. **Via terminal** is the portable foreground alternative
|
|
10
|
+
documented below.
|
|
4
11
|
|
|
5
12
|
## One-command join
|
|
6
13
|
|
|
@@ -23,7 +30,18 @@ npx --yes crewx-agent-cli@latest --join <join-code> --adapter codex
|
|
|
23
30
|
|
|
24
31
|
The versioned join payload is base64url-encoded JSON containing the server URL, adapter, and raw `crwx_…` bearer token. Base64url is transport encoding, not encryption. CrewX shows the generated token once, but the credential inside the command is reusable until its configured expiry or revocation. Anyone who obtains the command can decode and use it, and the command may remain in shell history. Treat it as a secret and revoke the token from the agent profile if it is exposed.
|
|
25
32
|
|
|
26
|
-
The server stores only the token digest. During a join-mode run, the CLI keeps
|
|
33
|
+
The server stores only the token digest. During a join-mode run, the CLI keeps
|
|
34
|
+
the decoded credential in memory, but it does not pass that reusable credential
|
|
35
|
+
to the coding runtime. For each assignment it opens an ephemeral loopback
|
|
36
|
+
capability proxy and supplies the runtime only that proxy's URL, random bearer,
|
|
37
|
+
and CLI path. The proxy allows the selected task, document, memory, and
|
|
38
|
+
integration operations with strict route/method, size, timeout, and redirect
|
|
39
|
+
limits, then closes when the assignment ends. A locally executed agent still
|
|
40
|
+
has the operating-system permissions of the user account running it. The proxy
|
|
41
|
+
prevents accidental credential inheritance and limits the normal tool surface;
|
|
42
|
+
it is not isolation from hostile code running as the same OS user, which may
|
|
43
|
+
inspect that user's files and processes. Use a separate OS account, container,
|
|
44
|
+
VM, or host for an untrusted runtime or materially different trust boundary.
|
|
27
45
|
|
|
28
46
|
## Persistent installation
|
|
29
47
|
|
|
@@ -46,7 +64,42 @@ crewx daemon --adapter codex
|
|
|
46
64
|
|
|
47
65
|
The saved connection lives at `$XDG_CONFIG_HOME/crewx/config.json` (or `~/.config/crewx/config.json`) with mode `0600`. `CREWX_URL`, `CREWX_TOKEN`, and `CREWX_ADAPTER` override saved settings and are useful in explicitly secured services and CI. The CLI includes operating-system certificate authorities in Node's trust store, so locally trusted HTTPS sites such as Laravel Herd work without disabling certificate verification.
|
|
48
66
|
|
|
49
|
-
Both join mode and `crewx daemon` run in the foreground. Start either command
|
|
67
|
+
Both join mode and `crewx daemon` run in the foreground. Start either command
|
|
68
|
+
inside a safe fallback directory. For each assignment, CrewX uses the project
|
|
69
|
+
channel's local path first, the agent profile's default working directory
|
|
70
|
+
second, and `--cwd` last. Use `--once` for one poll and
|
|
71
|
+
`--poll-interval 5000` to set the active/base polling cadence. During an idle
|
|
72
|
+
period the runner backs off with jitter to at most 8 seconds, honors server
|
|
73
|
+
retry guidance, and resets to the base cadence as soon as work appears.
|
|
74
|
+
|
|
75
|
+
Assignments carry an opaque claim-fencing token. The runner includes it on
|
|
76
|
+
heartbeats, messages, errors, and lifecycle events; CrewX rotates it when work
|
|
77
|
+
is reassigned so a replaced runner cannot apply late results. The token is
|
|
78
|
+
redacted from diagnostics and never enters the prompt or coding-runtime
|
|
79
|
+
environment. Final assistant messages, errors, and the terminal lifecycle event
|
|
80
|
+
are persisted as one owner-only outbox batch with stable event UUIDs before
|
|
81
|
+
delivery. A process restart replays that batch until the server acknowledges
|
|
82
|
+
it, while bounded backpressure coalesces replaceable output and never silently
|
|
83
|
+
evicts a terminal outcome. Every outbound event batch is capped against the
|
|
84
|
+
exact serialized HTTP body, including its envelope and separators; one event
|
|
85
|
+
that cannot fit fails visibly instead of creating an endlessly retried request.
|
|
86
|
+
|
|
87
|
+
The outbox is stored below
|
|
88
|
+
`~/Library/Application Support/CrewX Agent/outbox/` on macOS or
|
|
89
|
+
`${XDG_STATE_HOME:-~/.local/state}/crewx-agent/outbox/` on Linux. Its directory
|
|
90
|
+
is mode `0700`, files are mode `0600`, and unsafe ownership, modes, or symlinks
|
|
91
|
+
are rejected. New join codes carry an opaque, non-secret runner scope generated
|
|
92
|
+
once for the agent profile. It keys the outbox and its full-lifetime
|
|
93
|
+
single-writer lock, so a rotated connection token reuses the same durable
|
|
94
|
+
state. The token remains the sole authority. A second live daemon for the same
|
|
95
|
+
profile is rejected; after an unclean exit, a replacement verifies process
|
|
96
|
+
identity before recovering the lock. Legacy join codes without a runner scope
|
|
97
|
+
remain supported and derive an isolated fallback key from the token.
|
|
98
|
+
|
|
99
|
+
Bridge-started runners receive their per-run bearer through a one-shot
|
|
100
|
+
inherited pipe that closes immediately after startup, rather than through the
|
|
101
|
+
exec environment. This reduces accidental exposure but does not change the OS
|
|
102
|
+
account trust boundary.
|
|
50
103
|
|
|
51
104
|
## Built-in adapters
|
|
52
105
|
|
|
@@ -58,6 +111,13 @@ Both join mode and `crewx daemon` run in the foreground. Start either command in
|
|
|
58
111
|
|
|
59
112
|
`crewx doctor` probes all five binaries. Runtime profile changes are read before every assignment, so supported model, permission, chat-only, working-directory, token, and deactivation changes do not require reconnecting. Chat-only and deactivated agents cannot receive task runs.
|
|
60
113
|
|
|
114
|
+
The assignment capability proxy enforces the server profile locally:
|
|
115
|
+
`read_only` and chat-only profiles may use only GET operations; `standard` and
|
|
116
|
+
`full_access` profiles may use the explicitly allowlisted task, document,
|
|
117
|
+
memory, and integration mutations. A 401 or 403 from the assignment control
|
|
118
|
+
endpoint immediately aborts the active process group and terminates the daemon
|
|
119
|
+
instead of allowing revoked work to continue.
|
|
120
|
+
|
|
61
121
|
## Custom commands
|
|
62
122
|
|
|
63
123
|
Advanced users can replace the built-in invocation without editing CrewX:
|
|
@@ -72,7 +132,13 @@ crewx run codex "Review this repository" --coding-cmd 'my-agent {prompt} --quiet
|
|
|
72
132
|
|
|
73
133
|
CrewX uses a quote-aware argv parser and launches the resulting executable with `shell: false`. The prompt replaces one exact `{prompt}` argument or is appended as one argument when the placeholder is absent; it is never interpolated into a shell string. Pipes, redirection, variable expansion, and command substitution are therefore not interpreted by CrewX.
|
|
74
134
|
|
|
75
|
-
This prevents prompt text from becoming shell syntax, but it does not make an
|
|
135
|
+
This prevents prompt text from becoming shell syntax, but it does not make an
|
|
136
|
+
arbitrary executable safe. The selected program inherits the runner's
|
|
137
|
+
operating-system permissions and ordinary provider environment, but all
|
|
138
|
+
inherited `CREWX_*` variables are removed and replaced only by the
|
|
139
|
+
assignment-lifetime loopback capability. Do not use commands from an untrusted
|
|
140
|
+
profile or run them in an untrusted working directory. Explicitly wrapping a
|
|
141
|
+
command in a shell recreates the shell-risk boundary and is not recommended.
|
|
76
142
|
|
|
77
143
|
## Shared work commands
|
|
78
144
|
|
|
@@ -99,6 +165,15 @@ crewx integration search google_drive "quarterly plan" --json
|
|
|
99
165
|
|
|
100
166
|
Agents may update only tasks assigned to their own profile. Documents must be agent-readable and unprotected, and every document update requires the current version from `doc list`; stale updates fail instead of overwriting newer work. Agents may edit only memories they recorded, while channel-scoped memory is limited to channels they can see. Provider access tokens are encrypted by the web application and are never returned to the CLI; integration commands expose normalized search results only.
|
|
101
167
|
|
|
168
|
+
Task, document, and memory lists consume the server's bounded cursor pages
|
|
169
|
+
automatically. Each HTTP response remains below the CLI's 2 MiB safety limit,
|
|
170
|
+
while existing callers still receive one combined array up to 10,000 items or
|
|
171
|
+
16 MiB of encoded data. Larger results fail with an instruction to narrow the
|
|
172
|
+
filters or use `listTaskPage`, `listDocumentPage`, or `listMemoryPage` for
|
|
173
|
+
explicit page-by-page processing. The monotonic cursor is the last seen
|
|
174
|
+
resource ID, so concurrent inserts do not shift later pages. Legacy servers
|
|
175
|
+
without pagination metadata remain a supported one-page response.
|
|
176
|
+
|
|
102
177
|
## One-shot local runs
|
|
103
178
|
|
|
104
179
|
The same adapters can be exercised without a CrewX connection:
|
package/dist/adapters.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { type ChildProcessWithoutNullStreams, type SpawnOptionsWithoutStdio } from 'node:child_process';
|
|
2
2
|
import { type AgentAdapter } from 'crewx-agent-protocol';
|
|
3
|
+
export declare function adapterProbePlan(adapter: AgentAdapter): {
|
|
4
|
+
args: readonly string[];
|
|
5
|
+
timeoutMs: number;
|
|
6
|
+
};
|
|
3
7
|
export interface AdapterInvocation {
|
|
4
8
|
command: string;
|
|
5
9
|
args: string[];
|
|
@@ -50,6 +54,7 @@ export interface RunAdapterOptions {
|
|
|
50
54
|
profile?: AgentRuntimeProfile | undefined;
|
|
51
55
|
codingCommand?: string | undefined;
|
|
52
56
|
environment?: NodeJS.ProcessEnv | undefined;
|
|
57
|
+
controlEnvironment?: NodeJS.ProcessEnv | undefined;
|
|
53
58
|
timeoutMs?: number | undefined;
|
|
54
59
|
maxOutputBytes?: number | undefined;
|
|
55
60
|
signal?: AbortSignal | undefined;
|
|
@@ -58,6 +63,13 @@ export interface RunAdapterOptions {
|
|
|
58
63
|
onMessage?: ((message: string, role: 'assistant' | 'tool') => void) | undefined;
|
|
59
64
|
onStderr?: ((line: string) => void) | undefined;
|
|
60
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Coding runtimes inherit provider credentials and ordinary process settings,
|
|
68
|
+
* but never inherit the daemon's CrewX control-plane credentials. The only
|
|
69
|
+
* CrewX variables admitted are short-lived, capability-scoped proxy values
|
|
70
|
+
* supplied separately by the assignment executor.
|
|
71
|
+
*/
|
|
72
|
+
export declare function runtimeEnvironment(environment?: NodeJS.ProcessEnv, controlEnvironment?: NodeJS.ProcessEnv, inherited?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
61
73
|
export declare function runAdapter(options: RunAdapterOptions): Promise<AdapterRunResult>;
|
|
62
74
|
export declare function probeAdapter(adapter: AgentAdapter, spawnProcess?: SpawnProcess, timeoutMs?: number): Promise<{
|
|
63
75
|
available: boolean;
|
package/dist/adapters.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,8BAA8B,EAAE,KAAK,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAM/G,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAS7E,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,MAAM,EACF;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,GACjB;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACnC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,aAAa,CAAC;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAWD,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,MAAM,YAAY,GAAG,CACzB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,wBAAwB,KAC9B,8BAA8B,CAAC;AAuEpC,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAMxD;AAED,mFAAmF;AACnF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,mBAAwB,EACjC,aAAa,CAAC,EAAE,MAAM,GACrB,iBAAiB,CAmGnB;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA6C1D;AAwDD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAoDvF;
|
|
1
|
+
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"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"}
|
package/dist/adapters.js
CHANGED
|
@@ -9,6 +9,13 @@ import { CliError } from './errors.js';
|
|
|
9
9
|
const DEFAULT_RUNTIME_PROBE_TIMEOUT_MS = 5_000;
|
|
10
10
|
const OPENCLAW_RUNTIME_PROBE_TIMEOUT_MS = 30_000;
|
|
11
11
|
const OPENCLAW_SETUP_TIMEOUT_MS = 60_000;
|
|
12
|
+
export function adapterProbePlan(adapter) {
|
|
13
|
+
return adapter === 'hermes'
|
|
14
|
+
? { args: ['acp', '--check'], timeoutMs: DEFAULT_RUNTIME_PROBE_TIMEOUT_MS }
|
|
15
|
+
: adapter === 'openclaw'
|
|
16
|
+
? { args: ['agents', 'list', '--json'], timeoutMs: OPENCLAW_RUNTIME_PROBE_TIMEOUT_MS }
|
|
17
|
+
: { args: ['--version'], timeoutMs: DEFAULT_RUNTIME_PROBE_TIMEOUT_MS };
|
|
18
|
+
}
|
|
12
19
|
const CLAUDE_STANDARD_CREWX_TOOLS = [
|
|
13
20
|
'WebSearch',
|
|
14
21
|
'WebFetch',
|
|
@@ -351,12 +358,23 @@ function record(value) {
|
|
|
351
358
|
? value
|
|
352
359
|
: undefined;
|
|
353
360
|
}
|
|
361
|
+
function openClawError(code, message, cause) {
|
|
362
|
+
return new CliError(message, 1, {
|
|
363
|
+
code,
|
|
364
|
+
...(cause !== undefined ? { cause } : {}),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function publicOpenClawDiagnostic(value) {
|
|
368
|
+
return value
|
|
369
|
+
.replace(/(^|[\s"'`=:(])(?:\/(?!\/)[^\s"'`),;]+)+/g, "$1<local-path>")
|
|
370
|
+
.replace(/(^|[\s"'`=:(])[A-Za-z]:\\[^\s"'`),;]+/g, "$1<local-path>");
|
|
371
|
+
}
|
|
354
372
|
async function resolvedPath(value, label) {
|
|
355
373
|
try {
|
|
356
374
|
return await realpath(value);
|
|
357
375
|
}
|
|
358
376
|
catch (error) {
|
|
359
|
-
throw
|
|
377
|
+
throw openClawError('openclaw_workspace_unavailable', `${label} does not resolve to an accessible local directory.`, error);
|
|
360
378
|
}
|
|
361
379
|
}
|
|
362
380
|
async function openClawJsonProbe(args, options, timeoutMs = OPENCLAW_RUNTIME_PROBE_TIMEOUT_MS) {
|
|
@@ -365,14 +383,14 @@ async function openClawJsonProbe(args, options, timeoutMs = OPENCLAW_RUNTIME_PRO
|
|
|
365
383
|
try {
|
|
366
384
|
child = spawnProcess('openclaw', args, {
|
|
367
385
|
cwd: options.cwd,
|
|
368
|
-
env:
|
|
386
|
+
env: runtimeEnvironment(options.environment),
|
|
369
387
|
shell: false,
|
|
370
388
|
detached: process.platform !== 'win32',
|
|
371
389
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
372
390
|
});
|
|
373
391
|
}
|
|
374
392
|
catch (error) {
|
|
375
|
-
throw
|
|
393
|
+
throw openClawError('openclaw_unavailable', 'Unable to start openclaw. Is it installed and on PATH?', error);
|
|
376
394
|
}
|
|
377
395
|
let stdout = '';
|
|
378
396
|
let stderr = '';
|
|
@@ -417,18 +435,20 @@ async function openClawJsonProbe(args, options, timeoutMs = OPENCLAW_RUNTIME_PRO
|
|
|
417
435
|
throw error;
|
|
418
436
|
}),
|
|
419
437
|
]));
|
|
420
|
-
if (timedOut)
|
|
421
|
-
throw
|
|
422
|
-
|
|
423
|
-
|
|
438
|
+
if (timedOut) {
|
|
439
|
+
throw openClawError('openclaw_probe_timeout', `OpenClaw preflight timed out while running ${[args[0], args[1]].filter(Boolean).join(' ')}.`);
|
|
440
|
+
}
|
|
441
|
+
if (exceeded) {
|
|
442
|
+
throw openClawError('openclaw_probe_output_too_large', 'OpenClaw preflight exceeded its 1048576 byte output limit.');
|
|
443
|
+
}
|
|
424
444
|
if (code !== 0) {
|
|
425
|
-
throw
|
|
445
|
+
throw openClawError('openclaw_probe_failed', `OpenClaw preflight failed: ${publicOpenClawDiagnostic(stderr.trim()) || `exit ${String(code)}`}`);
|
|
426
446
|
}
|
|
427
447
|
try {
|
|
428
448
|
return JSON.parse(stdout);
|
|
429
449
|
}
|
|
430
450
|
catch (error) {
|
|
431
|
-
throw
|
|
451
|
+
throw openClawError('openclaw_probe_invalid_json', 'OpenClaw preflight returned invalid JSON.', error);
|
|
432
452
|
}
|
|
433
453
|
}
|
|
434
454
|
finally {
|
|
@@ -441,12 +461,14 @@ async function openClawJsonProbe(args, options, timeoutMs = OPENCLAW_RUNTIME_PRO
|
|
|
441
461
|
}
|
|
442
462
|
export async function verifyOpenClawRuntime(options) {
|
|
443
463
|
const agentId = options.profile.runtimeAgentId?.trim();
|
|
444
|
-
if (!agentId)
|
|
445
|
-
throw
|
|
464
|
+
if (!agentId) {
|
|
465
|
+
throw openClawError('openclaw_agent_id_missing', 'OpenClaw requires an explicit configured agent ID.');
|
|
466
|
+
}
|
|
446
467
|
const desiredWorkspace = await resolvedPath(options.cwd, 'CrewX assignment directory');
|
|
447
468
|
let agents = await openClawJsonProbe(['agents', 'list', '--json'], options);
|
|
448
|
-
if (!Array.isArray(agents))
|
|
449
|
-
throw
|
|
469
|
+
if (!Array.isArray(agents)) {
|
|
470
|
+
throw openClawError('openclaw_agent_list_invalid', 'OpenClaw returned an invalid agent list.');
|
|
471
|
+
}
|
|
450
472
|
let selected = agents.map(record).find((agent) => agent?.id === agentId);
|
|
451
473
|
if (!selected && agentId.startsWith('crewx-')) {
|
|
452
474
|
await openClawJsonProbe([
|
|
@@ -459,41 +481,74 @@ export async function verifyOpenClawRuntime(options) {
|
|
|
459
481
|
'--json',
|
|
460
482
|
], options, OPENCLAW_SETUP_TIMEOUT_MS);
|
|
461
483
|
agents = await openClawJsonProbe(['agents', 'list', '--json'], options);
|
|
462
|
-
if (!Array.isArray(agents))
|
|
463
|
-
throw
|
|
484
|
+
if (!Array.isArray(agents)) {
|
|
485
|
+
throw openClawError('openclaw_agent_list_invalid', 'OpenClaw returned an invalid agent list after setup.');
|
|
486
|
+
}
|
|
464
487
|
selected = agents.map(record).find((agent) => agent?.id === agentId);
|
|
465
488
|
if (!selected) {
|
|
466
|
-
throw
|
|
489
|
+
throw openClawError('openclaw_agent_setup_failed', `CrewX created OpenClaw agent "${agentId}", but OpenClaw did not return it.`);
|
|
467
490
|
}
|
|
468
491
|
}
|
|
469
492
|
if (!selected) {
|
|
470
|
-
throw
|
|
493
|
+
throw openClawError('openclaw_agent_missing', `OpenClaw agent "${agentId}" is not configured. Run \`openclaw agents list --json\` to choose one.`);
|
|
471
494
|
}
|
|
472
495
|
if (typeof selected.workspace !== 'string' || !selected.workspace.trim()) {
|
|
473
|
-
throw
|
|
496
|
+
throw openClawError('openclaw_workspace_missing', `OpenClaw agent "${agentId}" does not report a workspace.`);
|
|
474
497
|
}
|
|
475
498
|
const configuredWorkspace = await resolvedPath(selected.workspace, `OpenClaw agent "${agentId}" workspace`);
|
|
476
499
|
if (configuredWorkspace !== desiredWorkspace) {
|
|
477
|
-
throw
|
|
500
|
+
throw openClawError('openclaw_workspace_mismatch', `OpenClaw agent "${agentId}" is bound to a different local workspace than the CrewX-approved folder. OpenClaw has no per-run --cwd; create or select a dedicated OpenClaw agent for the approved folder, then retry.`);
|
|
501
|
+
}
|
|
502
|
+
const models = record(await openClawJsonProbe(['models', 'status', '--agent', agentId, '--json'], options, OPENCLAW_SETUP_TIMEOUT_MS));
|
|
503
|
+
if (!models || (typeof models.agentId === 'string' && models.agentId !== agentId)) {
|
|
504
|
+
throw openClawError('openclaw_model_status_invalid', `OpenClaw did not return model status for agent "${agentId}". Run \`openclaw models status --agent "${agentId}" --json\`.`);
|
|
505
|
+
}
|
|
506
|
+
const resolvedModel = typeof models.resolvedDefault === 'string' && models.resolvedDefault.trim()
|
|
507
|
+
? models.resolvedDefault.trim()
|
|
508
|
+
: typeof models.defaultModel === 'string' && models.defaultModel.trim()
|
|
509
|
+
? models.defaultModel.trim()
|
|
510
|
+
: undefined;
|
|
511
|
+
if (!resolvedModel) {
|
|
512
|
+
throw openClawError('openclaw_model_missing', `OpenClaw agent "${agentId}" has no default model. Configure one, then run \`openclaw models status --agent "${agentId}" --json\`.`);
|
|
513
|
+
}
|
|
514
|
+
const allowedModels = Array.isArray(models.allowed)
|
|
515
|
+
? models.allowed.filter((value) => typeof value === 'string')
|
|
516
|
+
: [];
|
|
517
|
+
if (allowedModels.length > 0 && !allowedModels.includes(resolvedModel)) {
|
|
518
|
+
throw openClawError('openclaw_model_unavailable', `OpenClaw agent "${agentId}" resolves to model "${resolvedModel}", but that model is not allowed. Run \`openclaw models status --agent "${agentId}" --json\` and select an allowed model.`);
|
|
519
|
+
}
|
|
520
|
+
const auth = record(models.auth);
|
|
521
|
+
const missingProviders = Array.isArray(auth?.missingProvidersInUse)
|
|
522
|
+
? auth.missingProvidersInUse.filter((value) => typeof value === 'string')
|
|
523
|
+
: [];
|
|
524
|
+
if (missingProviders.length > 0) {
|
|
525
|
+
throw openClawError('openclaw_auth_missing', `OpenClaw agent "${agentId}" is missing authentication for ${missingProviders.join(', ')}. Run \`openclaw models auth --agent "${agentId}" --help\`, configure the provider, then verify with \`openclaw models status --agent "${agentId}" --json\`.`);
|
|
526
|
+
}
|
|
527
|
+
const authRoutes = Array.isArray(auth?.runtimeAuthRoutes)
|
|
528
|
+
? auth.runtimeAuthRoutes.map(record).filter((route) => route !== undefined)
|
|
529
|
+
: [];
|
|
530
|
+
if (authRoutes.length > 0 &&
|
|
531
|
+
!authRoutes.some((route) => route.status === 'usable')) {
|
|
532
|
+
throw openClawError('openclaw_auth_unusable', `OpenClaw agent "${agentId}" has no usable authentication route for "${resolvedModel}" (credentials may be expired or cooling down). Run \`openclaw models auth --agent "${agentId}" list --json\`, repair authentication, then retry.`);
|
|
478
533
|
}
|
|
479
534
|
const sessionLeaf = options.profile.sessionKey ?? 'main';
|
|
480
535
|
const fullSessionKey = `agent:${agentId}:${sessionLeaf}`;
|
|
481
536
|
const explanation = record(await openClawJsonProbe(['sandbox', 'explain', '--session', fullSessionKey, '--json'], options));
|
|
482
537
|
const sandbox = record(explanation?.sandbox);
|
|
483
538
|
if (explanation?.agentId !== agentId || explanation?.sessionKey !== fullSessionKey || !sandbox) {
|
|
484
|
-
throw
|
|
539
|
+
throw openClawError('openclaw_sandbox_invalid', 'OpenClaw sandbox preflight did not describe the requested agent session.');
|
|
485
540
|
}
|
|
486
541
|
const effectiveHostRoot = typeof sandbox.effectiveHostWorkspaceRoot === 'string'
|
|
487
542
|
? await resolvedPath(sandbox.effectiveHostWorkspaceRoot, 'OpenClaw effective workspace')
|
|
488
543
|
: undefined;
|
|
489
544
|
if (effectiveHostRoot !== desiredWorkspace) {
|
|
490
|
-
throw
|
|
545
|
+
throw openClawError('openclaw_sandbox_workspace_mismatch', 'OpenClaw sandbox policy does not expose the exact CrewX assignment directory.');
|
|
491
546
|
}
|
|
492
547
|
if (sandbox.sessionIsSandboxed === true) {
|
|
493
548
|
if (sandbox.backend !== 'docker' ||
|
|
494
549
|
sandbox.workspaceAccess !== 'rw' ||
|
|
495
550
|
typeof sandbox.runtimeWorkdir !== 'string') {
|
|
496
|
-
throw
|
|
551
|
+
throw openClawError('openclaw_sandbox_read_only', 'OpenClaw must grant rw workspace access for CrewX coding assignments.');
|
|
497
552
|
}
|
|
498
553
|
const mounts = Array.isArray(sandbox.workspaceMounts) ? sandbox.workspaceMounts.map(record) : [];
|
|
499
554
|
let writableWorkspaceMount = false;
|
|
@@ -508,16 +563,16 @@ export async function verifyOpenClawRuntime(options) {
|
|
|
508
563
|
}
|
|
509
564
|
}
|
|
510
565
|
if (!writableWorkspaceMount) {
|
|
511
|
-
throw
|
|
566
|
+
throw openClawError('openclaw_sandbox_mount_missing', 'OpenClaw sandbox policy does not mount the CrewX assignment directory read-write at its runtime workdir.');
|
|
512
567
|
}
|
|
513
568
|
}
|
|
514
569
|
else {
|
|
515
570
|
if (typeof sandbox.runtimeWorkdir !== 'string') {
|
|
516
|
-
throw
|
|
571
|
+
throw openClawError('openclaw_runtime_workdir_missing', 'OpenClaw did not report its direct runtime working directory.');
|
|
517
572
|
}
|
|
518
573
|
const runtimeWorkdir = await resolvedPath(sandbox.runtimeWorkdir, 'OpenClaw runtime working directory');
|
|
519
574
|
if (runtimeWorkdir !== desiredWorkspace) {
|
|
520
|
-
throw
|
|
575
|
+
throw openClawError('openclaw_runtime_workdir_mismatch', 'OpenClaw direct runtime workdir does not match the CrewX assignment directory.');
|
|
521
576
|
}
|
|
522
577
|
}
|
|
523
578
|
}
|
|
@@ -553,6 +608,32 @@ function createLineConsumer(callback) {
|
|
|
553
608
|
},
|
|
554
609
|
};
|
|
555
610
|
}
|
|
611
|
+
const ALLOWED_RUNTIME_CONTROL_VARIABLES = [
|
|
612
|
+
'CREWX_URL',
|
|
613
|
+
'CREWX_TOKEN',
|
|
614
|
+
'CREWX_CLI_PATH',
|
|
615
|
+
];
|
|
616
|
+
/**
|
|
617
|
+
* Coding runtimes inherit provider credentials and ordinary process settings,
|
|
618
|
+
* but never inherit the daemon's CrewX control-plane credentials. The only
|
|
619
|
+
* CrewX variables admitted are short-lived, capability-scoped proxy values
|
|
620
|
+
* supplied separately by the assignment executor.
|
|
621
|
+
*/
|
|
622
|
+
export function runtimeEnvironment(environment = {}, controlEnvironment = {}, inherited = process.env) {
|
|
623
|
+
const result = {};
|
|
624
|
+
for (const [key, value] of Object.entries({ ...inherited, ...environment })) {
|
|
625
|
+
if (key.toUpperCase().startsWith('CREWX_'))
|
|
626
|
+
continue;
|
|
627
|
+
if (value !== undefined)
|
|
628
|
+
result[key] = value;
|
|
629
|
+
}
|
|
630
|
+
for (const key of ALLOWED_RUNTIME_CONTROL_VARIABLES) {
|
|
631
|
+
const value = controlEnvironment[key];
|
|
632
|
+
if (value !== undefined)
|
|
633
|
+
result[key] = value;
|
|
634
|
+
}
|
|
635
|
+
return result;
|
|
636
|
+
}
|
|
556
637
|
async function materializeInvocation(invocation, prompt) {
|
|
557
638
|
if (invocation.prompt.type === 'stdin') {
|
|
558
639
|
return { args: invocation.args, stdin: prompt, cleanup: async () => undefined };
|
|
@@ -603,10 +684,7 @@ async function runSpawnAdapter(options) {
|
|
|
603
684
|
try {
|
|
604
685
|
child = spawnProcess(invocation.command, materialized.args, {
|
|
605
686
|
cwd: options.cwd,
|
|
606
|
-
env:
|
|
607
|
-
...process.env,
|
|
608
|
-
...options.environment,
|
|
609
|
-
},
|
|
687
|
+
env: runtimeEnvironment(options.environment, options.controlEnvironment),
|
|
610
688
|
shell: false,
|
|
611
689
|
detached: process.platform !== 'win32',
|
|
612
690
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -767,7 +845,7 @@ async function runHermesAcp(options) {
|
|
|
767
845
|
try {
|
|
768
846
|
child = spawnProcess('hermes', ['acp'], {
|
|
769
847
|
cwd: options.cwd,
|
|
770
|
-
env:
|
|
848
|
+
env: runtimeEnvironment(options.environment, options.controlEnvironment),
|
|
771
849
|
shell: false,
|
|
772
850
|
detached: process.platform !== 'win32',
|
|
773
851
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -1006,20 +1084,14 @@ export async function runAdapter(options) {
|
|
|
1006
1084
|
return runSpawnAdapter(options);
|
|
1007
1085
|
}
|
|
1008
1086
|
export async function probeAdapter(adapter, spawnProcess = spawn, timeoutMs) {
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
? OPENCLAW_RUNTIME_PROBE_TIMEOUT_MS
|
|
1012
|
-
: DEFAULT_RUNTIME_PROBE_TIMEOUT_MS);
|
|
1087
|
+
const plan = adapterProbePlan(adapter);
|
|
1088
|
+
const effectiveTimeoutMs = timeoutMs ?? plan.timeoutMs;
|
|
1013
1089
|
const command = adapter;
|
|
1014
|
-
const probeArgs =
|
|
1015
|
-
? ['acp', '--check']
|
|
1016
|
-
: adapter === 'openclaw'
|
|
1017
|
-
? ['agents', 'list', '--json']
|
|
1018
|
-
: ['--version'];
|
|
1090
|
+
const probeArgs = plan.args;
|
|
1019
1091
|
let child;
|
|
1020
1092
|
try {
|
|
1021
1093
|
child = spawnProcess(command, probeArgs, {
|
|
1022
|
-
env:
|
|
1094
|
+
env: runtimeEnvironment(),
|
|
1023
1095
|
shell: false,
|
|
1024
1096
|
detached: process.platform !== 'win32',
|
|
1025
1097
|
stdio: ['pipe', 'pipe', 'pipe'],
|