spexcode 0.6.7 → 0.6.8

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 (205) hide show
  1. package/README.md +12 -7
  2. package/node_modules/@spexcode/session-application/dist/index.d.ts +17 -0
  3. package/node_modules/@spexcode/session-application/dist/index.js +35 -0
  4. package/node_modules/@spexcode/session-application/dist/migration.d.ts +51 -0
  5. package/node_modules/@spexcode/session-application/dist/migration.js +694 -0
  6. package/node_modules/@spexcode/session-application/dist/production.d.ts +109 -0
  7. package/node_modules/@spexcode/session-application/dist/production.js +438 -0
  8. package/node_modules/@spexcode/session-application/dist/schema.d.ts +2 -0
  9. package/node_modules/@spexcode/session-application/dist/schema.js +39 -0
  10. package/node_modules/@spexcode/session-application/package.json +29 -0
  11. package/node_modules/@spexcode/session-events/dist/errors.d.ts +6 -0
  12. package/node_modules/@spexcode/session-events/dist/errors.js +11 -0
  13. package/node_modules/@spexcode/session-events/dist/index.d.ts +46 -0
  14. package/node_modules/@spexcode/session-events/dist/index.js +245 -0
  15. package/node_modules/@spexcode/session-events/dist/schema.d.ts +3 -0
  16. package/node_modules/@spexcode/session-events/dist/schema.js +44 -0
  17. package/node_modules/@spexcode/session-events/package.json +24 -0
  18. package/node_modules/@spexcode/session-protocol/dist/canonical.d.ts +36 -0
  19. package/node_modules/@spexcode/session-protocol/dist/canonical.js +139 -0
  20. package/node_modules/@spexcode/session-protocol/dist/engine.d.ts +22 -0
  21. package/node_modules/@spexcode/session-protocol/dist/engine.js +478 -0
  22. package/node_modules/@spexcode/session-protocol/dist/errors.d.ts +8 -0
  23. package/node_modules/@spexcode/session-protocol/dist/errors.js +39 -0
  24. package/node_modules/@spexcode/session-protocol/dist/index.d.ts +60 -0
  25. package/node_modules/@spexcode/session-protocol/dist/index.js +4 -0
  26. package/node_modules/@spexcode/session-protocol/dist/schema.d.ts +36 -0
  27. package/node_modules/@spexcode/session-protocol/dist/schema.js +192 -0
  28. package/node_modules/@spexcode/{session-core → session-protocol}/package.json +4 -8
  29. package/node_modules/@spexcode/session-runtime/dist/errors.d.ts +6 -0
  30. package/node_modules/@spexcode/session-runtime/dist/errors.js +11 -0
  31. package/node_modules/@spexcode/session-runtime/dist/index.d.ts +33 -0
  32. package/node_modules/@spexcode/session-runtime/dist/index.js +193 -0
  33. package/node_modules/@spexcode/session-runtime/dist/schema.d.ts +2 -0
  34. package/node_modules/@spexcode/session-runtime/dist/schema.js +37 -0
  35. package/node_modules/@spexcode/session-runtime/package.json +24 -0
  36. package/node_modules/@spexcode/session-selflaunch/bin/spex-session.mjs +4 -0
  37. package/node_modules/@spexcode/session-selflaunch/dist/cli.d.ts +25 -0
  38. package/node_modules/@spexcode/session-selflaunch/dist/cli.js +156 -0
  39. package/node_modules/@spexcode/session-selflaunch/dist/index.d.ts +28 -0
  40. package/node_modules/@spexcode/session-selflaunch/dist/index.js +25 -0
  41. package/node_modules/@spexcode/session-selflaunch/dist/locality.d.ts +20 -0
  42. package/node_modules/@spexcode/session-selflaunch/dist/locality.js +83 -0
  43. package/node_modules/@spexcode/session-selflaunch/dist/path.d.ts +13 -0
  44. package/node_modules/@spexcode/session-selflaunch/dist/path.js +45 -0
  45. package/node_modules/@spexcode/session-selflaunch/package.json +35 -0
  46. package/node_modules/@spexcode/session-topology/dist/errors.d.ts +6 -0
  47. package/node_modules/@spexcode/session-topology/dist/errors.js +11 -0
  48. package/node_modules/@spexcode/session-topology/dist/index.d.ts +24 -0
  49. package/node_modules/@spexcode/session-topology/dist/index.js +173 -0
  50. package/node_modules/@spexcode/session-topology/dist/schema.d.ts +3 -0
  51. package/node_modules/@spexcode/session-topology/dist/schema.js +27 -0
  52. package/node_modules/@spexcode/session-topology/package.json +24 -0
  53. package/node_modules/@spexcode/spec-cli/bin/spex.mjs +56 -19
  54. package/node_modules/@spexcode/spec-cli/dist/cli.js +102 -59
  55. package/node_modules/@spexcode/spec-cli/dist/client.d.ts +1 -3
  56. package/node_modules/@spexcode/spec-cli/dist/client.js +49 -30
  57. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +11 -0
  58. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +46 -9
  59. package/node_modules/@spexcode/spec-cli/dist/delivery-lock.d.ts +2 -0
  60. package/node_modules/@spexcode/spec-cli/dist/delivery-lock.js +58 -0
  61. package/node_modules/@spexcode/spec-cli/dist/doctor.js +53 -11
  62. package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +1 -0
  63. package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +2 -2
  64. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +2 -1
  65. package/node_modules/@spexcode/spec-cli/dist/gateway.js +6 -3
  66. package/node_modules/@spexcode/spec-cli/dist/graphCache.js +32 -2
  67. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +57 -2
  68. package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +2 -0
  69. package/node_modules/@spexcode/spec-cli/dist/graphStream.js +83 -3
  70. package/node_modules/@spexcode/spec-cli/dist/guide.js +20 -7
  71. package/node_modules/@spexcode/spec-cli/dist/harness-select.js +16 -3
  72. package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +15 -3
  73. package/node_modules/@spexcode/spec-cli/dist/harness.js +331 -50
  74. package/node_modules/@spexcode/spec-cli/dist/help.js +11 -8
  75. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +8 -0
  76. package/node_modules/@spexcode/spec-cli/dist/host-resources.js +29 -8
  77. package/node_modules/@spexcode/spec-cli/dist/host.d.ts +7 -0
  78. package/node_modules/@spexcode/spec-cli/dist/host.js +93 -0
  79. package/node_modules/@spexcode/spec-cli/dist/index.js +324 -22
  80. package/node_modules/@spexcode/spec-cli/dist/init.js +1 -1
  81. package/node_modules/@spexcode/spec-cli/dist/lint.js +70 -35
  82. package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +3 -2
  83. package/node_modules/@spexcode/spec-cli/dist/listen.js +14 -2
  84. package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +1 -1
  85. package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +2 -2
  86. package/node_modules/@spexcode/spec-cli/dist/materialize.js +176 -35
  87. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +14 -14
  88. package/node_modules/@spexcode/spec-cli/dist/reviews.js +12 -7
  89. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +11 -0
  90. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +79 -1
  91. package/node_modules/@spexcode/spec-cli/dist/session-application.d.ts +23 -0
  92. package/node_modules/@spexcode/spec-cli/dist/session-application.js +189 -0
  93. package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +13 -1
  94. package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +6 -0
  95. package/node_modules/@spexcode/spec-cli/dist/session-files.js +13 -1
  96. package/node_modules/@spexcode/spec-cli/dist/session-follow.js +39 -22
  97. package/node_modules/@spexcode/{session-core/dist/record-lock.d.ts → spec-cli/dist/session-record-lock.d.ts} +0 -4
  98. package/node_modules/@spexcode/spec-cli/dist/session-record-lock.js +94 -0
  99. package/node_modules/@spexcode/spec-cli/dist/session-runtime-adapter.d.ts +44 -0
  100. package/node_modules/@spexcode/spec-cli/dist/session-runtime-adapter.js +37 -0
  101. package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +25 -2
  102. package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +68 -11
  103. package/node_modules/@spexcode/spec-cli/dist/session-web.js +4 -4
  104. package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +108 -15
  105. package/node_modules/@spexcode/spec-cli/dist/sessions.js +1465 -744
  106. package/node_modules/@spexcode/spec-cli/dist/source-list.d.ts +13 -0
  107. package/node_modules/@spexcode/spec-cli/dist/source-list.js +99 -0
  108. package/node_modules/@spexcode/spec-cli/dist/source-read.d.ts +16 -0
  109. package/node_modules/@spexcode/spec-cli/dist/source-read.js +84 -0
  110. package/node_modules/@spexcode/spec-cli/dist/spec-attachments.d.ts +7 -0
  111. package/node_modules/@spexcode/spec-cli/dist/spec-attachments.js +89 -0
  112. package/node_modules/@spexcode/spec-cli/dist/spec-body-edit.d.ts +23 -0
  113. package/node_modules/@spexcode/spec-cli/dist/spec-body-edit.js +138 -0
  114. package/node_modules/@spexcode/spec-cli/dist/supervise.js +15 -6
  115. package/node_modules/@spexcode/spec-cli/dist/transcript-reader.d.ts +36 -0
  116. package/node_modules/@spexcode/spec-cli/dist/transcript-reader.js +251 -0
  117. package/node_modules/@spexcode/spec-cli/hooks/dispatch.sh +19 -31
  118. package/node_modules/@spexcode/spec-cli/hooks/harness.sh +6 -6
  119. package/node_modules/@spexcode/spec-cli/package.json +6 -6
  120. package/node_modules/@spexcode/spec-cli/templates/hooks/post-checkout +4 -2
  121. package/node_modules/@spexcode/spec-cli/templates/hooks/post-merge +2 -1
  122. package/node_modules/@spexcode/spec-cli/templates/hooks/pre-commit +5 -3
  123. package/node_modules/@spexcode/spec-cli/templates/hooks/reference-transaction +5 -3
  124. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/spec.md +2 -7
  125. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/idle/idle.sh +4 -10
  126. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/idle/spec.md +1 -1
  127. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/mark-active/mark-active.sh +22 -24
  128. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/mark-active/spec.md +10 -2
  129. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-fail/fail.sh +8 -7
  130. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-fail/spec.md +3 -1
  131. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-listen/session-listen.sh +133 -0
  132. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-listen/spec.md +36 -0
  133. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/spec.md +2 -0
  134. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/stop-gate/spec.md +1 -1
  135. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +17 -20
  136. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/skills/merge/spec.md +33 -0
  137. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/skills/spec.md +2 -6
  138. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/spec.md +7 -0
  139. package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +13 -2
  140. package/node_modules/@spexcode/spec-core/dist/anchors.js +311 -15
  141. package/node_modules/@spexcode/spec-core/dist/git.d.ts +4 -0
  142. package/node_modules/@spexcode/spec-core/dist/git.js +9 -9
  143. package/node_modules/@spexcode/spec-core/dist/layout.d.ts +12 -0
  144. package/node_modules/@spexcode/spec-core/dist/layout.js +4 -2
  145. package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +1 -0
  146. package/node_modules/@spexcode/spec-core/dist/resilience.js +8 -0
  147. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +1 -0
  148. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +7 -3
  149. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +1 -0
  150. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +4 -0
  151. package/node_modules/@spexcode/spec-core/dist/specs.d.ts +2 -0
  152. package/node_modules/@spexcode/spec-core/dist/specs.js +30 -0
  153. package/node_modules/@spexcode/spec-core/package.json +4 -1
  154. package/node_modules/@spexcode/spec-eval/dist/cli.js +1 -1
  155. package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +1 -0
  156. package/node_modules/@spexcode/spec-eval/dist/evaltab.js +6 -1
  157. package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +1 -1
  158. package/node_modules/@spexcode/spec-eval/dist/freshness.js +12 -12
  159. package/node_modules/@spexcode/spec-eval/dist/host.d.ts +2 -1
  160. package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +2 -1
  161. package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +20 -3
  162. package/node_modules/@spexcode/spec-eval/package.json +2 -2
  163. package/node_modules/@spexcode/spec-forge/package.json +2 -2
  164. package/node_modules/@vscode/tree-sitter-wasm/LICENSE +21 -0
  165. package/node_modules/@vscode/tree-sitter-wasm/README.md +36 -0
  166. package/node_modules/@vscode/tree-sitter-wasm/SECURITY.md +41 -0
  167. package/node_modules/@vscode/tree-sitter-wasm/cgmanifest.json +16 -0
  168. package/node_modules/@vscode/tree-sitter-wasm/package.json +42 -0
  169. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-bash.wasm +0 -0
  170. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-c-sharp.wasm +0 -0
  171. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-cpp.wasm +0 -0
  172. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-css.wasm +0 -0
  173. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-go.wasm +0 -0
  174. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-ini.wasm +0 -0
  175. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-java.wasm +0 -0
  176. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-javascript.wasm +0 -0
  177. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-php.wasm +0 -0
  178. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-powershell.wasm +0 -0
  179. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-python.wasm +0 -0
  180. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-regex.wasm +0 -0
  181. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-ruby.wasm +0 -0
  182. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-rust.wasm +0 -0
  183. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-tsx.wasm +0 -0
  184. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-typescript.wasm +0 -0
  185. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.js +4075 -0
  186. package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.wasm +0 -0
  187. package/node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter.d.ts +1027 -0
  188. package/package.json +9 -3
  189. package/node_modules/@spexcode/session-core/dist/delivery-queue.d.ts +0 -24
  190. package/node_modules/@spexcode/session-core/dist/delivery-queue.js +0 -206
  191. package/node_modules/@spexcode/session-core/dist/index.d.ts +0 -5
  192. package/node_modules/@spexcode/session-core/dist/index.js +0 -5
  193. package/node_modules/@spexcode/session-core/dist/internal.d.ts +0 -3
  194. package/node_modules/@spexcode/session-core/dist/internal.js +0 -3
  195. package/node_modules/@spexcode/session-core/dist/message.d.ts +0 -22
  196. package/node_modules/@spexcode/session-core/dist/message.js +0 -53
  197. package/node_modules/@spexcode/session-core/dist/record-lock.js +0 -152
  198. package/node_modules/@spexcode/session-core/dist/runtime-session.d.ts +0 -62
  199. package/node_modules/@spexcode/session-core/dist/runtime-session.js +0 -326
  200. package/node_modules/@spexcode/session-core/dist/session-cursors.d.ts +0 -14
  201. package/node_modules/@spexcode/session-core/dist/session-cursors.js +0 -82
  202. package/node_modules/@spexcode/session-core/dist/session-timeline.d.ts +0 -47
  203. package/node_modules/@spexcode/session-core/dist/session-timeline.js +0 -216
  204. package/node_modules/@spexcode/spec-cli/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -53
  205. package/node_modules/@spexcode/spec-cli/hooks/compat/mark-active-sed-v0.fixture +0 -46
