@mikenguyen69/harness 0.1.0-beta.1

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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +43 -0
  3. package/dist/build-identity.json +1 -0
  4. package/dist/cancellation.js +138 -0
  5. package/dist/cli.js +47 -0
  6. package/dist/dispatch.js +70 -0
  7. package/dist/help.js +47 -0
  8. package/dist/internal/agent/adapter/conformance.js +53 -0
  9. package/dist/internal/agent/adapter/hook.js +113 -0
  10. package/dist/internal/agent/adapter/install.js +30 -0
  11. package/dist/internal/agent/adapter/scope.js +55 -0
  12. package/dist/internal/agent/adapter/session.js +63 -0
  13. package/dist/internal/agent/adapter/settings.js +51 -0
  14. package/dist/internal/agent/cli.js +79 -0
  15. package/dist/internal/agent/evals/index.js +111 -0
  16. package/dist/internal/agent/index.js +15 -0
  17. package/dist/internal/agent/ledger-read.js +27 -0
  18. package/dist/internal/agent/routing/cli.js +104 -0
  19. package/dist/internal/agent/routing/index.js +123 -0
  20. package/dist/internal/agent/telemetry/index.js +97 -0
  21. package/dist/internal/orchestration/activity/index.js +111 -0
  22. package/dist/internal/orchestration/analyze/index.js +237 -0
  23. package/dist/internal/orchestration/cli.js +475 -0
  24. package/dist/internal/orchestration/config/index.js +66 -0
  25. package/dist/internal/orchestration/doctor/index.js +202 -0
  26. package/dist/internal/orchestration/forge/index.js +121 -0
  27. package/dist/internal/orchestration/git/index.js +181 -0
  28. package/dist/internal/orchestration/harness/cli.js +378 -0
  29. package/dist/internal/orchestration/harness/index.js +186 -0
  30. package/dist/internal/orchestration/harness/shim.js +182 -0
  31. package/dist/internal/orchestration/index.js +29 -0
  32. package/dist/internal/orchestration/loop/index.js +1107 -0
  33. package/dist/internal/orchestration/prompts/index.js +199 -0
  34. package/dist/internal/orchestration/report/index.js +125 -0
  35. package/dist/internal/orchestration/routes/index.js +85 -0
  36. package/dist/internal/orchestration/run/index.js +221 -0
  37. package/dist/internal/orchestration/runner/index.js +90 -0
  38. package/dist/internal/orchestration/runners/claude/index.js +246 -0
  39. package/dist/internal/orchestration/runners/claude/sdk.js +198 -0
  40. package/dist/internal/orchestration/runners/claude/wiring.js +98 -0
  41. package/dist/internal/orchestration/runners/codex/exec.js +114 -0
  42. package/dist/internal/orchestration/runners/codex/index.js +72 -0
  43. package/dist/internal/orchestration/runners/conformance/index.js +169 -0
  44. package/dist/internal/orchestration/runners/cursor/exec.js +531 -0
  45. package/dist/internal/orchestration/runners/cursor/index.js +184 -0
  46. package/dist/internal/orchestration/runners/stub/index.js +168 -0
  47. package/dist/internal/orchestration/runners/telemetry.js +36 -0
  48. package/dist/internal/orchestration/runstate/index.js +213 -0
  49. package/dist/internal/orchestration/runstate/publish.js +115 -0
  50. package/dist/internal/orchestration/worktree/index.js +89 -0
  51. package/dist/internal/system/adapters/index.js +19 -0
  52. package/dist/internal/system/agentops/index.js +16 -0
  53. package/dist/internal/system/agentops/scope.js +67 -0
  54. package/dist/internal/system/assets.js +45 -0
  55. package/dist/internal/system/board/cli.js +56 -0
  56. package/dist/internal/system/board/index.js +276 -0
  57. package/dist/internal/system/board/live.js +277 -0
  58. package/dist/internal/system/calibration/cli.js +45 -0
  59. package/dist/internal/system/calibration/index.js +141 -0
  60. package/dist/internal/system/cli.js +174 -0
  61. package/dist/internal/system/corpus/replay.js +79 -0
  62. package/dist/internal/system/digest/cli.js +23 -0
  63. package/dist/internal/system/digest/index.js +145 -0
  64. package/dist/internal/system/doors/classify.js +94 -0
  65. package/dist/internal/system/doors/cli.js +184 -0
  66. package/dist/internal/system/doors/index.js +249 -0
  67. package/dist/internal/system/doors/metrics.js +47 -0
  68. package/dist/internal/system/escapes/cli.js +45 -0
  69. package/dist/internal/system/escapes/index.js +81 -0
  70. package/dist/internal/system/explore/cli.js +77 -0
  71. package/dist/internal/system/explore/index.js +91 -0
  72. package/dist/internal/system/gates/cli.js +43 -0
  73. package/dist/internal/system/gates/detectors.js +55 -0
  74. package/dist/internal/system/gates/pre.js +51 -0
  75. package/dist/internal/system/hub/cli.js +51 -0
  76. package/dist/internal/system/hub/index.js +146 -0
  77. package/dist/internal/system/init/index.js +83 -0
  78. package/dist/internal/system/ledger/cli.js +54 -0
  79. package/dist/internal/system/ledger/index.js +176 -0
  80. package/dist/internal/system/manifest/index.js +207 -0
  81. package/dist/internal/system/provenance/index.js +13 -0
  82. package/dist/internal/system/report/index.js +15 -0
  83. package/dist/internal/system/runner/index.js +74 -0
  84. package/dist/internal/system/runstatus/cli.js +86 -0
  85. package/dist/internal/system/runstatus/index.js +168 -0
  86. package/dist/internal/system/sequencer/briefing.js +75 -0
  87. package/dist/internal/system/sequencer/cli.js +170 -0
  88. package/dist/internal/system/sequencer/index.js +156 -0
  89. package/dist/internal/system/spec/cli.js +110 -0
  90. package/dist/internal/system/spec/index.js +212 -0
  91. package/dist/internal/system/spec/materialize.js +111 -0
  92. package/dist/internal/system/spec/openspec-parse.js +83 -0
  93. package/dist/internal/system/spec/project.js +208 -0
  94. package/dist/internal/system/substrate/index.js +14 -0
  95. package/dist/internal/system/system/index.js +159 -0
  96. package/dist/internal/system/units/index.js +87 -0
  97. package/dist/internal/system/verify/cli.js +103 -0
  98. package/dist/internal/system/verify/declined-reasons.js +61 -0
  99. package/dist/internal/system/verify/index.js +188 -0
  100. package/dist/internal/system/verify/signing.js +79 -0
  101. package/dist/paths.js +50 -0
  102. package/dist/routes.js +93 -0
  103. package/dist/version.js +30 -0
  104. package/dist/win32-process-group.js +360 -0
  105. package/package.json +41 -0
  106. package/profiles/python/bindings.toml +59 -0
  107. package/profiles/terraform/bindings.toml +66 -0
  108. package/profiles/typescript/bindings.toml +64 -0
  109. package/profiles/typescript/smoke/README.md +8 -0
  110. package/schema/baseline.schema.json +26 -0
  111. package/schema/decision-classes.yaml +45 -0
  112. package/schema/envelope.schema.json +186 -0
  113. package/schema/ledger-event.schema.json +235 -0
  114. package/schema/manifest.schema.json +119 -0
  115. package/schema/routes.schema.json +55 -0
  116. package/schema/run-status.schema.json +162 -0
  117. package/schema/session-event.schema.json +74 -0
  118. package/schema/spec.schema.json +114 -0
  119. package/schema/system.schema.json +142 -0
  120. package/templates/ci/verify.yml +23 -0
  121. package/templates/target-kit/README.md +78 -0
  122. package/templates/target-kit/component.toml +20 -0
  123. package/templates/target-kit/orchestration.json +9 -0
  124. package/templates/target-kit/profiles/target/bindings.toml +6 -0
  125. package/templates/target-kit/routes.toml +5 -0
  126. package/templates/target-kit/specs/first-unit.yaml +11 -0
  127. package/templates/target-kit/system.toml +7 -0
