spexcode 0.6.7 → 0.7.0-next.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.
- package/README.md +12 -7
- package/node_modules/@spexcode/session-application/dist/index.d.ts +17 -0
- package/node_modules/@spexcode/session-application/dist/index.js +35 -0
- package/node_modules/@spexcode/session-application/dist/migration.d.ts +51 -0
- package/node_modules/@spexcode/session-application/dist/migration.js +694 -0
- package/node_modules/@spexcode/session-application/dist/production.d.ts +109 -0
- package/node_modules/@spexcode/session-application/dist/production.js +438 -0
- package/node_modules/@spexcode/session-application/dist/schema.d.ts +2 -0
- package/node_modules/@spexcode/session-application/dist/schema.js +39 -0
- package/node_modules/@spexcode/session-application/package.json +29 -0
- package/node_modules/@spexcode/session-events/dist/errors.d.ts +6 -0
- package/node_modules/@spexcode/session-events/dist/errors.js +11 -0
- package/node_modules/@spexcode/session-events/dist/index.d.ts +46 -0
- package/node_modules/@spexcode/session-events/dist/index.js +245 -0
- package/node_modules/@spexcode/session-events/dist/schema.d.ts +3 -0
- package/node_modules/@spexcode/session-events/dist/schema.js +44 -0
- package/node_modules/@spexcode/session-events/package.json +24 -0
- package/node_modules/@spexcode/session-protocol/dist/canonical.d.ts +36 -0
- package/node_modules/@spexcode/session-protocol/dist/canonical.js +139 -0
- package/node_modules/@spexcode/session-protocol/dist/engine.d.ts +22 -0
- package/node_modules/@spexcode/session-protocol/dist/engine.js +478 -0
- package/node_modules/@spexcode/session-protocol/dist/errors.d.ts +8 -0
- package/node_modules/@spexcode/session-protocol/dist/errors.js +39 -0
- package/node_modules/@spexcode/session-protocol/dist/index.d.ts +60 -0
- package/node_modules/@spexcode/session-protocol/dist/index.js +4 -0
- package/node_modules/@spexcode/session-protocol/dist/schema.d.ts +36 -0
- package/node_modules/@spexcode/session-protocol/dist/schema.js +192 -0
- package/node_modules/@spexcode/{session-core → session-protocol}/package.json +4 -8
- package/node_modules/@spexcode/session-runtime/dist/errors.d.ts +6 -0
- package/node_modules/@spexcode/session-runtime/dist/errors.js +11 -0
- package/node_modules/@spexcode/session-runtime/dist/index.d.ts +33 -0
- package/node_modules/@spexcode/session-runtime/dist/index.js +193 -0
- package/node_modules/@spexcode/session-runtime/dist/schema.d.ts +2 -0
- package/node_modules/@spexcode/session-runtime/dist/schema.js +37 -0
- package/node_modules/@spexcode/session-runtime/package.json +24 -0
- package/node_modules/@spexcode/session-selflaunch/bin/spex-session.mjs +4 -0
- package/node_modules/@spexcode/session-selflaunch/dist/cli.d.ts +25 -0
- package/node_modules/@spexcode/session-selflaunch/dist/cli.js +156 -0
- package/node_modules/@spexcode/session-selflaunch/dist/index.d.ts +28 -0
- package/node_modules/@spexcode/session-selflaunch/dist/index.js +25 -0
- package/node_modules/@spexcode/session-selflaunch/dist/locality.d.ts +31 -0
- package/node_modules/@spexcode/session-selflaunch/dist/locality.js +135 -0
- package/node_modules/@spexcode/session-selflaunch/dist/path.d.ts +13 -0
- package/node_modules/@spexcode/session-selflaunch/dist/path.js +45 -0
- package/node_modules/@spexcode/session-selflaunch/package.json +35 -0
- package/node_modules/@spexcode/session-topology/dist/errors.d.ts +6 -0
- package/node_modules/@spexcode/session-topology/dist/errors.js +11 -0
- package/node_modules/@spexcode/session-topology/dist/index.d.ts +24 -0
- package/node_modules/@spexcode/session-topology/dist/index.js +173 -0
- package/node_modules/@spexcode/session-topology/dist/schema.d.ts +3 -0
- package/node_modules/@spexcode/session-topology/dist/schema.js +27 -0
- package/node_modules/@spexcode/session-topology/package.json +24 -0
- package/node_modules/@spexcode/spec-cli/bin/spex.mjs +175 -21
- package/node_modules/@spexcode/spec-cli/dist/cli.js +121 -59
- package/node_modules/@spexcode/spec-cli/dist/client.d.ts +1 -3
- package/node_modules/@spexcode/spec-cli/dist/client.js +49 -30
- package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +11 -0
- package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +46 -9
- package/node_modules/@spexcode/spec-cli/dist/delivery-lock.d.ts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/delivery-lock.js +58 -0
- package/node_modules/@spexcode/spec-cli/dist/doctor.js +53 -11
- package/node_modules/@spexcode/spec-cli/dist/file-write.js +14 -2
- package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +2 -1
- package/node_modules/@spexcode/spec-cli/dist/gateway.js +6 -3
- package/node_modules/@spexcode/spec-cli/dist/graphCache.js +32 -2
- package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +57 -2
- package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/graphStream.js +83 -3
- package/node_modules/@spexcode/spec-cli/dist/guide.js +29 -18
- package/node_modules/@spexcode/spec-cli/dist/harness-select.js +16 -3
- package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +24 -6
- package/node_modules/@spexcode/spec-cli/dist/harness.js +510 -72
- package/node_modules/@spexcode/spec-cli/dist/help.js +11 -8
- package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +8 -0
- package/node_modules/@spexcode/spec-cli/dist/host-resources.js +29 -8
- package/node_modules/@spexcode/spec-cli/dist/host.d.ts +7 -0
- package/node_modules/@spexcode/spec-cli/dist/host.js +98 -1
- package/node_modules/@spexcode/spec-cli/dist/index.js +294 -36
- package/node_modules/@spexcode/spec-cli/dist/init.js +1 -1
- package/node_modules/@spexcode/spec-cli/dist/lint.js +70 -35
- package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +3 -2
- package/node_modules/@spexcode/spec-cli/dist/listen.js +14 -2
- package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +2 -1
- package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +46 -15
- package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +2 -2
- package/node_modules/@spexcode/spec-cli/dist/materialize.js +176 -35
- package/node_modules/@spexcode/spec-cli/dist/opencode.js +10 -2
- package/node_modules/@spexcode/spec-cli/dist/pi-harness.js +18 -5
- package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +27 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +14 -14
- package/node_modules/@spexcode/spec-cli/dist/reviews.js +12 -7
- package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +11 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +79 -1
- package/node_modules/@spexcode/spec-cli/dist/session-application.d.ts +23 -0
- package/node_modules/@spexcode/spec-cli/dist/session-application.js +189 -0
- package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +13 -1
- package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/session-files.js +13 -1
- package/node_modules/@spexcode/spec-cli/dist/session-follow.js +39 -22
- package/node_modules/@spexcode/{session-core/dist/record-lock.d.ts → spec-cli/dist/session-record-lock.d.ts} +0 -4
- package/node_modules/@spexcode/spec-cli/dist/session-record-lock.js +94 -0
- package/node_modules/@spexcode/spec-cli/dist/session-runtime-adapter.d.ts +44 -0
- package/node_modules/@spexcode/spec-cli/dist/session-runtime-adapter.js +37 -0
- package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +25 -2
- package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +68 -11
- package/node_modules/@spexcode/spec-cli/dist/session-transcript.d.ts +57 -0
- package/node_modules/@spexcode/spec-cli/dist/session-transcript.js +137 -0
- package/node_modules/@spexcode/spec-cli/dist/session-web.js +4 -4
- package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +112 -15
- package/node_modules/@spexcode/spec-cli/dist/sessions.js +1553 -764
- package/node_modules/@spexcode/spec-cli/dist/shim-runtime.js +14 -0
- package/node_modules/@spexcode/spec-cli/dist/source-list.d.ts +13 -0
- package/node_modules/@spexcode/spec-cli/dist/source-list.js +99 -0
- package/node_modules/@spexcode/spec-cli/dist/source-read.d.ts +16 -0
- package/node_modules/@spexcode/spec-cli/dist/source-read.js +84 -0
- package/node_modules/@spexcode/spec-cli/dist/spec-attachments.d.ts +7 -0
- package/node_modules/@spexcode/spec-cli/dist/spec-attachments.js +89 -0
- package/node_modules/@spexcode/spec-cli/dist/spec-body-edit.d.ts +23 -0
- package/node_modules/@spexcode/spec-cli/dist/spec-body-edit.js +138 -0
- package/node_modules/@spexcode/spec-cli/dist/supervise.js +15 -6
- package/node_modules/@spexcode/spec-cli/hooks/dispatch.sh +19 -31
- package/node_modules/@spexcode/spec-cli/hooks/harness.sh +6 -6
- package/node_modules/@spexcode/spec-cli/package.json +8 -6
- package/node_modules/@spexcode/spec-cli/templates/hooks/post-checkout +4 -2
- package/node_modules/@spexcode/spec-cli/templates/hooks/post-merge +2 -1
- package/node_modules/@spexcode/spec-cli/templates/hooks/pre-commit +5 -3
- package/node_modules/@spexcode/spec-cli/templates/hooks/reference-transaction +5 -3
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/spec.md +2 -7
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/idle/idle.sh +4 -10
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/idle/spec.md +1 -1
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/mark-active/mark-active.sh +22 -24
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/mark-active/spec.md +10 -2
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-fail/fail.sh +8 -7
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-fail/spec.md +3 -1
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-listen/session-listen.sh +133 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-listen/spec.md +36 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/spec.md +2 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/stop-gate/spec.md +1 -1
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +17 -20
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/skills/merge/spec.md +33 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/skills/spec.md +2 -6
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/spec.md +7 -0
- package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +13 -2
- package/node_modules/@spexcode/spec-core/dist/anchors.js +311 -15
- package/node_modules/@spexcode/spec-core/dist/git.d.ts +4 -0
- package/node_modules/@spexcode/spec-core/dist/git.js +9 -9
- package/node_modules/@spexcode/spec-core/dist/layout.d.ts +12 -1
- package/node_modules/@spexcode/spec-core/dist/layout.js +4 -2
- package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +1 -0
- package/node_modules/@spexcode/spec-core/dist/resilience.js +8 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +1 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +7 -3
- package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +1 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +4 -0
- package/node_modules/@spexcode/spec-core/dist/specs.d.ts +2 -0
- package/node_modules/@spexcode/spec-core/dist/specs.js +30 -0
- package/node_modules/@spexcode/spec-core/package.json +4 -1
- package/node_modules/@spexcode/spec-core/templates/spexcode.json +36 -13
- package/node_modules/@spexcode/spec-eval/dist/cli.js +1 -1
- package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +1 -0
- package/node_modules/@spexcode/spec-eval/dist/evaltab.js +6 -1
- package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +1 -1
- package/node_modules/@spexcode/spec-eval/dist/freshness.js +12 -12
- package/node_modules/@spexcode/spec-eval/dist/host.d.ts +2 -1
- package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +2 -1
- package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +20 -3
- package/node_modules/@spexcode/spec-eval/package.json +2 -2
- package/node_modules/@spexcode/spec-forge/package.json +2 -2
- package/node_modules/@spexcode/transcript/dist/frames.d.ts +44 -0
- package/node_modules/@spexcode/transcript/dist/frames.js +85 -0
- package/node_modules/@spexcode/transcript/dist/index.d.ts +5 -0
- package/node_modules/@spexcode/transcript/dist/index.js +5 -0
- package/node_modules/@spexcode/transcript/dist/live.d.ts +16 -0
- package/node_modules/@spexcode/transcript/dist/live.js +65 -0
- package/node_modules/@spexcode/transcript/dist/parsers.d.ts +47 -0
- package/node_modules/@spexcode/transcript/dist/parsers.js +285 -0
- package/node_modules/@spexcode/transcript/dist/readers.d.ts +10 -0
- package/node_modules/@spexcode/transcript/dist/readers.js +296 -0
- package/node_modules/@spexcode/transcript/dist/turns.d.ts +42 -0
- package/node_modules/@spexcode/transcript/dist/turns.js +11 -0
- package/node_modules/@spexcode/transcript/package.json +30 -0
- package/node_modules/@vscode/tree-sitter-wasm/LICENSE +21 -0
- package/node_modules/@vscode/tree-sitter-wasm/README.md +36 -0
- package/node_modules/@vscode/tree-sitter-wasm/SECURITY.md +41 -0
- package/node_modules/@vscode/tree-sitter-wasm/cgmanifest.json +16 -0
- package/node_modules/@vscode/tree-sitter-wasm/package.json +42 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-bash.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-c-sharp.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-cpp.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-css.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-go.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-ini.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-java.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-javascript.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-php.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-powershell.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-python.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-regex.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-ruby.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-rust.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-tsx.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-typescript.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.js +4075 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter.d.ts +1027 -0
- package/node_modules/smol-toml/LICENSE +24 -0
- package/node_modules/smol-toml/README.md +418 -0
- package/node_modules/smol-toml/dist/date.d.ts +41 -0
- package/node_modules/smol-toml/dist/date.js +127 -0
- package/node_modules/smol-toml/dist/error.d.ts +38 -0
- package/node_modules/smol-toml/dist/error.js +63 -0
- package/node_modules/smol-toml/dist/extract.js +69 -0
- package/node_modules/smol-toml/dist/index.cjs +734 -0
- package/node_modules/smol-toml/dist/index.d.ts +43 -0
- package/node_modules/smol-toml/dist/index.js +33 -0
- package/node_modules/smol-toml/dist/parse.d.ts +36 -0
- package/node_modules/smol-toml/dist/parse.js +149 -0
- package/node_modules/smol-toml/dist/primitive.js +238 -0
- package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
- package/node_modules/smol-toml/dist/stringify.js +181 -0
- package/node_modules/smol-toml/dist/struct.js +179 -0
- package/node_modules/smol-toml/dist/util.d.ts +38 -0
- package/node_modules/smol-toml/dist/util.js +89 -0
- package/node_modules/smol-toml/package.json +68 -0
- package/package.json +9 -3
- package/node_modules/@spexcode/session-core/dist/delivery-queue.d.ts +0 -24
- package/node_modules/@spexcode/session-core/dist/delivery-queue.js +0 -206
- package/node_modules/@spexcode/session-core/dist/index.d.ts +0 -5
- package/node_modules/@spexcode/session-core/dist/index.js +0 -5
- package/node_modules/@spexcode/session-core/dist/internal.d.ts +0 -3
- package/node_modules/@spexcode/session-core/dist/internal.js +0 -3
- package/node_modules/@spexcode/session-core/dist/message.d.ts +0 -22
- package/node_modules/@spexcode/session-core/dist/message.js +0 -53
- package/node_modules/@spexcode/session-core/dist/record-lock.js +0 -152
- package/node_modules/@spexcode/session-core/dist/runtime-session.d.ts +0 -62
- package/node_modules/@spexcode/session-core/dist/runtime-session.js +0 -326
- package/node_modules/@spexcode/session-core/dist/session-cursors.d.ts +0 -14
- package/node_modules/@spexcode/session-core/dist/session-cursors.js +0 -82
- package/node_modules/@spexcode/session-core/dist/session-timeline.d.ts +0 -47
- package/node_modules/@spexcode/session-core/dist/session-timeline.js +0 -216
- package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +0 -26
- package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +0 -475
- package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +0 -10
- package/node_modules/@spexcode/spec-cli/dist/session-execution.js +0 -70
- package/node_modules/@spexcode/spec-cli/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -53
- package/node_modules/@spexcode/spec-cli/hooks/compat/mark-active-sed-v0.fixture +0 -46
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { closeSync, openSync, readFileSync, readSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
const cache = new Map();
|
|
6
|
-
const sessionJsonlPaths = new Map();
|
|
7
|
-
const localStoreCache = new Map();
|
|
8
|
-
const emptyTrace = (turn, revision = '0') => ({
|
|
9
|
-
revision: `${turn?.token ?? '0'}:${revision}`,
|
|
10
|
-
turnId: turn?.token ?? null,
|
|
11
|
-
workingNote: null,
|
|
12
|
-
steps: [],
|
|
13
|
-
});
|
|
14
|
-
const object = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
15
|
-
const items = (value) => Array.isArray(value) ? value : [];
|
|
16
|
-
const text = (value) => typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
17
|
-
const field = (value, ...names) => {
|
|
18
|
-
for (const name of names) {
|
|
19
|
-
const found = text(value[name]);
|
|
20
|
-
if (found)
|
|
21
|
-
return found;
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
24
|
-
};
|
|
25
|
-
const children = (dir) => { try {
|
|
26
|
-
return readdirSync(dir).sort().reverse();
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return [];
|
|
30
|
-
} };
|
|
31
|
-
const sensitive = /(token|secret|password|authorization|bearer|cookie|credential|api[_-]?key|private[ _-]?key)/i;
|
|
32
|
-
const detailFields = [
|
|
33
|
-
['cmd', 'cmd'], ['command', 'command'], ['path', 'path'], ['file_path', 'path'],
|
|
34
|
-
['query', 'query'], ['pattern', 'pattern'], ['url', 'url'], ['workdir', 'in'],
|
|
35
|
-
];
|
|
36
|
-
function toolKind(name) {
|
|
37
|
-
const normalized = name.toLowerCase();
|
|
38
|
-
if (/(search|browse|web)/.test(normalized))
|
|
39
|
-
return 'search';
|
|
40
|
-
if (/(read|list|find|grep|glob|stat)/.test(normalized))
|
|
41
|
-
return 'read';
|
|
42
|
-
if (/(write|edit|patch|create|delete|move|rename)/.test(normalized))
|
|
43
|
-
return 'write';
|
|
44
|
-
if (/(exec|shell|command|terminal|bash|npm|git)/.test(normalized))
|
|
45
|
-
return 'command';
|
|
46
|
-
return 'tool';
|
|
47
|
-
}
|
|
48
|
-
function toolLabel(name, kind) {
|
|
49
|
-
const labels = {
|
|
50
|
-
command: 'Run command', read: 'Read files', write: 'Update files', search: 'Search', tool: 'Use tool',
|
|
51
|
-
};
|
|
52
|
-
return name.trim() || labels[kind];
|
|
53
|
-
}
|
|
54
|
-
function recordInput(value) {
|
|
55
|
-
if (value && typeof value === 'object' && !Array.isArray(value))
|
|
56
|
-
return value;
|
|
57
|
-
if (typeof value !== 'string')
|
|
58
|
-
return null;
|
|
59
|
-
try {
|
|
60
|
-
const parsed = JSON.parse(value);
|
|
61
|
-
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function sensitiveInput(value) {
|
|
68
|
-
if (typeof value === 'string')
|
|
69
|
-
return sensitive.test(value);
|
|
70
|
-
if (Array.isArray(value))
|
|
71
|
-
return value.some(sensitiveInput);
|
|
72
|
-
if (value && typeof value === 'object')
|
|
73
|
-
return Object.entries(value)
|
|
74
|
-
.some(([key, entry]) => sensitive.test(key) || sensitiveInput(entry));
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
function compactValue(key, value) {
|
|
78
|
-
const compact = String(value).trim().replace(/\s+/g, ' ');
|
|
79
|
-
if (key === 'path' || key === 'file_path' || key === 'workdir') {
|
|
80
|
-
const parts = compact.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
81
|
-
return parts.slice(-2).join('/') || compact;
|
|
82
|
-
}
|
|
83
|
-
return compact.length > 96 ? `${compact.slice(0, 95)}...` : compact;
|
|
84
|
-
}
|
|
85
|
-
function detailFromInput(value) {
|
|
86
|
-
const input = recordInput(value);
|
|
87
|
-
if (!input || sensitiveInput(input))
|
|
88
|
-
return undefined;
|
|
89
|
-
const details = [];
|
|
90
|
-
for (const [key, title] of detailFields) {
|
|
91
|
-
const value = input[key];
|
|
92
|
-
if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean')
|
|
93
|
-
continue;
|
|
94
|
-
details.push(`${title}: ${compactValue(key, value)}`);
|
|
95
|
-
if (details.length === 2)
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
const lineStart = input.line_start;
|
|
99
|
-
const lineEnd = input.line_end;
|
|
100
|
-
if (details.length < 2 && (typeof lineStart === 'number' || typeof lineEnd === 'number')) {
|
|
101
|
-
details.push(`lines: ${typeof lineStart === 'number' ? lineStart : 1}-${typeof lineEnd === 'number' ? lineEnd : lineStart}`);
|
|
102
|
-
}
|
|
103
|
-
return details.length ? details.join(' · ') : undefined;
|
|
104
|
-
}
|
|
105
|
-
function displayableNote(value) {
|
|
106
|
-
const note = text(value)?.replace(/\s+/g, ' ');
|
|
107
|
-
return note ? (note.length > 240 ? `${note.slice(0, 239)}...` : note) : null;
|
|
108
|
-
}
|
|
109
|
-
function stepFromInput(id, name, input, state) {
|
|
110
|
-
const kind = toolKind(name);
|
|
111
|
-
const detail = detailFromInput(input);
|
|
112
|
-
return { id, kind, label: toolLabel(name, kind), ...(detail ? { detail } : {}), state };
|
|
113
|
-
}
|
|
114
|
-
function timestamp(value) {
|
|
115
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
116
|
-
return value;
|
|
117
|
-
if (typeof value !== 'string' || !value.trim())
|
|
118
|
-
return null;
|
|
119
|
-
const number = Number(value);
|
|
120
|
-
if (Number.isFinite(number))
|
|
121
|
-
return number;
|
|
122
|
-
const parsed = Date.parse(value);
|
|
123
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
124
|
-
}
|
|
125
|
-
function identities(...values) {
|
|
126
|
-
const result = new Set();
|
|
127
|
-
for (const value of values)
|
|
128
|
-
for (const key of ['id', 'uuid', 'message_id', 'messageId', 'client_id', 'clientId', 'client_user_message_id', 'clientUserMessageId', 'promptId']) {
|
|
129
|
-
const id = field(value || {}, key);
|
|
130
|
-
if (id)
|
|
131
|
-
result.add(id);
|
|
132
|
-
}
|
|
133
|
-
return [...result];
|
|
134
|
-
}
|
|
135
|
-
function at(value) {
|
|
136
|
-
for (const key of ['timestamp', 'created_at', 'createdAt', 'created', 'time']) {
|
|
137
|
-
const found = timestamp(value[key]);
|
|
138
|
-
if (found !== null)
|
|
139
|
-
return found;
|
|
140
|
-
}
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
function isPrompt(content) {
|
|
144
|
-
if (typeof content === 'string')
|
|
145
|
-
return !!content.trim();
|
|
146
|
-
const blocks = items(content);
|
|
147
|
-
return blocks.some((block) => object(block)?.type !== 'tool_result');
|
|
148
|
-
}
|
|
149
|
-
function project(events, sourceRevision, turn) {
|
|
150
|
-
let start = -1;
|
|
151
|
-
if (!turn) {
|
|
152
|
-
for (const [index, event] of events.entries())
|
|
153
|
-
if (event.type === 'boundary')
|
|
154
|
-
start = index;
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
const acceptedAt = timestamp(turn.acceptedAt);
|
|
158
|
-
const exact = events.flatMap((event, index) => event.type === 'boundary' && event.ids.includes(turn.token) ? [index] : []);
|
|
159
|
-
start = exact.length === 1 ? exact[0] : -1;
|
|
160
|
-
if (start < 0 && exact.length === 0 && acceptedAt !== null) {
|
|
161
|
-
const candidates = events.flatMap((event, index) => event.type === 'boundary' && event.at !== null && event.at >= acceptedAt
|
|
162
|
-
? [{ index, at: event.at }] : []);
|
|
163
|
-
const earliest = candidates.reduce((value, candidate) => value === null || candidate.at < value ? candidate.at : value, null);
|
|
164
|
-
const matches = earliest === null ? [] : candidates.filter((candidate) => candidate.at === earliest);
|
|
165
|
-
if (matches.length === 1)
|
|
166
|
-
start = matches[0].index;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
if (turn && start < 0)
|
|
170
|
-
return emptyTrace(turn, sourceRevision);
|
|
171
|
-
let note = null;
|
|
172
|
-
let steps = [];
|
|
173
|
-
for (let index = start + 1; index < events.length; index++) {
|
|
174
|
-
const event = events[index];
|
|
175
|
-
if (event.type === 'boundary')
|
|
176
|
-
return emptyTrace(turn, sourceRevision);
|
|
177
|
-
if (event.type === 'note') {
|
|
178
|
-
note = event.text;
|
|
179
|
-
steps = [];
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
if (event.type === 'call') {
|
|
183
|
-
if (note)
|
|
184
|
-
steps.push(event.step);
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const step = steps.find((value) => value.id === event.id);
|
|
188
|
-
if (step)
|
|
189
|
-
steps[steps.indexOf(step)] = { ...step, state: 'done' };
|
|
190
|
-
}
|
|
191
|
-
return note
|
|
192
|
-
? { revision: `${turn?.token ?? '0'}:${sourceRevision}`, turnId: turn?.token ?? null, workingNote: note, steps }
|
|
193
|
-
: emptyTrace(turn, sourceRevision);
|
|
194
|
-
}
|
|
195
|
-
function applyRolloutEvent(value, ordinal) {
|
|
196
|
-
const event = object(value);
|
|
197
|
-
const payload = object(event?.payload);
|
|
198
|
-
if (!event || !payload)
|
|
199
|
-
return [];
|
|
200
|
-
const result = [];
|
|
201
|
-
const type = field(payload, 'type');
|
|
202
|
-
if ((event.type === 'event_msg' && type === 'user_message')
|
|
203
|
-
|| (event.type === 'response_item' && (type === 'message' || type === 'input_message') && payload.role === 'user')) {
|
|
204
|
-
result.push({ type: 'boundary', ids: identities(payload, event), at: at(payload) ?? at(event) });
|
|
205
|
-
}
|
|
206
|
-
if (event.type === 'event_msg' && type === 'agent_message' && payload.phase === 'commentary') {
|
|
207
|
-
const note = displayableNote(payload.message ?? payload.text);
|
|
208
|
-
if (note)
|
|
209
|
-
result.push({ type: 'note', text: note });
|
|
210
|
-
}
|
|
211
|
-
if (event.type === 'response_item' && (type === 'custom_tool_call' || type === 'function_call')) {
|
|
212
|
-
const name = field(payload, 'name', 'tool_name') || 'tool';
|
|
213
|
-
const id = field(payload, 'call_id', 'id') || `tool-${ordinal}`;
|
|
214
|
-
result.push({ type: 'call', step: stepFromInput(id, name, payload.input ?? payload.arguments ?? payload.args, 'running') });
|
|
215
|
-
}
|
|
216
|
-
if (event.type === 'response_item' && (type === 'custom_tool_call_output' || type === 'function_call_output')) {
|
|
217
|
-
const id = field(payload, 'call_id', 'id');
|
|
218
|
-
if (id)
|
|
219
|
-
result.push({ type: 'done', id });
|
|
220
|
-
}
|
|
221
|
-
if (event.type === 'event_msg' && type === 'web_search_end') {
|
|
222
|
-
result.push({ type: 'call', step: stepFromInput(field(payload, 'id', 'call_id') || `search-${ordinal}`, 'Search', payload, 'done') });
|
|
223
|
-
}
|
|
224
|
-
return result;
|
|
225
|
-
}
|
|
226
|
-
function applyProjectJsonlEvent(value, ordinal) {
|
|
227
|
-
const entry = object(value);
|
|
228
|
-
const message = object(entry?.message);
|
|
229
|
-
if (!entry || !message)
|
|
230
|
-
return [];
|
|
231
|
-
const result = [];
|
|
232
|
-
if (entry.type === 'user' && message.role === 'user' && isPrompt(message.content))
|
|
233
|
-
result.push({ type: 'boundary', ids: identities(entry, message), at: at(entry) ?? at(message) });
|
|
234
|
-
if (entry.type === 'assistant' && message.role === 'assistant')
|
|
235
|
-
for (const blockValue of items(message.content)) {
|
|
236
|
-
const block = object(blockValue);
|
|
237
|
-
if (block?.type === 'text') {
|
|
238
|
-
const note = displayableNote(block.text);
|
|
239
|
-
if (note)
|
|
240
|
-
result.push({ type: 'note', text: note });
|
|
241
|
-
}
|
|
242
|
-
if (block?.type === 'tool_use') {
|
|
243
|
-
const id = field(block, 'id') || `tool-${ordinal}`;
|
|
244
|
-
result.push({ type: 'call', step: stepFromInput(id, field(block, 'name') || 'tool', block.input, 'running') });
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
if (entry.type === 'user' && message.role === 'user')
|
|
248
|
-
for (const blockValue of items(message.content)) {
|
|
249
|
-
const block = object(blockValue);
|
|
250
|
-
if (block?.type === 'tool_result') {
|
|
251
|
-
const id = field(block, 'tool_use_id');
|
|
252
|
-
if (id)
|
|
253
|
-
result.push({ type: 'done', id });
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return result;
|
|
257
|
-
}
|
|
258
|
-
function applySessionJsonlEvent(value, ordinal) {
|
|
259
|
-
const entry = object(value);
|
|
260
|
-
const message = object(entry?.message);
|
|
261
|
-
if (!entry || entry.type !== 'message' || !message)
|
|
262
|
-
return [];
|
|
263
|
-
const result = [];
|
|
264
|
-
if (message.role === 'user' && isPrompt(message.content))
|
|
265
|
-
result.push({ type: 'boundary', ids: identities(entry, message), at: at(entry) ?? at(message) });
|
|
266
|
-
if (message.role === 'assistant')
|
|
267
|
-
for (const blockValue of items(message.content)) {
|
|
268
|
-
const block = object(blockValue);
|
|
269
|
-
if (block?.type === 'text') {
|
|
270
|
-
const note = displayableNote(block.text);
|
|
271
|
-
if (note)
|
|
272
|
-
result.push({ type: 'note', text: note });
|
|
273
|
-
}
|
|
274
|
-
if (block?.type === 'toolCall') {
|
|
275
|
-
const id = field(block, 'id') || `tool-${ordinal}`;
|
|
276
|
-
result.push({ type: 'call', step: stepFromInput(id, field(block, 'name') || 'tool', block.arguments, 'running') });
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
if (message.role === 'toolResult') {
|
|
280
|
-
const id = field(message, 'toolCallId');
|
|
281
|
-
if (id)
|
|
282
|
-
result.push({ type: 'done', id });
|
|
283
|
-
}
|
|
284
|
-
return result;
|
|
285
|
-
}
|
|
286
|
-
function completeLines(buffer) {
|
|
287
|
-
const lines = [];
|
|
288
|
-
let start = 0;
|
|
289
|
-
for (let index = 0; index < buffer.length; index++) {
|
|
290
|
-
if (buffer[index] !== 10)
|
|
291
|
-
continue;
|
|
292
|
-
if (index > start)
|
|
293
|
-
lines.push(buffer.subarray(start, index));
|
|
294
|
-
start = index + 1;
|
|
295
|
-
}
|
|
296
|
-
return { lines, remainder: buffer.subarray(start) };
|
|
297
|
-
}
|
|
298
|
-
function readFrom(path, start) {
|
|
299
|
-
let fd = null;
|
|
300
|
-
try {
|
|
301
|
-
const size = statSync(path).size;
|
|
302
|
-
if (size <= start)
|
|
303
|
-
return Buffer.alloc(0);
|
|
304
|
-
const chunk = Buffer.allocUnsafe(size - start);
|
|
305
|
-
fd = openSync(path, 'r');
|
|
306
|
-
readSync(fd, chunk, 0, chunk.length, start);
|
|
307
|
-
return chunk;
|
|
308
|
-
}
|
|
309
|
-
catch {
|
|
310
|
-
return null;
|
|
311
|
-
}
|
|
312
|
-
finally {
|
|
313
|
-
if (fd !== null)
|
|
314
|
-
closeSync(fd);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
function readIncremental(path, apply, turn) {
|
|
318
|
-
if (!path)
|
|
319
|
-
return emptyTrace(turn);
|
|
320
|
-
let size;
|
|
321
|
-
try {
|
|
322
|
-
size = statSync(path).size;
|
|
323
|
-
}
|
|
324
|
-
catch {
|
|
325
|
-
return emptyTrace(turn);
|
|
326
|
-
}
|
|
327
|
-
const prior = cache.get(path);
|
|
328
|
-
const trace = prior && prior.size <= size
|
|
329
|
-
? { ...prior, events: [...prior.events] }
|
|
330
|
-
: { size: 0, remainder: Buffer.alloc(0), events: [] };
|
|
331
|
-
const appended = readFrom(path, trace.size);
|
|
332
|
-
if (appended === null)
|
|
333
|
-
return emptyTrace(turn);
|
|
334
|
-
const { lines, remainder } = completeLines(Buffer.concat([trace.remainder, appended]));
|
|
335
|
-
for (const [index, line] of lines.entries()) {
|
|
336
|
-
try {
|
|
337
|
-
trace.events.push(...apply(JSON.parse(line.toString('utf8')), trace.size + index));
|
|
338
|
-
}
|
|
339
|
-
catch { /* malformed native data is ignored */ }
|
|
340
|
-
}
|
|
341
|
-
trace.size = size;
|
|
342
|
-
trace.remainder = remainder;
|
|
343
|
-
cache.set(path, trace);
|
|
344
|
-
return project(trace.events, String(size), turn);
|
|
345
|
-
}
|
|
346
|
-
const codexSessionsDir = () => join(process.env.CODEX_HOME || join(homedir(), '.codex'), 'sessions');
|
|
347
|
-
export function codexRolloutPath(threadId, root = codexSessionsDir()) {
|
|
348
|
-
for (const year of children(root))
|
|
349
|
-
for (const month of children(join(root, year)))
|
|
350
|
-
for (const day of children(join(root, year, month))) {
|
|
351
|
-
const dir = join(root, year, month, day);
|
|
352
|
-
const file = children(dir).find((name) => name.includes(threadId));
|
|
353
|
-
if (file)
|
|
354
|
-
return join(dir, file);
|
|
355
|
-
}
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
|
-
export function readCodexExecutionTrace(threadId, turn, root = codexSessionsDir()) {
|
|
359
|
-
return readIncremental(codexRolloutPath(threadId, root), applyRolloutEvent, turn);
|
|
360
|
-
}
|
|
361
|
-
const projectTranscriptRoot = () => join(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'projects');
|
|
362
|
-
function projectJsonlPath(threadId, root = projectTranscriptRoot()) {
|
|
363
|
-
for (const project of children(root)) {
|
|
364
|
-
const path = join(root, project, `${threadId}.jsonl`);
|
|
365
|
-
try {
|
|
366
|
-
if (statSync(path).isFile())
|
|
367
|
-
return path;
|
|
368
|
-
}
|
|
369
|
-
catch { /* try next */ }
|
|
370
|
-
}
|
|
371
|
-
return null;
|
|
372
|
-
}
|
|
373
|
-
export function readProjectJsonlExecutionTrace(threadId, turn, root = projectTranscriptRoot()) {
|
|
374
|
-
return readIncremental(projectJsonlPath(threadId, root), applyProjectJsonlEvent, turn);
|
|
375
|
-
}
|
|
376
|
-
const sessionJsonlRoot = () => join(process.env.SPEXCODE_PI_AGENT_DIR || join(homedir(), '.pi', 'agent'), 'sessions');
|
|
377
|
-
function sessionJsonlPath(threadId, root = sessionJsonlRoot()) {
|
|
378
|
-
const key = `${root}:${threadId}`;
|
|
379
|
-
const cached = sessionJsonlPaths.get(key);
|
|
380
|
-
if (cached) {
|
|
381
|
-
try {
|
|
382
|
-
if (statSync(cached).isFile())
|
|
383
|
-
return cached;
|
|
384
|
-
}
|
|
385
|
-
catch {
|
|
386
|
-
sessionJsonlPaths.delete(key);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
for (const directory of children(root))
|
|
390
|
-
for (const file of children(join(root, directory))) {
|
|
391
|
-
if (!file.endsWith('.jsonl'))
|
|
392
|
-
continue;
|
|
393
|
-
const path = join(root, directory, file);
|
|
394
|
-
try {
|
|
395
|
-
const header = object(JSON.parse(readFileSync(path, 'utf8').split('\n', 1)[0]));
|
|
396
|
-
if (header?.type === 'session' && header.id === threadId) {
|
|
397
|
-
sessionJsonlPaths.set(key, path);
|
|
398
|
-
return path;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
catch { /* unreadable entries are not a match */ }
|
|
402
|
-
}
|
|
403
|
-
return null;
|
|
404
|
-
}
|
|
405
|
-
export function readSessionJsonlExecutionTrace(threadId, turn, root = sessionJsonlRoot()) {
|
|
406
|
-
return readIncremental(sessionJsonlPath(threadId, root), applySessionJsonlEvent, turn);
|
|
407
|
-
}
|
|
408
|
-
const localStoreRoot = () => process.env.SPEXCODE_OPENCODE_DATA_DIR
|
|
409
|
-
|| join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), 'opencode');
|
|
410
|
-
function readLocalExport(threadId) {
|
|
411
|
-
return execFileSync(process.env.SPEXCODE_OPENCODE_CMD || 'opencode', ['export', threadId, '--sanitize'], {
|
|
412
|
-
encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
function localStoreRevision(root) {
|
|
416
|
-
try {
|
|
417
|
-
const database = statSync(join(root, 'opencode.db'));
|
|
418
|
-
let writeAheadLog = '0:0';
|
|
419
|
-
try {
|
|
420
|
-
const stat = statSync(join(root, 'opencode.db-wal'));
|
|
421
|
-
writeAheadLog = `${stat.size}:${Math.floor(stat.mtimeMs)}`;
|
|
422
|
-
}
|
|
423
|
-
catch { /* a checkpointed database has no separate write-ahead log */ }
|
|
424
|
-
return `${database.size}:${Math.floor(database.mtimeMs)}:${writeAheadLog}`;
|
|
425
|
-
}
|
|
426
|
-
catch {
|
|
427
|
-
return null;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
function localStoreEvents(value) {
|
|
431
|
-
const events = [];
|
|
432
|
-
for (const messageValue of items(object(value)?.messages)) {
|
|
433
|
-
const message = object(messageValue);
|
|
434
|
-
const info = object(message?.info);
|
|
435
|
-
if (!message || !info)
|
|
436
|
-
continue;
|
|
437
|
-
if (info.role === 'user')
|
|
438
|
-
events.push({ type: 'boundary', ids: identities(info, message), at: at(object(info.time) || info) });
|
|
439
|
-
if (info.role !== 'assistant')
|
|
440
|
-
continue;
|
|
441
|
-
for (const partValue of items(message.parts)) {
|
|
442
|
-
const part = object(partValue);
|
|
443
|
-
if (part?.type === 'text') {
|
|
444
|
-
const note = displayableNote(part.text);
|
|
445
|
-
if (note)
|
|
446
|
-
events.push({ type: 'note', text: note });
|
|
447
|
-
}
|
|
448
|
-
if (part?.type === 'tool') {
|
|
449
|
-
const state = object(part.state);
|
|
450
|
-
const status = field(state || {}, 'status')?.toLowerCase() || '';
|
|
451
|
-
const id = field(part, 'callID', 'id') || `tool-${events.length}`;
|
|
452
|
-
events.push({ type: 'call', step: stepFromInput(id, field(part, 'tool') || 'tool', state?.input, /completed|error|cancelled/.test(status) ? 'done' : 'running') });
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
return events;
|
|
457
|
-
}
|
|
458
|
-
export function readLocalStoreExecutionTrace(threadId, turn, root = localStoreRoot(), load = readLocalExport) {
|
|
459
|
-
const revision = localStoreRevision(root);
|
|
460
|
-
if (!revision)
|
|
461
|
-
return emptyTrace(turn);
|
|
462
|
-
const key = `${root}:${threadId}`;
|
|
463
|
-
let cached = localStoreCache.get(key);
|
|
464
|
-
if (!cached || cached.revision !== revision) {
|
|
465
|
-
try {
|
|
466
|
-
cached = { revision, events: localStoreEvents(JSON.parse(load(threadId))) };
|
|
467
|
-
localStoreCache.set(key, cached);
|
|
468
|
-
}
|
|
469
|
-
catch {
|
|
470
|
-
return emptyTrace(turn, revision);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
return project(cached.events, revision, turn);
|
|
474
|
-
}
|
|
475
|
-
export const noExecutionTrace = (_threadId, _turn) => null;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { Context } from 'hono';
|
|
2
|
-
import type { ExecutionStep } from './execution-trace.js';
|
|
3
|
-
export type SessionExecution = Readonly<{
|
|
4
|
-
revision: string;
|
|
5
|
-
turnId: string | null;
|
|
6
|
-
workingNote: string | null;
|
|
7
|
-
steps: readonly ExecutionStep[];
|
|
8
|
-
}>;
|
|
9
|
-
export declare function readSessionExecution(id: string): SessionExecution | null;
|
|
10
|
-
export declare function sessionExecutionStream(c: Context): Promise<Response>;
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { streamSSE } from 'hono/streaming';
|
|
2
|
-
import { harnessById } from './harness.js';
|
|
3
|
-
import { readAliasedRawRecord } from '@spexcode/spec-core';
|
|
4
|
-
import { currentHumanTurn } from './session-timeline.js';
|
|
5
|
-
const absentExecution = (turn = null) => ({ revision: `turn:${turn?.token ?? '0'}`, turnId: turn?.token ?? null, workingNote: null, steps: [] });
|
|
6
|
-
// This is deliberately a read projection. Native transcript bytes stay within the adapter, while the API owns
|
|
7
|
-
// only enough information for the frontend's fixed renderer to paint the current execution slice.
|
|
8
|
-
export function readSessionExecution(id) {
|
|
9
|
-
let record;
|
|
10
|
-
try {
|
|
11
|
-
record = readAliasedRawRecord(id);
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
if (!record?.governed)
|
|
17
|
-
return null;
|
|
18
|
-
const turn = currentHumanTurn(id);
|
|
19
|
-
const threadId = typeof record.harness_session_id === 'string' ? record.harness_session_id : '';
|
|
20
|
-
if (!threadId)
|
|
21
|
-
return absentExecution(turn);
|
|
22
|
-
try {
|
|
23
|
-
const trace = harnessById(typeof record.harness === 'string' && record.harness ? record.harness : 'claude')
|
|
24
|
-
.executionTrace(threadId, turn);
|
|
25
|
-
return trace || absentExecution(turn);
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
// A missing/unreadable native source is an absent transient trace, not a synthetic conversation failure.
|
|
29
|
-
return absentExecution(turn);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
const TRACE_TICK_MS = 500;
|
|
33
|
-
const HEARTBEAT_TICKS = 20;
|
|
34
|
-
export async function sessionExecutionStream(c) {
|
|
35
|
-
const id = c.req.param('id') || '';
|
|
36
|
-
if (!readSessionExecution(id))
|
|
37
|
-
return c.json({ error: 'no such session' }, 404);
|
|
38
|
-
return streamSSE(c, async (stream) => {
|
|
39
|
-
let aborted = false;
|
|
40
|
-
let lastRevision = '';
|
|
41
|
-
let ticks = 0;
|
|
42
|
-
const publish = async () => {
|
|
43
|
-
const execution = readSessionExecution(id);
|
|
44
|
-
if (!execution)
|
|
45
|
-
return false;
|
|
46
|
-
if (execution.revision === lastRevision)
|
|
47
|
-
return true;
|
|
48
|
-
lastRevision = execution.revision;
|
|
49
|
-
await stream.writeSSE({ event: 'execution', data: JSON.stringify(execution) });
|
|
50
|
-
return true;
|
|
51
|
-
};
|
|
52
|
-
stream.onAbort(() => { aborted = true; });
|
|
53
|
-
try {
|
|
54
|
-
if (!await publish())
|
|
55
|
-
return;
|
|
56
|
-
while (!aborted) {
|
|
57
|
-
await stream.sleep(TRACE_TICK_MS);
|
|
58
|
-
if (aborted)
|
|
59
|
-
break;
|
|
60
|
-
if (!await publish())
|
|
61
|
-
break;
|
|
62
|
-
if (++ticks % HEARTBEAT_TICKS === 0)
|
|
63
|
-
await stream.writeSSE({ event: 'ping', data: 'x' });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
// EventSource reconnects a dropped stream. A native trace read must never take down the session server.
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
}
|
|
@@ -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
|