@abaigram/abai-stub 0.0.0-stage → 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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -2
  3. package/dist/audit.d.ts +70 -0
  4. package/dist/audit.d.ts.map +1 -0
  5. package/dist/audit.js +166 -0
  6. package/dist/audit.js.map +1 -0
  7. package/dist/cells/fit.d.ts +6 -0
  8. package/dist/cells/fit.d.ts.map +1 -0
  9. package/dist/cells/fit.js +44 -0
  10. package/dist/cells/fit.js.map +1 -0
  11. package/dist/content.d.ts +17 -0
  12. package/dist/content.d.ts.map +1 -0
  13. package/dist/content.js +71 -0
  14. package/dist/content.js.map +1 -0
  15. package/dist/endpoint.d.ts +90 -0
  16. package/dist/endpoint.d.ts.map +1 -0
  17. package/dist/endpoint.js +789 -0
  18. package/dist/endpoint.js.map +1 -0
  19. package/dist/entry.d.ts +36 -0
  20. package/dist/entry.d.ts.map +1 -0
  21. package/dist/entry.js +409 -0
  22. package/dist/entry.js.map +1 -0
  23. package/dist/env.d.ts +38 -0
  24. package/dist/env.d.ts.map +1 -0
  25. package/dist/env.js +69 -0
  26. package/dist/env.js.map +1 -0
  27. package/dist/harness-binding.d.ts +223 -0
  28. package/dist/harness-binding.d.ts.map +1 -0
  29. package/dist/harness-binding.js +589 -0
  30. package/dist/harness-binding.js.map +1 -0
  31. package/dist/index.d.ts +17 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +17 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/manifest/index.d.ts +3 -0
  36. package/dist/manifest/index.d.ts.map +1 -0
  37. package/dist/manifest/index.js +3 -0
  38. package/dist/manifest/index.js.map +1 -0
  39. package/dist/manifest/manifest.d.ts +172 -0
  40. package/dist/manifest/manifest.d.ts.map +1 -0
  41. package/dist/manifest/manifest.js +443 -0
  42. package/dist/manifest/manifest.js.map +1 -0
  43. package/dist/manifest/render.d.ts +6 -0
  44. package/dist/manifest/render.d.ts.map +1 -0
  45. package/dist/manifest/render.js +39 -0
  46. package/dist/manifest/render.js.map +1 -0
  47. package/dist/null-harness.d.ts +32 -0
  48. package/dist/null-harness.d.ts.map +1 -0
  49. package/dist/null-harness.js +162 -0
  50. package/dist/null-harness.js.map +1 -0
  51. package/dist/program.d.ts +250 -0
  52. package/dist/program.d.ts.map +1 -0
  53. package/dist/program.js +877 -0
  54. package/dist/program.js.map +1 -0
  55. package/dist/region.d.ts +72 -0
  56. package/dist/region.d.ts.map +1 -0
  57. package/dist/region.js +92 -0
  58. package/dist/region.js.map +1 -0
  59. package/dist/registry.d.ts +57 -0
  60. package/dist/registry.d.ts.map +1 -0
  61. package/dist/registry.js +101 -0
  62. package/dist/registry.js.map +1 -0
  63. package/dist/wire/descriptor.d.ts +38 -0
  64. package/dist/wire/descriptor.d.ts.map +1 -0
  65. package/dist/wire/descriptor.js +144 -0
  66. package/dist/wire/descriptor.js.map +1 -0
  67. package/dist/wire/framing.d.ts +11 -0
  68. package/dist/wire/framing.d.ts.map +1 -0
  69. package/dist/wire/framing.js +42 -0
  70. package/dist/wire/framing.js.map +1 -0
  71. package/dist/wire/index.d.ts +4 -0
  72. package/dist/wire/index.d.ts.map +1 -0
  73. package/dist/wire/index.js +4 -0
  74. package/dist/wire/index.js.map +1 -0
  75. package/dist/wire/protocol.d.ts +255 -0
  76. package/dist/wire/protocol.d.ts.map +1 -0
  77. package/dist/wire/protocol.js +15 -0
  78. package/dist/wire/protocol.js.map +1 -0
  79. package/package.json +36 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"null-harness.d.ts","sourceRoot":"","sources":["../src/null-harness.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAE3C,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,mIAAmI;IACnI,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,yHAAyH;IACzH,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,4BAA4B;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,8DAA8D;IAC9D,WAAW,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrG;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,MAAM,IAAI,iBAAiB,CAAC;IAC5B,2FAA2F;IAC3F,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAID,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,QAAQ,GAAG,WAAW,CAyKtF","sourcesContent":["// The null harness (abaigrammar section 2): a harness that attaches, drains\n// every channel, answers every decision with its default, and calls no\n// model. Passive (`ABAI_HARNESS=null`) it touches no control and exists to\n// measure overhead. Active (`ABAI_HARNESS=exercise`) it writes each knob's\n// default, calls each capability with its example, and reports what went\n// unexercised, so conformance (section 12) is demonstrable without a model.\n// It lives in the stub so a check run needs no harness package installed.\nimport type { HarnessBinding } from \"./harness-binding.ts\";\n\nexport type NullForm = \"null\" | \"exercise\";\n\nexport interface NullHarnessReport {\n\tform: NullForm;\n\tevents: number;\n\tdropped: number;\n\tdecisions: number;\n\tturns: number;\n\ttouchpoints: number;\n\t/** Cells the program wrote while the harness was attached: a cell whose version moved, a knob's beyond the harness's own write. */\n\tcellsWritten: string[];\n\t/** The program's own events emitted while attached, by name, each once; the library's `abai.*` are not the program's. */\n\teventsEmitted: string[];\n\t/** The active form only. */\n\tknobsWritten: string[];\n\tcapabilitiesCalled: string[];\n\t/** Declarations the run did not exercise, with the reason. */\n\tunexercised: Array<{ kind: \"capability\" | \"decision\" | \"turn\" | \"knob\"; name: string; why: string }>;\n}\n\nexport interface NullHarness {\n\treadonly form: NullForm;\n\treport(): NullHarnessReport;\n\t/** The active form's exercise, once it has run (resolves at once for the passive form). */\n\treadonly exercised: Promise<void>;\n\tdetach(reason: string): void;\n}\n\nconst REASON = \"null harness\";\n\nexport function attachNullHarness(binding: HarnessBinding, form: NullForm): NullHarness {\n\tlet events = 0;\n\tlet dropped = 0;\n\tlet decisions = 0;\n\tlet turns = 0;\n\tlet touchpoints = 0;\n\tlet cursor = 0;\n\tconst reached = { decisions: new Set<string>(), turns: new Set<string>() };\n\tconst knobsWritten: string[] = [];\n\tconst capabilitiesCalled: string[] = [];\n\tconst eventsEmitted: string[] = [];\n\tconst failed: NullHarnessReport[\"unexercised\"] = [];\n\t// the versions at attach: a cell whose version moved past them was written by the program, a knob's once\n\t// more than the exercise's own write of it, so level 2's \"a cell the program writes\" is told from the\n\t// harness's doing (section 12); one readCells() at attach and one at the report, none on a measured row\n\tconst versions = (): Record<string, number> => {\n\t\tconst seen: Record<string, number> = {};\n\t\tfor (const [name, reading] of Object.entries(binding.readCells().cells)) seen[name] = reading.version;\n\t\treturn seen;\n\t};\n\tconst baseline = versions();\n\tconst cellsWritten = (): string[] =>\n\t\tObject.entries(versions())\n\t\t\t.filter(([name, version]) => version - (baseline[name] ?? 0) - (knobsWritten.includes(name) ? 1 : 0) > 0)\n\t\t\t.map(([name]) => name);\n\n\t// the exercise starts at the program's first activity, or on the next macrotask, whichever comes first:\n\t// a program that never yields to the macrotask queue would otherwise finish before it began\n\tlet activity: () => void = () => {};\n\tconst firstActivity = new Promise<void>((done) => {\n\t\tactivity = done;\n\t});\n\n\tbinding.on(\"event\", () => {\n\t\tactivity();\n\t\t// drain through the ring so lapped events are counted as dropped, as a real harness would\n\t\tconst batch = binding.readEvents(cursor);\n\t\tevents += batch.events.length;\n\t\tfor (const event of batch.events) {\n\t\t\tif (!event.name.startsWith(\"abai.\") && !eventsEmitted.includes(event.name)) eventsEmitted.push(event.name);\n\t\t}\n\t\tdropped += batch.dropped;\n\t\tcursor = batch.next;\n\t});\n\tbinding.on(\"decision\", (request) => {\n\t\tactivity();\n\t\tdecisions += 1;\n\t\treached.decisions.add(request.name);\n\t\tbinding.answerDecision(request.id, null, `${REASON}: the default`);\n\t});\n\tbinding.on(\"turn\", (request) => {\n\t\tactivity();\n\t\tturns += 1;\n\t\treached.turns.add(request.name);\n\t\t// the exercise's calls run inside this turn when they are queued by now (section 5.3)\n\t\tbinding.endTurn(request.id);\n\t});\n\tbinding.on(\"touchpoint\", (request) => {\n\t\tactivity();\n\t\ttouchpoints += 1;\n\t\tbinding.endTouchpoint(request.id);\n\t});\n\n\tconst attempted = { knobs: new Set<string>(), capabilities: new Set<string>() };\n\tconst exercise = async (): Promise<void> => {\n\t\tif (form !== \"exercise\") return;\n\t\t// after the program's own startup code, so handlers registered after createAbaigram are in place\n\t\tawait Promise.race([new Promise<void>((done) => setImmediate(done)), firstActivity]);\n\t\tconst { manifest } = binding;\n\t\tconst work: Promise<void>[] = [];\n\t\tfor (const cell of manifest.cells) {\n\t\t\tif (cell.access !== \"readwrite\") continue;\n\t\t\tattempted.knobs.add(cell.name);\n\t\t\twork.push(\n\t\t\t\tbinding\n\t\t\t\t\t.writeKnob(cell.name, cell.default, `${REASON}: exercise writes the default`, \"auto\")\n\t\t\t\t\t.then((outcome) => {\n\t\t\t\t\t\tif (outcome.outcome === \"done\") knobsWritten.push(cell.name);\n\t\t\t\t\t\telse failed.push({ kind: \"knob\", name: cell.name, why: outcome.error ?? \"failed\" });\n\t\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tfor (const capability of manifest.capabilities) {\n\t\t\tif (capability.policy === \"forbid\") {\n\t\t\t\tfailed.push({ kind: \"capability\", name: capability.name, why: \"policy forbids it\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (capability.example === undefined) {\n\t\t\t\tfailed.push({ kind: \"capability\", name: capability.name, why: \"no example declared\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tattempted.capabilities.add(capability.name);\n\t\t\twork.push(\n\t\t\t\tbinding.call(capability.name, capability.example, `${REASON}: exercise calls the example`, \"auto\").then(\n\t\t\t\t\t() => {\n\t\t\t\t\t\tcapabilitiesCalled.push(capability.name);\n\t\t\t\t\t},\n\t\t\t\t\t(error) => {\n\t\t\t\t\t\tfailed.push({\n\t\t\t\t\t\t\tkind: \"capability\",\n\t\t\t\t\t\t\tname: capability.name,\n\t\t\t\t\t\t\twhy: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\t// concurrently, so one stalled delivery does not starve the rest\n\t\tawait Promise.allSettled(work);\n\t};\n\tconst exercised = exercise();\n\n\tconst report = (): NullHarnessReport => {\n\t\tconst unexercised = [...failed];\n\t\tif (form === \"exercise\") {\n\t\t\tfor (const cell of binding.manifest.cells) {\n\t\t\t\tif (\n\t\t\t\t\tcell.access !== \"readwrite\" ||\n\t\t\t\t\tknobsWritten.includes(cell.name) ||\n\t\t\t\t\tfailed.some((f) => f.kind === \"knob\" && f.name === cell.name)\n\t\t\t\t)\n\t\t\t\t\tcontinue;\n\t\t\t\tunexercised.push({\n\t\t\t\t\tkind: \"knob\",\n\t\t\t\t\tname: cell.name,\n\t\t\t\t\twhy: attempted.knobs.has(cell.name) ? \"still pending at exit\" : \"not attempted\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const capability of binding.manifest.capabilities) {\n\t\t\t\tif (\n\t\t\t\t\tcapabilitiesCalled.includes(capability.name) ||\n\t\t\t\t\tfailed.some((f) => f.kind === \"capability\" && f.name === capability.name)\n\t\t\t\t)\n\t\t\t\t\tcontinue;\n\t\t\t\tunexercised.push({\n\t\t\t\t\tkind: \"capability\",\n\t\t\t\t\tname: capability.name,\n\t\t\t\t\twhy: attempted.capabilities.has(capability.name) ? \"still pending at exit\" : \"not attempted\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const decision of binding.manifest.decisions) {\n\t\t\t\tif (!reached.decisions.has(decision.name))\n\t\t\t\t\tunexercised.push({ kind: \"decision\", name: decision.name, why: \"not reached\" });\n\t\t\t}\n\t\t\tfor (const turn of binding.manifest.turns) {\n\t\t\t\tif (!reached.turns.has(turn.name)) unexercised.push({ kind: \"turn\", name: turn.name, why: \"not reached\" });\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tform,\n\t\t\tevents,\n\t\t\tdropped,\n\t\t\tdecisions,\n\t\t\tturns,\n\t\t\ttouchpoints,\n\t\t\tcellsWritten: cellsWritten(),\n\t\t\teventsEmitted,\n\t\t\tknobsWritten,\n\t\t\tcapabilitiesCalled,\n\t\t\tunexercised,\n\t\t};\n\t};\n\n\treturn {\n\t\tform,\n\t\treport,\n\t\texercised,\n\t\tdetach: (reason) => binding.detach(reason),\n\t};\n}\n"]}