@@ -0,0 +1,184 @@
1
+ import { parseVerdict, renderImplementerPrompt, renderRetryFollowup, renderReviewFollowup, renderVerifierPrompt, } from "../../prompts/index.js";
2
+ import { runCursorAgent } from "./exec.js";
3
+ /** the agent has escalated when its output carries this sentinel (cursor has no escalate verb). */
4
+ const ESCALATE_RE = /ESCALATE:\s*(.+?)(?:\s+WANTS:\s*(.+))?$/im;
5
+ export class CursorRunner {
6
+ opts;
7
+ name = "cursor";
8
+ constructor(opts) {
9
+ this.opts = opts;
10
+ }
11
+ capabilities() {
12
+ return { scopeHook: false, sessionTelemetry: false };
13
+ }
14
+ async run(briefing, policy, kind, workspace, onActivity) {
15
+ const isVerifier = kind === "verifier";
16
+ const diff = isVerifier ? ((await this.opts.diffFor?.(briefing.unitId)) ?? "") : "";
17
+ const retry = isVerifier
18
+ ? undefined
19
+ : this.opts.retryFor?.(briefing.unitId, "implementer");
20
+ // Resume the prior session for this unit+kind when it ran on the same model
21
+ // — the agent keeps its own context, so the follow-up is terse instead of a
22
+ // full re-brief + re-exploration.
23
+ const stored = this.opts.resumeFor?.(briefing.unitId, kind);
24
+ const resumeSessionId = stored && stored.model === policy.model ? stored.id : undefined;
25
+ const prompt = resumeSessionId
26
+ ? isVerifier
27
+ ? renderReviewFollowup(diff)
28
+ : renderRetryFollowup(retry ?? { attempt: 0, source: "the previous attempt", evidence: "(no evidence captured)" })
29
+ : isVerifier
30
+ ? renderVerifierPrompt(briefing, diff)
31
+ : renderImplementerPrompt(briefing, retry);
32
+ process.stderr.write(formatCursorSessionBanner(briefing.unitId, kind) + "\n");
33
+ const onCursorActivity = (u) => {
34
+ if (onActivity !== undefined) {
35
+ // Forward unclamped (Cursor's own LINE_CLAMP is 400) and carry
36
+ // kind/tool/target through — the loop's makeActivitySink is where the
37
+ // 160-char board clamp happens (recordLaneActivity), not here. Re-
38
+ // clamping to 160 at this call site was a bug: it defeated the durable
39
+ // activity log's ability to keep the longer, more useful line.
40
+ onActivity({
41
+ line: u.line,
42
+ turn: u.turn,
43
+ tokens: u.tokens,
44
+ ...(u.kind ? { kind: u.kind } : {}),
45
+ ...(u.tool ? { tool: u.tool } : {}),
46
+ ...(u.target ? { target: u.target } : {}),
47
+ });
48
+ }
49
+ else if (this.opts.onActivity !== undefined) {
50
+ this.opts.onActivity(u);
51
+ }
52
+ const useWatch = this.opts.watchStderr === true ||
53
+ (onActivity === undefined && this.opts.onActivity === undefined);
54
+ if (useWatch) {
55
+ process.stderr.write(formatCursorWatchLine(briefing.unitId, u) + "\n");
56
+ }
57
+ };
58
+ const r = await runCursorAgent({
59
+ prompt,
60
+ model: policy.model,
61
+ cwd: workspace?.path ?? this.opts.cwd,
62
+ posture: isVerifier ? "read-only" : "workspace-write",
63
+ env: {
64
+ ...(this.opts.env ?? process.env),
65
+ HARNESS_BIN: this.opts.harnessBin,
66
+ HARNESS_UNIT_ID: briefing.unitId,
67
+ },
68
+ onActivity: onCursorActivity,
69
+ ...(resumeSessionId ? { resumeSessionId } : {}),
70
+ ...(this.opts.deadlineAt !== undefined
71
+ ? { timeoutMs: Math.max(1, this.opts.deadlineAt - Date.now()) }
72
+ : {}),
73
+ ...(this.opts.bin ? { bin: this.opts.bin } : {}),
74
+ ...(this.opts.execImpl ? { execImpl: this.opts.execImpl } : {}),
75
+ });
76
+ if (r.sessionId) {
77
+ this.opts.recordSession?.(briefing.unitId, kind, r.sessionId, policy.model);
78
+ }
79
+ if (isVerifier) {
80
+ return {
81
+ ok: r.ok,
82
+ filesTouched: [],
83
+ cost: r.costUsd,
84
+ transcriptRef: cursorTranscriptRef(briefing.unitId, "verifier", r.error),
85
+ verdict: parseVerdict(r.resultText),
86
+ };
87
+ }
88
+ const esc = ESCALATE_RE.exec(r.resultText);
89
+ const result = {
90
+ ok: r.ok && !esc,
91
+ filesTouched: [],
92
+ cost: r.costUsd,
93
+ transcriptRef: cursorTranscriptRef(briefing.unitId, "implementer", r.error),
94
+ };
95
+ if (esc) {
96
+ result.escalation = {
97
+ reason: esc[1]?.trim() ?? "cursor session escalated",
98
+ wants: esc[2]?.trim() ?? "operator decision",
99
+ };
100
+ }
101
+ return result;
102
+ }
103
+ }
104
+ function cursorTranscriptRef(unitId, kind, error) {
105
+ const ref = `cursor-agent://${unitId}/${kind}`;
106
+ return error ? `${ref}#error=${encodeURIComponent(error)}` : ref;
107
+ }
108
+ /** Session divider printed once when a Cursor lane starts. */
109
+ export function formatCursorSessionBanner(unitId, kind) {
110
+ const useColor = process.env.CURSOR_WATCH_COLOR !== "0";
111
+ const role = kind === "verifier" ? "verify" : "implement";
112
+ const label = `${shortUnitId(unitId)} · ${role}`;
113
+ const bar = "─".repeat(Math.max(8, 44 - label.length));
114
+ const line = `── ${label} ${bar}`;
115
+ if (!useColor)
116
+ return line;
117
+ return `\x1b[2m${line}\x1b[0m`;
118
+ }
119
+ /** ANSI watch line for orchestrate stderr — scannable columns, not a flat dump. */
120
+ export function formatCursorWatchLine(unitId, u) {
121
+ // Cursor IDE sets NO_COLOR; don't fight it with FORCE_COLOR (Node warns).
122
+ // Opt out with CURSOR_WATCH_COLOR=0.
123
+ const useColor = process.env.CURSOR_WATCH_COLOR !== "0";
124
+ const unit = shortUnitId(unitId);
125
+ const turn = String(u.turn).padStart(3, " ");
126
+ const badge = kindBadge(u.kind);
127
+ const { head, rest } = splitActivityLine(u.line, u.kind);
128
+ const talk = u.line.replace(/^assistant:\s*/i, "");
129
+ if (!useColor) {
130
+ const plain = u.kind === "text"
131
+ ? talk
132
+ : u.kind === "tool" && rest
133
+ ? `${head} ${rest}`
134
+ : u.line;
135
+ return ` ${unit.padEnd(10)} t${turn} ${badge.padEnd(4)} ${plain}`;
136
+ }
137
+ const reset = "\x1b[0m";
138
+ const dim = "\x1b[2m";
139
+ const bold = "\x1b[1m";
140
+ const cyan = "\x1b[36m";
141
+ const yellow = "\x1b[33m";
142
+ const red = "\x1b[31m";
143
+ const magenta = "\x1b[35m";
144
+ const unitCol = `${dim}${unit.padEnd(10)}${reset}`;
145
+ const turnCol = `${dim}t${turn}${reset}`;
146
+ const badgeCol = u.kind === "tool"
147
+ ? `${cyan}${bold}${badge.padEnd(4)}${reset}`
148
+ : u.kind === "tool-result"
149
+ ? `${red}${bold}${badge.padEnd(4)}${reset}`
150
+ : `${magenta}${bold}${badge.padEnd(4)}${reset}`;
151
+ let body;
152
+ if (u.kind === "tool") {
153
+ body = rest
154
+ ? `${cyan}${bold}${head}${reset} ${cyan}${rest}${reset}`
155
+ : `${cyan}${head}${reset}`;
156
+ }
157
+ else if (u.kind === "tool-result") {
158
+ body = `${red}${bold}${u.line}${reset}`;
159
+ }
160
+ else {
161
+ body = `${yellow}${talk}${reset}`;
162
+ }
163
+ return ` ${unitCol} ${turnCol} ${badgeCol} ${body}`;
164
+ }
165
+ function shortUnitId(unitId) {
166
+ return unitId.replace(/^live-activity-/, "").slice(0, 10);
167
+ }
168
+ function kindBadge(kind) {
169
+ if (kind === "tool")
170
+ return "TOOL";
171
+ if (kind === "tool-result")
172
+ return "FAIL";
173
+ return "SAY";
174
+ }
175
+ /** Split `tool: target` so the tool name can be emphasized. */
176
+ function splitActivityLine(line, kind) {
177
+ if (kind !== "tool")
178
+ return { head: line, rest: "" };
179
+ const i = line.indexOf(": ");
180
+ if (i <= 0)
181
+ return { head: line, rest: "" };
182
+ return { head: line.slice(0, i), rest: line.slice(i + 2) };
183
+ }
184
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,168 @@
1
+ /**
2
+ * StubRunner — a deterministic `Runner` with NO LLM, for the CLI-level
3
+ * end-to-end (G3). It exercises the whole wiring path — `runLoop` / `plan` /
4
+ * `step` / `startupSweep`, a real `ShellGit`, a real `LocalForge`, a real git
5
+ * repo — with only the *agent intelligence* replaced.
6
+ *
7
+ * Given a briefing it:
8
+ * - implementer: branches `briefing.branch` off the base, writes ONE real
9
+ * in-scope file, `git commit`s it with a `Unit: <id>` trailer, restores the
10
+ * base branch (so the orchestrator's checkout mirrors the real world, where
11
+ * the agent works elsewhere), and returns `ok` with the commit sha.
12
+ * - verifier: touches nothing and returns `VERDICT: APPROVE`.
13
+ *
14
+ * Declares `{scopeHook: false, sessionTelemetry: false}` so the M2.5 degraded
15
+ * tier (post-hoc scope check + orchestrator-reconstructed telemetry) is
16
+ * exercised too. Scriptable per unit to drive the failure branches:
17
+ * `.scriptOutOfScope(id)` — the implementer writes OUTSIDE `briefing.scope`
18
+ * `.scriptFail(id)` — the implementer returns `ok: false`
19
+ * `.scriptEscalate(id)` — the implementer raises an escalation
20
+ */
21
+ import { execFileSync } from "node:child_process";
22
+ import { mkdirSync, writeFileSync } from "node:fs";
23
+ import { dirname, join } from "node:path";
24
+ /**
25
+ * Deterministic P0 fixture: a couple of tool turns with `tool`/`target`, one
26
+ * text turn — enough for `ActivityLogWriter` golden tests and the
27
+ * `ORCHESTRATE_STUB_RUNNER=1` E2E acceptance check to see real `agent.activity`
28
+ * lines without needing a live LLM.
29
+ */
30
+ export const DEFAULT_STUB_ACTIVITY = [
31
+ { line: "read: src/spec.ts", turn: 1, tokens: { in: 0, out: 0 }, kind: "tool", tool: "read", target: "src/spec.ts" },
32
+ { line: "write: src/impl.ts", turn: 2, tokens: { in: 0, out: 0 }, kind: "tool", tool: "write", target: "src/impl.ts" },
33
+ { line: "assistant: implemented", turn: 3, tokens: { in: 12, out: 34 }, kind: "text" },
34
+ ];
35
+ export class StubRunner {
36
+ opts;
37
+ name;
38
+ baseBranch;
39
+ gitBin;
40
+ cost;
41
+ script = new Map();
42
+ activityScript = new Map();
43
+ constructor(opts) {
44
+ this.opts = opts;
45
+ this.name = opts.name ?? "claude";
46
+ this.baseBranch = opts.baseBranch ?? "main";
47
+ this.gitBin = opts.gitBin ?? "git";
48
+ this.cost = opts.costPerSession ?? 0.01;
49
+ }
50
+ capabilities() {
51
+ return this.opts.capabilities ?? { scopeHook: false, sessionTelemetry: false };
52
+ }
53
+ /** the implementer writes a file OUTSIDE the declared scope (M2.5 path). */
54
+ scriptOutOfScope(unitId) {
55
+ this.script.set(unitId, "out-of-scope");
56
+ return this;
57
+ }
58
+ /** the implementer returns `ok: false` (retry / exhaust path). */
59
+ scriptFail(unitId) {
60
+ this.script.set(unitId, "fail");
61
+ return this;
62
+ }
63
+ /** the implementer raises an escalation (park, never retry). */
64
+ scriptEscalate(unitId) {
65
+ this.script.set(unitId, "escalate");
66
+ return this;
67
+ }
68
+ /** replay this activity through `onActivity` on the implementer path for `unitId`. */
69
+ scriptActivity(unitId, activity) {
70
+ this.activityScript.set(unitId, activity);
71
+ return this;
72
+ }
73
+ git(args, cwd = this.opts.cwd) {
74
+ return execFileSync(this.gitBin, args, {
75
+ cwd,
76
+ encoding: "utf8",
77
+ }).trim();
78
+ }
79
+ async run(briefing, _policy, kind, workspace, onActivity) {
80
+ if (kind === "verifier") {
81
+ // read-only: touches nothing, approves. Verifier lanes stay silent —
82
+ // v0 only scripts implementer activity (deliberate, not an oversight;
83
+ // see plan doc).
84
+ return {
85
+ ok: true,
86
+ filesTouched: [],
87
+ cost: this.cost,
88
+ transcriptRef: `stub://${briefing.unitId}/verifier`,
89
+ verdict: {
90
+ approved: true,
91
+ reasons: ["VERDICT: APPROVE — stub verifier: meets acceptance criteria"],
92
+ },
93
+ };
94
+ }
95
+ const activity = this.activityScript.get(briefing.unitId) ??
96
+ this.opts.defaultActivity ??
97
+ DEFAULT_STUB_ACTIVITY;
98
+ for (const a of activity)
99
+ onActivity?.(a);
100
+ const mode = this.script.get(briefing.unitId);
101
+ if (mode === "escalate") {
102
+ return {
103
+ ok: false,
104
+ filesTouched: [],
105
+ cost: this.cost,
106
+ transcriptRef: `stub://${briefing.unitId}/implementer`,
107
+ escalation: {
108
+ reason: "stub: scripted escalation",
109
+ wants: "operator decision: widen | split | abort",
110
+ },
111
+ };
112
+ }
113
+ const rel = mode === "out-of-scope"
114
+ ? "src/__unscoped__/leak.ts"
115
+ : inScopePath(briefing.scope, briefing.unitId);
116
+ // work on the unit branch, forked from the base, then restore the base —
117
+ // the orchestrator's checkout stays on the base branch, exactly as it would
118
+ // if a real agent had done the work in its own sandbox.
119
+ const cwd = workspace?.path ?? this.opts.cwd;
120
+ if (!workspace)
121
+ this.git(["checkout", "-q", "-B", briefing.branch, this.baseBranch], cwd);
122
+ const abs = join(cwd, rel);
123
+ mkdirSync(dirname(abs), { recursive: true });
124
+ writeFileSync(abs, `// ${briefing.unitId} — deterministic stub implementation\n` +
125
+ `// intent: ${briefing.intent}\n` +
126
+ `export const unit = ${JSON.stringify(briefing.unitId)};\n`);
127
+ this.git(["add", "-A"], cwd);
128
+ this.git([
129
+ "commit",
130
+ "-q",
131
+ "-m",
132
+ `${firstLine(briefing.intent)}\n\nUnit: ${briefing.unitId}`,
133
+ ], cwd);
134
+ const sha = this.git(["rev-parse", "HEAD"], cwd);
135
+ if (!workspace)
136
+ this.git(["checkout", "-q", this.baseBranch], cwd);
137
+ await this.opts.afterImplement?.(briefing.unitId);
138
+ if (mode === "fail") {
139
+ return {
140
+ ok: false,
141
+ commitSha: sha,
142
+ filesTouched: [rel],
143
+ cost: this.cost,
144
+ transcriptRef: `stub://${briefing.unitId}/implementer`,
145
+ };
146
+ }
147
+ return {
148
+ ok: true,
149
+ commitSha: sha,
150
+ filesTouched: [rel],
151
+ cost: this.cost,
152
+ transcriptRef: `stub://${briefing.unitId}/implementer`,
153
+ };
154
+ }
155
+ }
156
+ /** the literal prefix of the first scope glob + `<unit>.ts` — always in scope. */
157
+ export function inScopePath(scope, unitId) {
158
+ const glob = scope[0] ?? "src/**";
159
+ const star = glob.search(/[*?[]/);
160
+ const head = star === -1 ? glob : glob.slice(0, star);
161
+ const dir = head.endsWith("/") ? head : head.replace(/[^/]*$/, "");
162
+ const safe = unitId.replace(/[^A-Za-z0-9_-]+/g, "-");
163
+ return `${dir || "src/"}${safe}.ts`;
164
+ }
165
+ function firstLine(s) {
166
+ return s.split("\n")[0].slice(0, 72);
167
+ }
168
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,36 @@
1
+ export function reconstructSessionEvents(p) {
2
+ const common = {
3
+ session: p.session,
4
+ body: {
5
+ unit_id: p.unitId,
6
+ session_id: p.session,
7
+ runner: p.runner,
8
+ model: p.model,
9
+ kind: p.kind,
10
+ emitter: "orchestrator",
11
+ },
12
+ };
13
+ const outcome = p.result.escalation
14
+ ? "escalated"
15
+ : p.result.ok
16
+ ? "ok"
17
+ : "failed";
18
+ return [
19
+ { type: "session.started", unitId: p.unitId, ...common },
20
+ {
21
+ type: "session.ended",
22
+ unitId: p.unitId,
23
+ session: p.session,
24
+ body: {
25
+ ...common.body,
26
+ outcome,
27
+ cost: p.result.cost,
28
+ transcript_ref: p.result.transcriptRef ?? null,
29
+ files_touched: p.result.filesTouched,
30
+ ...(p.result.escalation ? { escalation: p.result.escalation } : {}),
31
+ ...(p.result.verdict ? { verdict: p.result.verdict } : {}),
32
+ },
33
+ },
34
+ ];
35
+ }
36
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1,213 @@
1
+ /**
2
+ * runstate (D0) — the run's live picture, as a PURE PROJECTION of an in-process
3
+ * event log. Not the ledger: in-flight state (attempt counts, cost ticking, a
4
+ * session mid-run) is high-churn and worthless after the run, so it never
5
+ * touches the hash-chained provenance ledger (dashboard-plan §"Why the feed is
6
+ * not ledger events"). The durable outcomes it leads to reach the ledger through
7
+ * their own channels.
8
+ *
9
+ * RunLog.append(event) — the loop records every transition here
10
+ * projectRunState(events) → RunState — pure fold, golden-tested
11
+ *
12
+ * D1 serializes a `RunState` to a `run-status` document and publishes it via
13
+ * `harness runstatus publish`. D2 renders the final one as the end-of-run HTML
14
+ * report. Both are downstream of this projection.
15
+ */
16
+ /** Pure fold. Same events in → identical `RunState` out (golden test). */
17
+ export function projectRunState(events) {
18
+ const state = {
19
+ runId: "",
20
+ machineId: "",
21
+ systemId: "",
22
+ specRef: "",
23
+ startedAt: 0,
24
+ heartbeatAt: 0,
25
+ dagSize: 0,
26
+ lanes: [],
27
+ parked: [],
28
+ needsYou: [],
29
+ notes: [],
30
+ counts: { merged: 0, escalated: 0, parked: 0, inFlight: 0, needsYou: 0 },
31
+ };
32
+ const lanes = new Map();
33
+ const merged = new Set();
34
+ const escalated = new Set();
35
+ for (const e of events) {
36
+ switch (e.t) {
37
+ case "run.started":
38
+ state.runId = e.runId;
39
+ state.machineId = e.machineId;
40
+ state.systemId = e.systemId;
41
+ state.specRef = e.specRef;
42
+ state.startedAt = e.at;
43
+ state.heartbeatAt = e.at;
44
+ state.dagSize = e.dagSize;
45
+ break;
46
+ case "unit.parked":
47
+ if (!state.parked.some((p) => p.unitId === e.unitId))
48
+ state.parked.push({ unitId: e.unitId, reason: e.reason });
49
+ break;
50
+ case "lane.opened":
51
+ lanes.set(e.unitId, {
52
+ unitId: e.unitId,
53
+ session: e.session,
54
+ runner: e.runner,
55
+ model: e.model,
56
+ step: "claim",
57
+ attempt: 1,
58
+ retryBudget: e.retryBudget,
59
+ cost: 0,
60
+ emitter: e.emitter,
61
+ settled: false,
62
+ });
63
+ break;
64
+ case "lane.step": {
65
+ const lane = lanes.get(e.unitId);
66
+ if (lane) {
67
+ lane.step = e.step;
68
+ if (e.step === "merge" || e.step === "land")
69
+ lane.settled = true;
70
+ }
71
+ break;
72
+ }
73
+ case "lane.attempt": {
74
+ const lane = lanes.get(e.unitId);
75
+ if (lane)
76
+ lane.attempt = e.attempt;
77
+ break;
78
+ }
79
+ case "lane.cost": {
80
+ const lane = lanes.get(e.unitId);
81
+ if (lane)
82
+ lane.cost += e.delta;
83
+ break;
84
+ }
85
+ case "lane.retry": {
86
+ const lane = lanes.get(e.unitId);
87
+ if (lane) {
88
+ lane.attempt = e.attempt;
89
+ lane.step = "implement";
90
+ lane.settled = false;
91
+ }
92
+ break;
93
+ }
94
+ case "lane.note":
95
+ state.notes.push({ unitId: e.unitId, note: e.note });
96
+ break;
97
+ case "lane.activity": {
98
+ const lane = lanes.get(e.unitId);
99
+ if (lane) {
100
+ lane.activity = e.line;
101
+ lane.activityAt = e.at;
102
+ lane.turn = e.turn;
103
+ lane.tokens = e.tokens;
104
+ const recent = [...(lane.recent ?? []), e.line];
105
+ lane.recent = recent.length > 5 ? recent.slice(-5) : recent;
106
+ }
107
+ // A publishing agent IS a heartbeat — a step that runs long (implement
108
+ // can take 10s of minutes) must not go "stale" on the board while it
109
+ // is visibly, actively working (dashboard UX gap: activity vs. liveness
110
+ // were wrongly coupled through this single clock).
111
+ state.heartbeatAt = Math.max(state.heartbeatAt, e.at);
112
+ break;
113
+ }
114
+ case "lane.envelope": {
115
+ const lane = lanes.get(e.unitId);
116
+ if (lane)
117
+ lane.envelopeHash = e.hash;
118
+ break;
119
+ }
120
+ case "lane.door": {
121
+ const lane = lanes.get(e.unitId);
122
+ if (lane)
123
+ lane.doorId = e.doorId;
124
+ break;
125
+ }
126
+ case "unit.merged": {
127
+ merged.add(e.unitId);
128
+ const lane = lanes.get(e.unitId);
129
+ if (lane) {
130
+ lane.step = "land";
131
+ lane.settled = true;
132
+ }
133
+ break;
134
+ }
135
+ case "unit.escalated": {
136
+ escalated.add(e.unitId);
137
+ const lane = lanes.get(e.unitId);
138
+ if (lane)
139
+ lane.settled = true;
140
+ break;
141
+ }
142
+ case "needs.you":
143
+ // dedupe on (kind, unitId) — a heartbeat re-publish must not stack them.
144
+ if (!state.needsYou.some((n) => n.kind === e.kind && n.unitId === e.unitId)) {
145
+ const entry = {
146
+ kind: e.kind,
147
+ message: e.message,
148
+ unblock: e.unblock,
149
+ };
150
+ if (e.unitId !== undefined)
151
+ entry.unitId = e.unitId;
152
+ state.needsYou.push(entry);
153
+ }
154
+ break;
155
+ case "heartbeat":
156
+ state.heartbeatAt = e.at;
157
+ break;
158
+ case "run.ended":
159
+ state.outcome = e.outcome;
160
+ state.heartbeatAt = Math.max(state.heartbeatAt, e.at);
161
+ break;
162
+ }
163
+ }
164
+ state.lanes = [...lanes.values()];
165
+ state.counts = {
166
+ merged: merged.size,
167
+ escalated: escalated.size,
168
+ parked: state.parked.length,
169
+ inFlight: state.lanes.filter((l) => !l.settled).length,
170
+ needsYou: state.needsYou.length,
171
+ };
172
+ return state;
173
+ }
174
+ /** In-process event log the loop appends to; `snapshot()` folds it. */
175
+ export class RunLog {
176
+ events = [];
177
+ nowFn;
178
+ /** optional clock shared with the loop — activity_at is stamped from this, not wall-clock ad hoc. */
179
+ setClock(now) {
180
+ this.nowFn = now;
181
+ }
182
+ append(event) {
183
+ this.events.push(event);
184
+ }
185
+ /** Record a lane activity update; stamps `activity_at` from the log clock. */
186
+ recordLaneActivity(unitId, update) {
187
+ this.append({
188
+ t: "lane.activity",
189
+ unitId,
190
+ line: update.line.length <= 160 ? update.line : update.line.slice(0, 160),
191
+ turn: update.turn,
192
+ tokens: update.tokens,
193
+ at: this.clock(),
194
+ });
195
+ }
196
+ clock() {
197
+ if (this.nowFn)
198
+ return this.nowFn();
199
+ for (let i = this.events.length - 1; i >= 0; i--) {
200
+ const e = this.events[i];
201
+ if ("at" in e && typeof e.at === "number")
202
+ return e.at;
203
+ }
204
+ return 0;
205
+ }
206
+ all() {
207
+ return this.events;
208
+ }
209
+ snapshot() {
210
+ return projectRunState(this.events);
211
+ }
212
+ }
213
+ //# sourceMappingURL=index.js.map