@hizliemre/horse-code 0.1.2 → 0.3.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 (38) hide show
  1. package/dist/{app-LAJN3TWC.js → app-5FXHE7GX.js} +354 -350
  2. package/dist/{chunk-2SVAHH5N.js → chunk-63E73TGI.js} +2 -4
  3. package/dist/{chunk-DTWKSZXY.js → chunk-6OSEQOYY.js} +2 -2
  4. package/dist/{chunk-SSDLHWSF.js → chunk-6W4UH2BQ.js} +33 -1
  5. package/dist/{chunk-VPAWRRHL.js → chunk-AE36LLL2.js} +31 -21
  6. package/dist/{chunk-4EWK7HWQ.js → chunk-EAF22QIG.js} +32 -0
  7. package/dist/chunk-G45RWL7S.js +289 -0
  8. package/dist/{chunk-FFYBY2NA.js → chunk-KAGKX2YT.js} +2 -4
  9. package/dist/{chunk-DRZSUQ7Q.js → chunk-LLL7QWXB.js} +18 -7
  10. package/dist/{chunk-WYQBRCKY.js → chunk-LNW557IO.js} +2 -2
  11. package/dist/{chunk-IW2KBAVZ.js → chunk-LPQU436C.js} +12 -1
  12. package/dist/chunk-M2RKCIGV.js +11 -0
  13. package/dist/{chunk-NNTIACT4.js → chunk-MRZVA5JB.js} +4 -4
  14. package/dist/{chunk-RPVAIS3P.js → chunk-UEWVVN5L.js} +1511 -305
  15. package/dist/{chunk-EQX7BQYN.js → chunk-UGESK765.js} +1 -1
  16. package/dist/{chunk-PGOYDOI4.js → chunk-XEGQT5EN.js} +3 -5
  17. package/dist/{chunk-K2VERI5Q.js → chunk-XYZVZPAY.js} +365 -666
  18. package/dist/{chunk-KOWMHL23.js → chunk-YPZP7LYL.js} +2 -2
  19. package/dist/{chunk-FGVJFMK5.js → chunk-ZSQ24YDJ.js} +1 -1
  20. package/dist/cli.js +675 -95
  21. package/dist/{discover-5URG7C4J.js → discover-G2Z6XC3O.js} +9 -3
  22. package/dist/fix-ONLA45HD.js +34 -0
  23. package/dist/git-QZTDZSJY.js +8 -0
  24. package/dist/{ongoing-XP6WXNI7.js → ongoing-WHYXPW24.js} +4 -4
  25. package/dist/{project-graph-IOPCSZUA.js → project-graph-5HNPRFQG.js} +2 -3
  26. package/dist/{run-LQOZ5I7Z.js → run-P6ZYL5JL.js} +2 -3
  27. package/dist/{save-skills-NSLBU33X.js → save-skills-ZW5GY6KV.js} +2 -1
  28. package/dist/{trace-ZMB7LT7W.js → trace-X6TU3AG6.js} +2 -3
  29. package/dist/{trace-adopt-C6TUWFJL.js → trace-adopt-URECQWJV.js} +2 -3
  30. package/dist/{trace-run-F23MFTY4.js → trace-run-7U4WJZ3V.js} +6 -7
  31. package/dist/{triage-ES5OHZOS.js → triage-FCYHD2AQ.js} +8 -9
  32. package/dist/{verify-3R7DGUSI.js → verify-LC57A6H2.js} +19 -19
  33. package/package.json +1 -1
  34. package/dist/chunk-3UYA3KUG.js +0 -212
  35. package/dist/chunk-B67BK5GQ.js +0 -34
  36. package/dist/chunk-O74BDQKS.js +0 -28
  37. package/dist/fix-CMARU6JR.js +0 -34
  38. package/dist/git-VTSZALSR.js +0 -6
@@ -1,9 +1,7 @@
1
1
  import {
2
- stateRoot
3
- } from "./chunk-SSDLHWSF.js";
4
- import {
2
+ stateRoot,
5
3
  writeAtomic
6
- } from "./chunk-B67BK5GQ.js";
4
+ } from "./chunk-6W4UH2BQ.js";
7
5
 
8
6
  // src/migrate/migrated.ts
9
7
  import { readFile, mkdir } from "fs/promises";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  hashContent,
3
3
  traceDir
4
- } from "./chunk-FFYBY2NA.js";
4
+ } from "./chunk-KAGKX2YT.js";
5
5
 
6
6
  // src/engine/project-brief.ts
7
7
  import { readFile, writeFile, mkdir } from "fs/promises";