@@ -0,0 +1,162 @@
1
+ const REASON = "null harness";
2
+ export function attachNullHarness(binding, form) {
3
+ let events = 0;
4
+ let dropped = 0;
5
+ let decisions = 0;
6
+ let turns = 0;
7
+ let touchpoints = 0;
8
+ let cursor = 0;
9
+ const reached = { decisions: new Set(), turns: new Set() };
10
+ const knobsWritten = [];
11
+ const capabilitiesCalled = [];
12
+ const eventsEmitted = [];
13
+ const failed = [];
14
+ // the versions at attach: a cell whose version moved past them was written by the program, a knob's once
15
+ // more than the exercise's own write of it, so level 2's "a cell the program writes" is told from the
16
+ // harness's doing (section 12); one readCells() at attach and one at the report, none on a measured row
17
+ const versions = () => {
18
+ const seen = {};
19
+ for (const [name, reading] of Object.entries(binding.readCells().cells))
20
+ seen[name] = reading.version;
21
+ return seen;
22
+ };
23
+ const baseline = versions();
24
+ const cellsWritten = () => Object.entries(versions())
25
+ .filter(([name, version]) => version - (baseline[name] ?? 0) - (knobsWritten.includes(name) ? 1 : 0) > 0)
26
+ .map(([name]) => name);
27
+ // the exercise starts at the program's first activity, or on the next macrotask, whichever comes first:
28
+ // a program that never yields to the macrotask queue would otherwise finish before it began
29
+ let activity = () => { };
30
+ const firstActivity = new Promise((done) => {
31
+ activity = done;
32
+ });
33
+ binding.on("event", () => {
34
+ activity();
35
+ // drain through the ring so lapped events are counted as dropped, as a real harness would
36
+ const batch = binding.readEvents(cursor);
37
+ events += batch.events.length;
38
+ for (const event of batch.events) {
39
+ if (!event.name.startsWith("abai.") && !eventsEmitted.includes(event.name))
40
+ eventsEmitted.push(event.name);
41
+ }
42
+ dropped += batch.dropped;
43
+ cursor = batch.next;
44
+ });
45
+ binding.on("decision", (request) => {
46
+ activity();
47
+ decisions += 1;
48
+ reached.decisions.add(request.name);
49
+ binding.answerDecision(request.id, null, `${REASON}: the default`);
50
+ });
51
+ binding.on("turn", (request) => {
52
+ activity();
53
+ turns += 1;
54
+ reached.turns.add(request.name);
55
+ // the exercise's calls run inside this turn when they are queued by now (section 5.3)
56
+ binding.endTurn(request.id);
57
+ });
58
+ binding.on("touchpoint", (request) => {
59
+ activity();
60
+ touchpoints += 1;
61
+ binding.endTouchpoint(request.id);
62
+ });
63
+ const attempted = { knobs: new Set(), capabilities: new Set() };
64
+ const exercise = async () => {
65
+ if (form !== "exercise")
66
+ return;
67
+ // after the program's own startup code, so handlers registered after createAbaigram are in place
68
+ await Promise.race([new Promise((done) => setImmediate(done)), firstActivity]);
69
+ const { manifest } = binding;
70
+ const work = [];
71
+ for (const cell of manifest.cells) {
72
+ if (cell.access !== "readwrite")
73
+ continue;
74
+ attempted.knobs.add(cell.name);
75
+ work.push(binding
76
+ .writeKnob(cell.name, cell.default, `${REASON}: exercise writes the default`, "auto")
77
+ .then((outcome) => {
78
+ if (outcome.outcome === "done")
79
+ knobsWritten.push(cell.name);
80
+ else
81
+ failed.push({ kind: "knob", name: cell.name, why: outcome.error ?? "failed" });
82
+ }));
83
+ }
84
+ for (const capability of manifest.capabilities) {
85
+ if (capability.policy === "forbid") {
86
+ failed.push({ kind: "capability", name: capability.name, why: "policy forbids it" });
87
+ continue;
88
+ }
89
+ if (capability.example === undefined) {
90
+ failed.push({ kind: "capability", name: capability.name, why: "no example declared" });
91
+ continue;
92
+ }
93
+ attempted.capabilities.add(capability.name);
94
+ work.push(binding.call(capability.name, capability.example, `${REASON}: exercise calls the example`, "auto").then(() => {
95
+ capabilitiesCalled.push(capability.name);
96
+ }, (error) => {
97
+ failed.push({
98
+ kind: "capability",
99
+ name: capability.name,
100
+ why: error instanceof Error ? error.message : String(error),
101
+ });
102
+ }));
103
+ }
104
+ // concurrently, so one stalled delivery does not starve the rest
105
+ await Promise.allSettled(work);
106
+ };
107
+ const exercised = exercise();
108
+ const report = () => {
109
+ const unexercised = [...failed];
110
+ if (form === "exercise") {
111
+ for (const cell of binding.manifest.cells) {
112
+ if (cell.access !== "readwrite" ||
113
+ knobsWritten.includes(cell.name) ||
114
+ failed.some((f) => f.kind === "knob" && f.name === cell.name))
115
+ continue;
116
+ unexercised.push({
117
+ kind: "knob",
118
+ name: cell.name,
119
+ why: attempted.knobs.has(cell.name) ? "still pending at exit" : "not attempted",
120
+ });
121
+ }
122
+ for (const capability of binding.manifest.capabilities) {
123
+ if (capabilitiesCalled.includes(capability.name) ||
124
+ failed.some((f) => f.kind === "capability" && f.name === capability.name))
125
+ continue;
126
+ unexercised.push({
127
+ kind: "capability",
128
+ name: capability.name,
129
+ why: attempted.capabilities.has(capability.name) ? "still pending at exit" : "not attempted",
130
+ });
131
+ }
132
+ for (const decision of binding.manifest.decisions) {
133
+ if (!reached.decisions.has(decision.name))
134
+ unexercised.push({ kind: "decision", name: decision.name, why: "not reached" });
135
+ }
136
+ for (const turn of binding.manifest.turns) {
137
+ if (!reached.turns.has(turn.name))
138
+ unexercised.push({ kind: "turn", name: turn.name, why: "not reached" });
139
+ }
140
+ }
141
+ return {
142
+ form,
143
+ events,
144
+ dropped,
145
+ decisions,
146
+ turns,
147
+ touchpoints,
148
+ cellsWritten: cellsWritten(),
149
+ eventsEmitted,
150
+ knobsWritten,
151
+ capabilitiesCalled,
152
+ unexercised,
153
+ };
154
+ };
155
+ return {
156
+ form,
157
+ report,
158
+ exercised,
159
+ detach: (reason) => binding.detach(reason),
160
+ };
161
+ }
162
+ //# sourceMappingURL=null-harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"null-harness.js","sourceRoot":"","sources":["../src/null-harness.ts"],"names":[],"mappings":"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","sourcesContent":["// The null harness (abaigrammar section 2): a harness that attaches, drains\n// every channel, answers every decision with its default, and calls no\n// model. Passive (`ABAI_HARNESS=null`) it touches no control and exists to\n// measure overhead. Active (`ABAI_HARNESS=exercise`) it writes each knob's\n// default, calls each capability with its example, and reports what went\n// unexercised, so conformance (section 12) is demonstrable without a model.\n// It lives in the stub so a check run needs no harness package installed.\nimport type { HarnessBinding } from \"./harness-binding.ts\";\n\nexport type NullForm = \"null\" | \"exercise\";\n\nexport interface NullHarnessReport {\n\tform: NullForm;\n\tevents: number;\n\tdropped: number;\n\tdecisions: number;\n\tturns: number;\n\ttouchpoints: number;\n\t/** Cells the program wrote while the harness was attached: a cell whose version moved, a knob's beyond the harness's own write. */\n\tcellsWritten: string[];\n\t/** The program's own events emitted while attached, by name, each once; the library's `abai.*` are not the program's. */\n\teventsEmitted: string[];\n\t/** The active form only. */\n\tknobsWritten: string[];\n\tcapabilitiesCalled: string[];\n\t/** Declarations the run did not exercise, with the reason. */\n\tunexercised: Array<{ kind: \"capability\" | \"decision\" | \"turn\" | \"knob\"; name: string; why: string }>;\n}\n\nexport interface NullHarness {\n\treadonly form: NullForm;\n\treport(): NullHarnessReport;\n\t/** The active form's exercise, once it has run (resolves at once for the passive form). */\n\treadonly exercised: Promise<void>;\n\tdetach(reason: string): void;\n}\n\nconst REASON = \"null harness\";\n\nexport function attachNullHarness(binding: HarnessBinding, form: NullForm): NullHarness {\n\tlet events = 0;\n\tlet dropped = 0;\n\tlet decisions = 0;\n\tlet turns = 0;\n\tlet touchpoints = 0;\n\tlet cursor = 0;\n\tconst reached = { decisions: new Set<string>(), turns: new Set<string>() };\n\tconst knobsWritten: string[] = [];\n\tconst capabilitiesCalled: string[] = [];\n\tconst eventsEmitted: string[] = [];\n\tconst failed: NullHarnessReport[\"unexercised\"] = [];\n\t// the versions at attach: a cell whose version moved past them was written by the program, a knob's once\n\t// more than the exercise's own write of it, so level 2's \"a cell the program writes\" is told from the\n\t// harness's doing (section 12); one readCells() at attach and one at the report, none on a measured row\n\tconst versions = (): Record<string, number> => {\n\t\tconst seen: Record<string, number> = {};\n\t\tfor (const [name, reading] of Object.entries(binding.readCells().cells)) seen[name] = reading.version;\n\t\treturn seen;\n\t};\n\tconst baseline = versions();\n\tconst cellsWritten = (): string[] =>\n\t\tObject.entries(versions())\n\t\t\t.filter(([name, version]) => version - (baseline[name] ?? 0) - (knobsWritten.includes(name) ? 1 : 0) > 0)\n\t\t\t.map(([name]) => name);\n\n\t// the exercise starts at the program's first activity, or on the next macrotask, whichever comes first:\n\t// a program that never yields to the macrotask queue would otherwise finish before it began\n\tlet activity: () => void = () => {};\n\tconst firstActivity = new Promise<void>((done) => {\n\t\tactivity = done;\n\t});\n\n\tbinding.on(\"event\", () => {\n\t\tactivity();\n\t\t// drain through the ring so lapped events are counted as dropped, as a real harness would\n\t\tconst batch = binding.readEvents(cursor);\n\t\tevents += batch.events.length;\n\t\tfor (const event of batch.events) {\n\t\t\tif (!event.name.startsWith(\"abai.\") && !eventsEmitted.includes(event.name)) eventsEmitted.push(event.name);\n\t\t}\n\t\tdropped += batch.dropped;\n\t\tcursor = batch.next;\n\t});\n\tbinding.on(\"decision\", (request) => {\n\t\tactivity();\n\t\tdecisions += 1;\n\t\treached.decisions.add(request.name);\n\t\tbinding.answerDecision(request.id, null, `${REASON}: the default`);\n\t});\n\tbinding.on(\"turn\", (request) => {\n\t\tactivity();\n\t\tturns += 1;\n\t\treached.turns.add(request.name);\n\t\t// the exercise's calls run inside this turn when they are queued by now (section 5.3)\n\t\tbinding.endTurn(request.id);\n\t});\n\tbinding.on(\"touchpoint\", (request) => {\n\t\tactivity();\n\t\ttouchpoints += 1;\n\t\tbinding.endTouchpoint(request.id);\n\t});\n\n\tconst attempted = { knobs: new Set<string>(), capabilities: new Set<string>() };\n\tconst exercise = async (): Promise<void> => {\n\t\tif (form !== \"exercise\") return;\n\t\t// after the program's own startup code, so handlers registered after createAbaigram are in place\n\t\tawait Promise.race([new Promise<void>((done) => setImmediate(done)), firstActivity]);\n\t\tconst { manifest } = binding;\n\t\tconst work: Promise<void>[] = [];\n\t\tfor (const cell of manifest.cells) {\n\t\t\tif (cell.access !== \"readwrite\") continue;\n\t\t\tattempted.knobs.add(cell.name);\n\t\t\twork.push(\n\t\t\t\tbinding\n\t\t\t\t\t.writeKnob(cell.name, cell.default, `${REASON}: exercise writes the default`, \"auto\")\n\t\t\t\t\t.then((outcome) => {\n\t\t\t\t\t\tif (outcome.outcome === \"done\") knobsWritten.push(cell.name);\n\t\t\t\t\t\telse failed.push({ kind: \"knob\", name: cell.name, why: outcome.error ?? \"failed\" });\n\t\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tfor (const capability of manifest.capabilities) {\n\t\t\tif (capability.policy === \"forbid\") {\n\t\t\t\tfailed.push({ kind: \"capability\", name: capability.name, why: \"policy forbids it\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (capability.example === undefined) {\n\t\t\t\tfailed.push({ kind: \"capability\", name: capability.name, why: \"no example declared\" });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tattempted.capabilities.add(capability.name);\n\t\t\twork.push(\n\t\t\t\tbinding.call(capability.name, capability.example, `${REASON}: exercise calls the example`, \"auto\").then(\n\t\t\t\t\t() => {\n\t\t\t\t\t\tcapabilitiesCalled.push(capability.name);\n\t\t\t\t\t},\n\t\t\t\t\t(error) => {\n\t\t\t\t\t\tfailed.push({\n\t\t\t\t\t\t\tkind: \"capability\",\n\t\t\t\t\t\t\tname: capability.name,\n\t\t\t\t\t\t\twhy: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\t// concurrently, so one stalled delivery does not starve the rest\n\t\tawait Promise.allSettled(work);\n\t};\n\tconst exercised = exercise();\n\n\tconst report = (): NullHarnessReport => {\n\t\tconst unexercised = [...failed];\n\t\tif (form === \"exercise\") {\n\t\t\tfor (const cell of binding.manifest.cells) {\n\t\t\t\tif (\n\t\t\t\t\tcell.access !== \"readwrite\" ||\n\t\t\t\t\tknobsWritten.includes(cell.name) ||\n\t\t\t\t\tfailed.some((f) => f.kind === \"knob\" && f.name === cell.name)\n\t\t\t\t)\n\t\t\t\t\tcontinue;\n\t\t\t\tunexercised.push({\n\t\t\t\t\tkind: \"knob\",\n\t\t\t\t\tname: cell.name,\n\t\t\t\t\twhy: attempted.knobs.has(cell.name) ? \"still pending at exit\" : \"not attempted\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const capability of binding.manifest.capabilities) {\n\t\t\t\tif (\n\t\t\t\t\tcapabilitiesCalled.includes(capability.name) ||\n\t\t\t\t\tfailed.some((f) => f.kind === \"capability\" && f.name === capability.name)\n\t\t\t\t)\n\t\t\t\t\tcontinue;\n\t\t\t\tunexercised.push({\n\t\t\t\t\tkind: \"capability\",\n\t\t\t\t\tname: capability.name,\n\t\t\t\t\twhy: attempted.capabilities.has(capability.name) ? \"still pending at exit\" : \"not attempted\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const decision of binding.manifest.decisions) {\n\t\t\t\tif (!reached.decisions.has(decision.name))\n\t\t\t\t\tunexercised.push({ kind: \"decision\", name: decision.name, why: \"not reached\" });\n\t\t\t}\n\t\t\tfor (const turn of binding.manifest.turns) {\n\t\t\t\tif (!reached.turns.has(turn.name)) unexercised.push({ kind: \"turn\", name: turn.name, why: \"not reached\" });\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tform,\n\t\t\tevents,\n\t\t\tdropped,\n\t\t\tdecisions,\n\t\t\tturns,\n\t\t\ttouchpoints,\n\t\t\tcellsWritten: cellsWritten(),\n\t\t\teventsEmitted,\n\t\t\tknobsWritten,\n\t\t\tcapabilitiesCalled,\n\t\t\tunexercised,\n\t\t};\n\t};\n\n\treturn {\n\t\tform,\n\t\treport,\n\t\texercised,\n\t\tdetach: (reason) => binding.detach(reason),\n\t};\n}\n"]}
@@ -0,0 +1,250 @@
1
+ import { type ContentSlots } from "./content.ts";
2
+ import { Endpoint } from "./endpoint.ts";
3
+ import { type HarnessSelection } from "./env.ts";
4
+ import { DirectBinding, type HarnessBinding } from "./harness-binding.ts";
5
+ import { type DecisionDecl, type Manifest, type Mode, type TurnDecl } from "./manifest/manifest.ts";
6
+ import { type NullHarness } from "./null-harness.ts";
7
+ import { Region } from "./region.ts";
8
+ import { type AbaigramOptions, type CapabilityHandler, type KnobHandler, type StopHandler } from "./registry.ts";
9
+ /** Thrown by `decide` when the applied default is `abort` (section 5.4). */
10
+ export declare class AbaigramAbort extends Error {
11
+ readonly decision: string;
12
+ constructor(decision: string, cause: string);
13
+ }
14
+ export interface CellHandle<T = unknown> {
15
+ readonly name: string;
16
+ get(): T;
17
+ /** One store to the value and one to the version. Enum values must be declared; nothing else is checked. */
18
+ set(value: T): void;
19
+ }
20
+ export interface KnobHandle<T = unknown> extends CellHandle<T> {
21
+ /** Back to the declared default; a sized knob's reset reaches the harness as `abai.knob.reset`. */
22
+ reset(): void;
23
+ /** Sized knobs only: the handler that receives a delivered value at a safepoint. */
24
+ onChange(handler: KnobHandler): void;
25
+ }
26
+ export interface EventHandle {
27
+ readonly name: string;
28
+ /** One load of the attached flag when no harness is attached. */
29
+ emit(payload?: Record<string, unknown>): void;
30
+ }
31
+ export interface CounterSnapshot {
32
+ [name: string]: number;
33
+ }
34
+ /** What a harness binding supplies once attached; undefined while detached. */
35
+ export interface AttachedHarness {
36
+ /** Ask the harness; resolves to the answer, or rejects with the cause of a default. */
37
+ decide(decision: DecisionDecl, context: Record<string, unknown>, deadlineMs: number): Promise<unknown>;
38
+ /** Give the harness a turn; resolves when it is done or the timeout passes. */
39
+ turn(turn: TurnDecl, deadlineMs: number): Promise<void>;
40
+ /** An event was published; `seq` is its sequence number. */
41
+ emitted(seq: number): void;
42
+ /** The program detaches it. */
43
+ detach(reason: string): void;
44
+ }
45
+ /** Attaches an in-process harness to the binding the stub hands it. */
46
+ export type HarnessFactory = (binding: HarnessBinding) => void | Promise<void>;
47
+ export interface AbaigramExtras {
48
+ /** The program's root, where abaigram.yaml and the content live. Default: found from the cwd on first use, else the cwd. */
49
+ root?: string;
50
+ env?: NodeJS.ProcessEnv;
51
+ /** Injectable clock. */
52
+ now?: () => number;
53
+ /** The in-process harness for turns mode; overrides the dynamic import of the harness package. */
54
+ harness?: HarnessFactory;
55
+ /** Where the audit log goes; default: the manifest's path. */
56
+ auditPath?: string;
57
+ /** For the exercise form: where its report goes at exit. Default: stderr. */
58
+ report?: (text: string) => void;
59
+ }
60
+ export declare class Abaigram {
61
+ readonly manifest: Manifest;
62
+ readonly mode: Mode;
63
+ readonly modeFallback: boolean;
64
+ readonly harnessSelection: HarnessSelection;
65
+ /** A problem with the environment's harness selection, reported by doctor, never thrown. */
66
+ readonly harnessSelectionError: string | undefined;
67
+ readonly content: ContentSlots;
68
+ /**
69
+ * The sidecar endpoint, listening from startup when the selected mode is
70
+ * sidecar and a real harness is wanted (section 6.3). A check run under
71
+ * `ABAI_HARNESS` keeps its null harness in-process and opens no socket.
72
+ */
73
+ readonly endpoint: Endpoint | undefined;
74
+ /** A launcher handed this program its connection (section 6.3): the full lifecycle is available to it. */
75
+ readonly launched: boolean;
76
+ readonly startedAt: string;
77
+ /** The root given at construction, if any; otherwise the root is found lazily. */
78
+ readonly explicitRoot: string | undefined;
79
+ /** @internal The region; tests and the binding reach it, programs do not. */
80
+ readonly region: Region;
81
+ readonly now: () => number;
82
+ private readonly cellEntries;
83
+ private readonly eventEntries;
84
+ private readonly libraryEvents;
85
+ private readonly cellHandles;
86
+ private readonly eventHandles;
87
+ private readonly knobHandlers;
88
+ private readonly capabilityHandlers;
89
+ private readonly decisionIndex;
90
+ private readonly turnIndex;
91
+ private readonly queue;
92
+ private harness;
93
+ private binding;
94
+ private nullHarness;
95
+ private lastAttachError;
96
+ private readonly harnessFactory;
97
+ private readonly auditPathOverride;
98
+ private readonly report;
99
+ private readonly env;
100
+ readonly stopHandler: StopHandler | undefined;
101
+ /** Who waits for a cooperative pause to lift, and the ref'd timer that keeps a paused program alive. */
102
+ private resumeWaiters;
103
+ private pauseKeepAlive;
104
+ /** Under launch: the launcher connected at least once, so nothing waits for it a second time. */
105
+ private launcherSeen;
106
+ constructor(options: AbaigramOptions, extras?: AbaigramExtras);
107
+ get name(): string;
108
+ get attached(): boolean;
109
+ /** Why the last attach did not happen, if it did not. */
110
+ get attachError(): string | undefined;
111
+ /** The typed store and load of one cell, built once so the hot path is the store alone. */
112
+ private makeEntry;
113
+ private entry;
114
+ /** A handle, created once per cell and cached. */
115
+ cell<T = unknown>(name: string): CellHandle<T>;
116
+ /** A knob handle; the cell must be `readwrite`. */
117
+ knob<T = unknown>(name: string): KnobHandle<T>;
118
+ /** @internal The handler a sized knob delivers to, for the binding. */
119
+ knobHandler(name: string): KnobHandler | undefined;
120
+ /** @internal Every cell's current value, keyed by name, for the binding. */
121
+ readCells(): Record<string, unknown>;
122
+ /** @internal A cell's version counter. */
123
+ version(name: string): number;
124
+ /** A cooperative pause is in effect. */
125
+ get paused(): boolean;
126
+ /**
127
+ * @internal A cooperative pause: the flag is set, and the program is held
128
+ * at its next touchpoint (this instantiation's deviation: a safepoint
129
+ * cannot block the loop the resume arrives on). A ref'd timer keeps a
130
+ * paused program alive, since nothing else of its own may be pending.
131
+ */
132
+ requestPause(): void;
133
+ /** @internal Lift the pause: whoever is held at a touchpoint goes on, their clocks starting now. */
134
+ resumeFromPause(): void;
135
+ /** Hold here while a cooperative pause is in effect; the agent is told what the pause holds (4.3). */
136
+ private awaitResume;
137
+ /** @internal What the stub knows of its own process, for `lifecycle status`. */
138
+ lifecycleStatus(): {
139
+ pid: number;
140
+ name: string;
141
+ mode: string;
142
+ cwd: string;
143
+ startedAt: string;
144
+ connections: number;
145
+ paused: boolean;
146
+ launched: boolean;
147
+ };
148
+ /** A handle for a declared event, created once and cached; the name is checked here, never on the hot path. */
149
+ event(name: string): EventHandle;
150
+ /** Copy the declared fields, cut sized ones to their max, publish, and tell the harness. */
151
+ private publish;
152
+ /** @internal A library event under the reserved prefix. */
153
+ emitLibrary(name: string, payload: Record<string, unknown>): void;
154
+ /** @internal The declared event for a ring slot's ordinal, or the library event's name. */
155
+ eventName(ordinal: number): string;
156
+ /** Register the handler of a declared capability (or replace one given at declaration). */
157
+ capability(name: string, handler: CapabilityHandler): void;
158
+ /** @internal */
159
+ capabilityHandler(name: string): CapabilityHandler | undefined;
160
+ /** @internal Queue work for the next safepoint; the binding uses it for safepoint dispatch. */
161
+ enqueue(work: () => void): void;
162
+ /** One load of a flag when nothing is queued and no pause is in effect. */
163
+ safepoint(): void;
164
+ /** @internal Remove queued work that will not run (a timed-out dispatch). */
165
+ dequeue(work: () => void): void;
166
+ /** @internal Run everything queued, now, on this thread: a touchpoint holds the program at a safepoint. */
167
+ drain(): void;
168
+ private applyDefault;
169
+ /** Ask which way to go. With no harness attached the default applies at once and is counted. */
170
+ decide<T = unknown>(name: string, context?: Record<string, unknown>): Promise<T>;
171
+ /** Give the agent a turn. With no harness attached it returns at once and is counted. */
172
+ turn(name: string): Promise<void>;
173
+ /** The counters, by kind and name: defaulted decisions, skipped turns, and the mode fallback. */
174
+ counters(): CounterSnapshot;
175
+ /** @internal Read and reset the counters, for the harness that attaches. */
176
+ takeCounters(): CounterSnapshot;
177
+ /** @internal The binding attaches a harness. */
178
+ setHarness(harness: AttachedHarness): void;
179
+ /** @internal The binding detaches it; a decision in flight takes its default with `reason`. */
180
+ clearHarness(reason: string): void;
181
+ /** The direct binding, while an in-process harness is attached. */
182
+ get directBinding(): HarnessBinding | undefined;
183
+ /** The null harness, while one is attached. */
184
+ get attachedNullHarness(): NullHarness | undefined;
185
+ private createBinding;
186
+ /**
187
+ * @internal A front end of the library's own (the sidecar endpoint)
188
+ * attaches the program through a binding it then drives; the connection's
189
+ * id labels its acts in the audit. Refused while something else is
190
+ * attached: a second harness would take the program over silently.
191
+ */
192
+ bind(connection: string): DirectBinding;
193
+ private attachNull;
194
+ /**
195
+ * Connect or spawn a harness (one of the five places). Resolves true when
196
+ * attached. The real harness arrives through the `harness` extra, or the
197
+ * harness package's dynamic import once it exists; until then the reason
198
+ * is recorded.
199
+ */
200
+ attach(): Promise<boolean>;
201
+ private awaitLauncher;
202
+ private attachSidecar;
203
+ private spawnHarness;
204
+ /**
205
+ * @internal `<prog> abai mcp` under sidecar: the binary's MCP server is
206
+ * spawned against this program's endpoint with the terminal inherited, so
207
+ * the MCP client on this process's stdin and stdout talks to it, and the
208
+ * program waits at its entry, a safepoint it holds open, until the client
209
+ * goes. Resolves to the child's exit code; -1 when nothing could be
210
+ * spawned (the reason is in `attachError`).
211
+ */
212
+ serveMcpSidecar(endpoint: Endpoint): Promise<number>;
213
+ private touchpoint;
214
+ /** Run the install touchpoint; returns when the harness finishes, or at once when none can be attached. */
215
+ install(): Promise<void>;
216
+ /** Open a session; returns when the harness ends it, or at once when none can be attached. */
217
+ session(): Promise<void>;
218
+ }
219
+ /** Overrides where the in-process harness is imported from (a path or a package name). */
220
+ export declare const ENV_HARNESS_MODULE = "ABAI_HARNESS_MODULE";
221
+ /**
222
+ * The harness binary to spawn under sidecar: `ABAI_HARNESS_BIN` (a script
223
+ * path runs under this very node), else the program's own
224
+ * `node_modules/.bin/abai-embedded-harness`, else the name on PATH.
225
+ */
226
+ export declare function harnessBinary(env: NodeJS.ProcessEnv, root: string): {
227
+ command: string;
228
+ args: string[];
229
+ };
230
+ /** What the stub uses of the installed harness package. */
231
+ export interface HarnessModule {
232
+ attachEmbedded?: (binding: HarnessBinding) => Promise<unknown>;
233
+ /** `<prog> abai mcp` in-process: serve this binding to the MCP client on the process's stdio; resolves to the exit code. */
234
+ serveMcpEmbedded?: (binding: HarnessBinding) => Promise<number>;
235
+ runConfig?: (args: string[], io: {
236
+ out(text: string): void;
237
+ err(text: string): void;
238
+ env: NodeJS.ProcessEnv;
239
+ }) => Promise<number>;
240
+ doctorOnline?: (manifest: Manifest, env: NodeJS.ProcessEnv) => Promise<Array<{
241
+ name: string;
242
+ status: "ok" | "fail" | "skip";
243
+ detail: string;
244
+ }>>;
245
+ }
246
+ /** The harness package, found by dynamic import when a program installed it; undefined when it does not resolve. */
247
+ export declare function importHarnessModule(env: NodeJS.ProcessEnv, manifest: Manifest): Promise<HarnessModule | undefined>;
248
+ /** The program's one call: declarations in, the abaigram out. Throws when a declaration does not validate. */
249
+ export declare function createAbaigram(options: AbaigramOptions, extras?: AbaigramExtras): Abaigram;
250
+ //# sourceMappingURL=program.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"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","sourcesContent":["// createAbaigram: the program-facing API of the stub (abaigrammar sections 4\n// and 5). The program declares its manifest in code, obtains cell, knob and\n// event handles once, and then writes cells, emits events, serves\n// capabilities and calls touchpoints. With no harness attached, every\n// operation here costs what section 11.1 says: cell writes store, emits and\n// safepoints load one flag, decisions and turns load the flag and count.\n//\n// Touchpoints are awaited promises (see the instantiation's deviations in\n// registry.ts). The attached paths arrive with the harness binding; this\n// file carries the detached behaviour and the seams the binding fills.\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fitValue } from \"./cells/fit.ts\";\nimport { type ContentSlots, createContent, findRoot } from \"./content.ts\";\nimport { Endpoint } from \"./endpoint.ts\";\nimport {\n\tattachWaitMsFrom,\n\tENV_HARNESS,\n\tENV_HARNESS_BIN,\n\tENV_LAUNCH_SOCKET,\n\ttype HarnessSelection,\n\trunDirFrom,\n\tselectHarness,\n\tselectMode,\n} from \"./env.ts\";\nimport { DirectBinding, type HarnessBinding, type TouchpointKind } from \"./harness-binding.ts\";\nimport {\n\ttype CellDecl,\n\tcutUtf8,\n\ttype DecisionDecl,\n\tdurationMs,\n\ttype EventDecl,\n\tencodedBytes,\n\tfieldOf,\n\tLIBRARY_EVENTS,\n\ttype Manifest,\n\ttype Mode,\n\ttype TurnDecl,\n} from \"./manifest/manifest.ts\";\nimport { attachNullHarness, type NullHarness } from \"./null-harness.ts\";\nimport { FLAG_ATTACHED, FLAG_PAUSED, FLAG_QUEUE, FLAG_WORK, LIBRARY_EVENT_BASE, Region } from \"./region.ts\";\nimport {\n\ttype AbaigramOptions,\n\tbuildManifest,\n\ttype CapabilityHandler,\n\ttype KnobHandler,\n\ttype StopHandler,\n} from \"./registry.ts\";\n\n/** Thrown by `decide` when the applied default is `abort` (section 5.4). */\nexport class AbaigramAbort extends Error {\n\treadonly decision: string;\n\tconstructor(decision: string, cause: string) {\n\t\tsuper(`decision ${decision} applied its default, abort (${cause})`);\n\t\tthis.name = \"AbaigramAbort\";\n\t\tthis.decision = decision;\n\t}\n}\n\nexport interface CellHandle<T = unknown> {\n\treadonly name: string;\n\tget(): T;\n\t/** One store to the value and one to the version. Enum values must be declared; nothing else is checked. */\n\tset(value: T): void;\n}\n\nexport interface KnobHandle<T = unknown> extends CellHandle<T> {\n\t/** Back to the declared default; a sized knob's reset reaches the harness as `abai.knob.reset`. */\n\treset(): void;\n\t/** Sized knobs only: the handler that receives a delivered value at a safepoint. */\n\tonChange(handler: KnobHandler): void;\n}\n\nexport interface EventHandle {\n\treadonly name: string;\n\t/** One load of the attached flag when no harness is attached. */\n\temit(payload?: Record<string, unknown>): void;\n}\n\nexport interface CounterSnapshot {\n\t[name: string]: number;\n}\n\n/** What a harness binding supplies once attached; undefined while detached. */\nexport interface AttachedHarness {\n\t/** Ask the harness; resolves to the answer, or rejects with the cause of a default. */\n\tdecide(decision: DecisionDecl, context: Record<string, unknown>, deadlineMs: number): Promise<unknown>;\n\t/** Give the harness a turn; resolves when it is done or the timeout passes. */\n\tturn(turn: TurnDecl, deadlineMs: number): Promise<void>;\n\t/** An event was published; `seq` is its sequence number. */\n\temitted(seq: number): void;\n\t/** The program detaches it. */\n\tdetach(reason: string): void;\n}\n\ninterface CellEntry {\n\tdecl: CellDecl;\n\t/** Position in `manifest.cells`: the index into `versions`. */\n\tordinal: number;\n\t/** Index into `values` (scalar) or `sized`. */\n\tslot: number;\n\tscalar: boolean;\n\tenumIndex?: Map<string, number>;\n\t/** Built once per cell: the typed store and load (section 11.2 rows). */\n\tset: (value: unknown) => void;\n\tget: () => unknown;\n}\n\ninterface EventEntry {\n\tdecl: EventDecl;\n\tordinal: number;\n\tfields: Array<{ key: string; type: string; max: number | undefined }>;\n}\n\n/** Attaches an in-process harness to the binding the stub hands it. */\nexport type HarnessFactory = (binding: HarnessBinding) => void | Promise<void>;\n\nexport interface AbaigramExtras {\n\t/** The program's root, where abaigram.yaml and the content live. Default: found from the cwd on first use, else the cwd. */\n\troot?: string;\n\tenv?: NodeJS.ProcessEnv;\n\t/** Injectable clock. */\n\tnow?: () => number;\n\t/** The in-process harness for turns mode; overrides the dynamic import of the harness package. */\n\tharness?: HarnessFactory;\n\t/** Where the audit log goes; default: the manifest's path. */\n\tauditPath?: string;\n\t/** For the exercise form: where its report goes at exit. Default: stderr. */\n\treport?: (text: string) => void;\n}\n\nexport class Abaigram {\n\treadonly manifest: Manifest;\n\treadonly mode: Mode;\n\treadonly modeFallback: boolean;\n\treadonly harnessSelection: HarnessSelection;\n\t/** A problem with the environment's harness selection, reported by doctor, never thrown. */\n\treadonly harnessSelectionError: string | undefined;\n\treadonly content: ContentSlots;\n\t/**\n\t * The sidecar endpoint, listening from startup when the selected mode is\n\t * sidecar and a real harness is wanted (section 6.3). A check run under\n\t * `ABAI_HARNESS` keeps its null harness in-process and opens no socket.\n\t */\n\treadonly endpoint: Endpoint | undefined;\n\t/** A launcher handed this program its connection (section 6.3): the full lifecycle is available to it. */\n\treadonly launched: boolean = false;\n\treadonly startedAt: string = new Date().toISOString();\n\t/** The root given at construction, if any; otherwise the root is found lazily. */\n\treadonly explicitRoot: string | undefined;\n\t/** @internal The region; tests and the binding reach it, programs do not. */\n\treadonly region: Region;\n\treadonly now: () => number;\n\n\tprivate readonly cellEntries = new Map<string, CellEntry>();\n\tprivate readonly eventEntries = new Map<string, EventEntry>();\n\tprivate readonly libraryEvents = new Map<string, number>();\n\tprivate readonly cellHandles = new Map<string, CellHandle>();\n\tprivate readonly eventHandles = new Map<string, EventHandle>();\n\tprivate readonly knobHandlers = new Map<string, KnobHandler>();\n\tprivate readonly capabilityHandlers = new Map<string, CapabilityHandler>();\n\tprivate readonly decisionIndex = new Map<string, number>();\n\tprivate readonly turnIndex = new Map<string, number>();\n\tprivate readonly queue: Array<() => void> = [];\n\tprivate harness: AttachedHarness | undefined;\n\tprivate binding: DirectBinding | undefined;\n\tprivate nullHarness: NullHarness | undefined;\n\tprivate lastAttachError: string | undefined;\n\tprivate readonly harnessFactory: HarnessFactory | undefined;\n\tprivate readonly auditPathOverride: string | undefined;\n\tprivate readonly report: (text: string) => void;\n\tprivate readonly env: NodeJS.ProcessEnv;\n\treadonly stopHandler: StopHandler | undefined;\n\t/** Who waits for a cooperative pause to lift, and the ref'd timer that keeps a paused program alive. */\n\tprivate resumeWaiters: Array<() => void> = [];\n\tprivate pauseKeepAlive: NodeJS.Timeout | undefined;\n\t/** Under launch: the launcher connected at least once, so nothing waits for it a second time. */\n\tprivate launcherSeen = false;\n\n\tconstructor(options: AbaigramOptions, extras: AbaigramExtras = {}) {\n\t\tthis.manifest = buildManifest(options);\n\t\tthis.now = extras.now ?? Date.now;\n\t\tconst env = extras.env ?? process.env;\n\t\tthis.env = env;\n\t\tconst selection = selectMode(this.manifest.modes, env);\n\t\tthis.mode = selection.mode;\n\t\tthis.modeFallback = selection.fallback;\n\t\tconst harness = selectHarness(env);\n\t\tthis.harnessSelection = harness.selection;\n\t\tthis.harnessSelectionError = harness.error;\n\t\tthis.stopHandler = options.lifecycle?.stop?.handler;\n\t\tthis.explicitRoot = extras.root;\n\t\tthis.harnessFactory = extras.harness;\n\t\tthis.auditPathOverride = extras.auditPath;\n\t\tthis.report =\n\t\t\textras.report ??\n\t\t\t((text) => {\n\t\t\t\tprocess.stderr.write(`${text}\\n`);\n\t\t\t});\n\n\t\tlet scalars = 0;\n\t\tlet sized = 0;\n\t\tconst pending: Array<[CellDecl, number, number, boolean]> = [];\n\t\tthis.manifest.cells.forEach((decl, ordinal) => {\n\t\t\tconst scalar = decl.type !== \"string\" && decl.type !== \"object\" && decl.type !== \"list\";\n\t\t\tpending.push([decl, ordinal, scalar ? scalars++ : sized++, scalar]);\n\t\t});\n\t\tthis.manifest.decisions.forEach((decl, i) => {\n\t\t\tthis.decisionIndex.set(decl.name, i);\n\t\t});\n\t\tthis.manifest.turns.forEach((decl, i) => {\n\t\t\tthis.turnIndex.set(decl.name, i);\n\t\t});\n\t\tthis.region = new Region({\n\t\t\tscalars,\n\t\t\tsized,\n\t\t\tcounters: this.manifest.decisions.length + this.manifest.turns.length + 1,\n\t\t});\n\t\tif (this.modeFallback) this.region.counters[this.region.counters.length - 1] += 1;\n\t\tfor (const [decl, ordinal, slot, scalar] of pending)\n\t\t\tthis.cellEntries.set(decl.name, this.makeEntry(decl, ordinal, slot, scalar));\n\n\t\tthis.manifest.events.forEach((decl, ordinal) => {\n\t\t\tconst fields = Object.entries(decl.payload).map(([key, field]) => {\n\t\t\t\tconst f = fieldOf(field);\n\t\t\t\treturn { key, type: f.type, max: f.max };\n\t\t\t});\n\t\t\tthis.eventEntries.set(decl.name, { decl, ordinal, fields });\n\t\t});\n\t\tLIBRARY_EVENTS.forEach((name, i) => {\n\t\t\tthis.libraryEvents.set(name, LIBRARY_EVENT_BASE - i);\n\t\t});\n\n\t\tfor (const [name, cell] of Object.entries(options.cells ?? {})) {\n\t\t\tif (cell.access === \"readwrite\") {\n\t\t\t\tthis.cellEntries.get(name)?.set(cell.default);\n\t\t\t\tif (cell.onChange) this.knobHandlers.set(name, cell.onChange);\n\t\t\t}\n\t\t}\n\t\tfor (const [name, capability] of Object.entries(options.capabilities ?? {})) {\n\t\t\tif (capability.handler) this.capabilityHandlers.set(name, capability.handler);\n\t\t}\n\t\t// the root is found on first use, so a program that never attaches never touches the filesystem\n\t\tthis.content = createContent(this.manifest, () => extras.root ?? findRoot(this.manifest.name) ?? process.cwd());\n\t\t// startup is one of the five places (section 4.3): a null harness attaches here\n\t\tif (this.harnessSelection === \"null\" || this.harnessSelection === \"exercise\")\n\t\t\tthis.attachNull(this.harnessSelection);\n\t\t// sidecar: the receiving end waits from startup (section 6.3); its failure to listen is a reason\n\t\t// the next attach reports, never a throw into the program\n\t\tconst launchSocket = env[ENV_LAUNCH_SOCKET]?.trim();\n\t\tif (this.mode === \"sidecar\" && this.harnessSelection === \"real\") {\n\t\t\t// launched: the parent chose the socket and is waiting on it. Read once and removed, so a program\n\t\t\t// that spawns a copy of itself never hands it this very socket\n\t\t\tthis.launched = launchSocket !== undefined && launchSocket !== \"\";\n\t\t\tif (this.launched && env === process.env) delete process.env[ENV_LAUNCH_SOCKET];\n\t\t\tthis.endpoint = new Endpoint(this, {\n\t\t\t\trunDir: this.launched ? dirname(launchSocket as string) : runDirFrom(env),\n\t\t\t\t...(this.launched ? { socketPath: launchSocket as string } : {}),\n\t\t\t});\n\t\t\tthis.endpoint.ready.catch((error: unknown) => {\n\t\t\t\tthis.lastAttachError = `the endpoint could not listen: ${error instanceof Error ? error.message : String(error)}`;\n\t\t\t});\n\t\t} else if (launchSocket !== undefined && launchSocket !== \"\") {\n\t\t\t// a launcher is waiting, but this program cannot take the connection up: it runs as itself, observed\n\t\t\t// from outside only (section 5.7), and says why when asked\n\t\t\tif (env === process.env) delete process.env[ENV_LAUNCH_SOCKET];\n\t\t\tthis.lastAttachError =\n\t\t\t\tthis.harnessSelection === \"real\"\n\t\t\t\t\t? `launched, but ${this.manifest.name} declares no sidecar mode (declared: ${this.manifest.modes.join(\", \")})`\n\t\t\t\t\t: `launched under ${ENV_HARNESS}=${this.harnessSelection}`;\n\t\t}\n\t}\n\n\tget name(): string {\n\t\treturn this.manifest.name;\n\t}\n\n\tget attached(): boolean {\n\t\treturn this.region.flags[FLAG_ATTACHED] !== 0;\n\t}\n\n\t/** Why the last attach did not happen, if it did not. */\n\tget attachError(): string | undefined {\n\t\treturn this.lastAttachError;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Cells and knobs (section 5.2)\n\t// -----------------------------------------------------------------------\n\n\t/** The typed store and load of one cell, built once so the hot path is the store alone. */\n\tprivate makeEntry(decl: CellDecl, ordinal: number, slot: number, scalar: boolean): CellEntry {\n\t\tconst { values, versions, sized } = this.region;\n\t\tconst entry: CellEntry = { decl, ordinal, slot, scalar, set: () => {}, get: () => undefined };\n\t\tif (!scalar) {\n\t\t\tentry.set = (value) => {\n\t\t\t\tsized[slot] = fitValue(value, decl);\n\t\t\t\tversions[ordinal] += 1;\n\t\t\t};\n\t\t\tentry.get = () => sized[slot];\n\t\t\treturn entry;\n\t\t}\n\t\tswitch (decl.type) {\n\t\t\tcase \"bool\":\n\t\t\t\tentry.set = (value) => {\n\t\t\t\t\tvalues[slot] = value ? 1 : 0;\n\t\t\t\t\tversions[ordinal] += 1;\n\t\t\t\t};\n\t\t\t\tentry.get = () => values[slot] !== 0;\n\t\t\t\tbreak;\n\t\t\tcase \"enum\": {\n\t\t\t\tconst index = new Map((decl.enum ?? []).map((value, i) => [value, i]));\n\t\t\t\tconst names = decl.enum ?? [];\n\t\t\t\tentry.enumIndex = index;\n\t\t\t\tentry.set = (value) => {\n\t\t\t\t\tconst i = index.get(value as string);\n\t\t\t\t\tif (i === undefined) throw new Error(`${decl.name}: ${String(value)} is not a declared value`);\n\t\t\t\t\tvalues[slot] = i;\n\t\t\t\t\tversions[ordinal] += 1;\n\t\t\t\t};\n\t\t\t\tentry.get = () => names[values[slot]];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tentry.set = (value) => {\n\t\t\t\t\tvalues[slot] = value as number;\n\t\t\t\t\tversions[ordinal] += 1;\n\t\t\t\t};\n\t\t\t\tentry.get = () => values[slot];\n\t\t}\n\t\treturn entry;\n\t}\n\n\tprivate entry(name: string): CellEntry {\n\t\tconst entry = this.cellEntries.get(name);\n\t\tif (!entry) throw new Error(`${this.name} declares no cell ${name}`);\n\t\treturn entry;\n\t}\n\n\t/** A handle, created once per cell and cached. */\n\tcell<T = unknown>(name: string): CellHandle<T> {\n\t\tconst cached = this.cellHandles.get(name);\n\t\tif (cached) return cached as CellHandle<T>;\n\t\tconst entry = this.entry(name);\n\t\tconst handle: CellHandle = { name, get: entry.get, set: entry.set };\n\t\tthis.cellHandles.set(name, handle);\n\t\treturn handle as CellHandle<T>;\n\t}\n\n\t/** A knob handle; the cell must be `readwrite`. */\n\tknob<T = unknown>(name: string): KnobHandle<T> {\n\t\tconst entry = this.entry(name);\n\t\tif (entry.decl.access !== \"readwrite\") throw new Error(`${name} is a read cell, not a knob`);\n\t\tconst handle = this.cell<T>(name) as KnobHandle<T>;\n\t\tif (handle.reset === undefined) {\n\t\t\thandle.reset = () => {\n\t\t\t\tentry.set(entry.decl.default);\n\t\t\t\tif (!entry.scalar) this.emitLibrary(\"abai.knob.reset\", { cell: name, value: entry.get() });\n\t\t\t};\n\t\t\thandle.onChange = (handler) => {\n\t\t\t\tif (entry.scalar) throw new Error(`${name} is a scalar knob; the program reads it, nothing is delivered`);\n\t\t\t\tthis.knobHandlers.set(name, handler);\n\t\t\t};\n\t\t}\n\t\treturn handle;\n\t}\n\n\t/** @internal The handler a sized knob delivers to, for the binding. */\n\tknobHandler(name: string): KnobHandler | undefined {\n\t\treturn this.knobHandlers.get(name);\n\t}\n\n\t/** @internal Every cell's current value, keyed by name, for the binding. */\n\treadCells(): Record<string, unknown> {\n\t\tconst cells: Record<string, unknown> = {};\n\t\tfor (const [name, entry] of this.cellEntries) cells[name] = entry.get();\n\t\treturn cells;\n\t}\n\n\t/** @internal A cell's version counter. */\n\tversion(name: string): number {\n\t\treturn this.region.versions[this.entry(name).ordinal];\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Lifecycle (section 5.5): what the program itself can do\n\t// -----------------------------------------------------------------------\n\n\t/** A cooperative pause is in effect. */\n\tget paused(): boolean {\n\t\treturn this.region.flags[FLAG_PAUSED] !== 0;\n\t}\n\n\t/**\n\t * @internal A cooperative pause: the flag is set, and the program is held\n\t * at its next touchpoint (this instantiation's deviation: a safepoint\n\t * cannot block the loop the resume arrives on). A ref'd timer keeps a\n\t * paused program alive, since nothing else of its own may be pending.\n\t */\n\trequestPause(): void {\n\t\tif (this.paused) return;\n\t\tthis.region.flags[FLAG_PAUSED] = 1;\n\t\tthis.region.flags[FLAG_WORK] = 1;\n\t\tthis.pauseKeepAlive = setInterval(() => {}, 2 ** 31 - 1);\n\t}\n\n\t/** @internal Lift the pause: whoever is held at a touchpoint goes on, their clocks starting now. */\n\tresumeFromPause(): void {\n\t\tif (!this.paused) return;\n\t\tthis.region.flags[FLAG_PAUSED] = 0;\n\t\tthis.region.flags[FLAG_WORK] = this.queue.length > 0 ? 1 : 0;\n\t\tif (this.pauseKeepAlive !== undefined) clearInterval(this.pauseKeepAlive);\n\t\tthis.pauseKeepAlive = undefined;\n\t\tfor (const waiter of this.resumeWaiters.splice(0)) waiter();\n\t}\n\n\t/** Hold here while a cooperative pause is in effect; the agent is told what the pause holds (4.3). */\n\tprivate awaitResume(touchpoint: string): Promise<void> {\n\t\tif (!this.paused) return Promise.resolve();\n\t\tthis.emitLibrary(\"abai.pause.blocked\", { touchpoint });\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tthis.resumeWaiters.push(resolve);\n\t\t});\n\t}\n\n\t/** @internal What the stub knows of its own process, for `lifecycle status`. */\n\tlifecycleStatus(): {\n\t\tpid: number;\n\t\tname: string;\n\t\tmode: string;\n\t\tcwd: string;\n\t\tstartedAt: string;\n\t\tconnections: number;\n\t\tpaused: boolean;\n\t\tlaunched: boolean;\n\t} {\n\t\treturn {\n\t\t\tpid: process.pid,\n\t\t\tname: this.manifest.name,\n\t\t\tmode: this.mode,\n\t\t\tcwd: process.cwd(),\n\t\t\tstartedAt: this.startedAt,\n\t\t\tconnections: this.endpoint?.connectionCount ?? (this.attached ? 1 : 0),\n\t\t\tpaused: this.paused,\n\t\t\tlaunched: this.launched,\n\t\t};\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Events (section 5.1)\n\t// -----------------------------------------------------------------------\n\n\t/** A handle for a declared event, created once and cached; the name is checked here, never on the hot path. */\n\tevent(name: string): EventHandle {\n\t\tconst cached = this.eventHandles.get(name);\n\t\tif (cached) return cached;\n\t\tconst entry = this.eventEntries.get(name);\n\t\tif (!entry) throw new Error(`${this.name} declares no event ${name}`);\n\t\tconst flags = this.region.flags;\n\t\tconst handle: EventHandle = {\n\t\t\tname,\n\t\t\temit: (payload = {}) => {\n\t\t\t\tif (flags[FLAG_ATTACHED] === 0) return;\n\t\t\t\tthis.publish(entry, payload);\n\t\t\t},\n\t\t};\n\t\tthis.eventHandles.set(name, handle);\n\t\treturn handle;\n\t}\n\n\t/** Copy the declared fields, cut sized ones to their max, publish, and tell the harness. */\n\tprivate publish(entry: EventEntry, payload: Record<string, unknown>): void {\n\t\tlet truncated = false;\n\t\tconst copy: Record<string, unknown> = {};\n\t\tfor (const field of entry.fields) {\n\t\t\tconst value = payload[field.key];\n\t\t\tif (value === undefined) continue;\n\t\t\tswitch (field.type) {\n\t\t\t\tcase \"string\": {\n\t\t\t\t\tconst text = String(value);\n\t\t\t\t\tconst cut = cutUtf8(text, field.max ?? 256);\n\t\t\t\t\tif (cut !== text) truncated = true;\n\t\t\t\t\tcopy[field.key] = cut;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"object\":\n\t\t\t\tcase \"list\": {\n\t\t\t\t\tconst bytes = encodedBytes(value);\n\t\t\t\t\tif (bytes > (field.max ?? 1024)) {\n\t\t\t\t\t\ttruncated = true;\n\t\t\t\t\t\tcopy[field.key] = { truncated: true, bytes };\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcopy[field.key] = JSON.parse(JSON.stringify(value));\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tcopy[field.key] = value;\n\t\t\t}\n\t\t}\n\t\tconst seq = this.region.emit(entry.ordinal, copy, truncated, this.now());\n\t\tthis.harness?.emitted(seq);\n\t}\n\n\t/** @internal A library event under the reserved prefix. */\n\temitLibrary(name: string, payload: Record<string, unknown>): void {\n\t\tif (this.region.flags[FLAG_ATTACHED] === 0) return;\n\t\tconst ordinal = this.libraryEvents.get(name);\n\t\tif (ordinal === undefined) throw new Error(`${name} is not a library event`);\n\t\tconst seq = this.region.emit(ordinal, payload, false, this.now());\n\t\tthis.harness?.emitted(seq);\n\t}\n\n\t/** @internal The declared event for a ring slot's ordinal, or the library event's name. */\n\teventName(ordinal: number): string {\n\t\tif (ordinal < 0) return LIBRARY_EVENTS[LIBRARY_EVENT_BASE - ordinal] ?? `abai.unknown.${ordinal}`;\n\t\treturn this.manifest.events[ordinal]?.name ?? `unknown.${ordinal}`;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Capabilities and safepoints (section 5.3)\n\t// -----------------------------------------------------------------------\n\n\t/** Register the handler of a declared capability (or replace one given at declaration). */\n\tcapability(name: string, handler: CapabilityHandler): void {\n\t\tif (!this.manifest.capabilities.some((c) => c.name === name)) {\n\t\t\tthrow new Error(`${this.name} declares no capability ${name}`);\n\t\t}\n\t\tthis.capabilityHandlers.set(name, handler);\n\t}\n\n\t/** @internal */\n\tcapabilityHandler(name: string): CapabilityHandler | undefined {\n\t\treturn this.capabilityHandlers.get(name);\n\t}\n\n\t/** @internal Queue work for the next safepoint; the binding uses it for safepoint dispatch. */\n\tenqueue(work: () => void): void {\n\t\tthis.queue.push(work);\n\t\tthis.region.flags[FLAG_QUEUE] = this.queue.length;\n\t\tthis.region.flags[FLAG_WORK] = 1;\n\t}\n\n\t/** One load of a flag when nothing is queued and no pause is in effect. */\n\tsafepoint(): void {\n\t\tif (this.region.flags[FLAG_WORK] === 0) return;\n\t\tthis.drain();\n\t}\n\n\t/** @internal Remove queued work that will not run (a timed-out dispatch). */\n\tdequeue(work: () => void): void {\n\t\tconst index = this.queue.indexOf(work);\n\t\tif (index === -1) return;\n\t\tthis.queue.splice(index, 1);\n\t\tconst flags = this.region.flags;\n\t\tflags[FLAG_QUEUE] = this.queue.length;\n\t\tif (this.queue.length === 0 && flags[FLAG_PAUSED] === 0) flags[FLAG_WORK] = 0;\n\t}\n\n\t/** @internal Run everything queued, now, on this thread: a touchpoint holds the program at a safepoint. */\n\tdrain(): void {\n\t\tconst flags = this.region.flags;\n\t\twhile (this.queue.length > 0) {\n\t\t\tconst work = this.queue.shift() as () => void;\n\t\t\tflags[FLAG_QUEUE] = this.queue.length;\n\t\t\ttry {\n\t\t\t\twork();\n\t\t\t} catch {\n\t\t\t\t// a handler's failure is reported through its reply, never through the program's safepoint\n\t\t\t}\n\t\t}\n\t\tflags[FLAG_WORK] = flags[FLAG_PAUSED] !== 0 ? 1 : 0;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Decisions and turns (sections 5.4, 4.3)\n\t// -----------------------------------------------------------------------\n\n\tprivate applyDefault(decl: DecisionDecl, cause: string): unknown {\n\t\tif (decl.default === \"abort\") throw new AbaigramAbort(decl.name, cause);\n\t\treturn decl.default;\n\t}\n\n\t/** Ask which way to go. With no harness attached the default applies at once and is counted. */\n\tasync decide<T = unknown>(name: string, context: Record<string, unknown> = {}): Promise<T> {\n\t\tconst index = this.decisionIndex.get(name);\n\t\tif (index === undefined) throw new Error(`${this.name} declares no decision ${name}`);\n\t\tconst decl = this.manifest.decisions[index];\n\t\tconst harness = this.harness;\n\t\tif (this.region.flags[FLAG_ATTACHED] === 0 || harness === undefined) {\n\t\t\tthis.region.counters[index] += 1;\n\t\t\treturn this.applyDefault(decl, \"no_harness\") as T;\n\t\t}\n\t\t// a cooperative pause holds the decision here; its own clock starts when the pause lifts (4.3)\n\t\tif (this.paused) await this.awaitResume(name);\n\t\tif (this.region.flags[FLAG_ATTACHED] === 0 || this.harness === undefined) {\n\t\t\tthis.region.counters[index] += 1;\n\t\t\treturn this.applyDefault(decl, \"no_harness\") as T;\n\t\t}\n\t\tthis.drain();\n\t\ttry {\n\t\t\treturn (await this.harness.decide(decl, context, this.now() + durationMs(decl.timeout))) as T;\n\t\t} catch (error) {\n\t\t\treturn this.applyDefault(decl, error instanceof Error ? error.message : String(error)) as T;\n\t\t}\n\t}\n\n\t/** Give the agent a turn. With no harness attached it returns at once and is counted. */\n\tasync turn(name: string): Promise<void> {\n\t\tconst index = this.turnIndex.get(name);\n\t\tif (index === undefined) throw new Error(`${this.name} declares no turn ${name}`);\n\t\tconst decl = this.manifest.turns[index];\n\t\tconst harness = this.harness;\n\t\tif (this.region.flags[FLAG_ATTACHED] === 0 || harness === undefined) {\n\t\t\tthis.region.counters[this.manifest.decisions.length + index] += 1;\n\t\t\treturn;\n\t\t}\n\t\tif (this.paused) await this.awaitResume(name);\n\t\tif (this.region.flags[FLAG_ATTACHED] === 0 || this.harness === undefined) {\n\t\t\tthis.region.counters[this.manifest.decisions.length + index] += 1;\n\t\t\treturn;\n\t\t}\n\t\tthis.drain();\n\t\ttry {\n\t\t\tawait this.harness.turn(decl, this.now() + durationMs(decl.timeout));\n\t\t} catch {\n\t\t\t// a turn that fails is a turn that ended\n\t\t}\n\t\tthis.drain();\n\t}\n\n\t/** The counters, by kind and name: defaulted decisions, skipped turns, and the mode fallback. */\n\tcounters(): CounterSnapshot {\n\t\tconst snapshot: CounterSnapshot = {};\n\t\tthis.manifest.decisions.forEach((decl, i) => {\n\t\t\tsnapshot[`decision:${decl.name}`] = this.region.counters[i];\n\t\t});\n\t\tthis.manifest.turns.forEach((decl, i) => {\n\t\t\tsnapshot[`turn:${decl.name}`] = this.region.counters[this.manifest.decisions.length + i];\n\t\t});\n\t\tsnapshot[\"abai.mode_fallback\"] = this.region.counters[this.region.counters.length - 1];\n\t\treturn snapshot;\n\t}\n\n\t/** @internal Read and reset the counters, for the harness that attaches. */\n\ttakeCounters(): CounterSnapshot {\n\t\tconst snapshot = this.counters();\n\t\tthis.region.counters.fill(0);\n\t\treturn snapshot;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Attaching (section 4.3)\n\t// -----------------------------------------------------------------------\n\n\t/** @internal The binding attaches a harness. */\n\tsetHarness(harness: AttachedHarness): void {\n\t\tthis.harness = harness;\n\t\tthis.region.flags[FLAG_ATTACHED] = 1;\n\t\tthis.lastAttachError = undefined;\n\t}\n\n\t/** @internal The binding detaches it; a decision in flight takes its default with `reason`. */\n\tclearHarness(reason: string): void {\n\t\tconst harness = this.harness;\n\t\tthis.harness = undefined;\n\t\tthis.binding = undefined;\n\t\tthis.nullHarness = undefined;\n\t\tthis.region.flags[FLAG_ATTACHED] = 0;\n\t\tthis.lastAttachError = reason;\n\t\t// a pause is the attached harness's: when the harness goes the program runs as itself again (6.3)\n\t\tthis.resumeFromPause();\n\t\tharness?.detach(reason);\n\t}\n\n\t/** The direct binding, while an in-process harness is attached. */\n\tget directBinding(): HarnessBinding | undefined {\n\t\treturn this.binding;\n\t}\n\n\t/** The null harness, while one is attached. */\n\tget attachedNullHarness(): NullHarness | undefined {\n\t\treturn this.nullHarness;\n\t}\n\n\tprivate createBinding(connection?: string): DirectBinding {\n\t\tconst binding = new DirectBinding(this, {\n\t\t\t...(this.auditPathOverride === undefined ? {} : { auditPath: this.auditPathOverride }),\n\t\t\t...(connection === undefined ? {} : { connection }),\n\t\t});\n\t\tthis.binding = binding;\n\t\tthis.setHarness(binding);\n\t\tbinding.flushCounters();\n\t\treturn binding;\n\t}\n\n\t/**\n\t * @internal A front end of the library's own (the sidecar endpoint)\n\t * attaches the program through a binding it then drives; the connection's\n\t * id labels its acts in the audit. Refused while something else is\n\t * attached: a second harness would take the program over silently.\n\t */\n\tbind(connection: string): DirectBinding {\n\t\tif (this.attached) {\n\t\t\tthrow new Error(`${this.name} already has a harness attached; this connection cannot take it over`);\n\t\t}\n\t\tif (this.launched) this.launcherSeen = true;\n\t\treturn this.createBinding(connection);\n\t}\n\n\tprivate attachNull(form: \"null\" | \"exercise\"): void {\n\t\tconst binding = this.createBinding();\n\t\tthis.nullHarness = attachNullHarness(binding, form);\n\t\tif (form === \"exercise\") {\n\t\t\tconst harness = this.nullHarness;\n\t\t\tprocess.once(\"exit\", () => {\n\t\t\t\tif (this.nullHarness === harness) this.report(JSON.stringify(harness.report()));\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Connect or spawn a harness (one of the five places). Resolves true when\n\t * attached. The real harness arrives through the `harness` extra, or the\n\t * harness package's dynamic import once it exists; until then the reason\n\t * is recorded.\n\t */\n\tasync attach(): Promise<boolean> {\n\t\tif (this.attached) return true;\n\t\tswitch (this.harnessSelection) {\n\t\t\tcase \"off\":\n\t\t\t\tthis.lastAttachError = `${ENV_HARNESS}=off`;\n\t\t\t\treturn false;\n\t\t\tcase \"null\":\n\t\t\tcase \"exercise\":\n\t\t\t\tthis.attachNull(this.harnessSelection);\n\t\t\t\treturn true;\n\t\t\tdefault:\n\t\t\t\tif (this.harnessSelectionError !== undefined) {\n\t\t\t\t\tthis.lastAttachError = this.harnessSelectionError;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif (this.endpoint) {\n\t\t\t\t\t// launched: the parent is already connecting; the program never spawns anything itself\n\t\t\t\t\treturn this.launched ? this.awaitLauncher(this.endpoint) : this.attachSidecar(this.endpoint);\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst factory =\n\t\t\t\t\t\tthis.harnessFactory ??\n\t\t\t\t\t\t(await installedHarness(\n\t\t\t\t\t\t\tthis.env[ENV_HARNESS_MODULE] || this.manifest.harness?.path || \"@abaigram/abai-embedded-harness\",\n\t\t\t\t\t\t));\n\t\t\t\t\tif (factory === undefined) {\n\t\t\t\t\t\tthis.lastAttachError =\n\t\t\t\t\t\t\t\"no harness is installed (the @abaigram/abai-embedded-harness package is not resolvable)\";\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\tconst binding = this.createBinding();\n\t\t\t\t\tawait factory(binding);\n\t\t\t\t\treturn this.attached;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tthis.binding?.detach(reason);\n\t\t\t\t\tthis.lastAttachError = reason;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t}\n\t}\n\n\t/** Under launch: the launcher connects to the socket it chose; the program waits for it once, bounded. */\n\tprivate async awaitLauncher(endpoint: Endpoint): Promise<boolean> {\n\t\ttry {\n\t\t\tawait endpoint.ready;\n\t\t} catch (error) {\n\t\t\tthis.lastAttachError = `the endpoint could not listen: ${error instanceof Error ? error.message : String(error)}`;\n\t\t\treturn false;\n\t\t}\n\t\tif (this.attached) return true;\n\t\tif (this.launcherSeen) {\n\t\t\t// it was here and went: a later attach place returns at once, as 4.3 says of every touchpoint after a detach\n\t\t\tthis.lastAttachError = this.lastAttachError ?? \"the launcher disconnected\";\n\t\t\treturn false;\n\t\t}\n\t\tconst waitMs = attachWaitMsFrom(this.env);\n\t\tif (await endpoint.waitForConnection(waitMs)) {\n\t\t\tthis.launcherSeen = true;\n\t\t\treturn true;\n\t\t}\n\t\tthis.lastAttachError = `launched, but the launcher did not connect within ${waitMs} ms`;\n\t\treturn false;\n\t}\n\n\t/**\n\t * Sidecar attach (section 6.3): a harness already connected to the\n\t * endpoint means attached; otherwise the harness binary is spawned with\n\t * the terminal inherited and given `ABAI_ATTACH_WAIT_MS` to connect. A\n\t * child that exits first fails the attach at once with its code, so an\n\t * unconfigured machine costs well under a second (section 4.3); a child\n\t * that never connects is ended at the wait and the attach fails with the\n\t * reason. The child is unref'd: a program whose work is done exits, and\n\t * the closing socket tells the harness.\n\t */\n\tprivate async attachSidecar(endpoint: Endpoint): Promise<boolean> {\n\t\ttry {\n\t\t\tawait endpoint.ready;\n\t\t} catch {\n\t\t\t// spawnHarness reports it\n\t\t}\n\t\t// a harness that connected while the endpoint came up, or at any time before: attached already\n\t\tif (this.attached) return true;\n\t\tconst spawned = await this.spawnHarness(endpoint, \"attach\");\n\t\treturn spawned !== undefined;\n\t}\n\n\t/**\n\t * Spawn the harness binary with a verb against this program's endpoint\n\t * and wait for it to connect. Resolves to the child once attached, or to\n\t * undefined with the reason in `attachError`: the endpoint could not\n\t * listen, the child exited first (its code), it never connected within\n\t * the wait (it is ended), or it could not be started at all.\n\t */\n\tprivate async spawnHarness(endpoint: Endpoint, verb: \"attach\" | \"mcp\"): Promise<ChildProcess | undefined> {\n\t\ttry {\n\t\t\tawait endpoint.ready;\n\t\t} catch (error) {\n\t\t\tthis.lastAttachError = `the endpoint could not listen: ${error instanceof Error ? error.message : String(error)}`;\n\t\t\treturn undefined;\n\t\t}\n\t\tconst waitMs = attachWaitMsFrom(this.env);\n\t\tconst binary = harnessBinary(this.env, this.content.root);\n\t\tconst args = [...binary.args, verb, \"--target\", endpoint.socketPath, \"--run-dir\", endpoint.runDir];\n\t\tlet child: ChildProcess;\n\t\ttry {\n\t\t\tchild = spawn(binary.command, args, { stdio: \"inherit\", env: { ...process.env, ...this.env } });\n\t\t} catch (error) {\n\t\t\tthis.lastAttachError = `the harness binary could not be started: ${error instanceof Error ? error.message : String(error)}`;\n\t\t\treturn undefined;\n\t\t}\n\t\tchild.unref();\n\t\t// a ChildProcess with no error listener throws into the program; one stays for the child's whole life\n\t\tchild.on(\"error\", () => {});\n\t\tconst outcome = await new Promise<string>((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tconst settle = (result: string): void => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tchild.off(\"exit\", onExit);\n\t\t\t\tchild.off(\"error\", onError);\n\t\t\t\tresolve(result);\n\t\t\t};\n\t\t\tconst onExit = (code: number | null, signal: NodeJS.Signals | null): void =>\n\t\t\t\tsettle(`exited:${code === null ? `signal ${signal ?? \"unknown\"}` : String(code)}`);\n\t\t\tconst onError = (error: NodeJS.ErrnoException): void => settle(`error:${error.code ?? error.message}`);\n\t\t\tchild.once(\"exit\", onExit);\n\t\t\tchild.once(\"error\", onError);\n\t\t\tvoid endpoint.waitForConnection(waitMs).then((connected) => settle(connected ? \"connected\" : \"timeout\"));\n\t\t});\n\t\tif (outcome === \"connected\") return child;\n\t\tif (outcome === \"timeout\") {\n\t\t\t// the one process this program started, by its own handle: nothing else is touched\n\t\t\tchild.kill();\n\t\t\tthis.lastAttachError = `the harness binary did not connect within ${waitMs} ms (${binary.command})`;\n\t\t\treturn undefined;\n\t\t}\n\t\tif (outcome.startsWith(\"exited:\")) {\n\t\t\tthis.lastAttachError = `the harness binary exited with ${outcome.slice(\"exited:\".length)} before connecting`;\n\t\t\treturn undefined;\n\t\t}\n\t\tthis.lastAttachError = `the harness binary could not be started: ${outcome.slice(\"error:\".length)} (${binary.command})`;\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * @internal `<prog> abai mcp` under sidecar: the binary's MCP server is\n\t * spawned against this program's endpoint with the terminal inherited, so\n\t * the MCP client on this process's stdin and stdout talks to it, and the\n\t * program waits at its entry, a safepoint it holds open, until the client\n\t * goes. Resolves to the child's exit code; -1 when nothing could be\n\t * spawned (the reason is in `attachError`).\n\t */\n\tasync serveMcpSidecar(endpoint: Endpoint): Promise<number> {\n\t\tif (this.attached) {\n\t\t\tthis.lastAttachError = `${this.name} already has a harness attached`;\n\t\t\treturn -1;\n\t\t}\n\t\tconst child = await this.spawnHarness(endpoint, \"mcp\");\n\t\tif (child === undefined) return -1;\n\t\t// the program's whole purpose now is to wait for its server: the child holds the event loop open,\n\t\t// where the spawn left it unref'd for the attach case, whose touchpoint holds the loop itself\n\t\tchild.ref();\n\t\t// the MCP client spawned this program and ends it with a signal; the server behind it must go too\n\t\tconst forward = (signal: NodeJS.Signals): void => {\n\t\t\tif (child.exitCode === null && child.signalCode === null) child.kill(signal);\n\t\t};\n\t\tconst forwarded: NodeJS.Signals[] = [\"SIGTERM\", \"SIGINT\", \"SIGHUP\"];\n\t\tfor (const signal of forwarded) process.on(signal, forward);\n\t\tconst binding = this.binding;\n\t\tbinding?.enterSafepoint();\n\t\ttry {\n\t\t\tif (child.exitCode !== null) return child.exitCode;\n\t\t\treturn await new Promise<number>((resolve) => {\n\t\t\t\tchild.once(\"exit\", (code) => resolve(code ?? 1));\n\t\t\t});\n\t\t} finally {\n\t\t\tfor (const signal of forwarded) process.off(signal, forward);\n\t\t\tbinding?.leaveSafepoint();\n\t\t}\n\t}\n\n\tprivate async touchpoint(kind: TouchpointKind): Promise<void> {\n\t\tif (!(await this.attach())) return;\n\t\tif (this.paused) await this.awaitResume(kind);\n\t\tconst binding = this.binding;\n\t\tif (!binding) return;\n\t\tthis.drain();\n\t\tawait binding.touchpoint(kind);\n\t\tthis.drain();\n\t}\n\n\t/** Run the install touchpoint; returns when the harness finishes, or at once when none can be attached. */\n\tinstall(): Promise<void> {\n\t\treturn this.touchpoint(\"install\");\n\t}\n\n\t/** Open a session; returns when the harness ends it, or at once when none can be attached. */\n\tsession(): Promise<void> {\n\t\treturn this.touchpoint(\"session\");\n\t}\n}\n\n/** Overrides where the in-process harness is imported from (a path or a package name). */\nexport const ENV_HARNESS_MODULE = \"ABAI_HARNESS_MODULE\";\n\n/**\n * The harness binary to spawn under sidecar: `ABAI_HARNESS_BIN` (a script\n * path runs under this very node), else the program's own\n * `node_modules/.bin/abai-embedded-harness`, else the name on PATH.\n */\nexport function harnessBinary(env: NodeJS.ProcessEnv, root: string): { command: string; args: string[] } {\n\tconst explicit = env[ENV_HARNESS_BIN]?.trim();\n\tif (explicit) {\n\t\treturn /\\.(?:[mc]?js|ts)$/.test(explicit)\n\t\t\t? { command: process.execPath, args: [explicit] }\n\t\t\t: { command: explicit, args: [] };\n\t}\n\tconst local = join(root, \"node_modules\", \".bin\", \"abai-embedded-harness\");\n\tif (existsSync(local)) return { command: local, args: [] };\n\treturn { command: \"abai-embedded-harness\", args: [] };\n}\n\n/** What the stub uses of the installed harness package. */\nexport interface HarnessModule {\n\tattachEmbedded?: (binding: HarnessBinding) => Promise<unknown>;\n\t/** `<prog> abai mcp` in-process: serve this binding to the MCP client on the process's stdio; resolves to the exit code. */\n\tserveMcpEmbedded?: (binding: HarnessBinding) => Promise<number>;\n\trunConfig?: (\n\t\targs: string[],\n\t\tio: { out(text: string): void; err(text: string): void; env: NodeJS.ProcessEnv },\n\t) => Promise<number>;\n\tdoctorOnline?: (\n\t\tmanifest: Manifest,\n\t\tenv: NodeJS.ProcessEnv,\n\t) => Promise<Array<{ name: string; status: \"ok\" | \"fail\" | \"skip\"; detail: string }>>;\n}\n\n/** The harness package, found by dynamic import when a program installed it; undefined when it does not resolve. */\nexport async function importHarnessModule(\n\tenv: NodeJS.ProcessEnv,\n\tmanifest: Manifest,\n): Promise<HarnessModule | undefined> {\n\tconst specifier = env[ENV_HARNESS_MODULE] || manifest.harness?.path || \"@abaigram/abai-embedded-harness\";\n\ttry {\n\t\treturn (await import(specifier)) as HarnessModule;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** The harness package's in-process entry. */\nasync function installedHarness(specifier: string): Promise<HarnessFactory | undefined> {\n\tlet module: HarnessModule;\n\ttry {\n\t\tmodule = (await import(specifier)) as HarnessModule;\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (typeof module.attachEmbedded !== \"function\") return undefined;\n\tconst attach = module.attachEmbedded;\n\treturn async (binding) => {\n\t\tawait attach(binding);\n\t};\n}\n\n/** The program's one call: declarations in, the abaigram out. Throws when a declaration does not validate. */\nexport function createAbaigram(options: AbaigramOptions, extras: AbaigramExtras = {}): Abaigram {\n\treturn new Abaigram(options, extras);\n}\n"]}