@visual-z/xeno 0.8.4 → 0.9.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.
- package/CHANGELOG.md +19 -0
- package/README.md +31 -746
- package/bin/xeno.exe +26 -0
- package/install.cjs +88 -0
- package/package.json +18 -48
- package/docs/app-server.md +0 -115
- package/docs/foundations.md +0 -88
- package/docs/install.md +0 -244
- package/docs/providers.md +0 -172
- package/docs/research/foundation-clients-2026-10-09.md +0 -27
- package/docs/research/foundation-editing-2026-10-09.md +0 -56
- package/docs/research/foundation-network-2026-10-09.md +0 -37
- package/docs/research/foundation-workflow-2026-10-09.md +0 -37
- package/src/apps/acp-server.ts +0 -41
- package/src/apps/agent-profiles.ts +0 -19
- package/src/apps/app-server.ts +0 -30
- package/src/apps/approve.ts +0 -49
- package/src/apps/cli.ts +0 -99
- package/src/apps/commands.ts +0 -319
- package/src/apps/completions.ts +0 -87
- package/src/apps/control-transport.ts +0 -90
- package/src/apps/export-cli.ts +0 -24
- package/src/apps/goal-flow.ts +0 -70
- package/src/apps/image-draft.ts +0 -46
- package/src/apps/input.ts +0 -312
- package/src/apps/model-catalog.ts +0 -69
- package/src/apps/preferences.ts +0 -20
- package/src/apps/project-trust.ts +0 -9
- package/src/apps/provider-connect.ts +0 -346
- package/src/apps/question-ui.ts +0 -27
- package/src/apps/render.ts +0 -181
- package/src/apps/resources.ts +0 -66
- package/src/apps/review.ts +0 -109
- package/src/apps/session-hub.ts +0 -96
- package/src/apps/session-lifecycle.ts +0 -64
- package/src/apps/shell.ts +0 -2071
- package/src/apps/startup-input.ts +0 -135
- package/src/apps/startup.ts +0 -1677
- package/src/apps/ui/app.ts +0 -1818
- package/src/apps/ui/control-model.ts +0 -43
- package/src/apps/ui/extension-host.ts +0 -194
- package/src/apps/ui/image-cache.ts +0 -121
- package/src/apps/ui/palette.ts +0 -88
- package/src/apps/ui/project.ts +0 -127
- package/src/apps/ui/standalone.ts +0 -148
- package/src/apps/ui/status-model.ts +0 -68
- package/src/apps/ui/surface.ts +0 -91
- package/src/apps/ui/theme.ts +0 -86
- package/src/apps/update-check.ts +0 -112
- package/src/apps/workflow.ts +0 -58
- package/src/core/acceptance.ts +0 -139
- package/src/core/action-defaults.ts +0 -58
- package/src/core/activity-status.ts +0 -52
- package/src/core/agent-profile.ts +0 -130
- package/src/core/attachment-input.ts +0 -17
- package/src/core/blind-checks.ts +0 -72
- package/src/core/boundary.ts +0 -174
- package/src/core/branch-receipt.ts +0 -26
- package/src/core/catalog-merge.ts +0 -19
- package/src/core/checkpoints.ts +0 -85
- package/src/core/churn.ts +0 -61
- package/src/core/command-artifacts.ts +0 -94
- package/src/core/command-contract.ts +0 -151
- package/src/core/completion-action.ts +0 -20
- package/src/core/completion-review.ts +0 -98
- package/src/core/context-detail.ts +0 -37
- package/src/core/context-policy.ts +0 -133
- package/src/core/context-window.ts +0 -54
- package/src/core/continuation.ts +0 -130
- package/src/core/control-protocol.ts +0 -69
- package/src/core/control-view.ts +0 -39
- package/src/core/cycle.ts +0 -17
- package/src/core/data-query.ts +0 -201
- package/src/core/deadline.ts +0 -40
- package/src/core/derive.ts +0 -516
- package/src/core/draft-images.ts +0 -56
- package/src/core/edit-plan.ts +0 -200
- package/src/core/environment.ts +0 -181
- package/src/core/event-validation.ts +0 -220
- package/src/core/events.ts +0 -670
- package/src/core/evidence-ladder.ts +0 -62
- package/src/core/evidence-links.ts +0 -68
- package/src/core/evidence.ts +0 -240
- package/src/core/execution.ts +0 -18
- package/src/core/exhaustive.ts +0 -18
- package/src/core/export.ts +0 -179
- package/src/core/extension-flags.ts +0 -14
- package/src/core/external-effects.ts +0 -488
- package/src/core/failure-recovery.ts +0 -173
- package/src/core/file-precondition.ts +0 -78
- package/src/core/file-references.ts +0 -35
- package/src/core/frontmatter.ts +0 -213
- package/src/core/git-status.ts +0 -29
- package/src/core/goal-view.ts +0 -64
- package/src/core/goal.ts +0 -200
- package/src/core/handoff.ts +0 -479
- package/src/core/history-query.ts +0 -139
- package/src/core/history.ts +0 -237
- package/src/core/image-limits.ts +0 -72
- package/src/core/images.ts +0 -46
- package/src/core/integration-health.ts +0 -34
- package/src/core/json-event.ts +0 -131
- package/src/core/json-pointer.ts +0 -46
- package/src/core/keymap.ts +0 -153
- package/src/core/lsp-framing.ts +0 -34
- package/src/core/mcp-config.ts +0 -68
- package/src/core/model-picker.ts +0 -64
- package/src/core/model-preferences.ts +0 -28
- package/src/core/model-replay.ts +0 -22
- package/src/core/model-scope.ts +0 -53
- package/src/core/note-guidance.ts +0 -34
- package/src/core/oauth-fields.ts +0 -44
- package/src/core/observation-view.ts +0 -152
- package/src/core/outcome.ts +0 -44
- package/src/core/overflow.ts +0 -59
- package/src/core/packages.ts +0 -47
- package/src/core/patch.ts +0 -130
- package/src/core/path-suggestions.ts +0 -59
- package/src/core/permissions.ts +0 -410
- package/src/core/process-receipt.ts +0 -105
- package/src/core/process-state.ts +0 -37
- package/src/core/program-status.ts +0 -40
- package/src/core/progress.ts +0 -150
- package/src/core/project.ts +0 -431
- package/src/core/prompt-policy.ts +0 -46
- package/src/core/prompts.ts +0 -111
- package/src/core/question.ts +0 -94
- package/src/core/queue.ts +0 -78
- package/src/core/read-bounds.ts +0 -24
- package/src/core/read-observation.ts +0 -106
- package/src/core/recovery-sources.ts +0 -74
- package/src/core/regex-dialect.ts +0 -25
- package/src/core/request-budget.ts +0 -23
- package/src/core/request-progress.ts +0 -87
- package/src/core/request-state.ts +0 -273
- package/src/core/resource-selection.ts +0 -36
- package/src/core/response-recovery.ts +0 -59
- package/src/core/resume.ts +0 -59
- package/src/core/review.ts +0 -68
- package/src/core/ripgrep-release.ts +0 -64
- package/src/core/runtime-target.ts +0 -94
- package/src/core/scheduling.ts +0 -8
- package/src/core/search-observation.ts +0 -97
- package/src/core/secrets.ts +0 -188
- package/src/core/session-archive.ts +0 -4
- package/src/core/session-browser.ts +0 -82
- package/src/core/session-entry.ts +0 -61
- package/src/core/session-exit.ts +0 -42
- package/src/core/session-format.ts +0 -169
- package/src/core/session-position.ts +0 -26
- package/src/core/session-tree.ts +0 -147
- package/src/core/settings.ts +0 -192
- package/src/core/skills.ts +0 -165
- package/src/core/sql-query.ts +0 -211
- package/src/core/stall.ts +0 -128
- package/src/core/stop-report.ts +0 -68
- package/src/core/text-normalize.ts +0 -81
- package/src/core/theme.ts +0 -166
- package/src/core/thinking.ts +0 -26
- package/src/core/tokens.ts +0 -151
- package/src/core/tool-contract.ts +0 -153
- package/src/core/tool-discovery.ts +0 -87
- package/src/core/tool-failure.ts +0 -81
- package/src/core/tool-input.ts +0 -44
- package/src/core/transcript.ts +0 -108
- package/src/core/tui-mode.ts +0 -29
- package/src/core/update-check.ts +0 -157
- package/src/core/version.ts +0 -55
- package/src/core/web-content.ts +0 -80
- package/src/core/work-state.ts +0 -154
- package/src/core/workflow-mode.ts +0 -34
- package/src/core/working-set.ts +0 -119
- package/src/core/yaml.ts +0 -282
- package/src/runtime/acp-agent.ts +0 -289
- package/src/runtime/agent-profiles.ts +0 -21
- package/src/runtime/auth.ts +0 -367
- package/src/runtime/blind-check.ts +0 -36
- package/src/runtime/branch-jobs.ts +0 -36
- package/src/runtime/catalog.ts +0 -285
- package/src/runtime/clipboard.ts +0 -125
- package/src/runtime/command-artifacts.ts +0 -64
- package/src/runtime/connection-reuse.ts +0 -40
- package/src/runtime/control-client.ts +0 -149
- package/src/runtime/control-connection.ts +0 -139
- package/src/runtime/counter-check.ts +0 -74
- package/src/runtime/environment-tools.ts +0 -60
- package/src/runtime/evidence-notes.ts +0 -35
- package/src/runtime/exec-sessions.ts +0 -456
- package/src/runtime/execution-control.ts +0 -40
- package/src/runtime/extension-loader.ts +0 -37
- package/src/runtime/extension-ui.ts +0 -47
- package/src/runtime/extensions.ts +0 -370
- package/src/runtime/formatter-tool.ts +0 -38
- package/src/runtime/formatter.ts +0 -50
- package/src/runtime/foundation.ts +0 -116
- package/src/runtime/history-cache.ts +0 -57
- package/src/runtime/history-tools.ts +0 -168
- package/src/runtime/images.ts +0 -80
- package/src/runtime/integrations.ts +0 -216
- package/src/runtime/loop.ts +0 -3108
- package/src/runtime/lsp-client.ts +0 -201
- package/src/runtime/mcp-client.ts +0 -264
- package/src/runtime/mcp-oauth.ts +0 -168
- package/src/runtime/mcp-schema.ts +0 -51
- package/src/runtime/model-config.ts +0 -232
- package/src/runtime/model-data.LICENSE +0 -27
- package/src/runtime/model-data.ts +0 -21
- package/src/runtime/model-discovery.ts +0 -208
- package/src/runtime/model-replay.ts +0 -10
- package/src/runtime/model-scope.ts +0 -20
- package/src/runtime/model-sources.ts +0 -151
- package/src/runtime/model.ts +0 -686
- package/src/runtime/models-fetch.ts +0 -57
- package/src/runtime/oauth-device.ts +0 -86
- package/src/runtime/oauth.ts +0 -853
- package/src/runtime/open-image.ts +0 -42
- package/src/runtime/openai-responses.ts +0 -304
- package/src/runtime/openai.ts +0 -697
- package/src/runtime/output-schema.ts +0 -26
- package/src/runtime/package-manager.ts +0 -125
- package/src/runtime/patch-tool.ts +0 -38
- package/src/runtime/permission-resources.ts +0 -18
- package/src/runtime/pi-provider.ts +0 -423
- package/src/runtime/program-status.ts +0 -38
- package/src/runtime/progress.ts +0 -26
- package/src/runtime/provider-access.ts +0 -21
- package/src/runtime/provider-connect.ts +0 -109
- package/src/runtime/provider-presets.ts +0 -98
- package/src/runtime/provider-protocols.ts +0 -29
- package/src/runtime/provider-request.ts +0 -87
- package/src/runtime/provider-tool-policy.ts +0 -33
- package/src/runtime/provider.ts +0 -580
- package/src/runtime/question-tool.ts +0 -62
- package/src/runtime/retry.ts +0 -447
- package/src/runtime/review-tool.ts +0 -48
- package/src/runtime/rich-text.ts +0 -36
- package/src/runtime/runtime-target.ts +0 -149
- package/src/runtime/secrets.ts +0 -79
- package/src/runtime/self-update.ts +0 -74
- package/src/runtime/session-control.ts +0 -265
- package/src/runtime/session-hub.ts +0 -85
- package/src/runtime/stream-idle.ts +0 -80
- package/src/runtime/tool-broker.ts +0 -137
- package/src/runtime/tool-discovery.ts +0 -58
- package/src/runtime/tool-queue.ts +0 -77
- package/src/runtime/tools.ts +0 -1820
- package/src/runtime/turn-effects.ts +0 -31
- package/src/runtime/update-check.ts +0 -48
- package/src/runtime/usage.ts +0 -253
- package/src/runtime/version.ts +0 -12
- package/src/runtime/web-http.ts +0 -56
- package/src/runtime/web-tools.ts +0 -88
- package/src/runtime/working-notes.ts +0 -60
- package/src/runtime/workspace-lifecycle.ts +0 -141
- package/src/store/acceptance.ts +0 -15
- package/src/store/agent-profile-files.ts +0 -13
- package/src/store/atomic.ts +0 -31
- package/src/store/auth-store.ts +0 -50
- package/src/store/blind-checks.ts +0 -32
- package/src/store/blobs.ts +0 -143
- package/src/store/checkpoint-history.ts +0 -32
- package/src/store/command-artifacts.ts +0 -96
- package/src/store/command.ts +0 -111
- package/src/store/config.ts +0 -69
- package/src/store/context-files.ts +0 -436
- package/src/store/file-checkpoints.ts +0 -108
- package/src/store/file-index.ts +0 -174
- package/src/store/foundation-config.ts +0 -24
- package/src/store/git-checkpoints.ts +0 -67
- package/src/store/git-status.ts +0 -15
- package/src/store/history-search.ts +0 -19
- package/src/store/log.ts +0 -725
- package/src/store/lsp-document.ts +0 -30
- package/src/store/mcp-credentials.ts +0 -85
- package/src/store/models.ts +0 -60
- package/src/store/observations.ts +0 -224
- package/src/store/packages.ts +0 -98
- package/src/store/patch.ts +0 -92
- package/src/store/permission-paths.ts +0 -34
- package/src/store/process-receipts.ts +0 -34
- package/src/store/query-source.ts +0 -49
- package/src/store/recovery.ts +0 -44
- package/src/store/replay.ts +0 -76
- package/src/store/resource-index.ts +0 -170
- package/src/store/resources.ts +0 -285
- package/src/store/restore.ts +0 -110
- package/src/store/review.ts +0 -109
- package/src/store/runtime-target.ts +0 -64
- package/src/store/search.ts +0 -728
- package/src/store/secrets.ts +0 -44
- package/src/store/serial.ts +0 -13
- package/src/store/session-archive.ts +0 -165
- package/src/store/session-browser.ts +0 -68
- package/src/store/session-position.ts +0 -27
- package/src/store/sessions.ts +0 -525
- package/src/store/source-cancellation.ts +0 -6
- package/src/store/tool-binaries.ts +0 -90
- package/src/store/trust.ts +0 -82
- package/src/store/update-cache.ts +0 -36
- package/src/store/user-settings.ts +0 -30
- package/src/store/workspace-clone.ts +0 -42
- package/src/store/workspace-observation.ts +0 -113
- package/src/store/workspace.ts +0 -115
- package/src/store/worktree.ts +0 -278
- package/src/tui/ansi-spans.ts +0 -126
- package/src/tui/diff.ts +0 -160
- package/src/tui/exhaustive.ts +0 -14
- package/src/tui/fuzzy.ts +0 -75
- package/src/tui/style.ts +0 -208
- package/src/tui/ui/atomic-draft.ts +0 -121
- package/src/tui/ui/branch-panel.ts +0 -47
- package/src/tui/ui/canvas.ts +0 -96
- package/src/tui/ui/colors.ts +0 -70
- package/src/tui/ui/composer.ts +0 -199
- package/src/tui/ui/control-panel.ts +0 -25
- package/src/tui/ui/control-sidebar.ts +0 -49
- package/src/tui/ui/dialog.ts +0 -398
- package/src/tui/ui/draft.ts +0 -23
- package/src/tui/ui/duration.ts +0 -16
- package/src/tui/ui/edge.ts +0 -186
- package/src/tui/ui/image-size.ts +0 -44
- package/src/tui/ui/ink.ts +0 -210
- package/src/tui/ui/items.ts +0 -42
- package/src/tui/ui/keys.ts +0 -40
- package/src/tui/ui/layout.ts +0 -74
- package/src/tui/ui/list-model.ts +0 -86
- package/src/tui/ui/list-view.ts +0 -172
- package/src/tui/ui/mascot.ts +0 -67
- package/src/tui/ui/messages.ts +0 -518
- package/src/tui/ui/motion.ts +0 -28
- package/src/tui/ui/paths.ts +0 -10
- package/src/tui/ui/pictures.ts +0 -108
- package/src/tui/ui/prompt-history.ts +0 -60
- package/src/tui/ui/screen.ts +0 -579
- package/src/tui/ui/status.ts +0 -403
- package/src/tui/ui/styled.ts +0 -85
- package/src/tui/ui/tool-card.ts +0 -150
- package/src/tui/width.ts +0 -567
- package/src/vendor/pi-rich/LICENSE.upstream +0 -21
- package/src/vendor/pi-rich/UPSTREAM.json +0 -17
- package/src/vendor/pi-rich/clipboard-command.ts +0 -44
- package/src/vendor/pi-rich/exif-orientation.ts +0 -182
- package/src/vendor/pi-rich/html.ts +0 -51
- package/src/vendor/pi-rich/image-convert.ts +0 -49
- package/src/vendor/pi-rich/image-resize-core.ts +0 -164
- package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
- package/src/vendor/pi-rich/image-resize.ts +0 -123
- package/src/vendor/pi-rich/mime.ts +0 -116
- package/src/vendor/pi-rich/photon.ts +0 -139
- package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The evidence ladder (research doc S.1): how far a request's evidence goes,
|
|
3
|
-
* one rung at a time, read only from the request ledger.
|
|
4
|
-
*
|
|
5
|
-
* ran a required check has a fresh passing receipt
|
|
6
|
-
* discriminating the check fails once the request's changes are undone
|
|
7
|
-
* (a guard instead still passes there, and does not count; S.7)
|
|
8
|
-
* independent blind checks the model never touched pass
|
|
9
|
-
* human the user accepted what only a person can judge
|
|
10
|
-
*
|
|
11
|
-
* Each rung is its own fact; a higher rung does not imply the ones below it.
|
|
12
|
-
* The summary names the highest rung reached and the first gap, so the user
|
|
13
|
-
* sees what the evidence shows and what it does not, without a percentage.
|
|
14
|
-
*/
|
|
15
|
-
import type { RequestState } from "./request-state.ts";
|
|
16
|
-
|
|
17
|
-
export type Rung = "met" | "missing" | "failed" | "not_run" | "not_applicable";
|
|
18
|
-
export type EvidenceLadder = { ran: Rung; discriminating: Rung; independent: Rung; human: Rung; summary: string };
|
|
19
|
-
export const RUNGS = ["ran", "discriminating", "independent", "human"] as const;
|
|
20
|
-
|
|
21
|
-
const LABEL: Record<(typeof RUNGS)[number], string> = {
|
|
22
|
-
ran: "ran", discriminating: "tests the change", independent: "independent", human: "human accepted",
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/** Null when the request had nothing to check: a conversation turn shows no ladder. */
|
|
26
|
-
export function evidenceLadder(state: RequestState): EvidenceLadder | null {
|
|
27
|
-
if (state.status === "not_applicable") return null;
|
|
28
|
-
const evidence = state.evidence;
|
|
29
|
-
const ran: Rung = evidence.ran === "pass" ? "met" : evidence.ran === "fail" ? "failed" : evidence.ran === "not_applicable" ? "not_applicable" : "missing";
|
|
30
|
-
const discriminating: Rung = evidence.discriminating === "pass" ? "met" : evidence.discriminating === "vacuous" ? "failed"
|
|
31
|
-
: evidence.discriminating === "missing" ? "missing" : evidence.discriminating === "not_applicable" ? "not_applicable" : "not_run";
|
|
32
|
-
const independent: Rung = evidence.independent === "pass" ? "met" : evidence.independent === "fail" || evidence.independent === "exposed" ? "failed"
|
|
33
|
-
: evidence.independent === "not_applicable" ? "not_applicable" : "not_run";
|
|
34
|
-
const human: Rung = evidence.human === "accepted" ? "met" : evidence.human === "pending" ? "missing" : "not_applicable";
|
|
35
|
-
const rungs = { ran, discriminating, independent, human };
|
|
36
|
-
const reached = RUNGS.filter(rung => rungs[rung] === "met").at(-1);
|
|
37
|
-
const gap = RUNGS.find(rung => rungs[rung] === "failed" || rungs[rung] === "missing");
|
|
38
|
-
const why = !gap ? undefined
|
|
39
|
-
: gap === "ran" ? (ran === "failed" ? "the latest check failed" : state.acceptance ? "declared checks have not passed" : "changes not checked")
|
|
40
|
-
: gap === "discriminating" ? (discriminating === "missing" ? "no check tests the change; every check is a guard" : vacuousGap(state))
|
|
41
|
-
: gap === "independent" ? (evidence.independent === "exposed" ? "the model touched the blind checks" : "a blind check failed")
|
|
42
|
-
: "waiting for your /accept";
|
|
43
|
-
const summary = [reached ? `reached: ${LABEL[reached]}` : "nothing verified yet", ...(why ? [why] : [])].join(" · ");
|
|
44
|
-
return { ...rungs, summary };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Which checks pass without the request's changes, by id. Only line mode
|
|
49
|
-
* prints each check's counter-check; the sidebar shows just this summary, and
|
|
50
|
-
* a guard the user asked for ("nothing else changed") reads differently from
|
|
51
|
-
* a check that was meant to test the work (fix-ladder-names-vacuous-check).
|
|
52
|
-
*/
|
|
53
|
-
function vacuousGap(state: RequestState): string {
|
|
54
|
-
const ids = state.criteria.filter(item => item.counter === "vacuous").map(item => item.id);
|
|
55
|
-
const named = ids.length <= 2 ? ids.join(", ") : `${ids.slice(0, 2).join(", ")} +${ids.length - 2}`;
|
|
56
|
-
return `${named} ${ids.length === 1 ? "passes" : "pass"} without the changes`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** One glyph per rung for narrow status chrome: ✓ met, ✗ failed, ○ missing or not run, · not applicable. */
|
|
60
|
-
export function ladderGlyph(rung: Rung): string {
|
|
61
|
-
return rung === "met" ? "✓" : rung === "failed" ? "✗" : rung === "not_applicable" ? "·" : "○";
|
|
62
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/** O.6: explicit evidence relationships are projections, never verification claims. */
|
|
2
|
-
import type { LogEvent, ToolResultEvent } from "./events.ts";
|
|
3
|
-
|
|
4
|
-
export type EvidenceLink = {
|
|
5
|
-
id: string; work: string; what: string; event: string;
|
|
6
|
-
resource?: string; version?: string;
|
|
7
|
-
freshness: "current" | "stale" | "unknown";
|
|
8
|
-
superseded_by?: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/** Normalize path spelling only; never infer basename, symlink or repository aliases. */
|
|
12
|
-
export function resourceKey(path: string): string {
|
|
13
|
-
const parts: string[] = [];
|
|
14
|
-
for (const part of path.split("/")) {
|
|
15
|
-
if (!part || part === ".") continue;
|
|
16
|
-
if (part === ".." && parts.length > 0 && parts.at(-1) !== "..") parts.pop(); else parts.push(part);
|
|
17
|
-
}
|
|
18
|
-
return (path.startsWith("/") ? "/" : "") + parts.join("/");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function observationResources(event: ToolResultEvent, input: unknown): string[] {
|
|
22
|
-
if (event.effect && "path" in event.effect) return [resourceKey(event.effect.path)];
|
|
23
|
-
if (!event.ok || input === null || typeof input !== "object") return [];
|
|
24
|
-
const args = input as Record<string, unknown>;
|
|
25
|
-
const paths = typeof args["path"] === "string" ? [args["path"]] : Array.isArray(args["paths"]) ? args["paths"] : [];
|
|
26
|
-
return [...new Set(paths.filter((path): path is string => typeof path === "string").map(resourceKey))];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function observedVersion(events: readonly LogEvent[], resource: string): string | null | undefined {
|
|
30
|
-
let version: string | null | undefined;
|
|
31
|
-
for (const event of events) {
|
|
32
|
-
if (event.t === "tool_result" && event.effect && "path" in event.effect && resourceKey(event.effect.path) === resource) {
|
|
33
|
-
version = event.effect.kind === "read" ? event.effect.sha256 : event.effect.sha256_after;
|
|
34
|
-
}
|
|
35
|
-
if (event.t === "checkpoint" && event.phase === "applied") for (const [path, change] of Object.entries(event.changes)) {
|
|
36
|
-
if (resourceKey(path) === resource) version = change.after?.sha256 ?? null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return version;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function deriveEvidenceLinks(previous: readonly EvidenceLink[], events: readonly LogEvent[]): EvidenceLink[] {
|
|
43
|
-
const links = previous.map(link => ({ ...link }));
|
|
44
|
-
for (const event of events) {
|
|
45
|
-
if (event.t === "evidence_link") {
|
|
46
|
-
if (event.supersedes) for (const link of links) {
|
|
47
|
-
if (link.work === event.work && link.event === event.supersedes && link.superseded_by === undefined) link.superseded_by = event.id;
|
|
48
|
-
}
|
|
49
|
-
if (!links.some(link => link.id === event.id)) links.push({ id: event.id, work: event.work, what: event.what, event: event.source,
|
|
50
|
-
...(event.resource !== undefined ? { resource: resourceKey(event.resource) } : {}),
|
|
51
|
-
...(event.version !== undefined ? { version: event.version } : {}),
|
|
52
|
-
freshness: event.version === undefined || event.observed_version === undefined ? "unknown"
|
|
53
|
-
: event.version === event.observed_version ? "current" : "stale" });
|
|
54
|
-
}
|
|
55
|
-
for (const link of links) {
|
|
56
|
-
if (!link.resource || !link.version) continue;
|
|
57
|
-
if (event.t === "tool_result" && event.effect && "path" in event.effect && resourceKey(event.effect.path) === link.resource) {
|
|
58
|
-
const version = event.effect.kind === "read" ? event.effect.sha256 : event.effect.sha256_after;
|
|
59
|
-
link.freshness = version === link.version ? "current" : "stale";
|
|
60
|
-
}
|
|
61
|
-
if (event.t === "checkpoint" && event.phase === "applied") for (const [path, change] of Object.entries(event.changes)) {
|
|
62
|
-
if (resourceKey(path) === link.resource) link.freshness = change.after?.sha256 === link.version ? "current" : "stale";
|
|
63
|
-
}
|
|
64
|
-
if (event.t === "workspace_observation" && event.changed || event.t === "checkpoint" && event.phase === "unavailable") link.freshness = "unknown";
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return links;
|
|
68
|
-
}
|
package/src/core/evidence.ts
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/** Shared executed-evidence rules for handoff derivation and the turn watchdog. */
|
|
2
|
-
import type { EvidenceQuery, LogEvent, ToolResultEvent } from "./events.ts";
|
|
3
|
-
import type { Evidence } from "./handoff.ts";
|
|
4
|
-
import { changedPaths } from "./checkpoints.ts";
|
|
5
|
-
|
|
6
|
-
type CommandEffect = Extract<NonNullable<ToolResultEvent["effect"]>, { kind: "command" }>;
|
|
7
|
-
export type CommandResult = ToolResultEvent & { effect: CommandEffect };
|
|
8
|
-
export function isCommandResult(event: LogEvent): event is CommandResult {
|
|
9
|
-
return event.t === "tool_result" && event.effect?.kind === "command";
|
|
10
|
-
}
|
|
11
|
-
export function matchesQuery(query: EvidenceQuery, effect: CommandEffect): boolean {
|
|
12
|
-
return "gate" in query ? effect.gate === query.gate : effect.cmd === query.command;
|
|
13
|
-
}
|
|
14
|
-
export function evidenceStale(events: readonly LogEvent[], evidence: Evidence, initially = false): boolean {
|
|
15
|
-
const start = events.findIndex((event) => event.id === evidence.event_id);
|
|
16
|
-
let stale = initially;
|
|
17
|
-
for (const event of events.slice(start + 1)) {
|
|
18
|
-
if (event.t === "tool_result" && event.effect?.kind === "edit") stale = true;
|
|
19
|
-
if (event.t === "checkpoint" && (event.phase === "unavailable" || event.phase === "applied" && changedPaths(event.changes).length > 0)) stale = true;
|
|
20
|
-
if (event.t === "workspace_observation" && event.fingerprint === undefined ||
|
|
21
|
-
event.t === "tool_result" && event.effect?.kind === "command" && event.effect.workspace_observed === false || event.t === "user_bash") stale = true;
|
|
22
|
-
const sha = event.t === "workspace_observation" ? event.fingerprint
|
|
23
|
-
: event.t === "tool_result" && event.effect?.kind === "command" ? event.effect.workspace_sha256 : undefined;
|
|
24
|
-
const scope = event.t === "workspace_observation" ? event.scope : event.t === "tool_result" && event.effect?.kind === "command" ? event.effect.workspace_scope : undefined;
|
|
25
|
-
if (sha !== undefined && evidence.workspace_sha256 !== undefined) stale = evidence.workspace_scope === "source-v1"
|
|
26
|
-
? stale || scope !== evidence.workspace_scope || sha !== evidence.workspace_sha256 : scope !== undefined || sha !== evidence.workspace_sha256;
|
|
27
|
-
}
|
|
28
|
-
return stale;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type EvidenceResolution = { ok: true; evidence: Evidence } | {
|
|
32
|
-
ok: false; reason: "no_matching_event" | "nonzero_exit" | "stale_workspace"; exit?: number;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/** D11/O.2: callers supply only the current segment's prefix before the claim. */
|
|
36
|
-
export function resolveEvidence(events: readonly LogEvent[], query: EvidenceQuery, afterEvent?: string): EvidenceResolution {
|
|
37
|
-
const start = afterEvent === undefined ? 0 : events.findIndex(event => event.id === afterEvent) + 1;
|
|
38
|
-
const result = events.slice(start).findLast(event => isCommandResult(event) && matchesQuery(query, event.effect));
|
|
39
|
-
if (!result || !isCommandResult(result)) return { ok: false, reason: "no_matching_event" };
|
|
40
|
-
if (result.effect.exit !== 0) return { ok: false, reason: "nonzero_exit", exit: result.effect.exit };
|
|
41
|
-
if (result.effect.workspace_observed === false) return { ok: false, reason: "stale_workspace" };
|
|
42
|
-
const evidence: Evidence = { command: result.effect.cmd, exit: result.effect.exit, at: result.at, event_id: result.id,
|
|
43
|
-
...(result.effect.workspace_scope ? { workspace_scope: result.effect.workspace_scope } : {}),
|
|
44
|
-
...(result.effect.gate !== undefined ? { gate: result.effect.gate } : {}),
|
|
45
|
-
...(result.effect.workspace_sha256 ? { workspace_sha256: result.effect.workspace_sha256 } : {}) };
|
|
46
|
-
return evidenceStale(events, evidence) ? { ok: false, reason: "stale_workspace" } : { ok: true, evidence };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function eligibleProgressChecks(events: readonly LogEvent[], claimed: ReadonlySet<string>, failingGates: { has(gate: string): boolean }): Set<string> {
|
|
50
|
-
const eligible = new Set<string>();
|
|
51
|
-
for (const event of events) {
|
|
52
|
-
if (!isCommandResult(event)) continue;
|
|
53
|
-
const gate = event.effect.gate;
|
|
54
|
-
if (event.effect.exit !== 0 || event.effect.workspace_observed === false || (gate === undefined && !claimed.has(event.id)) || (gate !== undefined && failingGates.has(gate))) continue;
|
|
55
|
-
if (!evidenceStale(events, { command: event.effect.cmd, exit: 0, at: event.at, event_id: event.id,
|
|
56
|
-
...(event.effect.workspace_scope ? { workspace_scope: event.effect.workspace_scope } : {}),
|
|
57
|
-
...(event.effect.workspace_sha256 === undefined ? {} : { workspace_sha256: event.effect.workspace_sha256 }) })) eligible.add(event.id);
|
|
58
|
-
}
|
|
59
|
-
return eligible;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** An exchange can verify a gate or claim only an already executed command. */
|
|
63
|
-
export function exchangeProgressChecks(events: readonly LogEvent[]): Set<string> {
|
|
64
|
-
const commands: CommandResult[] = [], claimed = new Set<string>(), gates = new Map<string, number>();
|
|
65
|
-
for (const event of events) {
|
|
66
|
-
if (isCommandResult(event)) {
|
|
67
|
-
commands.push(event);
|
|
68
|
-
if (event.effect.gate !== undefined) gates.set(event.effect.gate, event.effect.exit);
|
|
69
|
-
} else if (event.t === "claim") {
|
|
70
|
-
const command = commands.findLast((result) => matchesQuery(event.evidence, result.effect));
|
|
71
|
-
if (command?.effect.exit === 0) claimed.add(command.id);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return eligibleProgressChecks(events, claimed, new Set([...gates].filter(([, exit]) => exit !== 0).map(([gate]) => gate)));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* D11: classify an actual verifier invocation, never a runner word in data.
|
|
79
|
-
*
|
|
80
|
-
* Only command shapes whose exit 0 proves every gate in them exited 0 are
|
|
81
|
-
* read: simple commands joined by `&&` (the chain stops at the first failure),
|
|
82
|
-
* each optionally ending in output redirects to fixed paths (a redirect that
|
|
83
|
-
* cannot open fails the command, it never masks one). Anything that can turn
|
|
84
|
-
* a failure into exit 0 or run text the log does not show -- `;`, `||`,
|
|
85
|
-
* pipes, background jobs, subshells, expansions, input redirects -- rejects
|
|
86
|
-
* the whole command, as before. A chain's gates join with `+` in order, so a
|
|
87
|
-
* failed chain is recorded as that chain failing, not as one of its parts.
|
|
88
|
-
*/
|
|
89
|
-
export function classifyGate(gates: Readonly<Record<string, string>>, command: string): string | undefined {
|
|
90
|
-
const segments = gateSegments(command);
|
|
91
|
-
if (!segments) return undefined;
|
|
92
|
-
const names = [...new Set(segments.flatMap((words) => segmentGates(gates, words)))];
|
|
93
|
-
return names.length === 0 ? undefined : names.join("+");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
type ShellToken = { kind: "word"; value: string } | { kind: "and" } | { kind: "redirect"; target: boolean };
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* A deliberately small tokenizer, not a shell parser (M.2): it accepts the
|
|
100
|
-
* subset above and returns undefined for everything else, so an unfamiliar
|
|
101
|
-
* construct can only lose a gate, never forge one. Quotes may hold plain text
|
|
102
|
-
* only; `$`, backticks and backslashes are rejected even inside them.
|
|
103
|
-
*/
|
|
104
|
-
function shellTokens(command: string): ShellToken[] | undefined {
|
|
105
|
-
const tokens: ShellToken[] = [];
|
|
106
|
-
let word: string | undefined, i = 0;
|
|
107
|
-
const end = () => { if (word !== undefined) tokens.push({ kind: "word", value: word }); word = undefined; };
|
|
108
|
-
while (i < command.length) {
|
|
109
|
-
const c = command[i]!;
|
|
110
|
-
if (c === " " || c === "\t") { end(); i++; continue; }
|
|
111
|
-
if (c === "#" && word === undefined) break; // a comment runs to the end of the (single) line
|
|
112
|
-
if (c === "'" || c === '"') {
|
|
113
|
-
const close = command.indexOf(c, i + 1);
|
|
114
|
-
if (close < 0) return undefined;
|
|
115
|
-
const quoted = command.slice(i + 1, close);
|
|
116
|
-
if (/[$`\\\n\r]/.test(quoted)) return undefined;
|
|
117
|
-
word = (word ?? "") + quoted; i = close + 1; continue;
|
|
118
|
-
}
|
|
119
|
-
if (c === "&" && command[i + 1] === "&") { end(); tokens.push({ kind: "and" }); i += 2; continue; }
|
|
120
|
-
if (c === ">" || c === "&" && command[i + 1] === ">") {
|
|
121
|
-
// An unquoted 1 or 2 directly before `>` is the descriptor, not a word.
|
|
122
|
-
if (c === ">" && (word === "1" || word === "2") && /[12]$/.test(command.slice(0, i))) word = undefined;
|
|
123
|
-
end();
|
|
124
|
-
i += c === "&" ? 2 : 1;
|
|
125
|
-
if (command[i] === ">") i++;
|
|
126
|
-
if (c === ">" && command[i] === "&") {
|
|
127
|
-
if (!/^[12](?![^\s&])/.test(command.slice(i + 1))) return undefined;
|
|
128
|
-
tokens.push({ kind: "redirect", target: false }); i += 2; continue;
|
|
129
|
-
}
|
|
130
|
-
if (command[i] === "|") return undefined;
|
|
131
|
-
tokens.push({ kind: "redirect", target: true }); continue;
|
|
132
|
-
}
|
|
133
|
-
if (/[\n\r;&|<`$(){}\\]/.test(c)) return undefined;
|
|
134
|
-
word = (word ?? "") + c; i++;
|
|
135
|
-
}
|
|
136
|
-
end();
|
|
137
|
-
return tokens;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** The `&&`-joined segments' words with trailing output redirects removed. */
|
|
141
|
-
function gateSegments(command: string): string[][] | undefined {
|
|
142
|
-
const tokens = shellTokens(command);
|
|
143
|
-
if (!tokens) return undefined;
|
|
144
|
-
const segments: string[][] = [[]];
|
|
145
|
-
let redirected = false, needTarget = false;
|
|
146
|
-
for (const token of tokens) {
|
|
147
|
-
const words = segments.at(-1)!;
|
|
148
|
-
if (token.kind === "and") {
|
|
149
|
-
if (needTarget || words.length === 0) return undefined;
|
|
150
|
-
segments.push([]); redirected = false; continue;
|
|
151
|
-
}
|
|
152
|
-
if (token.kind === "redirect") {
|
|
153
|
-
if (needTarget || words.length === 0) return undefined;
|
|
154
|
-
redirected = true; needTarget = token.target; continue;
|
|
155
|
-
}
|
|
156
|
-
if (needTarget) { needTarget = false; continue; }
|
|
157
|
-
// Only trailing redirects are stripped; one before an argument is rejected.
|
|
158
|
-
if (redirected) return undefined;
|
|
159
|
-
words.push(token.value);
|
|
160
|
-
}
|
|
161
|
-
return needTarget || segments.at(-1)!.length === 0 ? undefined : segments;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const INFORMATIONAL = ["--version", "-V", "--help", "-h", "--collect-only", "--listTests", "--showConfig", "--init", "--fixtures", "--markers"];
|
|
165
|
-
|
|
166
|
-
/** One simple command's gates: none, the runner's gate, or a target runner's gate targets. */
|
|
167
|
-
function segmentGates(gates: Readonly<Record<string, string>>, tokens: readonly string[]): string[] {
|
|
168
|
-
if (tokens.some((t) => INFORMATIONAL.includes(t))) return [];
|
|
169
|
-
const first = tokens[0] ?? "";
|
|
170
|
-
const runner = own(TARGET_RUNNERS, first);
|
|
171
|
-
if (runner && !Object.hasOwn(gates, first)) return targetGates(gates, runner, tokens.slice(1));
|
|
172
|
-
const single = runnerGate(gates, tokens);
|
|
173
|
-
return single === undefined ? [] : [single];
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function runnerGate(gates: Readonly<Record<string, string>>, tokens: readonly string[]): string | undefined {
|
|
177
|
-
let i = 0;
|
|
178
|
-
const first = tokens[0];
|
|
179
|
-
if (["bun", "npm", "pnpm", "yarn"].includes(first ?? "") && tokens[1] === "test") return own(gates, "test");
|
|
180
|
-
if (first === "python" || first === "python3") {
|
|
181
|
-
if (tokens[1] !== "-m") return undefined;
|
|
182
|
-
i = 2;
|
|
183
|
-
} else if (["npx", "bunx", "uvx"].includes(first ?? "")) i = 1;
|
|
184
|
-
else if (["uv", "poetry", "pdm", "pipenv"].includes(first ?? "")) {
|
|
185
|
-
if (tokens[1] !== "run") return undefined;
|
|
186
|
-
i = 2;
|
|
187
|
-
} else if (["bun", "npm", "pnpm", "yarn"].includes(first ?? "") && tokens[1] === "run") i = 2;
|
|
188
|
-
const executable = tokens[i];
|
|
189
|
-
if (executable === "cargo" && !["test", "check", "clippy", "build"].includes(tokens[i + 1] ?? "")) return undefined;
|
|
190
|
-
if (executable === "vitest" && ["list", "init"].includes(tokens[i + 1] ?? "")) return undefined;
|
|
191
|
-
return own(gates, executable ?? "");
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/** A table lookup that a word like `constructor` cannot satisfy through the prototype. */
|
|
195
|
-
function own<T>(table: Readonly<Record<string, T>>, key: string): T | undefined {
|
|
196
|
-
return Object.hasOwn(table, key) ? table[key] : undefined;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Options a target runner may carry and still prove its targets ran. An
|
|
201
|
-
* allowlist on purpose: an unknown option might be a dry run, a touch or an
|
|
202
|
-
* ignore-errors switch (exit 0 without the targets passing), so it makes the
|
|
203
|
-
* segment not a gate rather than being guessed at.
|
|
204
|
-
*/
|
|
205
|
-
type TargetRunner = { withValue: readonly string[]; flags: readonly string[]; attached: RegExp; argsAfterDashes: boolean };
|
|
206
|
-
const TARGET_RUNNERS: Readonly<Record<string, TargetRunner>> = {
|
|
207
|
-
make: { withValue: ["-C", "-f", "--directory", "--file", "--makefile"],
|
|
208
|
-
flags: ["-k", "-s", "-B", "-w", "-r", "-R", "--keep-going", "--silent", "--quiet", "--always-make", "--print-directory", "--no-print-directory"],
|
|
209
|
-
attached: /^(?:-C.+|-f.+|-j\d*|--(?:directory|file|makefile|jobs)=.+|--jobs)$/, argsAfterDashes: false },
|
|
210
|
-
just: { withValue: ["-f", "-d", "--justfile", "--working-directory"], flags: ["-q", "-v", "--quiet", "--verbose"],
|
|
211
|
-
attached: /^--(?:justfile|working-directory)=.+$/, argsAfterDashes: false },
|
|
212
|
-
task: { withValue: ["-d", "-t", "--dir", "--taskfile"], flags: ["-s", "-v", "-p", "-f", "--silent", "--verbose", "--parallel", "--force"],
|
|
213
|
-
attached: /^--(?:dir|taskfile)=.+$/, argsAfterDashes: true },
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* `make build size test`: the runner exits 0 only when every target
|
|
218
|
-
* succeeded, so each target named after a configured gate key (exactly, or
|
|
219
|
-
* as `<key>-…` / `<key>:…`) is that gate; other targets do not weaken it. No
|
|
220
|
-
* gate target, or no target at all (the default goal is unknown), is no gate.
|
|
221
|
-
*/
|
|
222
|
-
function targetGates(gates: Readonly<Record<string, string>>, runner: TargetRunner, args: readonly string[]): string[] {
|
|
223
|
-
const keys = Object.keys(gates).sort((a, b) => b.length - a.length);
|
|
224
|
-
const names: string[] = [];
|
|
225
|
-
for (let i = 0; i < args.length; i++) {
|
|
226
|
-
const arg = args[i]!;
|
|
227
|
-
if (arg === "--") { if (runner.argsAfterDashes) break; return []; }
|
|
228
|
-
if (arg.startsWith("-")) {
|
|
229
|
-
if (runner.withValue.includes(arg)) { if (++i >= args.length) return []; continue; }
|
|
230
|
-
if (arg === "-j" && /^\d+$/.test(args[i + 1] ?? "")) { i++; continue; }
|
|
231
|
-
if (runner.flags.includes(arg) || runner.attached.test(arg)) continue;
|
|
232
|
-
return [];
|
|
233
|
-
}
|
|
234
|
-
if (arg.includes("=")) continue; // a variable override, not a target
|
|
235
|
-
if (/[*?[\]~]/.test(arg)) return [];
|
|
236
|
-
const key = keys.find((k) => arg === k || arg.startsWith(`${k}-`) || arg.startsWith(`${k}:`));
|
|
237
|
-
if (key !== undefined) names.push(gates[key]!);
|
|
238
|
-
}
|
|
239
|
-
return names;
|
|
240
|
-
}
|
package/src/core/execution.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/** D5/D11: incomplete execution is unknown, never an instruction to run twice. */
|
|
2
|
-
import type { LogEvent, AssistantBlock } from "./events.ts";
|
|
3
|
-
|
|
4
|
-
export type PendingToolCall = Extract<AssistantBlock, { type: "tool_use" }> & { parent_call_id?: string; invocation_id?: string; origin?: "extension" };
|
|
5
|
-
export function pendingToolCalls(events: readonly LogEvent[]): PendingToolCall[] {
|
|
6
|
-
const pending = new Map<string, PendingToolCall>();
|
|
7
|
-
for (const event of events) {
|
|
8
|
-
if (event.t === "assistant") {
|
|
9
|
-
for (const block of event.blocks) if (block.type === "tool_use") pending.set(block.call_id, block);
|
|
10
|
-
} else if (event.t === "tool_invocation") {
|
|
11
|
-
const existing = pending.get(event.call_id);
|
|
12
|
-
pending.set(event.call_id, { type: "tool_use", call_id: event.call_id, name: event.name, input: event.input ?? existing?.input ?? null,
|
|
13
|
-
invocation_id: event.id, ...(event.origin ? { origin: event.origin } : {}),
|
|
14
|
-
...(event.parent_call_id ? { parent_call_id: event.parent_call_id } : {}) });
|
|
15
|
-
} else if (event.t === "tool_result") pending.delete(event.call_id);
|
|
16
|
-
}
|
|
17
|
-
return [...pending.values()];
|
|
18
|
-
}
|
package/src/core/exhaustive.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exhaustiveness for discriminated unions.
|
|
3
|
-
*
|
|
4
|
-
* `LogEvent` has ten variants and `Effect` three; the projection's claim to be
|
|
5
|
-
* a total function rests on every switch over them covering every case. In the
|
|
6
|
-
* default branch of such a switch the value narrows to `never`, so passing it
|
|
7
|
-
* here compiles only while the switch is complete -- adding a variant turns
|
|
8
|
-
* every incomplete switch into a type error rather than a silent fallthrough.
|
|
9
|
-
*
|
|
10
|
-
* This is the one exhaustiveness guarantee tsc will not give on its own: a
|
|
11
|
-
* switch that misses a case is legal TypeScript, it just falls through.
|
|
12
|
-
*/
|
|
13
|
-
export function assertNever(value: never, context: string): never {
|
|
14
|
-
throw new Error(
|
|
15
|
-
`${context}: unhandled variant ${JSON.stringify(value)} -- ` +
|
|
16
|
-
"a union gained a member without every switch over it being updated",
|
|
17
|
-
);
|
|
18
|
-
}
|
package/src/core/export.ts
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Transcript serialisation. Pure: no I/O, no clock.
|
|
3
|
-
*
|
|
4
|
-
* The HTML has to be a single file a person can open offline. A stylesheet
|
|
5
|
-
* or font loaded from a CDN is blank in the one situation export is for, so
|
|
6
|
-
* nothing here emits a URL. Timestamps are passed in so this module cannot
|
|
7
|
-
* invent "now".
|
|
8
|
-
*
|
|
9
|
-
* User text is escaped as HTML text. A transcript that contains
|
|
10
|
-
* `<script>alert(1)</script>` must stay text; emitting it as a tag is XSS
|
|
11
|
-
* from the log (card: feat-session-flags-and-export).
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import type { AssistantBlock, LogEvent } from "./events.ts";
|
|
15
|
-
import { counterVerdict } from "./acceptance.ts";
|
|
16
|
-
import { assertNever } from "./exhaustive.ts";
|
|
17
|
-
import { imageDescription } from "./images.ts";
|
|
18
|
-
|
|
19
|
-
export interface HtmlMeta {
|
|
20
|
-
title: string;
|
|
21
|
-
/** ISO timestamp supplied by the caller. Never read from the clock here. */
|
|
22
|
-
exportedAt: string;
|
|
23
|
-
sessionId?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function toJsonl(events: readonly LogEvent[]): string {
|
|
27
|
-
if (events.length === 0) return "";
|
|
28
|
-
return events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function toHtml(events: readonly LogEvent[], meta: HtmlMeta): string {
|
|
32
|
-
const title = escapeHtml(meta.title);
|
|
33
|
-
const when = escapeHtml(meta.exportedAt);
|
|
34
|
-
const id = meta.sessionId !== undefined ? escapeHtml(meta.sessionId) : "";
|
|
35
|
-
const body = events.map(eventHtml).join("\n");
|
|
36
|
-
return `<!DOCTYPE html>
|
|
37
|
-
<html lang="en">
|
|
38
|
-
<head>
|
|
39
|
-
<meta charset="utf-8">
|
|
40
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
41
|
-
<title>${title}</title>
|
|
42
|
-
<style>
|
|
43
|
-
body{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;max-width:72rem;margin:1.5rem auto;padding:0 1rem;color:#111;background:#fff;line-height:1.45}
|
|
44
|
-
header{border-bottom:1px solid #ddd;padding-bottom:0.75rem;margin-bottom:1rem}
|
|
45
|
-
header p{color:#555;margin:0.25rem 0 0}
|
|
46
|
-
.event{margin:0.85rem 0;padding:0.55rem 0.8rem;border-left:3px solid #ccc;background:#fafafa}
|
|
47
|
-
.user{border-color:#1565c0}
|
|
48
|
-
.assistant{border-color:#2e7d32}
|
|
49
|
-
.tool{border-color:#ef6c00}
|
|
50
|
-
.meta{color:#666;font-size:0.8rem;margin-bottom:0.35rem}
|
|
51
|
-
pre{white-space:pre-wrap;word-break:break-word;margin:0;font:inherit}
|
|
52
|
-
</style>
|
|
53
|
-
</head>
|
|
54
|
-
<body>
|
|
55
|
-
<header>
|
|
56
|
-
<h1>${title}</h1>
|
|
57
|
-
<p>exported ${when}${id.length > 0 ? ` · ${id}` : ""}</p>
|
|
58
|
-
</header>
|
|
59
|
-
<main>
|
|
60
|
-
${body}
|
|
61
|
-
</main>
|
|
62
|
-
</body>
|
|
63
|
-
</html>
|
|
64
|
-
`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Most recent assistant text block. `/copy` is this string, not a
|
|
69
|
-
* reconstruction of thinking or tool JSON.
|
|
70
|
-
*/
|
|
71
|
-
export function lastAssistantText(events: readonly LogEvent[]): string | null {
|
|
72
|
-
for (let i = events.length - 1; i >= 0; i--) {
|
|
73
|
-
const e = events[i]!;
|
|
74
|
-
if (e.t !== "assistant") continue;
|
|
75
|
-
const parts: string[] = [];
|
|
76
|
-
for (const b of e.blocks) {
|
|
77
|
-
if (b.type === "text") parts.push(b.text);
|
|
78
|
-
}
|
|
79
|
-
if (parts.length > 0) return parts.join("");
|
|
80
|
-
}
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function escapeHtml(s: string): string {
|
|
85
|
-
return s
|
|
86
|
-
.replace(/&/g, "&")
|
|
87
|
-
.replace(/</g, "<")
|
|
88
|
-
.replace(/>/g, ">")
|
|
89
|
-
.replace(/"/g, """)
|
|
90
|
-
.replace(/'/g, "'");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function eventHtml(e: LogEvent): string {
|
|
94
|
-
switch (e.t) {
|
|
95
|
-
case "tool_invocation": return wrap("event", "tool invocation", e.at, `${e.name} · ${e.call_id}${e.parent_call_id ? ` · parent ${e.parent_call_id}` : ""} · input ${e.input_ref.sha256} · schema ${e.schema_ref?.sha256 ?? "unavailable"}`);
|
|
96
|
-
case "checkpoint": return wrap("event", `file checkpoint ${e.phase}`, e.at, e.reason ?? Object.keys(e.changes).join("\n"));
|
|
97
|
-
case "workspace_observation": return wrap("event", "verification observation", e.at, e.fingerprint ?? e.reason ?? "unavailable");
|
|
98
|
-
case "extension": return e.display ? wrap("event", e.customType, e.at, e.content || JSON.stringify(e.data)) : "";
|
|
99
|
-
case "user":
|
|
100
|
-
return wrap("user", "user", e.at, [e.text, ...(e.images ?? []).map(imageDescription)].join("\n"));
|
|
101
|
-
case "user_bash": return wrap("tool", e.excludeFromContext ? "user bash (excluded)" : "user bash", e.at, `$ ${e.command}\n${e.content}`);
|
|
102
|
-
case "outcome": return wrap("event", e.outcome.status, e.at, `${e.outcome.reason}: ${e.reason}`);
|
|
103
|
-
case "guidance":
|
|
104
|
-
return wrap("event", `harness ${e.reason}`, e.at, e.text);
|
|
105
|
-
case "assistant":
|
|
106
|
-
return wrap("assistant", "assistant", e.at, assistantText(e.blocks));
|
|
107
|
-
case "interrupt":
|
|
108
|
-
return wrap("event", "interrupt", e.at, `cancelled ${e.user_id}`);
|
|
109
|
-
case "tool_result":
|
|
110
|
-
return wrap("tool", `tool_result ${e.ok ? "ok" : "fail"}`, e.at, [e.content, ...(e.images ?? []).map(imageDescription)].join("\n"));
|
|
111
|
-
case "cut":
|
|
112
|
-
return wrap("event", "cut", e.at, `${e.reason} → ${e.handoff_ref}`);
|
|
113
|
-
case "rewind":
|
|
114
|
-
return wrap("event", `rewind ${e.mode}`, e.at, e.prompt);
|
|
115
|
-
case "spawn":
|
|
116
|
-
return wrap("event", "spawn", e.at, e.purpose);
|
|
117
|
-
case "joined":
|
|
118
|
-
return wrap("event", "joined", e.at, e.summary);
|
|
119
|
-
case "adopt":
|
|
120
|
-
return wrap("event", "adopt", e.at, `branch ${e.branch.node}`);
|
|
121
|
-
case "discard":
|
|
122
|
-
return wrap("event", "discard", e.at, e.reason);
|
|
123
|
-
case "claim":
|
|
124
|
-
return wrap("event", "claim", e.at, e.what);
|
|
125
|
-
case "decision":
|
|
126
|
-
return wrap("event", "decision", e.at, `${e.chose} — ${e.because}`);
|
|
127
|
-
case "pitfall":
|
|
128
|
-
return wrap("event", "pitfall", e.at, `${e.tried} — ${e.failed_because}`);
|
|
129
|
-
case "blocked":
|
|
130
|
-
return wrap("event", "blocked", e.at, `${e.what} — ${e.reason}`);
|
|
131
|
-
case "question":
|
|
132
|
-
return wrap("event", "question", e.at, e.text);
|
|
133
|
-
case "next_action":
|
|
134
|
-
return wrap("event", "next_action", e.at, `${e.what} → ${e.next_action}`);
|
|
135
|
-
case "work_status":
|
|
136
|
-
return wrap("event", `work ${e.operation}`, e.at, `${e.work_id}: ${e.what}`);
|
|
137
|
-
case "evidence_link":
|
|
138
|
-
return wrap("event", "evidence link", e.at, `${e.work}: ${e.what} → ${e.source}`);
|
|
139
|
-
case "work_owner": case "work_dependencies": return wrap("event", e.t, e.at, JSON.stringify(e));
|
|
140
|
-
case "approval": return wrap("event", "approval", e.at,
|
|
141
|
-
[`${e.tool} · ${e.call_id} · ${e.decision} · ${e.source}`, ...(e.rule ? [`rule: ${e.rule}`] : []), ...(e.command !== undefined ? [`$ ${e.command}`] : [])].join("\n"));
|
|
142
|
-
case "skill_loaded": return wrap("event", "skill loaded", e.at, `${e.name} · ${e.path} · ${e.sha256} · ${e.mode}`);
|
|
143
|
-
case "process_exit": return wrap("event", "process exit", e.at, `${e.session} · exit ${e.exit} · output ${e.output.sha256}`);
|
|
144
|
-
case "acceptance_contract": return wrap("event", "acceptance contract", e.at, JSON.stringify(e.contract));
|
|
145
|
-
case "target_observation": return wrap("event", "runtime target", e.at, JSON.stringify(e.observation));
|
|
146
|
-
case "acceptance_decision": return wrap("event", "human acceptance", e.at, `${e.contract_id}: ${e.criterion}`);
|
|
147
|
-
case "counter_check": return wrap("event", "counter-check", e.at,
|
|
148
|
-
`${e.criterion}${e.guard ? " (guard)" : ""} · ${counterVerdict(e)}${"reason" in e ? ` · ${e.reason}` : ` · exit ${e.exit}`}`);
|
|
149
|
-
case "blind_check": return wrap("event", "blind check", e.at, `${e.check} · ${"reason" in e ? e.reason : `exit ${e.exit}`}${e.exposed ? ` · exposed: ${e.exposure ?? "yes"}` : ""}`);
|
|
150
|
-
case "goal": return wrap("event", e.action === "set" ? "goal set" : "goal cleared", e.at,
|
|
151
|
-
e.action === "set" ? `${e.objective}\naccepted: ${e.accepted.join(", ")}${e.rejected.length ? `\nrejected: ${e.rejected.join(", ")}` : ""}` : e.reason);
|
|
152
|
-
default:
|
|
153
|
-
return assertNever(e, "eventHtml");
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function assistantText(blocks: readonly AssistantBlock[]): string {
|
|
158
|
-
const parts: string[] = [];
|
|
159
|
-
for (const b of blocks) {
|
|
160
|
-
switch (b.type) {
|
|
161
|
-
case "text":
|
|
162
|
-
parts.push(b.text);
|
|
163
|
-
break;
|
|
164
|
-
case "thinking":
|
|
165
|
-
parts.push(b.text);
|
|
166
|
-
break;
|
|
167
|
-
case "tool_use":
|
|
168
|
-
parts.push(`${b.name} ${JSON.stringify(b.input)}`);
|
|
169
|
-
break;
|
|
170
|
-
default:
|
|
171
|
-
return assertNever(b, "assistantText");
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return parts.join("\n");
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function wrap(cls: string, kind: string, at: string, text: string): string {
|
|
178
|
-
return `<article class="event ${cls}"><div class="meta">${escapeHtml(kind)} · ${escapeHtml(at)}</div><pre>${escapeHtml(text)}</pre></article>`;
|
|
179
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/** Typed extension flags are data; factories and the final CLI pass share their interpretation. */
|
|
2
|
-
export interface ExtensionFlag { type: "string" | "boolean"; description?: string; default?: string | boolean }
|
|
3
|
-
export interface FlagInput { value?: string; inline: boolean }
|
|
4
|
-
export function extensionFlagValue(name: string, definition: ExtensionFlag, input?: FlagInput): string | boolean | undefined {
|
|
5
|
-
if (!input) return definition.default;
|
|
6
|
-
if (definition.type === "string") {
|
|
7
|
-
if (input.value === undefined) throw new Error(`--${name} requires a string value`);
|
|
8
|
-
return input.value;
|
|
9
|
-
}
|
|
10
|
-
if (input.value === "true") return true;
|
|
11
|
-
if (input.value === "false") return false;
|
|
12
|
-
if (input.inline) throw new Error(`--${name} requires true or false`);
|
|
13
|
-
return true;
|
|
14
|
-
}
|