@@ -70,7 +70,7 @@ async function gatherBriefInput(cwd, files) {
70
70
  async function ourOwnOutput(cwd) {
71
71
  const out = /* @__PURE__ */ new Set();
72
72
  try {
73
- const { loadTraceIndex, traceRootRel } = await import("./trace-ZMB7LT7W.js");
73
+ const { loadTraceIndex, traceRootRel } = await import("./trace-X6TU3AG6.js");
74
74
  const root = traceRootRel().replace(/\\/g, "/");
75
75
  out.add(`${root}/PROJECT.md`);
76
76
  out.add(`${root}/PROJECT.json`);
@@ -27,9 +27,41 @@ function inLinkedWorktree(cwd, run) {
27
27
  return resolve(dir) !== resolve(common);
28
28
  }
29
29
 
30
+ // src/session/atomic.ts
31
+ import { rename, rm, writeFile } from "fs/promises";
32
+ import { renameSync, rmSync, writeFileSync } from "fs";
33
+ var seq = 0;
34
+ var tmpName = (path) => `${path}.${process.pid}.${seq++}.tmp`;
35
+ async function writeAtomic(path, data) {
36
+ const tmp = tmpName(path);
37
+ try {
38
+ await writeFile(tmp, data, "utf8");
39
+ await rename(tmp, path);
40
+ } catch (e) {
41
+ await rm(tmp, { force: true }).catch(() => {
42
+ });
43
+ throw e;
44
+ }
45
+ }
46
+ function writeAtomicSync(path, data) {
47
+ const tmp = tmpName(path);
48
+ try {
49
+ writeFileSync(tmp, data, "utf8");
50
+ renameSync(tmp, path);
51
+ } catch (e) {
52
+ try {
53
+ rmSync(tmp, { force: true });
54
+ } catch {
55
+ }
56
+ throw e;
57
+ }
58
+ }
59
+
30
60
  export {
31
61
  sessionBase,
32
62
  stateRoot,
33
63
  writableStateRoot,
34
- inLinkedWorktree
64
+ inLinkedWorktree,
65
+ writeAtomic,
66
+ writeAtomicSync
35
67
  };
@@ -1,3 +1,7 @@
1
+ import {
2
+ sessionBase
3
+ } from "./chunk-6W4UH2BQ.js";
4
+
1
5
  // src/tui/format.ts
2
6
  function fmtTokens(n) {
3
7
  if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
@@ -443,22 +447,6 @@ function recallNote(tool, subject, turn, authored = false) {
443
447
  }
444
448
 
445
449
  // src/core/surrogates.ts
446
- var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
447
- var REPLACEMENT = "\uFFFD";
448
- function stripLoneSurrogates(text) {
449
- LONE_SURROGATE.lastIndex = 0;
450
- return LONE_SURROGATE.test(text) ? text.replace(LONE_SURROGATE, REPLACEMENT) : text;
451
- }
452
- function sanitizeForJson(value) {
453
- if (typeof value === "string") return stripLoneSurrogates(value);
454
- if (Array.isArray(value)) return value.map((v) => sanitizeForJson(v));
455
- if (value && typeof value === "object") {
456
- const out = {};
457
- for (const [k, v] of Object.entries(value)) out[k] = sanitizeForJson(v);
458
- return out;
459
- }
460
- return value;
461
- }
462
450
  function truncateSafe(text, max) {
463
451
  if (text.length <= max || max <= 0) return text;
464
452
  const code = text.charCodeAt(max - 1);
@@ -983,15 +971,19 @@ function shieldToolOutput(text) {
983
971
  }
984
972
 
985
973
  // src/agent/loop.ts
974
+ var DELEGATED_TOOL_NAMES = { file_change: "Write", command_execution: "Bash" };
986
975
  function fmtChars(n) {
987
976
  return n >= 1e3 ? `${(n / 1e3).toFixed(1)}k chars` : `${n} chars`;
988
977
  }
989
978
  function workingDirectoryNote(cwd) {
990
- return `
979
+ const here = `
991
980
 
992
981
  # Working directory
993
982
 
994
983
  You are already in \`${cwd}\`. Every relative path resolves from here, and every tool runs here \u2014 do not \`cd\` elsewhere, and do not go looking for the repository.`;
984
+ return sessionBase(cwd) === void 0 ? here : `${here}
985
+
986
+ This is a fresh git worktree, so everything git ignores is absent: no \`node_modules\`, no build output, no package caches. Source, tests and configuration are all here and current.`;
995
987
  }
996
988
  var COMPACT_KEYS_LOGGED = 24;
997
989
  var agentSeq = 0;
@@ -1067,6 +1059,16 @@ async function* runRoleAgent(opts) {
1067
1059
  toolCalls.push(ev.toolCall);
1068
1060
  } else if (ev.type === "tool-progress") {
1069
1061
  if (ev.path) opts.onLiveActivity?.(`writing ${ev.path.split("/").pop()} \xB7 ${fmtChars(ev.chars)}`);
1062
+ } else if (ev.type === "activity") {
1063
+ const what = ev.target ? ev.target.split("/").pop() ?? ev.target : "";
1064
+ opts.onActivity?.({
1065
+ tool: DELEGATED_TOOL_NAMES[ev.tool] ?? ev.tool,
1066
+ target: what,
1067
+ lines: 0,
1068
+ // Only a failure is worth a suffix. "done" on every row is the bullet said twice.
1069
+ summary: ev.ok === false ? "failed" : "",
1070
+ ok: ev.ok !== false
1071
+ });
1070
1072
  } else if (ev.type === "usage") {
1071
1073
  yield { type: "usage", promptTokens: ev.promptTokens, completionTokens: ev.completionTokens };
1072
1074
  opts.onUsage?.({ promptTokens: ev.promptTokens, completionTokens: ev.completionTokens, model: activeModel });
@@ -1082,7 +1084,7 @@ async function* runRoleAgent(opts) {
1082
1084
  break;
1083
1085
  }
1084
1086
  if (errored.retryable && !errored.capability && !errored.noBench) opts.onExhausted?.(activeModel, errored.message);
1085
- if (errored.retryable && !streamed && chainIdx < chain.length - 1) {
1087
+ if (errored.retryable && chainIdx < chain.length - 1) {
1086
1088
  const next = chain[chainIdx + 1];
1087
1089
  opts.onFallback?.(activeModel, next, errored.message);
1088
1090
  chainIdx++;
@@ -1240,6 +1242,10 @@ var ToolRegistry = class {
1240
1242
  }
1241
1243
  };
1242
1244
 
1245
+ // src/core/types.ts
1246
+ var DEADLINE_MESSAGE = "the model did not answer within its deadline";
1247
+ var CHAIN_BUDGET_MESSAGE = "the chain's total budget ran out before this model was given a fair turn";
1248
+
1243
1249
  // src/agent/structured.ts
1244
1250
  function valueAt(args, path) {
1245
1251
  let cur = args;
@@ -1255,6 +1261,11 @@ function whatWasWrong(issues, args) {
1255
1261
  const got = valueAt(args, i.path);
1256
1262
  const shown = got === void 0 ? "nothing" : JSON.stringify(got);
1257
1263
  const head = where ? `${where}: ${i.message}` : i.message;
1264
+ if (got === void 0 && /received\s+(undefined|null|nothing)/i.test(i.message)) {
1265
+ const parent = i.path.length > 1 ? valueAt(args, i.path.slice(0, -1)) : args;
1266
+ const sent = parent && typeof parent === "object" ? Object.keys(parent) : [];
1267
+ return sent.length ? `${head} \u2014 you sent only ${sent.map((k) => `\`${k}\``).join(", ")}` : head;
1268
+ }
1258
1269
  return `${head} \u2014 got ${shown.length > 120 ? `${shown.slice(0, 120)}\u2026` : shown}`;
1259
1270
  }).join("; ");
1260
1271
  }
@@ -1323,7 +1334,7 @@ async function runStructuredRole(opts, schema, maxAttempts = 2) {
1323
1334
  }
1324
1335
  if (ev.type === "abort") {
1325
1336
  if (opts.signal.aborted) throw new Error("cancelled");
1326
- errored = total?.aborted ? "the chain's total budget ran out before this model was given a fair turn" : "the model did not answer within its deadline";
1337
+ errored = total?.aborted ? CHAIN_BUDGET_MESSAGE : DEADLINE_MESSAGE;
1327
1338
  break;
1328
1339
  }
1329
1340
  if (ev.type === "message.done") lastText = ev.message.content ?? lastText;
@@ -1356,13 +1367,12 @@ async function runStructuredRole(opts, schema, maxAttempts = 2) {
1356
1367
  }
1357
1368
 
1358
1369
  export {
1359
- sanitizeForJson,
1360
- truncateSafe,
1361
1370
  fmtTokens,
1362
1371
  fmtDuration,
1363
1372
  relTime,
1364
1373
  stripThinking,
1365
1374
  handedOver,
1375
+ truncateSafe,
1366
1376
  Telemetry,
1367
1377
  sampleMemory,
1368
1378
  estimateFreezeSeconds,
@@ -1,3 +1,6 @@
1
+ import {
2
+ CLI_KINDS
3
+ } from "./chunk-G45RWL7S.js";
1
4
  import {
2
5
  arrayField,
3
6
  objectField,
@@ -18,6 +21,7 @@ var DEFAULT_CONFIG = {
18
21
  specKit: { version: "v0.13.2" },
19
22
  mcp: {},
20
23
  modelSources: [],
24
+ accounts: [],
21
25
  traceDir: "",
22
26
  /**
23
27
  * Shipped as a REFERENCE, not a copy, for the reasons skills/README.md gives for exactly this shape: it is
@@ -60,6 +64,33 @@ var fileSchema = z.object({
60
64
  council: z.object({ members: z.array(reviewerSchema) }).optional(),
61
65
  specKit: z.object({ version: z.string() }).optional(),
62
66
  modelSources: z.array(z.string()).optional(),
67
+ /**
68
+ * Logged-in profile directories, in spill order. A path each, never a credential.
69
+ *
70
+ * Two things here were wrong in a way that could not be seen from this file, and both were found by
71
+ * connecting a real account.
72
+ *
73
+ * The kinds were spelled out — `["claude", "codex"]` — and every entry of a kind added since was
74
+ * rejected. `CLI_KINDS` is where they are declared, so this reads them rather than repeating them, and a
75
+ * fifth subscription cannot be half-added again.
76
+ *
77
+ * `configDir` was REQUIRED, and the signed-in default is precisely the entry that has none: `withAmbient`
78
+ * records it without a directory, deliberately, because Claude Code keeps that session in the Keychain
79
+ * and naming a directory switches it to file credentials. So the one entry written to stop a second
80
+ * account quietly retiring the first was itself unloadable.
81
+ *
82
+ * `.catch([])` bounds what a bad row can cost. The loader reads `parsed.success ? parsed.data : {}`, so
83
+ * a single rejected entry did not merely drop that account — it discarded the WHOLE global config.
84
+ * Measured on a live one: the file held 64 role chains and an API key, and with one z.ai entry present
85
+ * `loadConfig` returned zero roles and no key, silently, for every session since it was connected.
86
+ */
87
+ accounts: z.array(z.object({
88
+ kind: z.enum(CLI_KINDS),
89
+ name: z.string(),
90
+ configDir: z.string().optional(),
91
+ email: z.string().optional(),
92
+ plan: z.string().optional()
93
+ })).catch([]).optional(),
63
94
  traceDir: z.string().optional(),
64
95
  // where /graph trace writes; empty = .horsecode/traces
65
96
  mainBranch: z.string().optional(),
@@ -103,6 +134,7 @@ function loadConfig(opts) {
103
134
  merged.roles = { ...global.roles ?? {}, ...projectSafe.roles ?? {} };
104
135
  merged.mcp = { ...global.mcp ?? {}, ...projectSafe.mcp ?? {} };
105
136
  merged.modelSources = projectSafe.modelSources ?? global.modelSources ?? [];
137
+ merged.accounts = global.accounts ?? [];
106
138
  merged.maxParallel = projectSafe.maxParallel ?? global.maxParallel ?? DEFAULT_CONFIG.maxParallel;
107
139
  merged.telemetry = projectSafe.telemetry ?? global.telemetry ?? DEFAULT_CONFIG.telemetry;
108
140
  const spoken = global.skillSources !== void 0 || projectSafe.skillSources !== void 0;
@@ -0,0 +1,289 @@
1
+ // src/agents/cli-auth.ts
2
+ import { spawnSync } from "child_process";
3
+
4
+ // src/agents/cli-agent.ts
5
+ import { spawn } from "child_process";
6
+ var CLI_KINDS = ["claude", "codex", "grok", "zai"];
7
+ function cliBinary(kind) {
8
+ return kind === "zai" ? "claude" : kind;
9
+ }
10
+ var CLI_ERROR_UNSPOKEN = "the CLI reported an error";
11
+ function cliArgs(kind, prompt, extra = []) {
12
+ if (kind === "claude" || kind === "zai") {
13
+ return ["--output-format", "stream-json", "--verbose", ...extra, "-p", "--", prompt];
14
+ }
15
+ if (kind === "codex") {
16
+ return ["exec", "--json", "--skip-git-repo-check", ...extra, "--", prompt];
17
+ }
18
+ return ["--output-format", "streaming-messages-json", ...extra, `--single=${prompt}`];
19
+ }
20
+ function decodeClaudeEvent(line) {
21
+ let e;
22
+ try {
23
+ e = JSON.parse(line);
24
+ } catch {
25
+ return void 0;
26
+ }
27
+ const type = e.type;
28
+ if (type === "rate_limit_event") {
29
+ const info = e.rate_limit_info ?? {};
30
+ const status = String(info.status ?? "unknown");
31
+ const raw = info.unifiedWindows ?? {};
32
+ const windows = {};
33
+ for (const [name, w] of Object.entries(raw)) windows[name] = w?.utilization ?? 0;
34
+ const quota = {
35
+ status,
36
+ windows,
37
+ ...typeof info.resetsAt === "number" ? { resetsAt: info.resetsAt } : {}
38
+ };
39
+ return status.startsWith("allowed") ? { quota } : {
40
+ quota,
41
+ /**
42
+ * The reset time rides along, as an ISO instant rather than prose.
43
+ *
44
+ * A spent five-hour window reopens; without saying when, the only safe bench is "the rest of the
45
+ * run", which on a ten-hour board writes off a subscription for hours after it recovered. The
46
+ * gateway's wordings said "reset after 4h" and nothing ever parsed them — see `quotaResetAt`.
47
+ */
48
+ rateLimited: `${status} \u2014 ${describeWindows(windows)}` + (quota.resetsAt ? ` (resets ${new Date(quota.resetsAt * 1e3).toISOString()})` : "")
49
+ };
50
+ }
51
+ if (type === "assistant") {
52
+ const msg = e.message;
53
+ const parts = Array.isArray(msg?.content) ? msg.content : [];
54
+ const text = parts.filter((b) => typeof b === "object" && b !== null && b.type === "text").map((b) => b.text).join("");
55
+ const tool = parts.find((b) => typeof b === "object" && b !== null && b.type === "tool_use");
56
+ return {
57
+ ...text ? { text } : {},
58
+ ...msg?.model ? { served: msg.model } : {},
59
+ ...tool ? { tool: { name: tool.name, ...targetOf(tool.input) ? { target: targetOf(tool.input) } : {} } } : {}
60
+ };
61
+ }
62
+ if (type === "user") {
63
+ const parts = e.message?.content;
64
+ const failed = (Array.isArray(parts) ? parts : []).find(
65
+ (b) => typeof b === "object" && b !== null && b.type === "tool_result" && b.is_error === true
66
+ );
67
+ return failed ? { tool: { name: "tool", ok: false } } : void 0;
68
+ }
69
+ if (type === "result") {
70
+ const u = e.usage ?? {};
71
+ const cost = e.total_cost_usd;
72
+ return {
73
+ usage: {
74
+ freshTokens: u.input_tokens ?? 0,
75
+ cachedTokens: u.cache_read_input_tokens ?? 0,
76
+ cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
77
+ outputTokens: u.output_tokens ?? 0,
78
+ ...cost !== void 0 ? { costUsd: cost } : {}
79
+ },
80
+ ...e.subtype === "error_during_execution" ? { error: String(e.result ?? CLI_ERROR_UNSPOKEN) } : {}
81
+ };
82
+ }
83
+ return void 0;
84
+ }
85
+ function decodeCodexEvent(line) {
86
+ let e;
87
+ try {
88
+ e = JSON.parse(line);
89
+ } catch {
90
+ return void 0;
91
+ }
92
+ const type = String(e.type ?? "");
93
+ if (/rate.?limit/i.test(type)) return { rateLimited: String(e.message ?? "rate limited by the CLI") };
94
+ if (type === "item.completed") {
95
+ const item = e.item;
96
+ if (item?.type === "agent_message" && item.text) return { text: item.text };
97
+ if (item?.type && item.type !== "agent_message") {
98
+ const changes = item.changes;
99
+ const first = Array.isArray(changes) ? changes.find((c) => typeof c?.path === "string")?.path : void 0;
100
+ const more = Array.isArray(changes) && changes.length > 1 ? ` +${changes.length - 1}` : "";
101
+ return {
102
+ tool: {
103
+ name: item.name ?? item.type,
104
+ ...first ? { target: `${first}${more}` } : {}
105
+ }
106
+ };
107
+ }
108
+ return void 0;
109
+ }
110
+ if (type === "turn.completed") {
111
+ const u = e.usage ?? {};
112
+ return {
113
+ usage: {
114
+ freshTokens: u.input_tokens ?? 0,
115
+ cachedTokens: u.cached_input_tokens ?? 0,
116
+ cacheWriteTokens: u.cache_write_input_tokens ?? 0,
117
+ outputTokens: u.output_tokens ?? 0
118
+ }
119
+ };
120
+ }
121
+ if (type === "turn.failed" || type === "error") {
122
+ return { error: String(e.message ?? "codex reported an error") };
123
+ }
124
+ return void 0;
125
+ }
126
+ function describeWindows(windows) {
127
+ const parts = Object.entries(windows).map(([k, v]) => `${k} ${Math.round(v * 100)}%`);
128
+ return parts.length ? parts.join(", ") : "no window reported";
129
+ }
130
+ var SYNTHETIC = "<synthetic>";
131
+ function targetOf(input) {
132
+ for (const k of ["file_path", "path", "filePath", "notebook_path"]) {
133
+ const v = input?.[k];
134
+ if (typeof v === "string" && v) return v;
135
+ }
136
+ return void 0;
137
+ }
138
+ function makeStreamReader(decode, onEvent) {
139
+ let pending = "";
140
+ const drain = (upToNewline) => {
141
+ const lines = pending.split("\n");
142
+ pending = upToNewline ? lines.pop() ?? "" : "";
143
+ for (const line of lines) {
144
+ if (!line.trim()) continue;
145
+ const ev = decode(line);
146
+ if (ev) onEvent(ev);
147
+ }
148
+ };
149
+ return {
150
+ push(chunk) {
151
+ pending += chunk;
152
+ drain(true);
153
+ },
154
+ end() {
155
+ if (pending.trim()) drain(false);
156
+ }
157
+ };
158
+ }
159
+ function reportedError(decoded, stderr, exitCode) {
160
+ if (decoded && decoded !== CLI_ERROR_UNSPOKEN) return decoded;
161
+ const spoken = exitCode !== 0 ? stderr.trim().slice(0, 500) : "";
162
+ return spoken || decoded || void 0;
163
+ }
164
+ async function runCliAgent(run) {
165
+ const decode = run.kind === "codex" ? decodeCodexEvent : decodeClaudeEvent;
166
+ const args = cliArgs(run.kind, run.prompt, run.args ?? []);
167
+ return new Promise((resolve) => {
168
+ let child;
169
+ try {
170
+ child = spawn(cliBinary(run.kind), args, {
171
+ cwd: run.cwd,
172
+ signal: run.signal,
173
+ stdio: ["ignore", "pipe", "pipe"],
174
+ ...run.configDir ? { env: { ...process.env, ...profileEnv(run.kind, run.configDir) } } : {}
175
+ });
176
+ } catch (e) {
177
+ resolve({ text: "", error: e instanceof Error ? e.message : String(e), exitCode: -1 });
178
+ return;
179
+ }
180
+ let text = "";
181
+ let usage;
182
+ let rateLimited;
183
+ let served;
184
+ let quota;
185
+ let error;
186
+ let stderr = "";
187
+ const reader = makeStreamReader(decode, (ev) => {
188
+ if (ev.text) text += ev.text;
189
+ if (ev.usage) usage = ev.usage;
190
+ if (ev.rateLimited) rateLimited = ev.rateLimited;
191
+ if (ev.served) served = ev.served;
192
+ if (ev.quota) quota = ev.quota;
193
+ if (ev.error) error = ev.error;
194
+ run.onEvent?.(ev);
195
+ });
196
+ child.stdout?.on("data", (d) => reader.push(d.toString()));
197
+ child.stderr?.on("data", (d) => {
198
+ stderr += d.toString();
199
+ });
200
+ child.on("error", (e) => resolve({ text, ...usage ? { usage } : {}, error: e.message, exitCode: -1 }));
201
+ child.on("close", (code) => {
202
+ reader.end();
203
+ const reported = reportedError(error, stderr, code ?? -1);
204
+ resolve({
205
+ text,
206
+ ...usage ? { usage } : {},
207
+ ...rateLimited ? { rateLimited } : {},
208
+ ...quota ? { quota } : {},
209
+ ...served ? { served } : {},
210
+ ...reported ? { error: reported } : {},
211
+ exitCode: code ?? -1
212
+ });
213
+ });
214
+ });
215
+ }
216
+
217
+ // src/agents/cli-auth.ts
218
+ function profileEnv(kind, configDir) {
219
+ if (!configDir) return {};
220
+ if (kind === "claude" || kind === "zai") return { CLAUDE_CONFIG_DIR: configDir };
221
+ if (kind === "codex") return { CODEX_HOME: configDir };
222
+ return { GROK_HOME: configDir };
223
+ }
224
+ function readAuthStatus(kind, out) {
225
+ if (kind === "grok") {
226
+ const m = /you are logged in with\s+(.+)/i.exec(out);
227
+ if (!m) return { loggedIn: false };
228
+ const plan = m[1].trim().replace(/\.$/, "");
229
+ return { loggedIn: true, ...plan ? { plan } : {} };
230
+ }
231
+ if (kind === "codex") {
232
+ const m = /logged in(?: using (.+))?/i.exec(out);
233
+ if (!m || /not logged in/i.test(out)) return { loggedIn: false };
234
+ const plan = m[1]?.trim();
235
+ return { loggedIn: true, ...plan ? { plan } : {} };
236
+ }
237
+ try {
238
+ const j = JSON.parse(out);
239
+ if (!j.loggedIn) return { loggedIn: false };
240
+ return {
241
+ loggedIn: true,
242
+ ...j.email ? { email: j.email } : {},
243
+ ...j.subscriptionType ? { plan: j.subscriptionType } : {}
244
+ };
245
+ } catch {
246
+ return { loggedIn: false };
247
+ }
248
+ }
249
+ function statusArgs(kind) {
250
+ if (kind === "claude" || kind === "zai") return ["auth", "status"];
251
+ if (kind === "codex") return ["login", "status"];
252
+ return ["models"];
253
+ }
254
+ function loginArgs(kind) {
255
+ return kind === "claude" ? ["auth", "login"] : ["login"];
256
+ }
257
+ function checkProfile(kind, configDir) {
258
+ if (kind === "zai" && !configDir) return { loggedIn: false };
259
+ const r = spawnSync(cliBinary(kind), statusArgs(kind), {
260
+ env: { ...process.env, ...profileEnv(kind, configDir) },
261
+ encoding: "utf8",
262
+ // A status check that hangs must not hang the startup summary with it.
263
+ timeout: 2e4
264
+ });
265
+ if (r.error) return { loggedIn: false };
266
+ return readAuthStatus(kind, `${r.stdout ?? ""}${r.stderr ?? ""}`);
267
+ }
268
+ function runLogin(kind, configDir) {
269
+ if (kind === "zai") {
270
+ return { ok: false, error: "z.ai has no sign-in \u2014 a profile is connected by writing its settings file" };
271
+ }
272
+ const r = spawnSync(cliBinary(kind), loginArgs(kind), {
273
+ env: { ...process.env, ...profileEnv(kind, configDir) },
274
+ stdio: "inherit"
275
+ });
276
+ if (r.error) {
277
+ const e = r.error;
278
+ return e.code === "ENOENT" ? { ok: false, error: `\`${kind}\` is not installed, or not on PATH` } : { ok: false, error: e.message };
279
+ }
280
+ return { ok: r.status === 0 };
281
+ }
282
+
283
+ export {
284
+ checkProfile,
285
+ runLogin,
286
+ CLI_KINDS,
287
+ SYNTHETIC,
288
+ runCliAgent
289
+ };
@@ -1,9 +1,7 @@
1
1
  import {
2
- stateRoot
3
- } from "./chunk-SSDLHWSF.js";
4
- import {
2
+ stateRoot,
5
3
  writeAtomic
6
- } from "./chunk-B67BK5GQ.js";
4
+ } from "./chunk-6W4UH2BQ.js";
7
5
 
8
6
  // src/engine/trace.ts
9
7
  import { readFile, writeFile, mkdir, rm } from "fs/promises";
@@ -2,21 +2,21 @@ import {
2
2
  isMigrated,
3
3
  loadMigratedSync,
4
4
  migratedNotice
5
- } from "./chunk-2SVAHH5N.js";
5
+ } from "./chunk-63E73TGI.js";
6
6
  import {
7
7
  telemetry
8
- } from "./chunk-VPAWRRHL.js";
8
+ } from "./chunk-AE36LLL2.js";
9
9
  import {
10
10
  readBriefSync
11
- } from "./chunk-DTWKSZXY.js";
11
+ } from "./chunk-6OSEQOYY.js";
12
12
  import {
13
13
  everTraceable,
14
14
  readTraceSync
15
- } from "./chunk-FFYBY2NA.js";
15
+ } from "./chunk-KAGKX2YT.js";
16
16
  import {
17
17
  areaOf,
18
18
  loadGraphSync
19
- } from "./chunk-PGOYDOI4.js";
19
+ } from "./chunk-XEGQT5EN.js";
20
20
 
