@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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/dist/broker/handoff.d.ts +57 -0
  4. package/dist/broker/handoff.js +87 -0
  5. package/dist/broker/handoff.js.map +1 -0
  6. package/dist/broker/server.d.ts +34 -0
  7. package/dist/broker/server.js +204 -0
  8. package/dist/broker/server.js.map +1 -0
  9. package/dist/broker/token.d.ts +36 -0
  10. package/dist/broker/token.js +48 -0
  11. package/dist/broker/token.js.map +1 -0
  12. package/dist/cli.d.ts +15 -0
  13. package/dist/cli.js +120 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config.d.ts +33 -0
  16. package/dist/config.js +132 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/frames.d.ts +311 -0
  19. package/dist/frames.js +137 -0
  20. package/dist/frames.js.map +1 -0
  21. package/dist/index.d.ts +9 -0
  22. package/dist/index.js +19 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/launch.d.ts +40 -0
  25. package/dist/launch.js +163 -0
  26. package/dist/launch.js.map +1 -0
  27. package/dist/logging.d.ts +27 -0
  28. package/dist/logging.js +91 -0
  29. package/dist/logging.js.map +1 -0
  30. package/dist/spawn/cli-runner.d.ts +91 -0
  31. package/dist/spawn/cli-runner.js +180 -0
  32. package/dist/spawn/cli-runner.js.map +1 -0
  33. package/dist/spawn/detect.d.ts +18 -0
  34. package/dist/spawn/detect.js +46 -0
  35. package/dist/spawn/detect.js.map +1 -0
  36. package/dist/spawn/disallowed-tools.d.ts +15 -0
  37. package/dist/spawn/disallowed-tools.js +30 -0
  38. package/dist/spawn/disallowed-tools.js.map +1 -0
  39. package/dist/spawn/git.d.ts +19 -0
  40. package/dist/spawn/git.js +56 -0
  41. package/dist/spawn/git.js.map +1 -0
  42. package/dist/spawn/llm-client.d.ts +52 -0
  43. package/dist/spawn/llm-client.js +61 -0
  44. package/dist/spawn/llm-client.js.map +1 -0
  45. package/dist/spawn/n2-runner.d.ts +33 -0
  46. package/dist/spawn/n2-runner.js +176 -0
  47. package/dist/spawn/n2-runner.js.map +1 -0
  48. package/dist/spawn/n2-tools.d.ts +44 -0
  49. package/dist/spawn/n2-tools.js +374 -0
  50. package/dist/spawn/n2-tools.js.map +1 -0
  51. package/dist/spawn/result-map.d.ts +40 -0
  52. package/dist/spawn/result-map.js +68 -0
  53. package/dist/spawn/result-map.js.map +1 -0
  54. package/dist/tunnel.d.ts +61 -0
  55. package/dist/tunnel.js +205 -0
  56. package/dist/tunnel.js.map +1 -0
  57. package/package.json +60 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * FALLBACK executor: the N2 local tool-loop (design §4 — ports
3
+ * `agentic_backend.py` A1/A2/A4/A5/A9 to TS). The fully-working, fully-tested path
4
+ * used when no verified CLI is available or `--cli` forces N2.
5
+ *
6
+ * Loop protocol (mirrors the Python backend):
7
+ * 1. Record HEAD as the diff base (A2 — workspace is a real repo, no `git init`).
8
+ * 2. system + user messages → model (through the broker) with the closed toolset.
9
+ * 3. Append ALL tool results before honoring `finish` (A4); `finish` ends the loop.
10
+ * 4. On `finish`: capture BASE..worktree diff; map to an outcome.
11
+ *
12
+ * `success` (Python) → `succeeded` (wire) happens in result-map.ts; this returns the
13
+ * internal `ExecOutcome`.
14
+ */
15
+ import { complete, LlmClientError, } from "./llm-client.js";
16
+ import { captureDiff, headSha } from "./git.js";
17
+ import { log } from "../logging.js";
18
+ import { dispatchTool, N2_TOOL_NAMES, toolSchemas, } from "./n2-tools.js";
19
+ const SYSTEM_PROMPT = `You are a coding agent working inside a single repository checkout.
20
+ You can only act through the provided tools — there is no shell. Workflow:
21
+ 1. Explore with list_dir / read_file to understand the code.
22
+ 2. Make changes with write_file / replace_in_file (paths are workspace-relative).
23
+ 3. Optionally verify with run_tests.
24
+ 4. Call finish with a one-line summary when done (it becomes the commit message).
25
+ Make the smallest correct change for the task. If the request is genuinely ambiguous and you
26
+ cannot proceed, call finish with needs_clarification=true and a specific question.
27
+ Do not ask for confirmation; do not explain at length — act through tools.`;
28
+ const MAX_OUTPUT_TOKENS = 4096;
29
+ const DEFAULT_MAX_TURNS = 25;
30
+ const MAX_CONTEXT_CHARS = 200_000;
31
+ function parseCall(call) {
32
+ const name = call.function?.name ?? "";
33
+ const raw = call.function?.arguments ?? "{}";
34
+ try {
35
+ const parsed = JSON.parse(raw || "{}");
36
+ if (typeof parsed !== "object" ||
37
+ parsed === null ||
38
+ Array.isArray(parsed)) {
39
+ return {
40
+ name,
41
+ args: {},
42
+ err: "ERROR: tool arguments must be a JSON object.",
43
+ };
44
+ }
45
+ return { name, args: parsed, err: null };
46
+ }
47
+ catch {
48
+ return {
49
+ name,
50
+ args: {},
51
+ err: "ERROR: could not parse tool arguments as JSON.",
52
+ };
53
+ }
54
+ }
55
+ function asToolDefs() {
56
+ return toolSchemas().map((s) => ({
57
+ type: "function",
58
+ function: {
59
+ name: s.name,
60
+ description: s.description,
61
+ parameters: s.parameters,
62
+ },
63
+ }));
64
+ }
65
+ /**
66
+ * Run the N2 tool-loop. Returns an internal `ExecOutcome`. NEVER throws on a
67
+ * model/tool error — that becomes a `failed` outcome (零静默: exactly one outcome).
68
+ */
69
+ export async function runN2(params) {
70
+ const { repo, prompt, model, brokerBaseUrl, token, env, testTimeoutMs, signal, } = params;
71
+ const maxTurns = Math.max(1, params.maxTurns ?? DEFAULT_MAX_TURNS);
72
+ const base = await headSha(repo);
73
+ if (base === null) {
74
+ return { kind: "failed", error: `repo '${repo}' is not a git repository` };
75
+ }
76
+ const ctx = { workspace: repo, baseEnv: env, testTimeoutMs };
77
+ const tools = asToolDefs();
78
+ const messages = [
79
+ { role: "system", content: SYSTEM_PROMPT },
80
+ { role: "user", content: prompt },
81
+ ];
82
+ let ctxChars = 0;
83
+ let finish = null;
84
+ for (let turn = 1; turn <= maxTurns; turn += 1) {
85
+ if (signal.aborted)
86
+ return { kind: "timed_out" };
87
+ if (ctxChars > MAX_CONTEXT_CHARS) {
88
+ return {
89
+ kind: "failed",
90
+ error: `context budget ${MAX_CONTEXT_CHARS} chars exceeded`,
91
+ };
92
+ }
93
+ let completion;
94
+ try {
95
+ completion = await complete({ baseUrl: brokerBaseUrl, token, model, maxTokens: MAX_OUTPUT_TOKENS }, messages, tools, signal);
96
+ }
97
+ catch (err) {
98
+ if (signal.aborted)
99
+ return { kind: "timed_out" };
100
+ const msg = err instanceof LlmClientError ? err.message : String(err);
101
+ // No silent fallback (C8): model unreachable → loud failed.
102
+ return { kind: "failed", error: `model unreachable: ${msg}` };
103
+ }
104
+ if (completion.finishReason === "length" ||
105
+ completion.finishReason === "content_filter") {
106
+ return {
107
+ kind: "failed",
108
+ error: `model stopped early (finish_reason=${completion.finishReason})`,
109
+ };
110
+ }
111
+ if (completion.toolCalls.length > 0) {
112
+ messages.push({
113
+ role: "assistant",
114
+ content: completion.content,
115
+ tool_calls: completion.toolCalls,
116
+ });
117
+ // Append ALL tool results before honoring finish (A4 — every id needs a reply).
118
+ for (const call of completion.toolCalls) {
119
+ const { name, args, err } = parseCall(call);
120
+ if (name === N2_TOOL_NAMES.FINISH && err === null) {
121
+ finish = {
122
+ summary: String(args["summary"] ?? "agent changes"),
123
+ needsClarification: Boolean(args["needs_clarification"]),
124
+ question: args["question"] ? String(args["question"]) : null,
125
+ };
126
+ messages.push({
127
+ role: "tool",
128
+ content: "finishing",
129
+ tool_call_id: call.id,
130
+ });
131
+ continue;
132
+ }
133
+ if (signal.aborted)
134
+ return { kind: "timed_out" };
135
+ const result = err ?? (await dispatchTool(ctx, name, args));
136
+ ctxChars += result.length;
137
+ messages.push({
138
+ role: "tool",
139
+ content: result,
140
+ tool_call_id: call.id,
141
+ name,
142
+ });
143
+ }
144
+ if (finish !== null)
145
+ break;
146
+ continue;
147
+ }
148
+ // No tool calls: the model produced prose and stopped — done talking.
149
+ break;
150
+ }
151
+ const { diff, files } = await captureDiff(repo, base);
152
+ log.info("spawn.n2.done", { repo, files: files.length });
153
+ if (finish !== null && finish.needsClarification) {
154
+ return {
155
+ kind: "needs_clarification",
156
+ question: finish.question ?? "Could you clarify the task?",
157
+ };
158
+ }
159
+ if (files.length === 0) {
160
+ if (finish === null) {
161
+ // Ran out of turns / stopped without finishing AND produced nothing → cut off.
162
+ return {
163
+ kind: "failed",
164
+ error: `reached max turns (${maxTurns}) without completing the task`,
165
+ };
166
+ }
167
+ return { kind: "no_changes", text: finish.summary };
168
+ }
169
+ return {
170
+ kind: "success",
171
+ text: finish?.summary ?? "agent changes",
172
+ diff: diff || null,
173
+ files,
174
+ };
175
+ }
176
+ //# sourceMappingURL=n2-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"n2-runner.js","sourceRoot":"","sources":["../../src/spawn/n2-runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Closed, workspace-scoped toolset for the N2 local tool-loop (design §4 FALLBACK).
3
+ * Ported from the Python `agentic_tools.py` contract: read/write/replace/run-tests/
4
+ * finish, every recoverable problem returned in-band as a string so the model can
5
+ * self-correct (A4), every path resolved-then-checked for workspace escape (A8/DR-6).
6
+ *
7
+ * The disallowed-tools blacklist is enforced by OMISSION here — none of those tools
8
+ * are ever registered, so the model literally cannot call them.
9
+ */
10
+ export declare const MAX_READ_BYTES = 64000;
11
+ export declare const MAX_WRITE_BYTES = 256000;
12
+ export declare const MAX_TEST_OUTPUT = 16000;
13
+ export declare const DEFAULT_TEST_TIMEOUT_MS = 120000;
14
+ export declare const ENV_ALLOWLIST: Set<string>;
15
+ /** True if `name` is a known-safe env var (exact allowlist or an `LC_*` locale var). */
16
+ export declare function isAllowedEnvVar(name: string): boolean;
17
+ /**
18
+ * Project `base` down to the shared allowlist. The SINGLE scrub used by both the CLI
19
+ * spawn env and the N2 `run_tests` env — start empty, copy ONLY allowlisted names.
20
+ */
21
+ export declare function scrubEnv(base: NodeJS.ProcessEnv): Record<string, string>;
22
+ export declare const N2_TOOL_NAMES: {
23
+ readonly LIST_DIR: "list_dir";
24
+ readonly READ_FILE: "read_file";
25
+ readonly WRITE_FILE: "write_file";
26
+ readonly REPLACE_IN_FILE: "replace_in_file";
27
+ readonly RUN_TESTS: "run_tests";
28
+ readonly FINISH: "finish";
29
+ };
30
+ export interface ToolContext {
31
+ /** MUST be an already-resolved absolute path. */
32
+ readonly workspace: string;
33
+ readonly baseEnv: NodeJS.ProcessEnv;
34
+ readonly testTimeoutMs: number;
35
+ }
36
+ export interface ToolSchema {
37
+ name: string;
38
+ description: string;
39
+ parameters: Record<string, unknown>;
40
+ }
41
+ /** The closed N2 toolset advertised to the model (OpenAI function-call shape). */
42
+ export declare function toolSchemas(): ToolSchema[];
43
+ /** Execute one tool call; always returns an in-band string (A4). */
44
+ export declare function dispatchTool(ctx: ToolContext, name: string, args: Record<string, unknown>): Promise<string>;
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Closed, workspace-scoped toolset for the N2 local tool-loop (design §4 FALLBACK).
3
+ * Ported from the Python `agentic_tools.py` contract: read/write/replace/run-tests/
4
+ * finish, every recoverable problem returned in-band as a string so the model can
5
+ * self-correct (A4), every path resolved-then-checked for workspace escape (A8/DR-6).
6
+ *
7
+ * The disallowed-tools blacklist is enforced by OMISSION here — none of those tools
8
+ * are ever registered, so the model literally cannot call them.
9
+ */
10
+ import { spawn } from "node:child_process";
11
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, } from "node:fs";
12
+ import { dirname, relative, resolve, sep } from "node:path";
13
+ // Bounded-output limits (mirror agentic_tools DR-7).
14
+ export const MAX_READ_BYTES = 64_000;
15
+ export const MAX_WRITE_BYTES = 256_000;
16
+ export const MAX_TEST_OUTPUT = 16_000;
17
+ export const DEFAULT_TEST_TIMEOUT_MS = 120_000;
18
+ // Shared env allowlist — the SINGLE source of truth for BOTH the CLI spawn env
19
+ // (`handoff.buildSpawnEnv`) and the N2 `run_tests` env (`scrubEnv` below). The
20
+ // boundary is an ALLOWLIST, never a denylist: a denylist of "known secret vars" is
21
+ // fundamentally incomplete (AWS_*/GOOGLE_APPLICATION_CREDENTIALS/AZURE_*/… leak),
22
+ // so we start from an empty env and copy ONLY these known-safe names. Prefix-matched
23
+ // locale vars (LC_*) are handled by `isAllowedEnvVar`. Any genuinely-needed CLI var
24
+ // is added HERE so both paths inherit it.
25
+ export const ENV_ALLOWLIST = new Set([
26
+ "PATH",
27
+ "HOME",
28
+ "USER",
29
+ "SHELL",
30
+ "LANG",
31
+ "LC_ALL",
32
+ "LC_CTYPE",
33
+ "TERM",
34
+ "TZ",
35
+ "TMPDIR",
36
+ "CI",
37
+ "VIRTUAL_ENV",
38
+ ]);
39
+ /** True if `name` is a known-safe env var (exact allowlist or an `LC_*` locale var). */
40
+ export function isAllowedEnvVar(name) {
41
+ return ENV_ALLOWLIST.has(name) || name.startsWith("LC_");
42
+ }
43
+ /**
44
+ * Project `base` down to the shared allowlist. The SINGLE scrub used by both the CLI
45
+ * spawn env and the N2 `run_tests` env — start empty, copy ONLY allowlisted names.
46
+ */
47
+ export function scrubEnv(base) {
48
+ const out = {};
49
+ for (const [k, v] of Object.entries(base)) {
50
+ if (v !== undefined && isAllowedEnvVar(k))
51
+ out[k] = v;
52
+ }
53
+ return out;
54
+ }
55
+ const SELECTOR_RE = /^[A-Za-z0-9_][A-Za-z0-9_./:[\]-]*$/;
56
+ export const N2_TOOL_NAMES = {
57
+ LIST_DIR: "list_dir",
58
+ READ_FILE: "read_file",
59
+ WRITE_FILE: "write_file",
60
+ REPLACE_IN_FILE: "replace_in_file",
61
+ RUN_TESTS: "run_tests",
62
+ FINISH: "finish",
63
+ };
64
+ /** The closed N2 toolset advertised to the model (OpenAI function-call shape). */
65
+ export function toolSchemas() {
66
+ return [
67
+ {
68
+ name: N2_TOOL_NAMES.LIST_DIR,
69
+ description: "List files and directories under a path in the repo workspace.",
70
+ parameters: {
71
+ type: "object",
72
+ properties: {
73
+ path: {
74
+ type: "string",
75
+ description: "Workspace-relative directory (default '.').",
76
+ },
77
+ },
78
+ },
79
+ },
80
+ {
81
+ name: N2_TOOL_NAMES.READ_FILE,
82
+ description: "Read a UTF-8 text file from the repo workspace.",
83
+ parameters: {
84
+ type: "object",
85
+ properties: {
86
+ path: {
87
+ type: "string",
88
+ description: "Workspace-relative file path.",
89
+ },
90
+ },
91
+ required: ["path"],
92
+ },
93
+ },
94
+ {
95
+ name: N2_TOOL_NAMES.WRITE_FILE,
96
+ description: "Create or overwrite a text file with the given full content.",
97
+ parameters: {
98
+ type: "object",
99
+ properties: {
100
+ path: {
101
+ type: "string",
102
+ description: "Workspace-relative file path.",
103
+ },
104
+ content: { type: "string", description: "Full new file content." },
105
+ },
106
+ required: ["path", "content"],
107
+ },
108
+ },
109
+ {
110
+ name: N2_TOOL_NAMES.REPLACE_IN_FILE,
111
+ description: "Replace an exact unique substring in a file ('old' must occur exactly once).",
112
+ parameters: {
113
+ type: "object",
114
+ properties: {
115
+ path: {
116
+ type: "string",
117
+ description: "Workspace-relative file path.",
118
+ },
119
+ old: {
120
+ type: "string",
121
+ description: "Exact text to replace (must be unique).",
122
+ },
123
+ new: { type: "string", description: "Replacement text." },
124
+ },
125
+ required: ["path", "old", "new"],
126
+ },
127
+ },
128
+ {
129
+ name: N2_TOOL_NAMES.RUN_TESTS,
130
+ description: "Run the repository's test suite (pytest or npm test, auto-detected). " +
131
+ "Does NOT accept arbitrary shell commands.",
132
+ parameters: {
133
+ type: "object",
134
+ properties: {
135
+ selector: {
136
+ type: "string",
137
+ description: "Optional test file path or pytest node id.",
138
+ },
139
+ },
140
+ },
141
+ },
142
+ {
143
+ name: N2_TOOL_NAMES.FINISH,
144
+ description: "Call when the task is complete (or you need the user to clarify).",
145
+ parameters: {
146
+ type: "object",
147
+ properties: {
148
+ summary: {
149
+ type: "string",
150
+ description: "Short summary (used as commit message).",
151
+ },
152
+ needs_clarification: { type: "boolean" },
153
+ question: { type: "string" },
154
+ },
155
+ required: ["summary"],
156
+ },
157
+ },
158
+ ];
159
+ }
160
+ /** Resolve `rel` under `workspace`, rejecting escape + the `.git` dir (A8/DR-6). */
161
+ function safePath(workspace, rel) {
162
+ if (typeof rel !== "string" || rel.includes("\0"))
163
+ return null;
164
+ const candidate = resolve(workspace, rel);
165
+ if (candidate !== workspace) {
166
+ const relToWs = relative(workspace, candidate);
167
+ if (relToWs.startsWith("..") ||
168
+ relToWs.startsWith(sep) ||
169
+ resolve(workspace, relToWs) !== candidate) {
170
+ return null;
171
+ }
172
+ const parts = relToWs.split(sep);
173
+ if (parts.some((p) => p.toLowerCase() === ".git"))
174
+ return null;
175
+ }
176
+ return candidate;
177
+ }
178
+ function listDir(ctx, path) {
179
+ const target = safePath(ctx.workspace, path || ".");
180
+ if (target === null)
181
+ return `ERROR: path '${path}' escapes the workspace.`;
182
+ if (!existsSync(target))
183
+ return `ERROR: '${path}' does not exist.`;
184
+ let st;
185
+ try {
186
+ st = statSync(target);
187
+ }
188
+ catch (err) {
189
+ return `ERROR: could not stat '${path}': ${err.message}`;
190
+ }
191
+ if (!st.isDirectory())
192
+ return `ERROR: '${path}' is not a directory.`;
193
+ const entries = readdirSync(target, { withFileTypes: true })
194
+ .filter((d) => d.name !== ".git")
195
+ .sort((a, b) => a.name.localeCompare(b.name))
196
+ .map((d) => d.name + (d.isDirectory() ? "/" : ""));
197
+ return entries.length ? entries.join("\n") : "(empty)";
198
+ }
199
+ function readFile(ctx, path) {
200
+ const target = safePath(ctx.workspace, path);
201
+ if (target === null)
202
+ return `ERROR: path '${path}' escapes the workspace.`;
203
+ if (!existsSync(target) || !statSync(target).isFile())
204
+ return `ERROR: file '${path}' not found.`;
205
+ let data;
206
+ try {
207
+ data = readFileSync(target);
208
+ }
209
+ catch (err) {
210
+ return `ERROR: could not read '${path}': ${err.message}`;
211
+ }
212
+ const truncated = data.length > MAX_READ_BYTES;
213
+ let text = data.subarray(0, MAX_READ_BYTES).toString("utf8");
214
+ if (truncated)
215
+ text += `\n... (truncated at ${MAX_READ_BYTES} bytes)`;
216
+ return text;
217
+ }
218
+ function writeFile(ctx, path, content) {
219
+ const target = safePath(ctx.workspace, path);
220
+ if (target === null)
221
+ return `ERROR: path '${path}' escapes the workspace.`;
222
+ if (typeof content !== "string")
223
+ return "ERROR: 'content' must be a string.";
224
+ if (Buffer.byteLength(content, "utf8") > MAX_WRITE_BYTES) {
225
+ return `ERROR: content exceeds ${MAX_WRITE_BYTES} bytes.`;
226
+ }
227
+ try {
228
+ mkdirSync(dirname(target), { recursive: true });
229
+ writeFileSync(target, content, "utf8");
230
+ }
231
+ catch (err) {
232
+ return `ERROR: could not write '${path}': ${err.message}`;
233
+ }
234
+ return `wrote ${content.length} chars to ${path}`;
235
+ }
236
+ function replaceInFile(ctx, path, oldText, newText) {
237
+ const target = safePath(ctx.workspace, path);
238
+ if (target === null)
239
+ return `ERROR: path '${path}' escapes the workspace.`;
240
+ if (!existsSync(target) || !statSync(target).isFile())
241
+ return `ERROR: file '${path}' not found.`;
242
+ if (typeof oldText !== "string" ||
243
+ typeof newText !== "string" ||
244
+ oldText === "") {
245
+ return "ERROR: 'old' and 'new' must be strings and 'old' must be non-empty.";
246
+ }
247
+ let text;
248
+ try {
249
+ text = readFileSync(target, "utf8");
250
+ }
251
+ catch (err) {
252
+ return `ERROR: could not read '${path}': ${err.message}`;
253
+ }
254
+ const count = text.split(oldText).length - 1;
255
+ if (count === 0)
256
+ return `ERROR: 'old' substring not found in '${path}'.`;
257
+ if (count > 1)
258
+ return `ERROR: 'old' occurs ${count} times in '${path}'; make it unique.`;
259
+ const updated = text.replace(oldText, newText);
260
+ if (Buffer.byteLength(updated, "utf8") > MAX_WRITE_BYTES) {
261
+ return `ERROR: result exceeds ${MAX_WRITE_BYTES} bytes.`;
262
+ }
263
+ try {
264
+ writeFileSync(target, updated, "utf8");
265
+ }
266
+ catch (err) {
267
+ return `ERROR: could not write '${path}': ${err.message}`;
268
+ }
269
+ return `replaced 1 occurrence in ${path}`;
270
+ }
271
+ function detectTestCommand(workspace) {
272
+ const has = (f) => existsSync(resolve(workspace, f));
273
+ if (has("pyproject.toml") ||
274
+ has("pytest.ini") ||
275
+ has("setup.py") ||
276
+ has("tests") ||
277
+ has("conftest.py")) {
278
+ return ["pytest", "-q"];
279
+ }
280
+ if (has("package.json"))
281
+ return ["npm", "test", "--silent"];
282
+ return null;
283
+ }
284
+ async function runTests(ctx, selector) {
285
+ let cmd = detectTestCommand(ctx.workspace);
286
+ if (cmd === null)
287
+ return "ERROR: no recognized test runner (looked for pytest/npm config).";
288
+ if (selector !== null) {
289
+ if (typeof selector !== "string" ||
290
+ !SELECTOR_RE.test(selector) ||
291
+ selector.includes("..")) {
292
+ return "ERROR: invalid selector. Use a test file path or pytest node id (no leading '-', no '..').";
293
+ }
294
+ cmd = [...cmd, selector];
295
+ }
296
+ const env = scrubEnv(ctx.baseEnv);
297
+ return new Promise((resolvePromise) => {
298
+ let out = "";
299
+ let settled = false;
300
+ const child = spawn(cmd[0], cmd.slice(1), {
301
+ cwd: ctx.workspace,
302
+ env,
303
+ stdio: ["ignore", "pipe", "pipe"],
304
+ });
305
+ const onData = (chunk) => {
306
+ out = (out + chunk.toString("utf8")).slice(0, MAX_TEST_OUTPUT + 1);
307
+ };
308
+ child.stdout?.on("data", onData);
309
+ child.stderr?.on("data", onData);
310
+ const timer = setTimeout(() => {
311
+ if (settled)
312
+ return;
313
+ settled = true;
314
+ child.kill("SIGKILL");
315
+ resolvePromise(`ERROR: tests exceeded ${Math.round(ctx.testTimeoutMs / 1000)}s timeout (killed).`);
316
+ }, ctx.testTimeoutMs);
317
+ child.on("error", (err) => {
318
+ if (settled)
319
+ return;
320
+ settled = true;
321
+ clearTimeout(timer);
322
+ resolvePromise(`ERROR: could not start tests (${cmd[0]}): ${err.message}`);
323
+ });
324
+ child.on("close", (code) => {
325
+ if (settled)
326
+ return;
327
+ settled = true;
328
+ clearTimeout(timer);
329
+ let body = out;
330
+ if (body.length > MAX_TEST_OUTPUT) {
331
+ body =
332
+ body.slice(0, MAX_TEST_OUTPUT) +
333
+ `\n... (truncated at ${MAX_TEST_OUTPUT} chars)`;
334
+ }
335
+ const verdict = code === 0 ? "PASSED" : `FAILED (exit ${code})`;
336
+ resolvePromise(`[${cmd.join(" ")}] ${verdict}\n${body}`);
337
+ });
338
+ });
339
+ }
340
+ /** Execute one tool call; always returns an in-band string (A4). */
341
+ export async function dispatchTool(ctx, name, args) {
342
+ if (typeof args !== "object" || args === null)
343
+ return "ERROR: tool arguments must be a JSON object.";
344
+ try {
345
+ switch (name) {
346
+ case N2_TOOL_NAMES.LIST_DIR:
347
+ return listDir(ctx, String(args["path"] ?? "."));
348
+ case N2_TOOL_NAMES.READ_FILE:
349
+ if (!("path" in args))
350
+ return "ERROR: 'path' is required.";
351
+ return readFile(ctx, String(args["path"]));
352
+ case N2_TOOL_NAMES.WRITE_FILE:
353
+ if (!("path" in args) || !("content" in args))
354
+ return "ERROR: 'path' and 'content' are required.";
355
+ return writeFile(ctx, String(args["path"]), args["content"]);
356
+ case N2_TOOL_NAMES.REPLACE_IN_FILE:
357
+ for (const k of ["path", "old", "new"]) {
358
+ if (!(k in args))
359
+ return `ERROR: '${k}' is required.`;
360
+ }
361
+ return replaceInFile(ctx, String(args["path"]), args["old"], args["new"]);
362
+ case N2_TOOL_NAMES.RUN_TESTS: {
363
+ const sel = args["selector"];
364
+ return await runTests(ctx, sel !== undefined && sel !== null ? String(sel) : null);
365
+ }
366
+ default:
367
+ return `ERROR: unknown tool '${name}'.`;
368
+ }
369
+ }
370
+ catch (err) {
371
+ return `ERROR: tool '${name}' failed unexpectedly: ${err.message}`;
372
+ }
373
+ }
374
+ //# sourceMappingURL=n2-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"n2-tools.js","sourceRoot":"","sources":["../../src/spawn/n2-tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Outcome → `Result` frame mapping (design §4 — DR-6, OQ-6).
3
+ *
4
+ * Maps the internal executor outcome (CLI exit code / N2 loop status) to the closed
5
+ * `Result.status` set `succeeded | failed | timed_out`, mirroring the Python
6
+ * `agentic_backend` contract (`success` → `succeeded`).
7
+ *
8
+ * - CLI exit 0 / N2 `success` → succeeded (text=summary, diff, files)
9
+ * - N2 `no_changes` → succeeded (diff null)
10
+ * - N2 `needs_clarification` → succeeded (text=question)
11
+ * - CLI nonzero / N2 `error`/crash → failed (error, NO secrets)
12
+ * - deadline → timed_out
13
+ *
14
+ * `cost_usd` is ALWAYS `null` (the tenant runs their own model — cost is unknown,
15
+ * NOT 0; OQ-6).
16
+ */
17
+ import type { Result } from "../frames.js";
18
+ /** Internal executor outcome, before mapping to a wire Result. */
19
+ export type ExecOutcome = {
20
+ kind: "success";
21
+ text: string | null;
22
+ diff: string | null;
23
+ files: string[];
24
+ } | {
25
+ kind: "no_changes";
26
+ text: string | null;
27
+ } | {
28
+ kind: "needs_clarification";
29
+ question: string;
30
+ } | {
31
+ kind: "failed";
32
+ error: string;
33
+ } | {
34
+ kind: "timed_out";
35
+ error?: string;
36
+ };
37
+ /** Trailing chars of stderr kept on a failure result (no full dump). */
38
+ export declare const ERROR_TAIL_CHARS = 4000;
39
+ /** Map an internal outcome to the wire `Result` frame for a launch. */
40
+ export declare function toResult(launchId: string, outcome: ExecOutcome): Result;