spexcode 0.6.8 → 0.7.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/node_modules/@spexcode/session-application/package.json +5 -5
  2. package/node_modules/@spexcode/session-events/package.json +2 -2
  3. package/node_modules/@spexcode/session-protocol/package.json +1 -1
  4. package/node_modules/@spexcode/session-runtime/package.json +2 -2
  5. package/node_modules/@spexcode/session-selflaunch/dist/locality.d.ts +12 -1
  6. package/node_modules/@spexcode/session-selflaunch/dist/locality.js +62 -10
  7. package/node_modules/@spexcode/session-selflaunch/package.json +3 -3
  8. package/node_modules/@spexcode/session-topology/package.json +2 -2
  9. package/node_modules/@spexcode/spec-cli/bin/spex.mjs +127 -10
  10. package/node_modules/@spexcode/spec-cli/dist/cli.js +19 -0
  11. package/node_modules/@spexcode/spec-cli/dist/file-write.js +14 -2
  12. package/node_modules/@spexcode/spec-cli/dist/guide.js +9 -11
  13. package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +11 -5
  14. package/node_modules/@spexcode/spec-cli/dist/harness.js +185 -28
  15. package/node_modules/@spexcode/spec-cli/dist/host.js +5 -1
  16. package/node_modules/@spexcode/spec-cli/dist/index.js +21 -65
  17. package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +2 -1
  18. package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +45 -14
  19. package/node_modules/@spexcode/spec-cli/dist/opencode.js +10 -2
  20. package/node_modules/@spexcode/spec-cli/dist/pi-harness.js +18 -5
  21. package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +2 -0
  22. package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +27 -0
  23. package/node_modules/@spexcode/spec-cli/dist/session-transcript.d.ts +57 -0
  24. package/node_modules/@spexcode/spec-cli/dist/session-transcript.js +137 -0
  25. package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +4 -0
  26. package/node_modules/@spexcode/spec-cli/dist/sessions.js +104 -36
  27. package/node_modules/@spexcode/spec-cli/dist/shim-runtime.js +14 -0
  28. package/node_modules/@spexcode/spec-cli/dist/supervise.js +1 -1
  29. package/node_modules/@spexcode/spec-cli/package.json +8 -6
  30. package/node_modules/@spexcode/spec-core/dist/layout.d.ts +0 -1
  31. package/node_modules/@spexcode/spec-core/package.json +1 -1
  32. package/node_modules/@spexcode/spec-core/templates/spexcode.json +36 -13
  33. package/node_modules/@spexcode/spec-eval/package.json +2 -2
  34. package/node_modules/@spexcode/spec-forge/package.json +2 -2
  35. package/node_modules/@spexcode/transcript/dist/frames.d.ts +46 -0
  36. package/node_modules/@spexcode/transcript/dist/frames.js +87 -0
  37. package/node_modules/@spexcode/transcript/dist/index.d.ts +5 -0
  38. package/node_modules/@spexcode/transcript/dist/index.js +5 -0
  39. package/node_modules/@spexcode/transcript/dist/live.d.ts +16 -0
  40. package/node_modules/@spexcode/transcript/dist/live.js +65 -0
  41. package/node_modules/@spexcode/transcript/dist/parsers.d.ts +50 -0
  42. package/node_modules/@spexcode/transcript/dist/parsers.js +395 -0
  43. package/node_modules/@spexcode/transcript/dist/readers.d.ts +16 -0
  44. package/node_modules/@spexcode/transcript/dist/readers.js +375 -0
  45. package/node_modules/@spexcode/{spec-cli/dist/transcript-reader.d.ts → transcript/dist/turns.d.ts} +11 -5
  46. package/node_modules/@spexcode/transcript/dist/turns.js +11 -0
  47. package/node_modules/@spexcode/transcript/package.json +30 -0
  48. package/node_modules/smol-toml/LICENSE +24 -0
  49. package/node_modules/smol-toml/README.md +418 -0
  50. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  51. package/node_modules/smol-toml/dist/date.js +127 -0
  52. package/node_modules/smol-toml/dist/error.d.ts +38 -0
  53. package/node_modules/smol-toml/dist/error.js +63 -0
  54. package/node_modules/smol-toml/dist/extract.js +69 -0
  55. package/node_modules/smol-toml/dist/index.cjs +734 -0
  56. package/node_modules/smol-toml/dist/index.d.ts +43 -0
  57. package/node_modules/smol-toml/dist/index.js +33 -0
  58. package/node_modules/smol-toml/dist/parse.d.ts +36 -0
  59. package/node_modules/smol-toml/dist/parse.js +149 -0
  60. package/node_modules/smol-toml/dist/primitive.js +238 -0
  61. package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
  62. package/node_modules/smol-toml/dist/stringify.js +181 -0
  63. package/node_modules/smol-toml/dist/struct.js +179 -0
  64. package/node_modules/smol-toml/dist/util.d.ts +38 -0
  65. package/node_modules/smol-toml/dist/util.js +89 -0
  66. package/node_modules/smol-toml/package.json +68 -0
  67. package/package.json +3 -3
  68. package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +0 -27
  69. package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +0 -475
  70. package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +0 -10
  71. package/node_modules/@spexcode/spec-cli/dist/session-execution.js +0 -70
  72. package/node_modules/@spexcode/spec-cli/dist/transcript-reader.js +0 -251
