@visual-z/xeno 0.8.4 → 0.9.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/CHANGELOG.md +8 -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
package/src/core/handoff.ts
DELETED
|
@@ -1,479 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Handoff: the structured projection that carries a segment's durable state into
|
|
3
|
-
* the next one. Generated deterministically from the log with zero LLM calls
|
|
4
|
-
* (decisions D2, D3).
|
|
5
|
-
*
|
|
6
|
-
* The field set borrows names from the closed MCP SEP #2683 proposal
|
|
7
|
-
* (handoff_version, completed, decisions, pitfalls) for future interop. That
|
|
8
|
-
* proposal states no types, no required fields and no invariants, so everything
|
|
9
|
-
* below is this project's own -- in particular the invariants in
|
|
10
|
-
* validateHandoff(), which are the part that actually does work.
|
|
11
|
-
*
|
|
12
|
-
* The contract is enforced here rather than in the prompt because the harness is
|
|
13
|
-
* the only layer that sees prompt assembly, tool mediation and lifecycle events
|
|
14
|
-
* at once -- the placement argument from ClawVM (arXiv:2604.10352).
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import type { LineRange, Sha256, NodeRef, IsoTime, EvidenceQuery, EventId } from "./events.ts";
|
|
18
|
-
import type { ProgressEvidence } from "./progress.ts";
|
|
19
|
-
import { isExecutionOutcome, type ExecutionOutcome } from "./outcome.ts";
|
|
20
|
-
import type { WorkItem } from "./work-state.ts";
|
|
21
|
-
import type { EvidenceLink } from "./evidence-links.ts";
|
|
22
|
-
import type { WorkingSet } from "./working-set.ts";
|
|
23
|
-
import { RECOVERY_SECTIONS, RECOVERY_KINDS } from "./recovery-sources.ts";
|
|
24
|
-
import type { AcceptanceState } from "./acceptance.ts";
|
|
25
|
-
import type { LoadedSkill } from "./skills.ts";
|
|
26
|
-
import type { ProcessObservation } from "./process-state.ts";
|
|
27
|
-
import { emitYaml } from "./yaml.ts";
|
|
28
|
-
|
|
29
|
-
export const HANDOFF_VERSION = 1 as const;
|
|
30
|
-
|
|
31
|
-
/** Resolved proof that a completion claim is real: a log event that exited zero. */
|
|
32
|
-
export type Evidence = {
|
|
33
|
-
/** Configured gate name, when the backing command had one. */
|
|
34
|
-
gate?: string;
|
|
35
|
-
command: string;
|
|
36
|
-
exit: number;
|
|
37
|
-
at: IsoTime;
|
|
38
|
-
/** Id of the backing tool_result event, so the claim is traceable to the log. */
|
|
39
|
-
event_id: string;
|
|
40
|
-
/** Exact observed workspace after the command; absence means legacy/unknown. */
|
|
41
|
-
workspace_sha256?: string;
|
|
42
|
-
/** P.3: omitted on historical full-workspace observations. */
|
|
43
|
-
workspace_scope?: "source-v1";
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export type CompletedItem = {
|
|
47
|
-
what: string;
|
|
48
|
-
work_id?: string;
|
|
49
|
-
/** The proof as it stood when the claim was made. */
|
|
50
|
-
evidence: Evidence;
|
|
51
|
-
evidence_stale?: true;
|
|
52
|
-
/**
|
|
53
|
-
* Set when the gate this item cited is failing as of the latest run.
|
|
54
|
-
*
|
|
55
|
-
* The item stays in `completed` because it really did pass when claimed, and
|
|
56
|
-
* deleting that record would throw away something true. But a reader scanning
|
|
57
|
-
* `completed` would otherwise have to cross-reference `verification` to notice
|
|
58
|
-
* the suite is now red, so the signal is repeated inline where it is read.
|
|
59
|
-
*/
|
|
60
|
-
evidence_superseded?: { gate: string; status: "fail"; exit?: number; at?: IsoTime };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* A claim whose evidence did not resolve. Kept visible on purpose: a skipped or
|
|
65
|
-
* failed check must never read as a pass, and silently dropping the claim would
|
|
66
|
-
* hide exactly the information a reviewer needs.
|
|
67
|
-
*/
|
|
68
|
-
export type UnverifiedClaim = {
|
|
69
|
-
what: string;
|
|
70
|
-
claimed: EvidenceQuery;
|
|
71
|
-
reason: "no_matching_event" | "nonzero_exit" | "stale_workspace";
|
|
72
|
-
/** Present when a matching event existed but failed. */
|
|
73
|
-
exit?: number;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export type InFlightItem = {
|
|
77
|
-
work_id?: string;
|
|
78
|
-
what: string;
|
|
79
|
-
next_action: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export type BlockedItem = {
|
|
83
|
-
work_id?: string;
|
|
84
|
-
what: string;
|
|
85
|
-
reason: string;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export type DecisionItem = {
|
|
89
|
-
work_id?: string;
|
|
90
|
-
source?: { node: number; event_id: string };
|
|
91
|
-
chose: string;
|
|
92
|
-
because: string;
|
|
93
|
-
rejected?: readonly string[];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export type PitfallItem = {
|
|
97
|
-
work_id?: string;
|
|
98
|
-
source?: { node: number; event_id: string };
|
|
99
|
-
tried: string;
|
|
100
|
-
failed_because: string;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export type FileAnchor = {
|
|
104
|
-
path: string;
|
|
105
|
-
/** Checked against subsequent workspace snapshots; true marks a mismatch. */
|
|
106
|
-
sha256: Sha256;
|
|
107
|
-
stale?: true;
|
|
108
|
-
read_ranges?: readonly LineRange[];
|
|
109
|
-
touched: readonly ("read" | "edit")[];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* A branch this node opened, and what became of it.
|
|
114
|
-
*
|
|
115
|
-
* Recorded in the handoff rather than only in the log because the handoff is
|
|
116
|
-
* the only thing that crosses a boundary: a branch absent from it is a branch
|
|
117
|
-
* the next context does not know exists.
|
|
118
|
-
*/
|
|
119
|
-
export type BranchRecord = {
|
|
120
|
-
node: number;
|
|
121
|
-
/** Event id in this node where the branch was spawned. */
|
|
122
|
-
spawned_at: EventId;
|
|
123
|
-
purpose: string;
|
|
124
|
-
background?: true;
|
|
125
|
-
/** null means unresolved, which D16 requires be visible rather than quiet. */
|
|
126
|
-
resolution: "merged" | "discarded" | null;
|
|
127
|
-
/** Present once the branch joined; a path, never the handoff's contents. */
|
|
128
|
-
handoff_ref?: string;
|
|
129
|
-
summary?: string;
|
|
130
|
-
receipt_ref?: string;
|
|
131
|
-
outcome?: ExecutionOutcome;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
export type VerificationStatus = "pass" | "fail" | "not_run";
|
|
135
|
-
|
|
136
|
-
export type VerificationItem = {
|
|
137
|
-
gate: string;
|
|
138
|
-
status: VerificationStatus;
|
|
139
|
-
at?: IsoTime;
|
|
140
|
-
exit?: number;
|
|
141
|
-
workspace_sha256?: string;
|
|
142
|
-
workspace_scope?: "source-v1";
|
|
143
|
-
stale?: true;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export type UserTurnRecord = {
|
|
147
|
-
at: IsoTime;
|
|
148
|
-
text: string;
|
|
149
|
-
/**
|
|
150
|
-
* Set when a later interrupt event cancelled this turn. Omitted rather
|
|
151
|
-
* than false: absence means the turn was not abandoned, which is the
|
|
152
|
-
* common case, and deleting the turn would rewrite history.
|
|
153
|
-
*/
|
|
154
|
-
abandoned?: true;
|
|
155
|
-
/**
|
|
156
|
-
* Working set v3 only: the original user event, present exactly when the turn
|
|
157
|
-
* carried images, so `history(event=ID)` can reattach them.
|
|
158
|
-
*/
|
|
159
|
-
event?: EventId;
|
|
160
|
-
/**
|
|
161
|
-
* Working set v3 only: references to the turn's attached images (D8: digest
|
|
162
|
-
* and identity, never bytes). Absent from the full derived state, so legacy
|
|
163
|
-
* projections and frozen replay are unchanged.
|
|
164
|
-
*/
|
|
165
|
-
images?: readonly UserTurnImage[];
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
/** Enough for the model to know an image exists and which one; bytes stay in the blob store. */
|
|
169
|
-
export type UserTurnImage = { sha256: Sha256; name: string; width: number; height: number };
|
|
170
|
-
|
|
171
|
-
export type Handoff = {
|
|
172
|
-
handoff_version: typeof HANDOFF_VERSION | 2;
|
|
173
|
-
working_set?: WorkingSet;
|
|
174
|
-
acceptance?: AcceptanceState;
|
|
175
|
-
loaded_skills?: readonly LoadedSkill[];
|
|
176
|
-
/** O.14: last observations only; live capabilities cannot survive a restart. */
|
|
177
|
-
processes?: readonly ProcessObservation[];
|
|
178
|
-
/** V2 arrays are carried suffixes; the immutable source rebuilds every page. */
|
|
179
|
-
history?: {
|
|
180
|
-
source: NodeRef;
|
|
181
|
-
sections: Record<string, { total: number; offset: number }>;
|
|
182
|
-
};
|
|
183
|
-
/**
|
|
184
|
-
* The node this state was derived FROM.
|
|
185
|
-
*
|
|
186
|
-
* There is deliberately no field naming the node it is FOR: a fork makes one
|
|
187
|
-
* handoff the basis for several children at once, so that question has no
|
|
188
|
-
* single answer. Identity lives here and nowhere else.
|
|
189
|
-
*/
|
|
190
|
-
predecessor: NodeRef | null;
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Verbatim from the first user turn of segment 0 that was not later
|
|
194
|
-
* cancelled. Never rewritten. If every user turn in that segment was
|
|
195
|
-
* abandoned, this is the last of them (EMPTY_OBJECTIVE forbids blanking it);
|
|
196
|
-
* the abandoned mark lives on `user_turns`.
|
|
197
|
-
*/
|
|
198
|
-
objective: string;
|
|
199
|
-
/**
|
|
200
|
-
* Every user turn, verbatim, accumulated across segments. Which sentence is a
|
|
201
|
-
* hard constraint is not derivable, and user turns are both the smallest and
|
|
202
|
-
* highest-value content in a session -- so all of them are kept rather than
|
|
203
|
-
* asking the model to label some of them.
|
|
204
|
-
*/
|
|
205
|
-
user_turns: readonly UserTurnRecord[];
|
|
206
|
-
/** Set only when the cap dropped turns; names the log refs that still hold them. */
|
|
207
|
-
user_turns_truncated?: { dropped: number; recover_from: readonly string[] };
|
|
208
|
-
|
|
209
|
-
state: {
|
|
210
|
-
/** O.2: canonical identified work; legacy lists below are compatible projections. */
|
|
211
|
-
work_items?: readonly WorkItem[];
|
|
212
|
-
completed: readonly CompletedItem[];
|
|
213
|
-
unverified_claims: readonly UnverifiedClaim[];
|
|
214
|
-
in_flight: readonly InFlightItem[];
|
|
215
|
-
blocked: readonly BlockedItem[];
|
|
216
|
-
/** Derived from the log, so always present even when the model annotated nothing. */
|
|
217
|
-
last_activity: string | null;
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
decisions: readonly DecisionItem[];
|
|
221
|
-
pitfalls: readonly PitfallItem[];
|
|
222
|
-
file_anchors: readonly FileAnchor[];
|
|
223
|
-
verification: readonly VerificationItem[];
|
|
224
|
-
/** Branches opened from this node, resolved or not. */
|
|
225
|
-
branches: readonly BranchRecord[];
|
|
226
|
-
open_questions: readonly string[];
|
|
227
|
-
/** D9 exact novelty ledger. Absence is legacy; rebuild it from original events. */
|
|
228
|
-
progress_evidence?: readonly ProgressEvidence[];
|
|
229
|
-
/** O.6: original evidence references and replacement relationships, never inline history. */
|
|
230
|
-
evidence_links?: readonly EvidenceLink[];
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* True only when work remains AND some in-flight item has a concrete next
|
|
234
|
-
* action. Blocked-with-no-next-action is therefore invalid by construction,
|
|
235
|
-
* which is the correct reading: a human has to look at it.
|
|
236
|
-
*/
|
|
237
|
-
continuation_valid: boolean;
|
|
238
|
-
|
|
239
|
-
budget: {
|
|
240
|
-
/** Length of the path from the root to this node, inclusive. */
|
|
241
|
-
nodes_on_path: number;
|
|
242
|
-
/** Consecutive nodes along this path with no verifiable progress (D9). */
|
|
243
|
-
nodes_without_progress: number;
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// ---------------------------------------------------------------------------
|
|
248
|
-
// YAML field order: schema-defined, not alphabetical, so the file reads top-down
|
|
249
|
-
// the way a person would want to read it (decision D10).
|
|
250
|
-
// ---------------------------------------------------------------------------
|
|
251
|
-
|
|
252
|
-
const ORDER: Record<string, readonly string[]> = {
|
|
253
|
-
"": [
|
|
254
|
-
"handoff_version", "predecessor", "history",
|
|
255
|
-
"objective", "working_set", "user_turns", "user_turns_truncated",
|
|
256
|
-
"state", "acceptance", "loaded_skills", "processes", "decisions", "pitfalls", "file_anchors",
|
|
257
|
-
"verification", "branches", "open_questions", "progress_evidence", "evidence_links",
|
|
258
|
-
"continuation_valid", "budget",
|
|
259
|
-
],
|
|
260
|
-
predecessor: ["node", "log_ref", "sha256"],
|
|
261
|
-
user_turns: ["at", "text", "abandoned", "event", "images"],
|
|
262
|
-
"user_turns.images": ["sha256", "name", "width", "height"],
|
|
263
|
-
user_turns_truncated: ["dropped", "recover_from"],
|
|
264
|
-
state: ["completed", "unverified_claims", "in_flight", "blocked", "last_activity", "work_items"],
|
|
265
|
-
"state.completed": ["work_id", "what", "evidence", "evidence_superseded"],
|
|
266
|
-
"state.completed.evidence_superseded": ["gate", "status", "exit", "at"],
|
|
267
|
-
"state.completed.evidence": ["gate", "command", "exit", "at", "event_id"],
|
|
268
|
-
"state.unverified_claims": ["what", "claimed", "reason", "exit"],
|
|
269
|
-
"state.in_flight": ["work_id", "what", "next_action"],
|
|
270
|
-
"state.blocked": ["work_id", "what", "reason"],
|
|
271
|
-
"state.work_items": ["id", "what", "status", "verification", "opened_at", "updated_at", "next_action", "reason", "evidence", "previous_evidence"],
|
|
272
|
-
decisions: ["chose", "because", "rejected"],
|
|
273
|
-
pitfalls: ["tried", "failed_because"],
|
|
274
|
-
file_anchors: ["path", "sha256", "read_ranges", "touched"],
|
|
275
|
-
verification: ["gate", "status", "at", "exit"],
|
|
276
|
-
branches: ["node", "spawned_at", "purpose", "resolution", "handoff_ref", "summary", "outcome", "receipt_ref", "background"],
|
|
277
|
-
processes: ["session", "node", "event", "observed", "command", "exit", "output"],
|
|
278
|
-
"processes.output": ["sha256", "path", "bytes"],
|
|
279
|
-
"branches.outcome": ["status", "reason"],
|
|
280
|
-
evidence_links: ["id", "work", "what", "event", "resource", "version", "freshness", "superseded_by"],
|
|
281
|
-
budget: ["nodes_on_path", "nodes_without_progress"],
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
/** Key-order resolver for emitYaml(). Array indices are transparent to the path. */
|
|
285
|
-
export function handoffKeyOrder(path: readonly string[]): readonly string[] | undefined {
|
|
286
|
-
return ORDER[path.join(".")];
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* The one way to serialize a handoff.
|
|
291
|
-
*
|
|
292
|
-
* Callers cannot forget the key order and silently get alphabetical output. The
|
|
293
|
-
* types above are declared as type aliases rather than interfaces so they carry
|
|
294
|
-
* an implicit index signature and satisfy YamlValue directly -- no cast.
|
|
295
|
-
*/
|
|
296
|
-
export function emitHandoff(h: Handoff): string {
|
|
297
|
-
return emitYaml(h, { keyOrder: handoffKeyOrder });
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// ---------------------------------------------------------------------------
|
|
301
|
-
// Invariants
|
|
302
|
-
// ---------------------------------------------------------------------------
|
|
303
|
-
|
|
304
|
-
export type Violation = {
|
|
305
|
-
code:
|
|
306
|
-
| "COMPLETED_WITHOUT_PASSING_EVIDENCE"
|
|
307
|
-
| "PASS_WITHOUT_ZERO_EXIT"
|
|
308
|
-
| "ANCHOR_WITHOUT_SHA"
|
|
309
|
-
| "EMPTY_OBJECTIVE"
|
|
310
|
-
| "PREDECESSOR_MISSING"
|
|
311
|
-
| "PREDECESSOR_WITHOUT_SHA"
|
|
312
|
-
| "EMPTY_PATH"
|
|
313
|
-
| "CONTINUATION_WITHOUT_NEXT_ACTION"
|
|
314
|
-
| "INVALID_HISTORY"
|
|
315
|
-
| "INVALID_EXECUTION_OUTCOME"
|
|
316
|
-
| "INVALID_WORK_STATE"
|
|
317
|
-
| "INVALID_EVIDENCE_LINK"
|
|
318
|
-
| "INVALID_WORKING_SET"
|
|
319
|
-
| "INVALID_USER_TURN"
|
|
320
|
-
| "INVALID_ACCEPTANCE"
|
|
321
|
-
| "INVALID_SKILL_SOURCE"
|
|
322
|
-
| "INVALID_PROCESS_STATE"
|
|
323
|
-
| "BAD_VERSION";
|
|
324
|
-
detail: string;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* The enforceable contract. Every rule here exists because some real harness got
|
|
329
|
-
* it wrong: "migration applied successfully" with a nonzero exit code nobody
|
|
330
|
-
* read; a skipped check reported as a pass; a stale file snapshot presented as
|
|
331
|
-
* current state.
|
|
332
|
-
*/
|
|
333
|
-
export function validateHandoff(h: Handoff): Violation[] {
|
|
334
|
-
const v: Violation[] = [];
|
|
335
|
-
for (const link of h.evidence_links ?? []) if (!link.id || !link.work || !link.event ||
|
|
336
|
-
!["current", "stale", "unknown"].includes(link.freshness) || (link.freshness !== "unknown" && !link.version)) {
|
|
337
|
-
v.push({ code: "INVALID_EVIDENCE_LINK", detail: `invalid evidence reference ${link.id}` });
|
|
338
|
-
}
|
|
339
|
-
const workIds = new Set<string>();
|
|
340
|
-
for (const item of h.state.work_items ?? []) {
|
|
341
|
-
if (item.depends_on && (new Set(item.depends_on).size !== item.depends_on.length || item.depends_on.includes(item.id) ||
|
|
342
|
-
item.depends_on.some(dep => !dep.trim())) || item.owner !== undefined && (!item.owner.trim() || !item.owner_event)) {
|
|
343
|
-
v.push({ code: "INVALID_WORK_STATE", detail: `invalid dependencies or ownership for work ${item.id}` });
|
|
344
|
-
}
|
|
345
|
-
const proven = item.verification === "verified" || item.verification === "stale";
|
|
346
|
-
if (!item.id.trim() || workIds.has(item.id) || !item.what.trim() || !item.opened_at || !item.updated_at ||
|
|
347
|
-
!["active", "blocked", "completed", "cancelled"].includes(item.status) ||
|
|
348
|
-
!["not_run", "verified", "unverified", "stale"].includes(item.verification) ||
|
|
349
|
-
(item.status === "active" && !item.next_action?.trim()) ||
|
|
350
|
-
(item.status === "completed" && !proven) ||
|
|
351
|
-
(proven && (!item.evidence || item.evidence.exit !== 0)) ||
|
|
352
|
-
(!proven && item.evidence !== undefined)) {
|
|
353
|
-
v.push({ code: "INVALID_WORK_STATE", detail: `invalid identity, lifecycle or evidence for work ${item.id}` });
|
|
354
|
-
}
|
|
355
|
-
workIds.add(item.id);
|
|
356
|
-
}
|
|
357
|
-
for (const branch of h.branches) {
|
|
358
|
-
if (branch.outcome !== undefined && !isExecutionOutcome(branch.outcome)) {
|
|
359
|
-
v.push({ code: "INVALID_EXECUTION_OUTCOME", detail: `branch ${branch.node} has an invalid execution outcome` });
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (h.handoff_version !== HANDOFF_VERSION && h.handoff_version !== 2) {
|
|
364
|
-
v.push({ code: "BAD_VERSION", detail: `expected ${HANDOFF_VERSION}, got ${h.handoff_version}` });
|
|
365
|
-
}
|
|
366
|
-
if (h.working_set) {
|
|
367
|
-
const view = h.working_set;
|
|
368
|
-
if (![1, 2, 3].includes(view.version) || view.source.node !== h.predecessor?.node || view.source.sha256 !== h.predecessor?.sha256 ||
|
|
369
|
-
!Number.isSafeInteger(view.omitted.work) || view.omitted.work < 0 || !Number.isSafeInteger(view.omitted.references) || view.omitted.references < 0 ||
|
|
370
|
-
new Set(view.work.map(item => item.id)).size !== view.work.length || view.work.some(item => !["active", "blocked"].includes(item.status)) ||
|
|
371
|
-
view.references.some(reference => !reference.event) ||
|
|
372
|
-
view.version >= 2 && (!view.recovery_sections || RECOVERY_SECTIONS.some(section => {
|
|
373
|
-
const range = view.recovery_sections![section], references = view.references.filter(ref => ref.kind === RECOVERY_KINDS[section]);
|
|
374
|
-
return !range || !Number.isSafeInteger(range.total) || !Number.isSafeInteger(range.omitted) || range.omitted < 0 || range.omitted > range.total ||
|
|
375
|
-
range.total - range.omitted !== references.length || new Set(references.map(ref => ref.event)).size !== references.length ||
|
|
376
|
-
references.some(ref => !Number.isSafeInteger(ref.node) || Number(ref.node) < 0 || ref.provenance !==
|
|
377
|
-
(section === "users" ? "user_message" : section === "next_actions" ? "model_annotation" : "observation"));
|
|
378
|
-
}))) {
|
|
379
|
-
v.push({ code: "INVALID_WORKING_SET", detail: "working set must reference its source and explicit active state" });
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
// D8: an image reference must name its blob and the original event that reattaches it.
|
|
383
|
-
if (h.user_turns.some(turn => turn.event !== undefined && !turn.images?.length ||
|
|
384
|
-
turn.images !== undefined && (!turn.event || turn.images.length === 0 || turn.images.some(image => !/^[a-f0-9]{64}$/.test(image.sha256))))) {
|
|
385
|
-
v.push({ code: "INVALID_USER_TURN", detail: "user turn images need their original event and blob digests" });
|
|
386
|
-
}
|
|
387
|
-
if (h.handoff_version === 2) {
|
|
388
|
-
const history = h.history;
|
|
389
|
-
if (!history || !h.predecessor || history.source.node !== h.predecessor.node ||
|
|
390
|
-
history.source.sha256 !== h.predecessor.sha256) {
|
|
391
|
-
v.push({ code: "INVALID_HISTORY", detail: "paged handoff must identify its immutable predecessor" });
|
|
392
|
-
} else for (const [section, range] of Object.entries(history.sections)) {
|
|
393
|
-
const values = section === "acceptance.criteria" ? h.acceptance?.criteria : section === "acceptance.targets" ? h.acceptance?.targets : section.startsWith("state.") ? h.state[section.slice(6) as keyof Handoff["state"]]
|
|
394
|
-
: h[section as keyof Handoff];
|
|
395
|
-
if (!Array.isArray(values) || !Number.isSafeInteger(range.total) || !Number.isSafeInteger(range.offset) ||
|
|
396
|
-
range.offset < 0 || range.total < range.offset || range.total - range.offset !== values.length) {
|
|
397
|
-
v.push({ code: "INVALID_HISTORY", detail: `invalid carried range for ${section}` });
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (h.acceptance?.criteria.some(item => item.status === "pass" && (!item.evidence || item.evidence.exit !== 0) ||
|
|
403
|
-
item.status === "accepted" && (item.kind !== "human" || !item.accepted_event || item.target && !item.accepted_binding))) {
|
|
404
|
-
v.push({ code: "INVALID_ACCEPTANCE", detail: "criteria need actual command or user decision evidence" });
|
|
405
|
-
}
|
|
406
|
-
if (h.acceptance?.targets?.some(target => target.status === "ready" && (!target.observation || !target.binding || !target.session || !target.pid || !target.entry || !target.build_event))) {
|
|
407
|
-
v.push({ code: "INVALID_ACCEPTANCE", detail: "ready targets need original build and owned instance observations" });
|
|
408
|
-
}
|
|
409
|
-
if (h.loaded_skills?.some(item => !/^[a-f0-9]{64}$/.test(item.sha256) || item.sha256 !== item.artifact ||
|
|
410
|
-
!item.path || !item.event || !Number.isSafeInteger(item.node) || item.node < 0)) {
|
|
411
|
-
v.push({ code: "INVALID_SKILL_SOURCE", detail: "loaded skill must reference original instruction bytes and event" });
|
|
412
|
-
}
|
|
413
|
-
if (h.processes?.some(item => !Number.isSafeInteger(item.session) || item.session < 1 || !Number.isSafeInteger(item.node) || item.node < 0 ||
|
|
414
|
-
!item.event || !["running", "exited"].includes(item.observed) || item.observed === "exited" && !Number.isInteger(item.exit) ||
|
|
415
|
-
item.output && (!/^[a-f0-9]{64}$/.test(item.output.sha256) || !Number.isSafeInteger(item.output.bytes) || item.output.bytes < 0 || !item.output.path))) {
|
|
416
|
-
v.push({ code: "INVALID_PROCESS_STATE", detail: "process state requires original observation and actual exit evidence" });
|
|
417
|
-
}
|
|
418
|
-
for (const c of h.state.completed) {
|
|
419
|
-
if (c.evidence.exit !== 0) {
|
|
420
|
-
v.push({
|
|
421
|
-
code: "COMPLETED_WITHOUT_PASSING_EVIDENCE",
|
|
422
|
-
detail: `"${c.what}" cites ${c.evidence.command} which exited ${c.evidence.exit}`,
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
for (const item of h.verification) {
|
|
428
|
-
if (item.status === "pass" && item.exit !== 0) {
|
|
429
|
-
v.push({
|
|
430
|
-
code: "PASS_WITHOUT_ZERO_EXIT",
|
|
431
|
-
detail: `gate "${item.gate}" is pass but exit is ${item.exit ?? "absent"}`,
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
for (const a of h.file_anchors) {
|
|
437
|
-
if (!a.sha256) {
|
|
438
|
-
v.push({ code: "ANCHOR_WITHOUT_SHA", detail: `anchor for ${a.path} has no sha256` });
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (h.objective.trim().length === 0) {
|
|
443
|
-
v.push({ code: "EMPTY_OBJECTIVE", detail: "objective is empty" });
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Every handoff is derived from some node, so a null predecessor means the
|
|
447
|
-
// state has no provenance -- there is nothing to grep back to.
|
|
448
|
-
if (h.predecessor === null) {
|
|
449
|
-
v.push({ code: "PREDECESSOR_MISSING", detail: "handoff has no source node" });
|
|
450
|
-
} else if (!h.predecessor.sha256) {
|
|
451
|
-
// Same rule as ANCHOR_WITHOUT_SHA: a reference you cannot check is a
|
|
452
|
-
// promise, not a pointer (D8). predecessor is the chain's most important
|
|
453
|
-
// anchor; filling it without a digest is how the empty-string default
|
|
454
|
-
// survived every call site. node 0 with predecessor === null is a
|
|
455
|
-
// different case and is already covered above.
|
|
456
|
-
v.push({
|
|
457
|
-
code: "PREDECESSOR_WITHOUT_SHA",
|
|
458
|
-
detail: `predecessor node ${h.predecessor.node} has no sha256`,
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// A path always contains at least the node itself.
|
|
463
|
-
if (h.budget.nodes_on_path < 1) {
|
|
464
|
-
v.push({
|
|
465
|
-
code: "EMPTY_PATH",
|
|
466
|
-
detail: `nodes_on_path is ${h.budget.nodes_on_path}, but a path includes its own node`,
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
if (h.continuation_valid && !h.state.in_flight.some((i) => i.next_action.trim().length > 0) &&
|
|
471
|
-
!(h.handoff_version === 2 && (h.history?.sections["state.in_flight"]?.offset ?? 0) > 0)) {
|
|
472
|
-
v.push({
|
|
473
|
-
code: "CONTINUATION_WITHOUT_NEXT_ACTION",
|
|
474
|
-
detail: "continuation_valid is true but no in_flight item has a next_action",
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return v;
|
|
479
|
-
}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/** N.2: searchable views retain provenance; they can always be rebuilt from the log. */
|
|
2
|
-
import type { LogEvent, ToolResultEvent } from "./events.ts";
|
|
3
|
-
import { canonicalValue } from "./continuation.ts";
|
|
4
|
-
import { deriveEvidenceLinks, observationResources, resourceKey, type EvidenceLink } from "./evidence-links.ts";
|
|
5
|
-
import type { EnvironmentValidity } from "./environment.ts";
|
|
6
|
-
|
|
7
|
-
export interface HistoryEntry extends Pick<ToolResultEvent, "effect" | "outcome" | "data" | "data_ref" | "diagnostic" | "observation" | "warnings"> {
|
|
8
|
-
node: number;
|
|
9
|
-
event: string;
|
|
10
|
-
kind: string;
|
|
11
|
-
text: string;
|
|
12
|
-
tool?: string;
|
|
13
|
-
input?: unknown;
|
|
14
|
-
ok?: boolean;
|
|
15
|
-
artifact?: string;
|
|
16
|
-
resources?: readonly string[];
|
|
17
|
-
work_ids?: readonly string[];
|
|
18
|
-
evidence?: readonly EvidenceLink[];
|
|
19
|
-
source_available?: boolean;
|
|
20
|
-
/** Runtime consumption view only; never changes the original capability receipt. */
|
|
21
|
-
environment?: EnvironmentValidity;
|
|
22
|
-
invocation?: { event: string; input: string; schema: string | null; parent_call_id?: string; origin?: "extension" };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** O.15: bump when historyEntries or freshness semantics change. */
|
|
26
|
-
export const HISTORY_INDEX_VERSION = 6;
|
|
27
|
-
export function historyKeyInput(nodes: readonly { node: number; events: readonly LogEvent[] }[], version = HISTORY_INDEX_VERSION): string {
|
|
28
|
-
const calls = new Map<string, string>();
|
|
29
|
-
const self = (name: string | undefined) => name === "history" || name === "note";
|
|
30
|
-
return canonicalValue({ version, nodes: nodes.map(part => ({ node: part.node, events: part.events.flatMap<LogEvent>(event => {
|
|
31
|
-
// The index deliberately excludes its own reads. Effects still affect
|
|
32
|
-
// freshness, and annotations have their own durable events (O.6).
|
|
33
|
-
if (event.t === "tool_invocation") { calls.set(event.call_id, event.name); return self(event.name) ? [] : [event]; }
|
|
34
|
-
if (event.t === "tool_result" && self(calls.get(event.call_id)) && event.effect === undefined) return [];
|
|
35
|
-
if (event.t !== "assistant") return [event];
|
|
36
|
-
for (const block of event.blocks) if (block.type === "tool_use") calls.set(block.call_id, block.name);
|
|
37
|
-
const blocks = event.blocks.filter(block => block.type === "text" || block.type === "tool_use" && !self(block.name));
|
|
38
|
-
return blocks.length ? [{ ...event, blocks }] : [];
|
|
39
|
-
}) })) });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function historyEntries(nodes: readonly { node: number; events: readonly LogEvent[] }[]): HistoryEntry[] {
|
|
43
|
-
const entries: HistoryEntry[] = [];
|
|
44
|
-
const calls = new Map<string, { name: string; input: unknown }>();
|
|
45
|
-
const invocations = new Map<string, NonNullable<HistoryEntry["invocation"]>>();
|
|
46
|
-
for (const { node, events } of nodes) for (const e of events) {
|
|
47
|
-
if (e.t === "assistant") {
|
|
48
|
-
for (const block of e.blocks) if (block.type === "tool_use") calls.set(block.call_id, { name: block.name, input: block.input });
|
|
49
|
-
const text = e.blocks.filter((b) => b.type === "text").map((b) => b.text).join("\n");
|
|
50
|
-
if (text) entries.push({ node, event: e.id, kind: e.t, text });
|
|
51
|
-
} else if (e.t === "tool_invocation") {
|
|
52
|
-
calls.set(e.call_id, { name: e.name, input: e.input ?? calls.get(e.call_id)?.input ?? { artifact: e.input_ref.sha256 } });
|
|
53
|
-
invocations.set(e.call_id, { event: e.id, input: e.input_ref.sha256, schema: e.schema_ref?.sha256 ?? null,
|
|
54
|
-
...(e.origin ? { origin: e.origin } : {}),
|
|
55
|
-
...(e.parent_call_id ? { parent_call_id: e.parent_call_id } : {}) });
|
|
56
|
-
} else if (e.t === "tool_result") {
|
|
57
|
-
const call = calls.get(e.call_id);
|
|
58
|
-
// Indexing query results recursively would make the index amplify itself.
|
|
59
|
-
if (call?.name === "history" || call?.name === "note") continue;
|
|
60
|
-
entries.push({ node, event: e.id, kind: e.t, text: e.content, ok: e.ok,
|
|
61
|
-
...(call ? { tool: call.name, input: call.input } : {}),
|
|
62
|
-
...(invocations.has(e.call_id) ? { invocation: invocations.get(e.call_id)! } : {}),
|
|
63
|
-
resources: observationResources(e, call?.input),
|
|
64
|
-
...(e.effect ? { effect: e.effect } : {}), ...(e.outcome ? { outcome: e.outcome } : {}),
|
|
65
|
-
...(e.data !== undefined ? { data: e.data } : {}), ...(e.diagnostic ? { diagnostic: e.diagnostic } : {}),
|
|
66
|
-
...(e.data_ref ? { data_ref: e.data_ref } : {}),
|
|
67
|
-
...(e.observation ? { observation: e.observation } : {}),
|
|
68
|
-
...(e.warnings ? { warnings: e.warnings } : {}),
|
|
69
|
-
...(e.content_ref ? { artifact: e.content_ref.sha256 } : {}) });
|
|
70
|
-
} else if (e.t === "target_observation") {
|
|
71
|
-
entries.push({ node, event: e.id, kind: e.t, text: canonicalValue(e.observation),
|
|
72
|
-
...(e.observation.manifest ? { artifact: e.observation.manifest.sha256 } : {}) });
|
|
73
|
-
} else if (e.t === "process_exit") {
|
|
74
|
-
entries.push({ node, event: e.id, kind: e.t, text: canonicalValue(e), artifact: e.output.sha256 });
|
|
75
|
-
} else if (["work_owner", "work_dependencies", "approval", "skill_loaded", "acceptance_contract", "acceptance_decision", "user", "next_action", "decision", "pitfall", "blocked", "question", "claim", "work_status", "evidence_link"].includes(e.t)) {
|
|
76
|
-
entries.push({ node, event: e.id, kind: e.t, text: e.t === "user" ? e.text : canonicalValue(e),
|
|
77
|
-
...("work_id" in e && e.work_id ? { work_ids: [e.work_id] } : {}),
|
|
78
|
-
...(e.t === "evidence_link" ? { work_ids: [e.work], ...(e.resource ? { resources: [resourceKey(e.resource)] } : {}) } : {}) });
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
const lookup = new Map(entries.map(entry => [entry.event, entry]));
|
|
82
|
-
for (const link of deriveEvidenceLinks([], nodes.flatMap(node => [...node.events]))) {
|
|
83
|
-
const source = lookup.get(link.event);
|
|
84
|
-
const annotation = lookup.get(link.id);
|
|
85
|
-
if (annotation) { annotation.source_available = source !== undefined; annotation.evidence = [link]; }
|
|
86
|
-
if (source) {
|
|
87
|
-
source.work_ids = [...new Set([...(source.work_ids ?? []), link.work])];
|
|
88
|
-
source.evidence = [...(source.evidence ?? []), link];
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return entries;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function historyIndex(entries: readonly HistoryEntry[]): {
|
|
95
|
-
byEvent: ReadonlyMap<string, HistoryEntry>; byResource: ReadonlyMap<string, readonly HistoryEntry[]>; byWork: ReadonlyMap<string, readonly HistoryEntry[]>;
|
|
96
|
-
} {
|
|
97
|
-
const byResource = new Map<string, HistoryEntry[]>(), byWork = new Map<string, HistoryEntry[]>();
|
|
98
|
-
const add = (index: Map<string, HistoryEntry[]>, key: string, entry: HistoryEntry): void => {
|
|
99
|
-
const items = index.get(key);
|
|
100
|
-
if (items) items.push(entry); else index.set(key, [entry]);
|
|
101
|
-
};
|
|
102
|
-
for (const entry of entries) {
|
|
103
|
-
for (const resource of entry.resources ?? []) add(byResource, resource, entry);
|
|
104
|
-
for (const work of entry.work_ids ?? []) add(byWork, work, entry);
|
|
105
|
-
}
|
|
106
|
-
return { byEvent: new Map(entries.map(entry => [entry.event, entry])), byResource, byWork };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Literal AND search, newest first. No regex execution, hidden ranking, or LLM. */
|
|
110
|
-
export function queryHistory(entries: readonly HistoryEntry[], query: string, tool?: string): HistoryEntry[] {
|
|
111
|
-
const terms = query.toLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
112
|
-
return entries.filter((entry) => {
|
|
113
|
-
if (tool !== undefined && entry.tool !== tool) return false;
|
|
114
|
-
const text = `${entry.text}\n${canonicalValue(entry.input)}`.toLowerCase();
|
|
115
|
-
return terms.every((term) => text.includes(term));
|
|
116
|
-
}).toReversed();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Lines of an entry that contain a query term, so a caller can see the match
|
|
121
|
-
* without reading back the whole observation. Literal and case-insensitive,
|
|
122
|
-
* like queryHistory; an index view, never replacement evidence.
|
|
123
|
-
*/
|
|
124
|
-
export function historyHits(text: string, terms: readonly string[], limit = 5, width = 300): { line: number; text: string }[] {
|
|
125
|
-
const needles = terms.map((term) => term.toLowerCase()).filter(Boolean);
|
|
126
|
-
if (needles.length === 0 || limit < 1) return [];
|
|
127
|
-
const hits: { line: number; text: string }[] = [];
|
|
128
|
-
const lines = text.split("\n");
|
|
129
|
-
for (let i = 0; i < lines.length && hits.length < limit; i++) {
|
|
130
|
-
const lower = lines[i]!.toLowerCase();
|
|
131
|
-
if (!needles.some((needle) => lower.includes(needle))) continue;
|
|
132
|
-
const line = lines[i]!;
|
|
133
|
-
// Center a long line on its first match so the term survives the cut.
|
|
134
|
-
const at = Math.min(...needles.map((needle) => lower.indexOf(needle)).filter((index) => index >= 0));
|
|
135
|
-
const start = line.length <= width ? 0 : Math.max(0, Math.min(at - Math.floor(width / 3), line.length - width));
|
|
136
|
-
hits.push({ line: i + 1, text: line.slice(start, start + width) });
|
|
137
|
-
}
|
|
138
|
-
return hits;
|
|
139
|
-
}
|