@@ -1,216 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { existsSync, readFileSync, appendFileSync, mkdirSync, statSync, readdirSync, openSync, closeSync, readSync } from 'node:fs';
3
- import { basename, join } from 'node:path';
4
- import { sessionStoreDir, sessionArtifactPath } from '@spexcode/spec-core';
5
- const timelinePath = (id) => sessionArtifactPath(id, 'timeline.ndjson');
6
- const segmentsDir = (id) => sessionArtifactPath(id, 'timeline');
7
- const SEGMENT = /^(\d+)\.ndjson$/;
8
- const SEGMENT_NAME_WIDTH = 12;
9
- const TAIL_BLOCK_BYTES = 64 * 1024;
10
- // One logical timeline is legacy timeline.ndjson followed by immutable numbered segments. The directory
11
- // listing is its only index: numbering is append order, so there is no mutable manifest to repair.
12
- function segmentFiles(id) {
13
- try {
14
- const dir = segmentsDir(id);
15
- const names = readdirSync(dir).filter((name) => SEGMENT.test(name)).sort((a, b) => {
16
- const an = BigInt(SEGMENT.exec(a)[1]), bn = BigInt(SEGMENT.exec(b)[1]);
17
- return an < bn ? -1 : an > bn ? 1 : 0;
18
- });
19
- return names.map((name) => join(dir, name));
20
- }
21
- catch { /* no numbered segments yet */ }
22
- return [];
23
- }
24
- function timelineFiles(id) {
25
- const files = [];
26
- const legacy = timelinePath(id);
27
- if (existsSync(legacy))
28
- files.push(legacy);
29
- files.push(...segmentFiles(id));
30
- return files;
31
- }
32
- const segmentLimit = () => {
33
- const configured = Number(process.env.SPEXCODE_TIMELINE_SEGMENT_BYTES);
34
- return Number.isFinite(configured) ? Math.max(1024, Math.floor(configured)) : 4 * 1024 * 1024;
35
- };
36
- function activeSegment(id, bytes) {
37
- const dir = segmentsDir(id);
38
- mkdirSync(dir, { recursive: true });
39
- const segments = segmentFiles(id);
40
- const current = segments.at(-1);
41
- if (!current)
42
- return join(dir, `${String(1).padStart(SEGMENT_NAME_WIDTH, '0')}.ndjson`);
43
- try {
44
- if (statSync(current).size === 0 || statSync(current).size + bytes <= segmentLimit())
45
- return current;
46
- }
47
- catch { /* a vanished active segment is recreated under its next number */ }
48
- const n = BigInt(SEGMENT.exec(basename(current))[1]) + 1n;
49
- return join(dir, `${String(n).padStart(SEGMENT_NAME_WIDTH, '0')}.ndjson`);
50
- }
51
- function append(id, ev) {
52
- mkdirSync(sessionStoreDir(id), { recursive: true });
53
- const line = JSON.stringify(ev) + '\n';
54
- appendFileSync(activeSegment(id, Buffer.byteLength(line)), line);
55
- }
56
- function parseLines(lines) {
57
- return lines.map((l) => {
58
- try {
59
- return JSON.parse(l);
60
- }
61
- catch {
62
- return null;
63
- }
64
- }).filter((e) => e != null && (e.kind === 'status' || e.kind === 'sent' || e.kind === 'dispatch-settled'));
65
- }
66
- function tailPublicEvents(path, limit) {
67
- let fd = null;
68
- try {
69
- const size = statSync(path).size;
70
- fd = openSync(path, 'r');
71
- let start = size;
72
- let text = '';
73
- while (start > 0) {
74
- const next = Math.max(0, start - TAIL_BLOCK_BYTES);
75
- const buf = Buffer.alloc(start - next);
76
- readSync(fd, buf, 0, buf.length, next);
77
- text = buf.toString('utf8') + text;
78
- const publicCount = parseLines(text.split('\n').filter(Boolean)).filter((event) => event.kind !== 'dispatch-settled').length;
79
- if (publicCount >= limit || next === 0)
80
- break;
81
- start = next;
82
- }
83
- return parseLines(text.split('\n').filter(Boolean))
84
- .filter((event) => event.kind !== 'dispatch-settled')
85
- .slice(-limit);
86
- }
87
- catch {
88
- return [];
89
- }
90
- finally {
91
- if (fd !== null)
92
- closeSync(fd);
93
- }
94
- }
95
- // Record a lifecycle value that has already landed in session.json. TypeScript state writers call this
96
- // synchronously before returning, so a later write cannot erase an intermediate declaration note from the
97
- // conversation. Best-effort: history is an accessory to the state machine, and failing to write it must never
98
- // break the transition that already happened.
99
- export function recordStatus(id, status, proposal, note) {
100
- try {
101
- append(id, { ts: new Date().toISOString(), kind: 'status', status, proposal, note });
102
- }
103
- catch { /* the record already moved; the history line is the only loss */ }
104
- }
105
- // The DELIVERY ([[dispatch]]): appending this line IS the send, so unlike a status line it must fail LOUD —
106
- // the caller reports the throw rather than a false success. `text` is the message BEFORE any mechanism insert
107
- // (hints are transport, not conversation); `replyVia` is the effective channel the prompt seam chose. Returns
108
- // the new line's `mid`, which a best-effort poke carries.
109
- export function appendSent(id, text, from, replyVia, dispatchReceipt) {
110
- const mid = randomUUID();
111
- append(id, { ts: new Date().toISOString(), kind: 'sent', mid, text, from, ...(replyVia ? { replyVia } : {}), ...(dispatchReceipt ? { dispatchReceipt } : {}) });
112
- return { mid };
113
- }
114
- export function sentDispatchReceipt(id, operation, requestDigest) {
115
- let found = null;
116
- const settled = new Set();
117
- for (const path of timelineFiles(id)) {
118
- for (const event of parseLines(readFileSync(path, 'utf8').split('\n').filter(Boolean))) {
119
- if (event.kind === 'sent' && !found && event.dispatchReceipt?.operation === operation && event.dispatchReceipt.requestDigest === requestDigest) {
120
- found = { mid: event.mid, payloadHash: event.dispatchReceipt.payloadHash, delivery: event.dispatchReceipt.delivery ?? null };
121
- }
122
- else if (event.kind === 'dispatch-settled' && event.operation === operation && event.requestDigest === requestDigest) {
123
- settled.add(event.mid);
124
- }
125
- }
126
- }
127
- return found ? { ...found, delivered: settled.has(found.mid) } : null;
128
- }
129
- export function settleSentDispatch(id, mid) {
130
- let receipt = null;
131
- let settled = false;
132
- for (const path of timelineFiles(id)) {
133
- for (const event of parseLines(readFileSync(path, 'utf8').split('\n').filter(Boolean))) {
134
- if (event.kind === 'sent' && event.mid === mid && event.dispatchReceipt?.delivery)
135
- receipt = event.dispatchReceipt;
136
- if (event.kind === 'dispatch-settled' && event.mid === mid)
137
- settled = true;
138
- }
139
- }
140
- if (!receipt || settled)
141
- return;
142
- append(id, { ts: new Date().toISOString(), kind: 'dispatch-settled', operation: receipt.operation, requestDigest: receipt.requestDigest, mid });
143
- }
144
- // The unowned read: any process may take it with nothing but filesystem access, and taking it perturbs
145
- // nothing. Index = event position, which is what a cursor names ([[session-cursors]]).
146
- export function timelineEvents(id) {
147
- try {
148
- return timelineFiles(id).flatMap((path) => parseLines(readFileSync(path, 'utf8').split('\n').filter(Boolean)))
149
- .flatMap((stored) => {
150
- if (stored.kind === 'dispatch-settled')
151
- return [];
152
- if (stored.kind === 'status')
153
- return [stored];
154
- const { dispatchReceipt: _receipt, ...event } = stored;
155
- return [event];
156
- });
157
- }
158
- catch {
159
- return [];
160
- }
161
- }
162
- // the same L0 read taken as CHEAPLY as it can be: a follower ([[session-follow]]) ticks over many logs, so it
163
- // stats first and parses only what grew. null = no log yet (a session that has authored nothing).
164
- export function timelineStamp(id) {
165
- try {
166
- const path = timelineFiles(id).at(-1);
167
- if (!path)
168
- return null;
169
- const s = statSync(path);
170
- return `${path}:${s.size}:${s.mtimeMs}`;
171
- }
172
- catch {
173
- return null;
174
- }
175
- }
176
- // the channel of the LAST HUMAN send (from == null): 'note' when the note-reply hint rode along, else null.
177
- // This is what makes the reply-channel hints SYMMETRIC ([[session-timeline]]): a human send with no note flag
178
- // arriving after a note-send is the "back at a terminal" transition, and the delivery gets the counter-insert.
179
- // Derived from the durable log — no new state, and it survives a server restart. Agent senders (`from` set)
180
- // say nothing about where the HUMAN is reading, so they neither set nor clear it.
181
- export function lastHumanSendVia(id) {
182
- const evs = timelineEvents(id);
183
- for (let i = evs.length - 1; i >= 0; i--) {
184
- const e = evs[i];
185
- if (e.kind === 'sent' && e.from == null)
186
- return e.replyVia === 'note' ? 'note' : null;
187
- }
188
- return null;
189
- }
190
- export function currentHumanTurn(id) {
191
- const evs = timelineEvents(id);
192
- for (let i = evs.length - 1; i >= 0; i--) {
193
- const e = evs[i];
194
- if (e.kind === 'sent' && e.from == null)
195
- return { token: e.mid, acceptedAt: e.ts };
196
- }
197
- return null;
198
- }
199
- // Durable tail read with no board/governance policy. A runtime that owns a compatible session directory can
200
- // consume the same protocol; product surfaces decide separately whether that id is visible to them.
201
- export function timelineTail(id, limit = 500) {
202
- const wanted = Math.max(1, limit);
203
- const tail = [];
204
- for (const path of timelineFiles(id).reverse()) {
205
- const remaining = wanted - tail.length;
206
- if (remaining <= 0)
207
- break;
208
- tail.unshift(...tailPublicEvents(path, remaining));
209
- }
210
- return tail.map((e) => {
211
- if (e.kind === 'status')
212
- return e;
213
- const { dispatchReceipt: _receipt, ...event } = e;
214
- return event;
215
- });
216
- }
@@ -1,53 +0,0 @@
1
- #!/usr/bin/env bash
2
- # @@@ mark-active - the SINGLE freshness hook, wired to BOTH UserPromptSubmit and PreToolUse. It branches
3
- # on ONE structured signal read straight from the hook payload (stdin JSON), so the state is HARD — never
4
- # text-sniffed from the TUI:
5
- # the agent is pausing to ask the HUMAN (hp_is_ask) → status: asking, with the question text as the note
6
- # (the deterministic capture of a question).
7
- # any other tool, or a prompt submit → the agent is working → status: active (drop a now-stale proposal/note).
8
- # WHAT counts as "asking" is the [[harness-adapter]]'s call (Claude: the AskUserQuestion tool; Codex: the
9
- # request_user_input tool) — read via hp_is_ask, so this hook never names a harness tool.
10
- # Fires BEFORE the tool runs, so a `spex session done` declaration (itself a tool) lands AFTER this and wins;
11
- # the next real tool flips back to active, forcing a fresh Stop-gate declaration. Pure shell (no node/tsx) so
12
- # it stays cheap on every tool call — it value-replaces status/proposal/note in session.json with sed, never jq.
13
- # @@@ global store - state lives NOT in the worktree but in the per-session GLOBAL record session.json, keyed
14
- # by the harness session_id, grouped per-project (see hp_store_dir). GATED on `governed`: a user-self-launched
15
- # (non-governed) session has no board to feed, so this no-ops on it. cwd = the session worktree.
16
- . "${SPEXCODE_HARNESS_LIB:?harness.sh not exported by dispatch.sh}"
17
- payload=$(cat 2>/dev/null)
18
- # an IN-PROCESS SUBAGENT's tool call (Claude's Task tool) fires the parent's hooks with the PARENT's
19
- # session_id — flipping here let a supervising parent's own subagents erase its declared park/ask within
20
- # seconds and race the stop-gate into "undeclared stop" (issue #60). A subagent working is not the parent
21
- # agent ACTING, so its calls never touch the record; the parent's own next tool call still flips. The
22
- # discriminator is the payload's own top-level agent_id stamp (hp_is_subagent) — deterministic, never a
23
- # timing window.
24
- [ -n "$(hp_is_subagent "$payload")" ] && exit 0
25
- sid=$(hp_session_id "$payload"); [ -n "$sid" ] || exit 0
26
- sdir=$(hp_store_dir "$sid") || exit 0
27
- rec="$sdir/session.json"
28
- # board-lifecycle gate: only a GOVERNED (dashboard-launched) session has a board state to maintain.
29
- grep -q '"governed"[[:space:]]*:[[:space:]]*true' "$rec" 2>/dev/null || exit 0
30
-
31
- jget() { sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$rec" 2>/dev/null | head -1; }
32
-
33
- if [ -n "$(hp_is_ask "$payload")" ]; then
34
- status=asking
35
- note=$(hp_ask_note "$payload") # first question's text → the note (best-effort)
36
- else
37
- status=active
38
- note=
39
- fi
40
-
41
- # cheap path: already active with nothing stale to clear → no-op (the common every-tool case).
42
- [ "$status" = active ] && [ "$(jget status)" = active ] && [ -z "$(jget proposal)" ] && [ -z "$(jget note)" ] && exit 0
43
-
44
- # value-replace status + clear proposal + (re)set note, in place. The record is written one-field-per-line
45
- # with these keys ALWAYS present (sessions.ts writeRecord), so each is a single value substitution — no key
46
- # add/remove, no JSON parser. Escape \ / & in the note for the sed REPLACEMENT (the note never contains ").
47
- note_esc=$(printf '%s' "$note" | sed 's/[\\/&]/\\&/g')
48
- tmp=$(mktemp) || exit 0
49
- sed -e "s/\(\"status\"[[:space:]]*:[[:space:]]*\)\"[^\"]*\"/\1\"$status\"/" \
50
- -e "s/\(\"proposal\"[[:space:]]*:[[:space:]]*\)\"[^\"]*\"/\1\"\"/" \
51
- -e "s/\(\"note\"[[:space:]]*:[[:space:]]*\)\"[^\"]*\"/\1\"$note_esc\"/" \
52
- "$rec" > "$tmp" && mv "$tmp" "$rec"
53
- exit 0
@@ -1,46 +0,0 @@
1
- #!/usr/bin/env bash
2
- # @@@ mark-active - the SINGLE freshness hook, wired to BOTH UserPromptSubmit and PreToolUse. It branches
3
- # on ONE structured signal read straight from the hook payload (stdin JSON), so the state is HARD — never
4
- # text-sniffed from the TUI:
5
- # the agent is pausing to ask the HUMAN (hp_is_ask) → status: asking, with the question text as the note
6
- # (the deterministic capture of a question).
7
- # any other tool, or a prompt submit → the agent is working → status: active (drop a now-stale proposal/note).
8
- # WHAT counts as "asking" is the [[harness-adapter]]'s call (Claude: the AskUserQuestion tool; Codex: the
9
- # request_user_input tool) — read via hp_is_ask, so this hook never names a harness tool.
10
- # Fires BEFORE the tool runs, so a `spex session done` declaration (itself a tool) lands AFTER this and wins;
11
- # the next real tool flips back to active, forcing a fresh Stop-gate declaration. Pure shell (no node/tsx) so
12
- # it stays cheap on every tool call — it value-replaces status/proposal/note in session.json with sed, never jq.
13
- # @@@ global store - state lives NOT in the worktree but in the per-session GLOBAL record session.json, keyed
14
- # by the harness session_id, grouped per-project (see hp_store_dir). GATED on `governed`: a user-self-launched
15
- # (non-governed) session has no board to feed, so this no-ops on it. cwd = the session worktree.
16
- . "${SPEXCODE_HARNESS_LIB:?harness.sh not exported by dispatch.sh}"
17
- payload=$(cat 2>/dev/null)
18
- sid=$(hp_session_id "$payload"); [ -n "$sid" ] || exit 0
19
- sdir=$(hp_store_dir "$sid") || exit 0
20
- rec="$sdir/session.json"
21
- # board-lifecycle gate: only a GOVERNED (dashboard-launched) session has a board state to maintain.
22
- grep -q '"governed"[[:space:]]*:[[:space:]]*true' "$rec" 2>/dev/null || exit 0
23
-
24
- jget() { sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$rec" 2>/dev/null | head -1; }
25
-
26
- if [ -n "$(hp_is_ask "$payload")" ]; then
27
- status=asking
28
- note=$(hp_ask_note "$payload") # first question's text → the note (best-effort)
29
- else
30
- status=active
31
- note=
32
- fi
33
-
34
- # cheap path: already active with nothing stale to clear → no-op (the common every-tool case).
35
- [ "$status" = active ] && [ "$(jget status)" = active ] && [ -z "$(jget proposal)" ] && [ -z "$(jget note)" ] && exit 0
36
-
37
- # value-replace status + clear proposal + (re)set note, in place. The record is written one-field-per-line
38
- # with these keys ALWAYS present (sessions.ts writeRecord), so each is a single value substitution — no key
39
- # add/remove, no JSON parser. Escape \ / & in the note for the sed REPLACEMENT (the note never contains ").
40
- note_esc=$(printf '%s' "$note" | sed 's/[\\/&]/\\&/g')
41
- tmp=$(mktemp) || exit 0
42
- sed -e "s/\(\"status\"[[:space:]]*:[[:space:]]*\)\"[^\"]*\"/\1\"$status\"/" \
43
- -e "s/\(\"proposal\"[[:space:]]*:[[:space:]]*\)\"[^\"]*\"/\1\"\"/" \
44
- -e "s/\(\"note\"[[:space:]]*:[[:space:]]*\)\"[^\"]*\"/\1\"$note_esc\"/" \
45
- "$rec" > "$tmp" && mv "$tmp" "$rec"
46
- exit 0