21
21
  // src/tools/read.ts
22
22
  import { readFile } from "fs/promises";
@@ -948,9 +948,20 @@ ${core.join("\n")}`;
948
948
  );
949
949
  var graphTraceTool = {
950
950
  name: "graph_trace",
951
- description: `What a source file is responsible for and what to be careful of when changing it, in the product's terms. Far cheaper than reading the file. Use it to orient before opening unfamiliar code. Pass "project" instead of a path to get the project brief: what the product is, its domain vocabulary, and the business rules the code must not violate. Read that FIRST in an unfamiliar codebase.`,
951
+ /**
952
+ * The limit is stated HERE because this is the only place an agent reads before calling.
953
+ *
954
+ * Measured live in one run: four different lenses — `risk-judge`, `plan-observability`,
955
+ * `plan-architecture` and one more — each asked for a trace of `spec.md` or `plan.md`. The error they got
956
+ * back is a good one and says the failure is permanent, so none of them asked twice; but each learned it
957
+ * separately, at a turn apiece, because a lens has its own memo and cannot be told by the last one. An
958
+ * error teaches one agent after the fact. A description tells every agent before.
959
+ */
960
+ description: `What a source file is responsible for and what to be careful of when changing it, in the product's terms. Far cheaper than reading the file. Use it to orient before opening unfamiliar code. SOURCE CODE ONLY (.ts, .cs, .py, .go, \u2026): a .md, .json or .txt path has no trace and never will \u2014 read documents with read_file. Pass "project" instead of a path to get the project brief: what the product is, its domain vocabulary, and the business rules the code must not violate. Read that FIRST in an unfamiliar codebase.`,
952
961
  permissionLevel: "safe",