@@ -1,251 +0,0 @@
1
- import { createReadStream, statSync } from 'node:fs';
2
- import { createInterface } from 'node:readline';
3
- import { claudeTranscriptPath, codexRolloutPath } from './execution-trace.js';
4
- export class TranscriptReadError extends Error {
5
- reason;
6
- constructor(reason, message) {
7
- super(message);
8
- this.reason = reason;
9
- this.name = 'TranscriptReadError';
10
- }
11
- }
12
- const MAX_TURNS = 200;
13
- const MAX_OUTPUT_BYTES = 64 * 1024;
14
- const POST_RANGE_LOOKAHEAD_LINES = 256;
15
- const object = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : null;
16
- const items = (value) => Array.isArray(value) ? value : [];
17
- const string = (value) => typeof value === 'string' && value.trim() ? value : null;
18
- const idOf = (value) => {
19
- if (!value)
20
- return null;
21
- for (const key of ['id', 'uuid', 'message_id', 'messageId', 'call_id', 'callId', 'client_id', 'clientId']) {
22
- const found = string(value[key]);
23
- if (found)
24
- return found;
25
- }
26
- return null;
27
- };
28
- const timestamp = (value) => {
29
- if (typeof value === 'number' && Number.isFinite(value))
30
- return value;
31
- if (typeof value !== 'string' || !value.trim())
32
- return null;
33
- const numeric = Number(value);
34
- if (Number.isFinite(numeric))
35
- return numeric;
36
- const parsed = Date.parse(value);
37
- return Number.isFinite(parsed) ? parsed : null;
38
- };
39
- const at = (value) => {
40
- if (!value)
41
- return null;
42
- for (const key of ['timestamp', 'created_at', 'createdAt', 'created', 'time']) {
43
- const candidate = timestamp(value[key]);
44
- if (candidate !== null)
45
- return candidate;
46
- }
47
- return null;
48
- };
49
- const compact = (value) => {
50
- if (typeof value === 'string')
51
- return value;
52
- try {
53
- return JSON.stringify(value) ?? String(value);
54
- }
55
- catch {
56
- return String(value);
57
- }
58
- };
59
- const lineCount = (value) => value ? value.split(/\r?\n/).length : 0;
60
- function claudeEvent(value) {
61
- const entry = object(value);
62
- const message = object(entry?.message);
63
- if (!entry || !message)
64
- return null;
65
- const eventAt = at(entry) ?? at(message);
66
- if (eventAt === null)
67
- return { at: null, turn: null };
68
- if (entry.type === 'user' && message.role === 'user') {
69
- const blocks = items(message.content);
70
- const text = typeof message.content === 'string'
71
- ? string(message.content)
72
- : blocks.map((block) => string(object(block)?.text)).filter(Boolean).join('\n') || null;
73
- const outputs = blocks.flatMap((block) => {
74
- const b = object(block);
75
- const id = string(b?.tool_use_id);
76
- return b?.type === 'tool_result' && id ? [{ id, text: compact(b?.content ?? '') }] : [];
77
- });
78
- if (outputs.length)
79
- return { at: eventAt, turn: null, toolOutputs: outputs };
80
- if (text)
81
- return { at: eventAt, turn: { id: idOf(entry) ?? idOf(message), at: eventAt, role: 'user', text, tools: [] } };
82
- }
83
- if (entry.type === 'assistant' && message.role === 'assistant') {
84
- const turn = { id: idOf(entry) ?? idOf(message), at: eventAt, role: 'assistant', tools: [] };
85
- for (const blockValue of items(message.content)) {
86
- const block = object(blockValue);
87
- if (block?.type === 'text')
88
- turn.text = [turn.text, string(block.text)].filter(Boolean).join('\n') || undefined;
89
- if (block?.type === 'tool_use') {
90
- const id = string(block.id) ?? `tool-${turn.tools.length}`;
91
- turn.tools.push({ id, name: string(block.name) ?? 'tool', input: block.input === undefined ? undefined : compact(block.input), outputLines: 0, outputBytes: 0 });
92
- }
93
- }
94
- return { at: eventAt, turn };
95
- }
96
- return null;
97
- }
98
- function codexEvent(value) {
99
- const entry = object(value);
100
- const payload = object(entry?.payload);
101
- if (!entry || !payload)
102
- return null;
103
- const eventAt = at(payload) ?? at(entry);
104
- if (eventAt === null)
105
- return { at: null, turn: null };
106
- const type = string(payload.type);
107
- if ((entry.type === 'event_msg' && type === 'user_message')
108
- || (entry.type === 'response_item' && (type === 'message' || type === 'input_message') && payload.role === 'user')) {
109
- const text = typeof payload.message === 'string' ? payload.message : compact(payload.message ?? payload.content ?? '');
110
- return text ? { at: eventAt, turn: { id: idOf(payload) ?? idOf(entry), at: eventAt, role: 'user', text, tools: [] } } : null;
111
- }
112
- if (entry.type === 'event_msg' && type === 'agent_message' && payload.phase === 'commentary') {
113
- const text = string(payload.message ?? payload.text);
114
- return text ? { at: eventAt, turn: { id: idOf(payload) ?? idOf(entry), at: eventAt, role: 'assistant', text, tools: [] } } : null;
115
- }
116
- if (entry.type === 'response_item' && (type === 'custom_tool_call' || type === 'function_call')) {
117
- const id = string(payload.call_id ?? payload.id) ?? 'tool';
118
- return { at: eventAt, turn: { id: idOf(payload) ?? idOf(entry), at: eventAt, role: 'assistant', tools: [{ id, name: string(payload.name ?? payload.tool_name) ?? 'tool', input: payload.input === undefined && payload.arguments === undefined ? undefined : compact(payload.input ?? payload.arguments), outputLines: 0, outputBytes: 0 }] } };
119
- }
120
- if (entry.type === 'response_item' && (type === 'custom_tool_call_output' || type === 'function_call_output')) {
121
- const id = string(payload.call_id ?? payload.id);
122
- const output = payload.output ?? payload.result ?? '';
123
- return id ? { at: eventAt, turn: null, toolOutputs: [{ id, text: compact(output) }] } : null;
124
- }
125
- return null;
126
- }
127
- function sourcePath(harness, threadId) {
128
- if (harness === 'claude' || harness === 'claude-headless')
129
- return claudeTranscriptPath(threadId);
130
- if (harness === 'codex' || harness === 'codex-headless')
131
- return codexRolloutPath(threadId);
132
- throw new TranscriptReadError('unsupported', `${harness} does not support transcript access`);
133
- }
134
- export async function readTranscript(harness, threadId, range) {
135
- const path = sourcePath(harness, threadId);
136
- if (!path)
137
- throw new TranscriptReadError('missing', `${harness} transcript for ${threadId} is unavailable: file was not found`);
138
- let size = 0;
139
- try {
140
- size = statSync(path).size;
141
- }
142
- catch (error) {
143
- throw new TranscriptReadError('unreadable', `${harness} transcript is unreadable: ${error instanceof Error ? error.message : String(error)}`);
144
- }
145
- if (size <= 0)
146
- throw new TranscriptReadError('unreadable', `${harness} transcript is unreadable: file is empty`);
147
- const parse = harness === 'claude' || harness === 'claude-headless' ? claudeEvent : codexEvent;
148
- const turns = [];
149
- const byTool = new Map();
150
- let sawTimestamp = false;
151
- let omittedTurns = 0;
152
- let omittedBytes = 0;
153
- let outOfOrderEvents = 0;
154
- let lookingPastRange = false;
155
- let postRangeLines = 0;
156
- const input = createReadStream(path, { encoding: 'utf8', highWaterMark: 64 * 1024 });
157
- const lines = createInterface({ input, crlfDelay: Infinity });
158
- try {
159
- for await (const line of lines) {
160
- if (!line.trim())
161
- continue;
162
- let value;
163
- try {
164
- value = JSON.parse(line);
165
- }
166
- catch (error) {
167
- throw new TranscriptReadError('invalid', `${harness} transcript cannot be parsed: ${error instanceof Error ? error.message : String(error)}`);
168
- }
169
- const event = parse(value);
170
- const stopAfterLine = lookingPastRange && ++postRangeLines >= POST_RANGE_LOOKAHEAD_LINES;
171
- if (!event) {
172
- if (stopAfterLine)
173
- break;
174
- continue;
175
- }
176
- const eventAt = event.at;
177
- const hasTimestamp = eventAt !== null;
178
- if (hasTimestamp)
179
- sawTimestamp = true;
180
- if (!lookingPastRange && hasTimestamp && eventAt > range.to) {
181
- lookingPastRange = true;
182
- }
183
- else if (lookingPastRange && hasTimestamp && eventAt <= range.to) {
184
- outOfOrderEvents++;
185
- }
186
- if (hasTimestamp && eventAt >= range.from && eventAt <= range.to) {
187
- if (event.toolOutputs) {
188
- for (const output of event.toolOutputs) {
189
- const bytes = Buffer.byteLength(output.text);
190
- const tool = byTool.get(output.id);
191
- if (!tool) {
192
- omittedBytes += bytes;
193
- continue;
194
- }
195
- tool.outputBytes += bytes;
196
- tool.outputLines += lineCount(output.text);
197
- if (tool.output === undefined)
198
- tool.output = '';
199
- const remaining = Math.max(0, MAX_OUTPUT_BYTES - Buffer.byteLength(tool.output));
200
- tool.output += output.text.slice(0, remaining);
201
- if (bytes > remaining)
202
- omittedBytes += bytes - remaining;
203
- }
204
- }
205
- else if (event.turn) {
206
- if (turns.length >= MAX_TURNS)
207
- omittedTurns++;
208
- else {
209
- turns.push(event.turn);
210
- for (const tool of event.turn.tools)
211
- byTool.set(tool.id, tool);
212
- }
213
- }
214
- }
215
- if (stopAfterLine)
216
- break;
217
- }
218
- }
219
- catch (error) {
220
- if (error instanceof TranscriptReadError)
221
- throw error;
222
- throw new TranscriptReadError('unreadable', `${harness} transcript could not be read: ${error instanceof Error ? error.message : String(error)}`);
223
- }
224
- finally {
225
- input.destroy();
226
- }
227
- if (!sawTimestamp)
228
- throw new TranscriptReadError('invalid', `${harness} transcript has no reliable timestamps; interval reads are unavailable`);
229
- const normalized = turns.map((turn) => ({
230
- ...turn,
231
- tools: turn.tools.length ? turn.tools.map((tool) => ({ ...tool })) : undefined,
232
- }));
233
- return {
234
- from: range.from,
235
- to: range.to,
236
- turns: normalized,
237
- truncated: omittedTurns > 0 || omittedBytes > 0 || outOfOrderEvents > 0,
238
- omittedTurns,
239
- omittedBytes,
240
- outOfOrderEvents,
241
- };
242
- }
243
- export async function readClaudeTranscript(threadId, range) {
244
- return readTranscript('claude', threadId, range);
245
- }
246
- export async function readCodexTranscript(threadId, range) {
247
- return readTranscript('codex', threadId, range);
248
- }
249
- export async function unsupportedTranscriptReader(harness, _threadId, _range) {
250
- throw new TranscriptReadError('unsupported', `${harness} does not support transcript access`);
251
- }