@ai-codebot/daemon 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/broker/handoff.d.ts +57 -0
- package/dist/broker/handoff.js +87 -0
- package/dist/broker/handoff.js.map +1 -0
- package/dist/broker/server.d.ts +34 -0
- package/dist/broker/server.js +204 -0
- package/dist/broker/server.js.map +1 -0
- package/dist/broker/token.d.ts +36 -0
- package/dist/broker/token.js +48 -0
- package/dist/broker/token.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +120 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.js +132 -0
- package/dist/config.js.map +1 -0
- package/dist/frames.d.ts +311 -0
- package/dist/frames.js +137 -0
- package/dist/frames.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/launch.d.ts +40 -0
- package/dist/launch.js +163 -0
- package/dist/launch.js.map +1 -0
- package/dist/logging.d.ts +27 -0
- package/dist/logging.js +91 -0
- package/dist/logging.js.map +1 -0
- package/dist/spawn/cli-runner.d.ts +91 -0
- package/dist/spawn/cli-runner.js +180 -0
- package/dist/spawn/cli-runner.js.map +1 -0
- package/dist/spawn/detect.d.ts +18 -0
- package/dist/spawn/detect.js +46 -0
- package/dist/spawn/detect.js.map +1 -0
- package/dist/spawn/disallowed-tools.d.ts +15 -0
- package/dist/spawn/disallowed-tools.js +30 -0
- package/dist/spawn/disallowed-tools.js.map +1 -0
- package/dist/spawn/git.d.ts +19 -0
- package/dist/spawn/git.js +56 -0
- package/dist/spawn/git.js.map +1 -0
- package/dist/spawn/llm-client.d.ts +52 -0
- package/dist/spawn/llm-client.js +61 -0
- package/dist/spawn/llm-client.js.map +1 -0
- package/dist/spawn/n2-runner.d.ts +33 -0
- package/dist/spawn/n2-runner.js +176 -0
- package/dist/spawn/n2-runner.js.map +1 -0
- package/dist/spawn/n2-tools.d.ts +44 -0
- package/dist/spawn/n2-tools.js +374 -0
- package/dist/spawn/n2-tools.js.map +1 -0
- package/dist/spawn/result-map.d.ts +40 -0
- package/dist/spawn/result-map.js +68 -0
- package/dist/spawn/result-map.js.map +1 -0
- package/dist/tunnel.d.ts +61 -0
- package/dist/tunnel.js +205 -0
- package/dist/tunnel.js.map +1 -0
- package/package.json +60 -0
package/dist/frames.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire frame protocol for `/daemon/connect` — TS mirror of the server's
|
|
3
|
+
* `daemon_frames.py` (PR2). Field-for-field, snake_case keys.
|
|
4
|
+
*
|
|
5
|
+
* Directionality (matches the Python contract):
|
|
6
|
+
* - Server -> daemon (INBOUND, zod-validated at the boundary):
|
|
7
|
+
* HelloAck, Launch, Ping, ServerError
|
|
8
|
+
* - Daemon -> server (OUTBOUND, constructed by us, trusted):
|
|
9
|
+
* Hello, Progress, Result, Pong
|
|
10
|
+
*
|
|
11
|
+
* The server validates the daemon->server direction; we validate the
|
|
12
|
+
* server->daemon direction. `parseInbound` is the boundary validator: an unknown
|
|
13
|
+
* `type` or a malformed field throws a `ZodError` the receive loop catches and
|
|
14
|
+
* ignores (never crashes the loop) — mirroring `parse_inbound`.
|
|
15
|
+
*
|
|
16
|
+
* Secrets: `ModelOverride.api_key` is a custom-provider credential — NEVER logged.
|
|
17
|
+
*/
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
/**
|
|
20
|
+
* Largest frame we accept (design §8.7 / `daemon_frames.MAX_FRAME_BYTES`): 256 KiB.
|
|
21
|
+
* Used as the `ws` `maxPayload`. Must equal the server's ceiling field-for-field.
|
|
22
|
+
*/
|
|
23
|
+
export const MAX_FRAME_BYTES = 256 * 1024;
|
|
24
|
+
/** Terminal launch outcomes a daemon may report (`Result.status`). */
|
|
25
|
+
export const RESULT_STATUSES = ["succeeded", "failed", "timed_out"];
|
|
26
|
+
// --------------------------------------------------------------------------- //
|
|
27
|
+
// Shared sub-models
|
|
28
|
+
// --------------------------------------------------------------------------- //
|
|
29
|
+
/** Custom-provider model override carried on a Launch (C4/C8). `apiKey` is secret. */
|
|
30
|
+
export const ModelOverrideSchema = z.object({
|
|
31
|
+
kind: z.literal("custom").default("custom"),
|
|
32
|
+
api_url: z.string(),
|
|
33
|
+
api_key: z.string(),
|
|
34
|
+
});
|
|
35
|
+
// --------------------------------------------------------------------------- //
|
|
36
|
+
// Server -> daemon (INBOUND — we validate these)
|
|
37
|
+
// --------------------------------------------------------------------------- //
|
|
38
|
+
export const HelloAckSchema = z.object({
|
|
39
|
+
type: z.literal("hello_ack"),
|
|
40
|
+
machine_id: z.string(),
|
|
41
|
+
server_time: z.string(),
|
|
42
|
+
heartbeat_interval_s: z.number().int(),
|
|
43
|
+
});
|
|
44
|
+
export const LaunchSchema = z.object({
|
|
45
|
+
type: z.literal("launch"),
|
|
46
|
+
launch_id: z.string(),
|
|
47
|
+
agent_id: z.string(),
|
|
48
|
+
prompt: z.string(),
|
|
49
|
+
repo: z.string().nullish(),
|
|
50
|
+
model_override: ModelOverrideSchema.nullish(),
|
|
51
|
+
capabilities: z.array(z.string()).default([]),
|
|
52
|
+
deadline_s: z.number().int(),
|
|
53
|
+
});
|
|
54
|
+
export const PingSchema = z.object({
|
|
55
|
+
type: z.literal("ping"),
|
|
56
|
+
nonce: z.string(),
|
|
57
|
+
});
|
|
58
|
+
export const ServerErrorSchema = z.object({
|
|
59
|
+
type: z.literal("error"),
|
|
60
|
+
code: z.string(),
|
|
61
|
+
detail: z.string(),
|
|
62
|
+
launch_id: z.string().nullish(),
|
|
63
|
+
});
|
|
64
|
+
/** Discriminated union of every frame the SERVER may send us (inbound). */
|
|
65
|
+
export const InboundFrameSchema = z.discriminatedUnion("type", [
|
|
66
|
+
HelloAckSchema,
|
|
67
|
+
LaunchSchema,
|
|
68
|
+
PingSchema,
|
|
69
|
+
ServerErrorSchema,
|
|
70
|
+
]);
|
|
71
|
+
// --------------------------------------------------------------------------- //
|
|
72
|
+
// Daemon -> server (OUTBOUND — we construct these)
|
|
73
|
+
// --------------------------------------------------------------------------- //
|
|
74
|
+
// Bounds mirror the server's Hello (`max_length=200`, `max_length=100`): a buggy
|
|
75
|
+
// or oversized Hello cannot store an unbounded scope-attestation blob.
|
|
76
|
+
export const HELLO_MAX_TOOL_LEN = 200;
|
|
77
|
+
export const HELLO_MAX_TOOLS = 100;
|
|
78
|
+
export const HELLO_MAX_VERSION_LEN = 200;
|
|
79
|
+
export const HelloSchema = z.object({
|
|
80
|
+
type: z.literal("hello"),
|
|
81
|
+
daemon_version: z.string().max(HELLO_MAX_VERSION_LEN),
|
|
82
|
+
advertised_tools: z
|
|
83
|
+
.array(z.string().max(HELLO_MAX_TOOL_LEN))
|
|
84
|
+
.max(HELLO_MAX_TOOLS)
|
|
85
|
+
.default([]),
|
|
86
|
+
});
|
|
87
|
+
export const ProgressSchema = z.object({
|
|
88
|
+
type: z.literal("progress"),
|
|
89
|
+
launch_id: z.string(),
|
|
90
|
+
stage: z.string(),
|
|
91
|
+
message: z.string().default(""),
|
|
92
|
+
});
|
|
93
|
+
export const ResultSchema = z.object({
|
|
94
|
+
type: z.literal("result"),
|
|
95
|
+
launch_id: z.string(),
|
|
96
|
+
status: z.enum(RESULT_STATUSES),
|
|
97
|
+
text: z.string().nullish(),
|
|
98
|
+
diff: z.string().nullish(),
|
|
99
|
+
files: z.array(z.string()).nullish(),
|
|
100
|
+
cost_usd: z.number().nullish(),
|
|
101
|
+
tokens: z.number().int().nullish(),
|
|
102
|
+
error: z.string().nullish(),
|
|
103
|
+
});
|
|
104
|
+
export const PongSchema = z.object({
|
|
105
|
+
type: z.literal("pong"),
|
|
106
|
+
nonce: z.string(),
|
|
107
|
+
});
|
|
108
|
+
// --------------------------------------------------------------------------- //
|
|
109
|
+
// Boundary validators
|
|
110
|
+
// --------------------------------------------------------------------------- //
|
|
111
|
+
/**
|
|
112
|
+
* Validate one decoded server->daemon message at the boundary (mirrors
|
|
113
|
+
* `parse_inbound`). Throws `ZodError` on an unknown `type` or any malformed
|
|
114
|
+
* field; the receive loop catches it, logs, and ignores the frame.
|
|
115
|
+
*/
|
|
116
|
+
export function parseInbound(raw) {
|
|
117
|
+
return InboundFrameSchema.parse(raw);
|
|
118
|
+
}
|
|
119
|
+
// --------------------------------------------------------------------------- //
|
|
120
|
+
// Outbound constructors (typed, defaults filled — never validated, we trust us)
|
|
121
|
+
// --------------------------------------------------------------------------- //
|
|
122
|
+
export function makeHello(daemonVersion, advertisedTools = []) {
|
|
123
|
+
return {
|
|
124
|
+
type: "hello",
|
|
125
|
+
daemon_version: daemonVersion.slice(0, HELLO_MAX_VERSION_LEN),
|
|
126
|
+
advertised_tools: advertisedTools
|
|
127
|
+
.slice(0, HELLO_MAX_TOOLS)
|
|
128
|
+
.map((t) => t.slice(0, HELLO_MAX_TOOL_LEN)),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export function makePong(nonce) {
|
|
132
|
+
return { type: "pong", nonce };
|
|
133
|
+
}
|
|
134
|
+
export function makeProgress(launchId, stage, message = "") {
|
|
135
|
+
return { type: "progress", launch_id: launchId, stage, message };
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=frames.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frames.js","sourceRoot":"","sources":["../src/frames.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `@ai-codebot/daemon` entrypoint (bin: `codebot-daemon`).
|
|
4
|
+
*
|
|
5
|
+
* slock local-runner PR4 — dials the cloud relay over outbound WSS and runs local
|
|
6
|
+
* coding-agent launches through a 127.0.0.1 capability broker. See README / design
|
|
7
|
+
* doc `docs/superpowers/specs/2026-06-08-slock-PR4-daemon-package-design.md`.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `@ai-codebot/daemon` entrypoint (bin: `codebot-daemon`).
|
|
4
|
+
*
|
|
5
|
+
* slock local-runner PR4 — dials the cloud relay over outbound WSS and runs local
|
|
6
|
+
* coding-agent launches through a 127.0.0.1 capability broker. See README / design
|
|
7
|
+
* doc `docs/superpowers/specs/2026-06-08-slock-PR4-daemon-package-design.md`.
|
|
8
|
+
*/
|
|
9
|
+
import { main } from "./cli.js";
|
|
10
|
+
import { log } from "./logging.js";
|
|
11
|
+
main(process.argv.slice(2))
|
|
12
|
+
.then((code) => {
|
|
13
|
+
process.exitCode = code;
|
|
14
|
+
})
|
|
15
|
+
.catch((err) => {
|
|
16
|
+
log.error("daemon.fatal", { error: err.message });
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
});
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACxB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;IACb,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC,CAAC;KACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACb,GAAG,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
package/dist/launch.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launch orchestration (design §4/§5/§6): on a `Launch` frame, stand up the
|
|
3
|
+
* per-launch capability broker, build the scrubbed spawn env, run the executor
|
|
4
|
+
* (PRIMARY CLI → N2 fallback), map the outcome to exactly one `Result`, and tear
|
|
5
|
+
* everything down idempotently.
|
|
6
|
+
*
|
|
7
|
+
* Invariants:
|
|
8
|
+
* - 零静默 (C8/C9): exactly ONE terminal `Result` per launch. No silent fallback —
|
|
9
|
+
* model unreachable / no model configured / no CLI + no N2 → loud `failed`.
|
|
10
|
+
* - The real LLM key lives ONLY in the broker; the child sees a per-launch token.
|
|
11
|
+
* - Idempotent teardown on result / deadline / signal: broker close, token unlink,
|
|
12
|
+
* child kill. Tracked so SIGINT/SIGTERM tears down ALL live launches.
|
|
13
|
+
*/
|
|
14
|
+
import type { Launch, Result } from "./frames.js";
|
|
15
|
+
import type { CliKind } from "./config.js";
|
|
16
|
+
export interface LaunchRunnerDeps {
|
|
17
|
+
readonly cliMode: CliKind;
|
|
18
|
+
/** Optional `--model-url` fallback when the launch carries no model_override. */
|
|
19
|
+
readonly modelUrlFallback: string | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Owns all in-flight launches so a signal can tear them ALL down (no orphan
|
|
23
|
+
* children / `0600` token files). One `GrantRegistry` is shared; each launch gets a
|
|
24
|
+
* fresh token + broker.
|
|
25
|
+
*/
|
|
26
|
+
export declare class LaunchManager {
|
|
27
|
+
private readonly deps;
|
|
28
|
+
private readonly grants;
|
|
29
|
+
private readonly live;
|
|
30
|
+
constructor(deps: LaunchRunnerDeps);
|
|
31
|
+
/**
|
|
32
|
+
* Run one launch end-to-end and return exactly one terminal `Result`. Never
|
|
33
|
+
* throws — every failure path becomes a `failed` / `timed_out` result.
|
|
34
|
+
*/
|
|
35
|
+
run(launch: Launch, onProgress: (stage: string) => void): Promise<Result>;
|
|
36
|
+
/** PRIMARY CLI → N2 fallback. No CLI + N2 unusable → loud failed (handled by N2). */
|
|
37
|
+
private execute;
|
|
38
|
+
/** Tear down every live launch (SIGINT/SIGTERM). Idempotent. */
|
|
39
|
+
shutdownAll(): Promise<void>;
|
|
40
|
+
}
|
package/dist/launch.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launch orchestration (design §4/§5/§6): on a `Launch` frame, stand up the
|
|
3
|
+
* per-launch capability broker, build the scrubbed spawn env, run the executor
|
|
4
|
+
* (PRIMARY CLI → N2 fallback), map the outcome to exactly one `Result`, and tear
|
|
5
|
+
* everything down idempotently.
|
|
6
|
+
*
|
|
7
|
+
* Invariants:
|
|
8
|
+
* - 零静默 (C8/C9): exactly ONE terminal `Result` per launch. No silent fallback —
|
|
9
|
+
* model unreachable / no model configured / no CLI + no N2 → loud `failed`.
|
|
10
|
+
* - The real LLM key lives ONLY in the broker; the child sees a per-launch token.
|
|
11
|
+
* - Idempotent teardown on result / deadline / signal: broker close, token unlink,
|
|
12
|
+
* child kill. Tracked so SIGINT/SIGTERM tears down ALL live launches.
|
|
13
|
+
*/
|
|
14
|
+
import { CliContractUnverifiedError, runCli } from "./spawn/cli-runner.js";
|
|
15
|
+
import { detectCli } from "./spawn/detect.js";
|
|
16
|
+
import { buildSpawnEnv, createTokenFile, removeTokenDir, } from "./broker/handoff.js";
|
|
17
|
+
import { GrantRegistry } from "./broker/token.js";
|
|
18
|
+
import { startBroker } from "./broker/server.js";
|
|
19
|
+
import { log } from "./logging.js";
|
|
20
|
+
import { runN2 } from "./spawn/n2-runner.js";
|
|
21
|
+
import { toResult } from "./spawn/result-map.js";
|
|
22
|
+
// Default N2 model id when the launch carries no explicit one.
|
|
23
|
+
// TODO(OQ-6): the Launch wire frame carries NO model id — the broker forwards to the
|
|
24
|
+
// tenant's own endpoint, so the upstream provider resolves the actual model. This
|
|
25
|
+
// placeholder is only the OpenAI-compatible `model` field the request must include;
|
|
26
|
+
// it is NOT authoritative. Track resolving the real per-launch model id in OQ-6.
|
|
27
|
+
const DEFAULT_N2_MODEL = "claude-3-5-sonnet-latest";
|
|
28
|
+
const N2_TEST_TIMEOUT_MS = 120_000;
|
|
29
|
+
/**
|
|
30
|
+
* Owns all in-flight launches so a signal can tear them ALL down (no orphan
|
|
31
|
+
* children / `0600` token files). One `GrantRegistry` is shared; each launch gets a
|
|
32
|
+
* fresh token + broker.
|
|
33
|
+
*/
|
|
34
|
+
export class LaunchManager {
|
|
35
|
+
deps;
|
|
36
|
+
grants = new GrantRegistry();
|
|
37
|
+
live = new Map();
|
|
38
|
+
constructor(deps) {
|
|
39
|
+
this.deps = deps;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run one launch end-to-end and return exactly one terminal `Result`. Never
|
|
43
|
+
* throws — every failure path becomes a `failed` / `timed_out` result.
|
|
44
|
+
*/
|
|
45
|
+
async run(launch, onProgress) {
|
|
46
|
+
const launchId = launch.launch_id;
|
|
47
|
+
const repo = launch.repo ?? process.cwd();
|
|
48
|
+
const deadlineMs = Math.max(1, launch.deadline_s) * 1000;
|
|
49
|
+
// Resolve the upstream model endpoint: launch.model_override wins, else
|
|
50
|
+
// --model-url. No model configured → loud failed (P5, no silent server fallback).
|
|
51
|
+
const upstreamUrl = launch.model_override?.api_url ?? this.deps.modelUrlFallback;
|
|
52
|
+
const upstreamKey = launch.model_override?.api_key ?? "";
|
|
53
|
+
if (!upstreamUrl) {
|
|
54
|
+
return toResult(launchId, {
|
|
55
|
+
kind: "failed",
|
|
56
|
+
error: "no model configured (launch has no model_override and no --model-url was given)",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const grant = this.grants.create(launchId, upstreamUrl, upstreamKey);
|
|
60
|
+
const abort = new AbortController();
|
|
61
|
+
let broker = null;
|
|
62
|
+
let tokenFileHandle = null;
|
|
63
|
+
let torndown = false;
|
|
64
|
+
const teardown = async () => {
|
|
65
|
+
if (torndown)
|
|
66
|
+
return;
|
|
67
|
+
torndown = true;
|
|
68
|
+
abort.abort();
|
|
69
|
+
this.grants.revoke(grant.token);
|
|
70
|
+
if (tokenFileHandle !== null)
|
|
71
|
+
removeTokenDir(tokenFileHandle.dir);
|
|
72
|
+
if (broker !== null) {
|
|
73
|
+
try {
|
|
74
|
+
await broker.close();
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
log.warn("launch.broker_close_failed", {
|
|
78
|
+
launch_id: launchId,
|
|
79
|
+
error: err.message,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
this.live.delete(launchId);
|
|
84
|
+
};
|
|
85
|
+
this.live.set(launchId, { launchId, teardown });
|
|
86
|
+
const deadlineTimer = setTimeout(() => abort.abort(), deadlineMs);
|
|
87
|
+
try {
|
|
88
|
+
onProgress("preparing");
|
|
89
|
+
broker = await startBroker(this.grants);
|
|
90
|
+
tokenFileHandle = createTokenFile(grant.token);
|
|
91
|
+
const env = buildSpawnEnv({
|
|
92
|
+
brokerBaseUrl: broker.baseUrl,
|
|
93
|
+
token: grant.token,
|
|
94
|
+
tokenFile: tokenFileHandle.path,
|
|
95
|
+
capabilities: launch.capabilities ?? [],
|
|
96
|
+
});
|
|
97
|
+
onProgress("running");
|
|
98
|
+
const outcome = await this.execute(launch, repo, env, broker.baseUrl, grant.token, abort.signal, deadlineMs);
|
|
99
|
+
return toResult(launchId, outcome);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
// Defensive: any unexpected throw still yields exactly one failed Result.
|
|
103
|
+
log.error("launch.unexpected_error", {
|
|
104
|
+
launch_id: launchId,
|
|
105
|
+
error: err.message,
|
|
106
|
+
});
|
|
107
|
+
return toResult(launchId, {
|
|
108
|
+
kind: "failed",
|
|
109
|
+
error: `launch failed: ${err.message}`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
clearTimeout(deadlineTimer);
|
|
114
|
+
await teardown();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** PRIMARY CLI → N2 fallback. No CLI + N2 unusable → loud failed (handled by N2). */
|
|
118
|
+
async execute(launch, repo, env, brokerBaseUrl, token, signal, deadlineMs) {
|
|
119
|
+
const detected = detectCli(this.deps.cliMode, env);
|
|
120
|
+
if (detected !== null) {
|
|
121
|
+
try {
|
|
122
|
+
return await runCli({
|
|
123
|
+
cli: detected,
|
|
124
|
+
repo,
|
|
125
|
+
prompt: launch.prompt,
|
|
126
|
+
env,
|
|
127
|
+
deadlineMs,
|
|
128
|
+
signal,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
if (err instanceof CliContractUnverifiedError) {
|
|
133
|
+
// OQ-3: refuse a possibly-wrong invocation → fall through to N2.
|
|
134
|
+
log.warn("launch.cli_unverified_fallback_n2", {
|
|
135
|
+
launch_id: launch.launch_id,
|
|
136
|
+
cli: detected,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// N2 fallback (the fully-working path). Uses the launch's model id if carried in
|
|
145
|
+
// the prompt's provider expectation — default otherwise; the broker forwards it.
|
|
146
|
+
return runN2({
|
|
147
|
+
repo,
|
|
148
|
+
prompt: launch.prompt,
|
|
149
|
+
model: DEFAULT_N2_MODEL,
|
|
150
|
+
brokerBaseUrl,
|
|
151
|
+
token,
|
|
152
|
+
env,
|
|
153
|
+
testTimeoutMs: N2_TEST_TIMEOUT_MS,
|
|
154
|
+
signal,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/** Tear down every live launch (SIGINT/SIGTERM). Idempotent. */
|
|
158
|
+
async shutdownAll() {
|
|
159
|
+
const pending = [...this.live.values()].map((l) => l.teardown());
|
|
160
|
+
await Promise.allSettled(pending);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=launch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launch.js","sourceRoot":"","sources":["../src/launch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured stderr logging with hard secret redaction (design §10.8).
|
|
3
|
+
*
|
|
4
|
+
* Everything goes to **stderr** (stdout is reserved for any future machine-readable
|
|
5
|
+
* output and must stay clean). Every value is run through `redact()` before it is
|
|
6
|
+
* written, so the real LLM `api_key`, the per-launch capability token, and the
|
|
7
|
+
* `ck_machine_` machine key can never appear in a log line — even if a caller
|
|
8
|
+
* passes one in by mistake.
|
|
9
|
+
*
|
|
10
|
+
* Redaction is value-based (mask anything that looks like a known secret) AND
|
|
11
|
+
* key-based (mask the value of any field whose name is sensitive). Defense in depth:
|
|
12
|
+
* the daemon never intentionally logs secrets, but a single careless interpolation
|
|
13
|
+
* must not leak a Tier-A credential.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Deep-redact an arbitrary value: mask sensitive-keyed fields wholesale and any
|
|
17
|
+
* string value carrying a known secret prefix. Returns a NEW structure (never
|
|
18
|
+
* mutates the input, per the codebase immutability rule).
|
|
19
|
+
*/
|
|
20
|
+
export declare function redact(value: unknown, depth?: number): unknown;
|
|
21
|
+
export type LogFields = Record<string, unknown>;
|
|
22
|
+
export declare const log: {
|
|
23
|
+
info: (event: string, fields?: LogFields) => void;
|
|
24
|
+
warn: (event: string, fields?: LogFields) => void;
|
|
25
|
+
error: (event: string, fields?: LogFields) => void;
|
|
26
|
+
debug: (event: string, fields?: LogFields) => void;
|
|
27
|
+
};
|
package/dist/logging.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured stderr logging with hard secret redaction (design §10.8).
|
|
3
|
+
*
|
|
4
|
+
* Everything goes to **stderr** (stdout is reserved for any future machine-readable
|
|
5
|
+
* output and must stay clean). Every value is run through `redact()` before it is
|
|
6
|
+
* written, so the real LLM `api_key`, the per-launch capability token, and the
|
|
7
|
+
* `ck_machine_` machine key can never appear in a log line — even if a caller
|
|
8
|
+
* passes one in by mistake.
|
|
9
|
+
*
|
|
10
|
+
* Redaction is value-based (mask anything that looks like a known secret) AND
|
|
11
|
+
* key-based (mask the value of any field whose name is sensitive). Defense in depth:
|
|
12
|
+
* the daemon never intentionally logs secrets, but a single careless interpolation
|
|
13
|
+
* must not leak a Tier-A credential.
|
|
14
|
+
*/
|
|
15
|
+
// Sensitive FIELD names — the value of any of these keys is masked wholesale.
|
|
16
|
+
const SENSITIVE_KEYS = new Set([
|
|
17
|
+
"api_key",
|
|
18
|
+
"apikey",
|
|
19
|
+
"apiKey",
|
|
20
|
+
"key",
|
|
21
|
+
"token",
|
|
22
|
+
"capability_token",
|
|
23
|
+
"capabilityToken",
|
|
24
|
+
"machine_key",
|
|
25
|
+
"machineKey",
|
|
26
|
+
"authorization",
|
|
27
|
+
"bearer",
|
|
28
|
+
"secret",
|
|
29
|
+
"password",
|
|
30
|
+
]);
|
|
31
|
+
// Value PREFIXES that mark a string as a known secret regardless of where it sits.
|
|
32
|
+
const SECRET_PREFIXES = ["ck_machine_", "sk-", "sk_"];
|
|
33
|
+
// The per-launch capability token is 32 bytes base64url (≈43 chars, charset
|
|
34
|
+
// [A-Za-z0-9_-], no prefix). Mask anything matching that shape so a future log of the
|
|
35
|
+
// token under an arbitrary field name is still redacted — defense in depth, since the
|
|
36
|
+
// daemon never intentionally logs it. The 40-48 band is tight enough to avoid masking
|
|
37
|
+
// ordinary identifiers (UUIDs carry `-` groups far shorter than 40 contiguous chars).
|
|
38
|
+
const CAPABILITY_TOKEN_RE = /^[A-Za-z0-9_-]{40,48}$/;
|
|
39
|
+
const MASK = "***REDACTED***";
|
|
40
|
+
/** Mask a single string value if it looks like a known secret. */
|
|
41
|
+
function redactString(value) {
|
|
42
|
+
for (const prefix of SECRET_PREFIXES) {
|
|
43
|
+
if (value.startsWith(prefix))
|
|
44
|
+
return MASK;
|
|
45
|
+
}
|
|
46
|
+
if (CAPABILITY_TOKEN_RE.test(value))
|
|
47
|
+
return MASK;
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Deep-redact an arbitrary value: mask sensitive-keyed fields wholesale and any
|
|
52
|
+
* string value carrying a known secret prefix. Returns a NEW structure (never
|
|
53
|
+
* mutates the input, per the codebase immutability rule).
|
|
54
|
+
*/
|
|
55
|
+
export function redact(value, depth = 0) {
|
|
56
|
+
if (depth > 8)
|
|
57
|
+
return value; // bound recursion on pathological input
|
|
58
|
+
if (typeof value === "string")
|
|
59
|
+
return redactString(value);
|
|
60
|
+
if (Array.isArray(value))
|
|
61
|
+
return value.map((v) => redact(v, depth + 1));
|
|
62
|
+
if (value && typeof value === "object") {
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const [k, v] of Object.entries(value)) {
|
|
65
|
+
out[k] = SENSITIVE_KEYS.has(k) ? MASK : redact(v, depth + 1);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
function emit(level, event, fields) {
|
|
72
|
+
const line = {
|
|
73
|
+
ts: new Date().toISOString(),
|
|
74
|
+
level,
|
|
75
|
+
event,
|
|
76
|
+
};
|
|
77
|
+
if (fields) {
|
|
78
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
79
|
+
line[k] = SENSITIVE_KEYS.has(k) ? MASK : redact(v);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// stderr only — keep stdout clean.
|
|
83
|
+
process.stderr.write(JSON.stringify(line) + "\n");
|
|
84
|
+
}
|
|
85
|
+
export const log = {
|
|
86
|
+
info: (event, fields) => emit("info", event, fields),
|
|
87
|
+
warn: (event, fields) => emit("warn", event, fields),
|
|
88
|
+
error: (event, fields) => emit("error", event, fields),
|
|
89
|
+
debug: (event, fields) => emit("debug", event, fields),
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=logging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.js","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRIMARY executor: spawn a real coding-agent CLI (Claude Code / Codex) in
|
|
3
|
+
* `cwd=repo`, headless, talking to the broker, with the disallowed-tools blacklist
|
|
4
|
+
* applied (design §4 — R1-NEW / DR-6).
|
|
5
|
+
*
|
|
6
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
* OQ-3: the headless / JSON-output / disallowed-tools flag contract per shipped
|
|
8
|
+
* CLI. `claude` (Claude Code 2.1.169) flag NAMES are VERIFIED against the real
|
|
9
|
+
* binary (see the contract entry below); `codex` (codex-cli 0.137.0) is left
|
|
10
|
+
* `verified:false` because it has NO disallowed-tools concept — its sandbox /
|
|
11
|
+
* approval model does not map to the C5 tool blacklist, so enabling PRIMARY-codex
|
|
12
|
+
* needs a design decision and until then the runner FAILS FAST → N2. The runner
|
|
13
|
+
* NEVER emits a silent wrong invocation. The N2 fallback (n2-runner.ts) is the
|
|
14
|
+
* fully-tested path that covers "no verified CLI".
|
|
15
|
+
*
|
|
16
|
+
* NOTE: the live-model E2E spawn proof remains the MANUAL `verify-daemon.mjs`
|
|
17
|
+
* step — these unit tests pin the argv shape, not a real spawn against a model.
|
|
18
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* To enable a CLI here: verify the real flag names against the shipped binary,
|
|
21
|
+
* flip the `verified: true` contract entry, and add an integration proof in
|
|
22
|
+
* `verify-daemon.mjs`. Until then this runner throws `CliContractUnverifiedError`
|
|
23
|
+
* and `launch.ts` routes to N2.
|
|
24
|
+
*/
|
|
25
|
+
import type { ExecOutcome } from "./result-map.js";
|
|
26
|
+
/**
|
|
27
|
+
* Per-CLI flag contract. Each entry describes how to invoke that CLI headlessly
|
|
28
|
+
* with JSON output and (where the CLI supports it) the C5 disallowed-tools
|
|
29
|
+
* blacklist.
|
|
30
|
+
*
|
|
31
|
+
* `verified: false` means the runner refuses to spawn (fail fast → N2). It is set
|
|
32
|
+
* either because the flag names are unconfirmed OR because the CLI has no way to
|
|
33
|
+
* honour the C5 blacklist (codex — see below).
|
|
34
|
+
*/
|
|
35
|
+
interface CliFlagContract {
|
|
36
|
+
/** Confirmed against a shipped CLI version? If false → fail fast, never spawn. */
|
|
37
|
+
readonly verified: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Non-interactive SUBCOMMAND that precedes the flags, if the CLI requires one
|
|
40
|
+
* (codex's headless mode is `codex exec …`). `null` when the CLI is headless at
|
|
41
|
+
* the top level (claude's `--print`).
|
|
42
|
+
*/
|
|
43
|
+
readonly subcommand: string | null;
|
|
44
|
+
/** Headless / non-interactive prompt flag. */
|
|
45
|
+
readonly headlessFlag: string;
|
|
46
|
+
/** JSON output flag. */
|
|
47
|
+
readonly jsonOutputFlag: string;
|
|
48
|
+
/** Permission/sandbox-bypass flag — tenant's own box; the broker is the boundary. */
|
|
49
|
+
readonly bypassFlag: string;
|
|
50
|
+
/**
|
|
51
|
+
* How the C5 disallowed-tools blacklist maps to argv:
|
|
52
|
+
* `variadic` => ONE flag followed by all tools as separate args
|
|
53
|
+
* (claude's `--disallowedTools A B C`);
|
|
54
|
+
* `repeated` => one flag per tool;
|
|
55
|
+
* `csv` => one flag with a comma-joined value;
|
|
56
|
+
* `none` => the CLI has no disallowed-tools concept (codex — its
|
|
57
|
+
* sandbox/approval model does not map to a tool blacklist).
|
|
58
|
+
*/
|
|
59
|
+
readonly disallowStyle: "variadic" | "repeated" | "csv" | "none";
|
|
60
|
+
/** The disallowed-tools flag name (empty when `disallowStyle === "none"`). */
|
|
61
|
+
readonly disallowFlag: string;
|
|
62
|
+
}
|
|
63
|
+
export declare class CliContractUnverifiedError extends Error {
|
|
64
|
+
constructor(cli: string);
|
|
65
|
+
}
|
|
66
|
+
export interface CliRunParams {
|
|
67
|
+
readonly cli: "claude" | "codex";
|
|
68
|
+
readonly repo: string;
|
|
69
|
+
readonly prompt: string;
|
|
70
|
+
readonly env: Record<string, string>;
|
|
71
|
+
readonly deadlineMs: number;
|
|
72
|
+
/** Abort signal fired on launch teardown / deadline. */
|
|
73
|
+
readonly signal: AbortSignal;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build the argv for a CLI invocation. PUBLIC for unit testing the argv shape
|
|
77
|
+
* (disallowed-tools present, bypass flag, headless+json) without spawning.
|
|
78
|
+
*/
|
|
79
|
+
export declare function buildCliArgv(cli: "claude" | "codex", prompt: string): {
|
|
80
|
+
command: string;
|
|
81
|
+
args: string[];
|
|
82
|
+
contract: CliFlagContract;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Run the PRIMARY CLI. Throws `CliContractUnverifiedError` if the flag contract is
|
|
86
|
+
* not verified (OQ-3 fail-fast) so the caller routes to N2 — NEVER a silent wrong
|
|
87
|
+
* invocation. On a verified contract, spawns, captures the BASE..HEAD diff, and
|
|
88
|
+
* maps the exit code to an outcome.
|
|
89
|
+
*/
|
|
90
|
+
export declare function runCli(params: CliRunParams): Promise<ExecOutcome>;
|
|
91
|
+
export {};
|