953
- parameters: z4.object({ file: z4.string().describe('Repo-relative path, e.g. "src/config/config.ts"') }),
962
+ parameters: z4.object({
963
+ file: z4.string().describe('Repo-relative path to SOURCE code, e.g. "src/config/config.ts". Not a document.')
964
+ }),
954
965
  describe: (args) => ({ allowKey: "graph:trace", preview: `graph_trace ${JSON.stringify(args)}`.slice(0, 120) }),
955
966
  async run(args, ctx) {
956
967
  const file = String(args.file ?? "");
@@ -5,11 +5,11 @@ import {
5
5
  memoryHints,
6
6
  readFileTool,
7
7
  reinforceUsed
8
- } from "./chunk-DRZSUQ7Q.js";
8
+ } from "./chunk-LLL7QWXB.js";
9
9
  import {
10
10
  ToolRegistry,
11
11
  runStructuredRole
12
- } from "./chunk-VPAWRRHL.js";
12
+ } from "./chunk-AE36LLL2.js";
13
13
 
14
14
  // src/engine/triage.ts
15
15
  import { z } from "zod";
@@ -15,7 +15,18 @@ var defaultGitRunner = (args, cwd) => new Promise((resolve) => {
15
15
  child.on("error", (e) => resolve({ stdout, stderr: stderr + e.message, code: -1 }));
16
16
  child.on("close", (code) => resolve({ stdout, stderr, code: code ?? -1 }));
17
17
  });
18
+ var TAKES_A_VALUE = /* @__PURE__ */ new Set(["-c", "-C", "--git-dir", "--work-tree", "--namespace", "--exec-path", "--config-env"]);
19
+ function gitVerb(args) {
20
+ for (let i = 0; i < args.length; i++) {
21
+ const a = args[i];
22
+ if (a === void 0) continue;
23
+ if (!a.startsWith("-")) return a;
24
+ if (TAKES_A_VALUE.has(a)) i++;
25
+ }
26
+ return void 0;
27
+ }
18
28
 
19
29
  export {
20
- defaultGitRunner
30
+ defaultGitRunner,
31
+ gitVerb
21
32
  };
@@ -0,0 +1,11 @@
1
+ // src/engine/language.ts
2
+ function respondIn(language) {
3
+ if (!language || /^english$/i.test(language)) return "";
4
+ return `
5
+
6
+ Respond to the user in ${language}: everything you say to them, and every question you ask, is in ${language}. Code, identifiers, logs and commit messages keep whatever language the project uses.`;
7
+ }
8
+
9
+ export {
10
+ respondIn
11
+ };
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  defaultGitRunner
3
- } from "./chunk-IW2KBAVZ.js";
3
+ } from "./chunk-LPQU436C.js";
4
4
  import {
5
5
  briefForPrompt,
6
6
  briefPrompt,
7
7
  briefStatus,
8
8
  gatherBriefInput,
9
9
  saveBrief
10
- } from "./chunk-DTWKSZXY.js";
10
+ } from "./chunk-6OSEQOYY.js";
11
11
  import {
12
12
  ensureGitignore,
13
13
  loadTraceIndex,
@@ -19,10 +19,10 @@ import {
19
19
  tracePrompt,
20
20
  traceRootRel,
21
21
  traceable
22
- } from "./chunk-FFYBY2NA.js";
22
+ } from "./chunk-KAGKX2YT.js";
23
23
  import {
24
24
  loadGraph
25
- } from "./chunk-PGOYDOI4.js";
25
+ } from "./chunk-XEGQT5EN.js";
26
26
 
27
27
  // src/engine/trace-run.ts
28
28
  async function traceableFiles(cwd, opts) {