@visual-z/xeno 0.2.1 → 0.3.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 +9 -0
- package/README.md +106 -26
- package/docs/install.md +51 -13
- package/docs/providers.md +11 -0
- package/package.json +5 -4
- package/src/apps/approve.ts +3 -3
- package/src/apps/commands.ts +38 -5
- package/src/apps/completions.ts +73 -19
- package/src/apps/image-draft.ts +46 -0
- package/src/apps/project-trust.ts +2 -42
- package/src/apps/provider-connect.ts +346 -0
- package/src/apps/render.ts +22 -47
- package/src/apps/resources.ts +5 -2
- package/src/apps/shell.ts +298 -1526
- package/src/apps/startup-input.ts +45 -9
- package/src/apps/startup.ts +128 -86
- package/src/apps/ui/app.ts +1383 -0
- package/src/apps/ui/extension-host.ts +194 -0
- package/src/apps/ui/image-cache.ts +78 -0
- package/src/apps/ui/palette.ts +88 -0
- package/src/apps/ui/project.ts +127 -0
- package/src/apps/ui/standalone.ts +146 -0
- package/src/apps/ui/status-model.ts +68 -0
- package/src/apps/ui/surface.ts +84 -0
- package/src/apps/ui/theme.ts +86 -0
- package/src/core/acceptance.ts +107 -0
- package/src/core/action-defaults.ts +47 -295
- package/src/core/benchmark.ts +2 -2
- package/src/core/branch-receipt.ts +24 -0
- package/src/core/checkpoints.ts +64 -0
- package/src/core/churn.ts +2 -0
- package/src/core/command-artifacts.ts +94 -0
- package/src/core/command-contract.ts +35 -0
- package/src/core/completion-review.ts +21 -5
- package/src/core/context-detail.ts +37 -0
- package/src/core/context-policy.ts +74 -1
- package/src/core/continuation.ts +17 -2
- package/src/core/derive.ts +29 -3
- package/src/core/draft-images.ts +56 -0
- package/src/core/environment.ts +145 -0
- package/src/core/evaluation-coverage.ts +74 -0
- package/src/core/event-validation.ts +80 -5
- package/src/core/events.ts +97 -3
- package/src/core/evidence-links.ts +7 -0
- package/src/core/evidence.ts +13 -3
- package/src/core/execution.ts +8 -2
- package/src/core/experiment.ts +133 -0
- package/src/core/export.ts +9 -0
- package/src/core/failure-recovery.ts +173 -0
- package/src/core/file-precondition.ts +72 -0
- package/src/core/handoff.ts +65 -4
- package/src/core/history-query.ts +38 -2
- package/src/core/history.ts +38 -1
- package/src/core/integration-health.ts +34 -0
- package/src/core/keymap.ts +8 -20
- package/src/core/model-picker.ts +64 -0
- package/src/core/native-eval.ts +25 -0
- package/src/core/note-guidance.ts +34 -0
- package/src/core/observation-view.ts +49 -8
- package/src/core/permissions.ts +24 -2
- package/src/core/process-receipt.ts +105 -0
- package/src/core/process-state.ts +37 -0
- package/src/core/project.ts +45 -18
- package/src/core/prompt-policy.ts +37 -0
- package/src/core/query-eval-contract.ts +14 -0
- package/src/core/read-observation.ts +82 -0
- package/src/core/recovery-corpus.ts +17 -0
- package/src/core/recovery-evaluation.ts +60 -0
- package/src/core/recovery-sources.ts +74 -0
- package/src/core/runtime-target.ts +94 -0
- package/src/core/search-observation.ts +97 -0
- package/src/core/session-tree.ts +147 -0
- package/src/core/settings.ts +26 -10
- package/src/core/skills.ts +44 -0
- package/src/core/sql-query.ts +211 -0
- package/src/core/theme.ts +36 -58
- package/src/core/tool-contract.ts +51 -2
- package/src/core/tool-discovery.ts +87 -0
- package/src/core/tool-failure.ts +60 -0
- package/src/core/tool-mode-protocol.ts +25 -0
- package/src/core/transcript.ts +19 -4
- package/src/core/trial-lifecycle.ts +14 -0
- package/src/core/tui-mode.ts +29 -0
- package/src/core/update-check.ts +97 -0
- package/src/core/version.ts +55 -0
- package/src/core/work-state.ts +49 -3
- package/src/core/working-set.ts +91 -0
- package/src/runtime/auth.ts +2 -2
- package/src/runtime/branch-jobs.ts +36 -0
- package/src/runtime/clipboard.ts +15 -4
- package/src/runtime/command-artifacts.ts +64 -0
- package/src/runtime/environment-tools.ts +60 -0
- package/src/runtime/evidence-notes.ts +4 -2
- package/src/runtime/exec-sessions.ts +206 -61
- package/src/runtime/execution-control.ts +1 -0
- package/src/runtime/extension-ui.ts +22 -30
- package/src/runtime/extensions.ts +95 -16
- package/src/runtime/harness-comparison.ts +27 -7
- package/src/runtime/harness-eval.ts +460 -0
- package/src/runtime/history-cache.ts +57 -0
- package/src/runtime/history-tools.ts +62 -12
- package/src/runtime/integrations.ts +210 -0
- package/src/runtime/loop.ts +1021 -268
- package/src/runtime/model-discovery.ts +208 -0
- package/src/runtime/native-eval-fixtures.ts +33 -0
- package/src/runtime/native-eval.ts +143 -0
- package/src/runtime/oauth.ts +1 -1
- package/src/runtime/provider-connect.ts +109 -0
- package/src/runtime/provider-presets.ts +87 -0
- package/src/runtime/query-eval-verifier.ts +28 -0
- package/src/runtime/recovery-coverage.ts +71 -0
- package/src/runtime/recovery-evidence.ts +76 -0
- package/src/runtime/recovery-fixtures.ts +116 -0
- package/src/runtime/runtime-target.ts +149 -0
- package/src/runtime/tool-broker.ts +137 -0
- package/src/runtime/tool-discovery.ts +57 -0
- package/src/runtime/tool-mode-eval.ts +146 -0
- package/src/runtime/tool-queue.ts +77 -0
- package/src/runtime/tools.ts +628 -183
- package/src/runtime/trial-control.ts +45 -0
- package/src/runtime/update-check.ts +47 -0
- package/src/runtime/window-trial.ts +2 -1
- package/src/runtime/working-notes.ts +33 -3
- package/src/runtime/workspace-lifecycle.ts +97 -0
- package/src/store/acceptance.ts +15 -0
- package/src/store/checkpoint-history.ts +43 -0
- package/src/store/command-artifacts.ts +96 -0
- package/src/store/command.ts +65 -37
- package/src/store/file-checkpoints.ts +104 -0
- package/src/store/file-index.ts +13 -1
- package/src/store/git-checkpoints.ts +67 -0
- package/src/store/observations.ts +61 -20
- package/src/store/process-receipts.ts +34 -0
- package/src/store/query-source.ts +13 -8
- package/src/store/recovery.ts +2 -3
- package/src/store/replay.ts +16 -4
- package/src/store/runtime-target.ts +64 -0
- package/src/store/search.ts +171 -132
- package/src/store/session-archive.ts +4 -0
- package/src/store/source-cancellation.ts +6 -0
- package/src/store/update-cache.ts +36 -0
- package/src/store/workspace-observation.ts +113 -0
- package/src/tui/ansi-spans.ts +126 -0
- package/src/tui/style.ts +19 -16
- package/src/tui/ui/atomic-draft.ts +121 -0
- package/src/tui/ui/canvas.ts +96 -0
- package/src/tui/ui/colors.ts +70 -0
- package/src/tui/ui/composer.ts +199 -0
- package/src/tui/ui/dialog.ts +379 -0
- package/src/tui/ui/draft.ts +23 -0
- package/src/tui/ui/edge.ts +186 -0
- package/src/tui/ui/image-size.ts +40 -0
- package/src/tui/ui/ink.ts +210 -0
- package/src/tui/ui/items.ts +40 -0
- package/src/tui/ui/keys.ts +40 -0
- package/src/tui/ui/layout.ts +74 -0
- package/src/tui/ui/list-model.ts +86 -0
- package/src/tui/ui/list-view.ts +172 -0
- package/src/tui/ui/mascot.ts +67 -0
- package/src/tui/ui/messages.ts +518 -0
- package/src/tui/ui/motion.ts +28 -0
- package/src/tui/ui/paths.ts +10 -0
- package/src/tui/ui/pictures.ts +78 -0
- package/src/tui/ui/screen.ts +421 -0
- package/src/tui/ui/status.ts +271 -0
- package/src/tui/ui/styled.ts +85 -0
- package/src/tui/ui/testing.ts +5 -0
- package/src/tui/ui/tool-card.ts +150 -0
- package/src/apps/extension-ui.ts +0 -330
- package/src/apps/resource-browser.ts +0 -75
- package/src/apps/session-browser.ts +0 -113
- package/src/apps/session-picker.ts +0 -55
- package/src/tui/ansi.ts +0 -67
- package/src/tui/autocomplete.ts +0 -29
- package/src/tui/chat-viewport.ts +0 -47
- package/src/tui/complete.ts +0 -87
- package/src/tui/editor.ts +0 -940
- package/src/tui/host.ts +0 -264
- package/src/tui/input.ts +0 -75
- package/src/tui/keys.ts +0 -378
- package/src/tui/markdown.ts +0 -17
- package/src/tui/render.ts +0 -153
- package/src/tui/select.ts +0 -258
- package/src/tui/settings.ts +0 -44
- package/src/tui/statusline.ts +0 -162
- package/src/tui/system-theme.ts +0 -700
- package/src/tui/terminal.ts +0 -30
- package/src/tui/text.ts +0 -52
- package/src/tui/toolview.ts +0 -386
- package/src/tui/transcript.ts +0 -123
- package/src/tui/tree.ts +0 -471
- package/src/vendor/pi-tui/LICENSE.upstream +0 -21
- package/src/vendor/pi-tui/UPSTREAM.json +0 -50
- package/src/vendor/pi-tui/VENDOR.md +0 -25
- package/src/vendor/pi-tui/alt-screen-search.ts +0 -327
- package/src/vendor/pi-tui/autocomplete.ts +0 -860
- package/src/vendor/pi-tui/colors.ts +0 -367
- package/src/vendor/pi-tui/components/alt-screen-flash.ts +0 -51
- package/src/vendor/pi-tui/components/box.ts +0 -166
- package/src/vendor/pi-tui/components/cancellable-loader.ts +0 -40
- package/src/vendor/pi-tui/components/editor.ts +0 -2472
- package/src/vendor/pi-tui/components/h-stack.ts +0 -44
- package/src/vendor/pi-tui/components/image.ts +0 -127
- package/src/vendor/pi-tui/components/input.ts +0 -494
- package/src/vendor/pi-tui/components/loader.ts +0 -101
- package/src/vendor/pi-tui/components/markdown.ts +0 -1021
- package/src/vendor/pi-tui/components/mouse-region.ts +0 -33
- package/src/vendor/pi-tui/components/scroll-view.ts +0 -224
- package/src/vendor/pi-tui/components/select-list.ts +0 -273
- package/src/vendor/pi-tui/components/settings-list.ts +0 -328
- package/src/vendor/pi-tui/components/spacer.ts +0 -28
- package/src/vendor/pi-tui/components/stack.ts +0 -154
- package/src/vendor/pi-tui/components/text.ts +0 -107
- package/src/vendor/pi-tui/components/truncated-text.ts +0 -65
- package/src/vendor/pi-tui/components/v-stack.ts +0 -33
- package/src/vendor/pi-tui/editor-component.ts +0 -74
- package/src/vendor/pi-tui/fuzzy.ts +0 -138
- package/src/vendor/pi-tui/index.ts +0 -186
- package/src/vendor/pi-tui/keybindings.ts +0 -320
- package/src/vendor/pi-tui/keys.ts +0 -1401
- package/src/vendor/pi-tui/kill-ring.ts +0 -46
- package/src/vendor/pi-tui/latex.ts +0 -1506
- package/src/vendor/pi-tui/layout-node.ts +0 -51
- package/src/vendor/pi-tui/layout.ts +0 -449
- package/src/vendor/pi-tui/native/UPSTREAM.json +0 -23
- package/src/vendor/pi-tui/native/clipboard.h +0 -208
- package/src/vendor/pi-tui/native/darwin/README.md +0 -22
- package/src/vendor/pi-tui/native/darwin/build.sh +0 -69
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +0 -132
- package/src/vendor/pi-tui/native/linux/README.md +0 -23
- package/src/vendor/pi-tui/native/linux/build.sh +0 -65
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +0 -70
- package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +0 -274
- package/src/vendor/pi-tui/native/napi.h +0 -91
- package/src/vendor/pi-tui/native/win32/README.md +0 -31
- package/src/vendor/pi-tui/native/win32/build.mjs +0 -232
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/src/win32-platform.c +0 -241
- package/src/vendor/pi-tui/native-modifiers.ts +0 -13
- package/src/vendor/pi-tui/native-module-path.ts +0 -31
- package/src/vendor/pi-tui/native-platform.ts +0 -65
- package/src/vendor/pi-tui/oklab.ts +0 -233
- package/src/vendor/pi-tui/stdin-buffer.ts +0 -444
- package/src/vendor/pi-tui/terminal-colors.ts +0 -91
- package/src/vendor/pi-tui/terminal-image.ts +0 -731
- package/src/vendor/pi-tui/terminal.ts +0 -554
- package/src/vendor/pi-tui/tui-alt-screen.ts +0 -1750
- package/src/vendor/pi-tui/tui-main-screen.ts +0 -655
- package/src/vendor/pi-tui/tui.ts +0 -1493
- package/src/vendor/pi-tui/undo-stack.ts +0 -28
- package/src/vendor/pi-tui/utils.ts +0 -1385
- package/src/vendor/pi-tui/wheel-scroll.ts +0 -82
- package/src/vendor/pi-tui/word-navigation.ts +0 -117
|
@@ -3,6 +3,14 @@ import type { LogEvent, NodeRef } from "./events.ts";
|
|
|
3
3
|
import { isExecutionOutcome } from "./outcome.ts";
|
|
4
4
|
import { isJsonValue, isToolDiagnostic } from "./tool-contract.ts";
|
|
5
5
|
import { validImageRef } from "./images.ts";
|
|
6
|
+
import { parseAcceptanceContract } from "./acceptance.ts";
|
|
7
|
+
import { validFileChanges } from "./checkpoints.ts";
|
|
8
|
+
import { isObservationReceipt } from "./read-observation.ts";
|
|
9
|
+
import { isSearchReceipt } from "./search-observation.ts";
|
|
10
|
+
import { isProcessReceipt } from "./process-receipt.ts";
|
|
11
|
+
import { isObservationArtifact } from "./observation-view.ts";
|
|
12
|
+
import { isCommandArtifactReceipt } from "./command-artifacts.ts";
|
|
13
|
+
import { isRuntimeTargetReceipt } from "./runtime-target.ts";
|
|
6
14
|
|
|
7
15
|
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
8
16
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -11,6 +19,7 @@ const strings = (value: unknown): boolean => Array.isArray(value) && value.every
|
|
|
11
19
|
const stringMap = (value: unknown): boolean => isRecord(value) && Object.values(value).every((v) => typeof v === "string");
|
|
12
20
|
const optionalString = (value: unknown): boolean => value === undefined || typeof value === "string";
|
|
13
21
|
const natural = (value: unknown): boolean => Number.isSafeInteger(value) && Number(value) >= 0;
|
|
22
|
+
const eventRef = (value: unknown): boolean => isRecord(value) && natural(value["node"]) && typeof value["event"] === "string" && value["event"] !== "";
|
|
14
23
|
export function validNodeRef(value: unknown): value is NodeRef {
|
|
15
24
|
return isRecord(value) && Number.isSafeInteger(value["node"]) && Number(value["node"]) >= 0 &&
|
|
16
25
|
typeof value["log_ref"] === "string" && typeof value["sha256"] === "string" &&
|
|
@@ -27,6 +36,37 @@ export function isLogEvent(value: unknown): value is LogEvent {
|
|
|
27
36
|
const images = value["images"];
|
|
28
37
|
if (images !== undefined && (!Array.isArray(images) || !images.every(validImageRef))) return false;
|
|
29
38
|
switch (value["t"]) {
|
|
39
|
+
case "tool_invocation": return has("call_id", "name") && value["call_id"] !== "" && value["name"] !== "" &&
|
|
40
|
+
(value["origin"] === undefined || value["origin"] === "extension") &&
|
|
41
|
+
isObservationArtifact(value["input_ref"]) && (value["schema_ref"] === null || isObservationArtifact(value["schema_ref"])) &&
|
|
42
|
+
(value["input"] === undefined || isJsonValue(value["input"])) && optionalString(value["parent_call_id"]) &&
|
|
43
|
+
(value["parent_ref"] === undefined ? value["parent_call_id"] === undefined : eventRef(value["parent_ref"]) && value["parent_call_id"] !== undefined) &&
|
|
44
|
+
value["parent_call_id"] !== "" && value["parent_call_id"] !== value["call_id"];
|
|
45
|
+
case "checkpoint": return has("operation") && ["edit", "git", "restore"].includes(String(value["source"])) &&
|
|
46
|
+
["prepared", "applied", "unavailable"].includes(String(value["phase"])) && validFileChanges(value["changes"]) &&
|
|
47
|
+
optionalString(value["tree"]) && optionalString(value["reason"]);
|
|
48
|
+
case "workspace_observation": return value["scope"] === "source-v1" && typeof value["changed"] === "boolean" && optionalString(value["reason"]) &&
|
|
49
|
+
(value["fingerprint"] === undefined || typeof value["fingerprint"] === "string" && /^[a-f0-9]{64}$/.test(value["fingerprint"]));
|
|
50
|
+
case "process_exit": {
|
|
51
|
+
const output = value["output"];
|
|
52
|
+
const receipt = value["receipt"];
|
|
53
|
+
if (receipt !== undefined && (!isProcessReceipt(receipt) || receipt.replayed || receipt.running !== false ||
|
|
54
|
+
receipt.session_id !== value["session"] || receipt.exit !== value["exit"] || !isRecord(output) ||
|
|
55
|
+
receipt.artifacts !== undefined && receipt.artifacts.command !== value["command"] ||
|
|
56
|
+
receipt.output === undefined || receipt.output.sha256 !== output["sha256"] || receipt.output.bytes !== output["bytes"])) return false;
|
|
57
|
+
return has("command") && natural(value["session"]) && Number(value["session"]) > 0 && Number.isInteger(value["exit"]) &&
|
|
58
|
+
value["reason"] === "disposed" && isRecord(output) && typeof output["path"] === "string" &&
|
|
59
|
+
typeof output["sha256"] === "string" && /^[a-f0-9]{64}$/.test(output["sha256"]) && natural(output["bytes"]);
|
|
60
|
+
}
|
|
61
|
+
case "skill_loaded": return has("name", "path", "sha256", "artifact") &&
|
|
62
|
+
["sha256", "artifact"].every(key => /^[a-f0-9]{64}$/.test(String(value[key]))) &&
|
|
63
|
+
["inline", "fork", "read"].includes(String(value["mode"])) && ["user", "model"].includes(String(value["origin"]));
|
|
64
|
+
case "acceptance_contract": {
|
|
65
|
+
try { parseAcceptanceContract(value["contract"]); return has("request"); } catch { return false; }
|
|
66
|
+
}
|
|
67
|
+
case "acceptance_decision": return has("request", "contract_id", "criterion") && value["actor"] === "user" &&
|
|
68
|
+
(value["target_binding"] === undefined ? value["target_observation"] === undefined : has("target_binding", "target_observation"));
|
|
69
|
+
case "target_observation": return isRuntimeTargetReceipt(value["observation"]);
|
|
30
70
|
case "extension": return has("customType", "content") && typeof value["display"] === "boolean" && typeof value["inContext"] === "boolean" && (value["data"] === undefined || isJsonValue(value["data"]));
|
|
31
71
|
case "user": return has("text");
|
|
32
72
|
case "outcome": return has("reason") && isExecutionOutcome(value["outcome"]);
|
|
@@ -40,7 +80,7 @@ export function isLogEvent(value: unknown): value is LogEvent {
|
|
|
40
80
|
isRecord(ref) && typeof ref["sha256"] === "string" &&
|
|
41
81
|
typeof ref["path"] === "string" && natural(ref["bytes"]);
|
|
42
82
|
}
|
|
43
|
-
case "guidance": return has("text") && ["continuation", "repetition", "budget", "boundary", "stall", "output_recovery", "completion_review", "churn", "deadline"].includes(String(value["reason"])) && strings(value["sources"]);
|
|
83
|
+
case "guidance": return has("text") && ["continuation", "repetition", "budget", "boundary", "stall", "output_recovery", "completion_review", "churn", "deadline", "runtime_acceptance"].includes(String(value["reason"])) && strings(value["sources"]);
|
|
44
84
|
case "interrupt": return has("user_id");
|
|
45
85
|
case "assistant": return (value["stop_reason"] === null || optionalString(value["stop_reason"])) &&
|
|
46
86
|
(value["replay_key"] === undefined || typeof value["replay_key"] === "string" && /^[a-f0-9]{64}$/.test(value["replay_key"])) &&
|
|
@@ -51,8 +91,34 @@ export function isLogEvent(value: unknown): value is LogEvent {
|
|
|
51
91
|
return b["type"] === "tool_use" && typeof b["call_id"] === "string" && typeof b["name"] === "string" && "input" in b;
|
|
52
92
|
});
|
|
53
93
|
case "tool_result": {
|
|
94
|
+
if (!optionalString(value["parent_call_id"]) || value["parent_call_id"] === "" || value["parent_call_id"] === value["call_id"]) return false;
|
|
95
|
+
if (value["origin"] !== undefined && value["origin"] !== "extension") return false;
|
|
96
|
+
if (value["invocation_ref"] !== undefined && !eventRef(value["invocation_ref"])) return false;
|
|
97
|
+
const observation = value["observation"];
|
|
98
|
+
if (observation !== undefined && (!isObservationReceipt(observation) ||
|
|
99
|
+
!isRecord(value["content_ref"]) || value["content_ref"]["sha256"] !== observation.artifact.sha256 ||
|
|
100
|
+
value["content_ref"]["bytes"] !== observation.artifact.bytes || value["content_sha256"] !== observation.artifact.sha256)) return false;
|
|
54
101
|
if (value["data"] !== undefined && !isJsonValue(value["data"])) return false;
|
|
102
|
+
if (value["data_ref"] !== undefined && (!isObservationArtifact(value["data_ref"]) || value["data"] !== undefined)) return false;
|
|
103
|
+
if (isRecord(value["data"]) && value["data"]["kind"] === "process") {
|
|
104
|
+
const process = value["data"];
|
|
105
|
+
if (!isProcessReceipt(process)) return false;
|
|
106
|
+
if (process.output && (!isRecord(value["content_ref"]) || value["content_ref"]["sha256"] !== process.output.sha256 ||
|
|
107
|
+
value["content_ref"]["bytes"] !== process.output.bytes || value["content_sha256"] !== process.output.sha256)) return false;
|
|
108
|
+
if (process.replayed && value["effect"] !== undefined) return false;
|
|
109
|
+
if (value["effect"] !== undefined && (!isRecord(value["effect"]) || value["effect"]["kind"] !== "command" ||
|
|
110
|
+
process.running !== false || value["effect"]["exit"] !== process.exit ||
|
|
111
|
+
JSON.stringify(value["effect"]["artifacts"]) !== JSON.stringify(process.artifacts))) return false;
|
|
112
|
+
}
|
|
113
|
+
if (isObservationReceipt(observation) && observation.descriptor !== undefined &&
|
|
114
|
+
(!isRecord(value["data"]) || value["data"]["read_handle"] !== observation.descriptor.sha256)) return false;
|
|
115
|
+
if (isRecord(value["data"]) && value["data"]["kind"] === "search") {
|
|
116
|
+
const search = value["data"];
|
|
117
|
+
if (!isSearchReceipt(search) || !isRecord(value["content_ref"]) || value["content_ref"]["sha256"] !== search.artifact.sha256 ||
|
|
118
|
+
value["content_ref"]["bytes"] !== search.artifact.bytes || value["content_sha256"] !== search.artifact.sha256) return false;
|
|
119
|
+
}
|
|
55
120
|
if (value["diagnostic"] !== undefined && !isToolDiagnostic(value["diagnostic"])) return false;
|
|
121
|
+
if (value["warnings"] !== undefined && (!Array.isArray(value["warnings"]) || !value["warnings"].every(isToolDiagnostic))) return false;
|
|
56
122
|
if (!has("call_id", "content") || typeof value["ok"] !== "boolean") return false;
|
|
57
123
|
if (!optionalString(value["content_sha256"])) return false;
|
|
58
124
|
if (!optionalString(value["observation_sha256"])) return false;
|
|
@@ -63,14 +129,19 @@ export function isLogEvent(value: unknown): value is LogEvent {
|
|
|
63
129
|
if (effect === undefined) return true;
|
|
64
130
|
if (!isRecord(effect)) return false;
|
|
65
131
|
if (effect["kind"] === "command") return typeof effect["cmd"] === "string" && Number.isInteger(effect["exit"]) &&
|
|
66
|
-
optionalString(effect["gate"]) && optionalString(effect["workspace_sha256"])
|
|
132
|
+
optionalString(effect["gate"]) && optionalString(effect["workspace_sha256"]) &&
|
|
133
|
+
(effect["artifacts"] === undefined || isCommandArtifactReceipt(effect["artifacts"]) &&
|
|
134
|
+
effect["artifacts"].exit === effect["exit"] && effect["artifacts"].command === effect["cmd"]) &&
|
|
135
|
+
(effect["workspace_scope"] === undefined || effect["workspace_scope"] === "source-v1") &&
|
|
136
|
+
(effect["workspace_observed"] === undefined || typeof effect["workspace_observed"] === "boolean");
|
|
67
137
|
if (effect["kind"] === "read") return typeof effect["path"] === "string" && typeof effect["sha256"] === "string" &&
|
|
68
138
|
(effect["ranges"] === undefined || (Array.isArray(effect["ranges"]) && effect["ranges"].every((r: unknown) =>
|
|
69
139
|
Array.isArray(r) && r.length === 2 && r.every((n) => Number.isSafeInteger(n)) && r[0] >= 1 && r[1] >= r[0])));
|
|
70
140
|
return effect["kind"] === "edit" && typeof effect["path"] === "string" && typeof effect["sha256_after"] === "string" &&
|
|
71
141
|
(effect["sha256_before"] === null || typeof effect["sha256_before"] === "string");
|
|
72
142
|
}
|
|
73
|
-
case "cut": return has("handoff_ref") && ["trigger", "hard_limit", "model_checkpoint"].includes(String(value["reason"])) &&
|
|
143
|
+
case "cut": return has("handoff_ref") && (value["working_set_version"] === undefined || value["working_set_version"] === 1 || value["working_set_version"] === 2) && ["trigger", "hard_limit", "model_checkpoint"].includes(String(value["reason"])) &&
|
|
144
|
+
(value["guidance_tools"] === undefined || Array.isArray(value["guidance_tools"]) && value["guidance_tools"].every(item => typeof item === "string")) &&
|
|
74
145
|
optionalString(value["retained_from"]) && optionalString(value["next_context_ref"]) &&
|
|
75
146
|
(value["next_node"] === undefined || natural(value["next_node"])) && optionalString(value["next_model"]) &&
|
|
76
147
|
(value["handoff_tokens"] === undefined || (typeof value["handoff_tokens"] === "number" && Number.isFinite(value["handoff_tokens"]) && value["handoff_tokens"] > 0)) &&
|
|
@@ -83,7 +154,9 @@ export function isLogEvent(value: unknown): value is LogEvent {
|
|
|
83
154
|
case "pitfall": return has("tried", "failed_because");
|
|
84
155
|
case "blocked": return has("what", "reason");
|
|
85
156
|
case "question": return has("text");
|
|
86
|
-
case "next_action": return has("what", "next_action");
|
|
157
|
+
case "next_action": return has("what", "next_action") && (value["depends_on"] === undefined || strings(value["depends_on"]));
|
|
158
|
+
case "work_dependencies": return has("work_id", "what") && strings(value["depends_on"]);
|
|
159
|
+
case "work_owner": return has("work_id", "what", "owner") && ["claim", "release"].includes(String(value["operation"]));
|
|
87
160
|
case "evidence_link": return has("work", "what", "source") && optionalString(value["resource"]) && optionalString(value["version"]) &&
|
|
88
161
|
optionalString(value["supersedes"]) && (value["observed_version"] === null || optionalString(value["observed_version"]));
|
|
89
162
|
case "work_status": return has("work_id", "what") && ["unblock", "reopen", "cancel"].includes(String(value["operation"])) &&
|
|
@@ -93,6 +166,7 @@ export function isLogEvent(value: unknown): value is LogEvent {
|
|
|
93
166
|
strings(value["undeclared"]) && strings(value["declared_but_unchanged"]) && optionalString(value["workspace_sha256"]) &&
|
|
94
167
|
(value["phase"] === undefined || value["phase"] === "turn" || value["phase"] === "idle");
|
|
95
168
|
case "rewind": return ["both", "files", "context"].includes(String(value["mode"])) && validNodeRef(value["from"]) &&
|
|
169
|
+
(value["files_from"] === undefined || validNodeRef(value["files_from"])) &&
|
|
96
170
|
has("prompt") && strings(value["restored"]) && strings(value["removed"]) && strings(value["unrecoverable"]) && strings(value["divergence"]);
|
|
97
171
|
case "spawn": {
|
|
98
172
|
const input = value["input"];
|
|
@@ -103,10 +177,11 @@ export function isLogEvent(value: unknown): value is LogEvent {
|
|
|
103
177
|
if (!(workspace["kind"] === "shared_readonly" || (workspace["kind"] === "git_tree" &&
|
|
104
178
|
typeof workspace["head"] === "string" && typeof workspace["tree"] === "string"))) return false;
|
|
105
179
|
}
|
|
106
|
-
return validNodeRef(value["branch"]) && has("purpose") && (value["workspace"] === undefined ||
|
|
180
|
+
return (value["background"] === undefined || value["background"] === true) && validNodeRef(value["branch"]) && has("purpose") && (value["workspace"] === undefined ||
|
|
107
181
|
(isRecord(value["workspace"]) && typeof value["workspace"]["path"] === "string" && typeof value["workspace"]["branch"] === "string"));
|
|
108
182
|
}
|
|
109
183
|
case "joined": return validNodeRef(value["branch"]) && has("handoff_ref", "summary") && optionalString(value["model"]) &&
|
|
184
|
+
(value["receipt_ref"] === undefined || typeof value["receipt_ref"] === "string" && /^[a-f0-9]{64}$/.test(value["receipt_ref"])) &&
|
|
110
185
|
(value["tip"] === undefined || validNodeRef(value["tip"]));
|
|
111
186
|
case "adopt": return validNodeRef(value["branch"]) && (value["next_node"] === undefined || natural(value["next_node"])) &&
|
|
112
187
|
(value["applied"] === undefined || value["applied"] === true);
|
package/src/core/events.ts
CHANGED
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
import type { ExecutionOutcome } from "./outcome.ts";
|
|
22
22
|
import type { ImageRef } from "./images.ts";
|
|
23
23
|
import type { JsonValue, ToolDiagnostic } from "./tool-contract.ts";
|
|
24
|
+
import type { AcceptanceContract } from "./acceptance.ts";
|
|
25
|
+
import type { CheckpointEvent, WorkspaceObservationEvent } from "./checkpoints.ts";
|
|
26
|
+
import type { ObservationReceipt } from "./read-observation.ts";
|
|
27
|
+
import type { CommandArtifactReceipt } from "./command-artifacts.ts";
|
|
28
|
+
import type { RuntimeTargetReceipt } from "./runtime-target.ts";
|
|
24
29
|
|
|
25
30
|
export type EventId = string;
|
|
26
31
|
export type Sha256 = string;
|
|
@@ -68,7 +73,7 @@ export type NodeRef = {
|
|
|
68
73
|
export type Effect =
|
|
69
74
|
| { kind: "read"; path: string; sha256: Sha256; ranges?: readonly LineRange[] }
|
|
70
75
|
| { kind: "edit"; path: string; sha256_before: Sha256 | null; sha256_after: Sha256 }
|
|
71
|
-
| { kind: "command"; cmd: string; exit: number; gate?: string; workspace_sha256?: Sha256 };
|
|
76
|
+
| { kind: "command"; cmd: string; exit: number; gate?: string; workspace_sha256?: Sha256; workspace_observed?: boolean; workspace_scope?: "source-v1"; artifacts?: CommandArtifactReceipt };
|
|
72
77
|
|
|
73
78
|
// ---------------------------------------------------------------------------
|
|
74
79
|
// Harness-derived events
|
|
@@ -109,7 +114,7 @@ export interface GuidanceEvent {
|
|
|
109
114
|
t: "guidance";
|
|
110
115
|
id: EventId;
|
|
111
116
|
at: IsoTime;
|
|
112
|
-
reason: "continuation" | "repetition" | "budget" | "boundary" | "stall" | "output_recovery" | "completion_review" | "churn" | "deadline";
|
|
117
|
+
reason: "continuation" | "repetition" | "budget" | "boundary" | "stall" | "output_recovery" | "completion_review" | "churn" | "deadline" | "runtime_acceptance";
|
|
113
118
|
text: string;
|
|
114
119
|
sources: readonly EventId[];
|
|
115
120
|
}
|
|
@@ -167,6 +172,11 @@ export interface ToolResultEvent {
|
|
|
167
172
|
id: EventId;
|
|
168
173
|
at: IsoTime;
|
|
169
174
|
call_id: string;
|
|
175
|
+
/** Trusted host execution has no provider tool_use and is excluded from provider replay. */
|
|
176
|
+
origin?: "extension";
|
|
177
|
+
/** Pi A: nested observations remain evidence without creating provider-visible tool pairs. */
|
|
178
|
+
parent_call_id?: string;
|
|
179
|
+
invocation_ref?: { node: number; event: EventId };
|
|
170
180
|
ok: boolean;
|
|
171
181
|
/** What the model sees. Large payloads belong on disk, referenced by effect. */
|
|
172
182
|
content: string;
|
|
@@ -175,17 +185,36 @@ export interface ToolResultEvent {
|
|
|
175
185
|
content_sha256?: Sha256;
|
|
176
186
|
/** O.3: semantic observation digest excludes presentation metadata. */
|
|
177
187
|
observation_sha256?: Sha256;
|
|
188
|
+
/** R2: exact preview coverage and immutable source identity; old logs omit this receipt. */
|
|
189
|
+
observation?: ObservationReceipt;
|
|
178
190
|
content_ref?: ArtifactRef;
|
|
179
191
|
effect?: Effect;
|
|
180
192
|
/** O.1: actual termination, independent of effect/verification; legacy logs omit it. */
|
|
181
193
|
outcome?: ExecutionOutcome;
|
|
182
194
|
/** O.3: machine-readable observations and recovery hints, not additional proof. */
|
|
183
195
|
data?: JsonValue;
|
|
196
|
+
/** Pi A: large structured results stay recoverable without bloating each JSONL event. */
|
|
197
|
+
data_ref?: ArtifactRef;
|
|
184
198
|
diagnostic?: ToolDiagnostic;
|
|
199
|
+
/** Post-operation observer failures cannot erase the operation's factual result. */
|
|
200
|
+
warnings?: readonly ToolDiagnostic[];
|
|
185
201
|
}
|
|
186
202
|
|
|
187
203
|
export type ArtifactRef = { sha256: Sha256; path: string; bytes: number };
|
|
188
204
|
|
|
205
|
+
/** Pi A: an invocation is durable before its admitted action may start. */
|
|
206
|
+
export interface ToolInvocationEvent {
|
|
207
|
+
t: "tool_invocation"; id: EventId; at: IsoTime;
|
|
208
|
+
call_id: string; name: string; parent_call_id?: string;
|
|
209
|
+
origin?: "extension";
|
|
210
|
+
parent_ref?: { node: number; event: EventId };
|
|
211
|
+
/** Original request and the exact declarative contract are independently recoverable. */
|
|
212
|
+
input_ref: ArtifactRef;
|
|
213
|
+
schema_ref: ArtifactRef | null;
|
|
214
|
+
/** Bounded actual arguments; absent for a direct unchanged request or an oversized refusal. */
|
|
215
|
+
input?: JsonValue;
|
|
216
|
+
}
|
|
217
|
+
|
|
189
218
|
/** Terminates the segment it appears in. Carries refs only -- see rule 1 above. */
|
|
190
219
|
export interface CutEvent {
|
|
191
220
|
t: "cut";
|
|
@@ -205,6 +234,10 @@ export interface CutEvent {
|
|
|
205
234
|
next_context_ref?: Sha256;
|
|
206
235
|
/** V2 carried-state budget, fixed in the log for identical replay. */
|
|
207
236
|
handoff_tokens?: number;
|
|
237
|
+
/** O.10: absent preserves the original D20 carried projection. */
|
|
238
|
+
working_set_version?: 1 | 2;
|
|
239
|
+
/** R10: declared builtin advice capabilities at this cut, not an authorization grant. Absent replays old text. */
|
|
240
|
+
guidance_tools?: readonly string[];
|
|
208
241
|
/** N.8: first retained event on the effective ancestry; no copied transcript. */
|
|
209
242
|
retained_from?: EventId;
|
|
210
243
|
}
|
|
@@ -242,6 +275,7 @@ export interface DecisionEvent {
|
|
|
242
275
|
chose: string;
|
|
243
276
|
because: string;
|
|
244
277
|
rejected?: readonly string[];
|
|
278
|
+
work_id?: string;
|
|
245
279
|
/** Set when an adopt moved this event from another node. */
|
|
246
280
|
carried_from?: CarriedFrom;
|
|
247
281
|
}
|
|
@@ -252,6 +286,7 @@ export interface PitfallEvent {
|
|
|
252
286
|
at: IsoTime;
|
|
253
287
|
tried: string;
|
|
254
288
|
failed_because: string;
|
|
289
|
+
work_id?: string;
|
|
255
290
|
/** Set when an adopt moved this event from another node. */
|
|
256
291
|
carried_from?: CarriedFrom;
|
|
257
292
|
}
|
|
@@ -340,6 +375,8 @@ export interface RewindEvent {
|
|
|
340
375
|
id: EventId;
|
|
341
376
|
at: IsoTime;
|
|
342
377
|
mode: RewindMode;
|
|
378
|
+
/** P.3: context navigation keeps the file history at this separate cursor. */
|
|
379
|
+
files_from?: NodeRef;
|
|
343
380
|
/** Where things were moved back to. */
|
|
344
381
|
from: NodeRef;
|
|
345
382
|
/** The prompt that opened the exchange moved back to, for a human reader. */
|
|
@@ -368,6 +405,8 @@ export interface SpawnEvent {
|
|
|
368
405
|
branch: NodeRef;
|
|
369
406
|
/** What the branch is for, in one line. */
|
|
370
407
|
purpose: string;
|
|
408
|
+
/** O.14: execution mode is durable; it never proves a live handle exists. */
|
|
409
|
+
background?: true;
|
|
371
410
|
/** The isolated checkout belongs to this branch, including after restart. */
|
|
372
411
|
workspace?: { path: string; branch: string };
|
|
373
412
|
/** O.4: a task input envelope references the original request, never copies history. */
|
|
@@ -393,6 +432,8 @@ export interface JoinedEvent {
|
|
|
393
432
|
branch: NodeRef;
|
|
394
433
|
handoff_ref: string;
|
|
395
434
|
summary: string;
|
|
435
|
+
/** O.9: artifact contains a deterministic evidence index, never raw history. */
|
|
436
|
+
receipt_ref?: Sha256;
|
|
396
437
|
/** A branch can cut before joining; its last node need not be its root. */
|
|
397
438
|
tip?: NodeRef;
|
|
398
439
|
model?: string;
|
|
@@ -445,6 +486,7 @@ export interface NextActionEvent {
|
|
|
445
486
|
what: string;
|
|
446
487
|
work_id?: string;
|
|
447
488
|
next_action: string;
|
|
489
|
+
depends_on?: readonly string[];
|
|
448
490
|
/** Set when an adopt moved this event from another node. */
|
|
449
491
|
carried_from?: CarriedFrom;
|
|
450
492
|
}
|
|
@@ -462,6 +504,15 @@ export interface WorkStatusEvent {
|
|
|
462
504
|
carried_from?: CarriedFrom;
|
|
463
505
|
}
|
|
464
506
|
|
|
507
|
+
export interface WorkDependenciesEvent {
|
|
508
|
+
t: "work_dependencies"; id: EventId; at: IsoTime; work_id: string; what: string;
|
|
509
|
+
depends_on: readonly string[]; carried_from?: CarriedFrom;
|
|
510
|
+
}
|
|
511
|
+
export interface WorkOwnerEvent {
|
|
512
|
+
t: "work_owner"; id: EventId; at: IsoTime; work_id: string; what: string;
|
|
513
|
+
operation: "claim" | "release"; owner: string; carried_from?: CarriedFrom;
|
|
514
|
+
}
|
|
515
|
+
|
|
465
516
|
/** O.6: a model associates an original observation with work; the runtime resolves its source. */
|
|
466
517
|
export interface EvidenceLinkEvent {
|
|
467
518
|
t: "evidence_link"; id: EventId; at: IsoTime;
|
|
@@ -479,7 +530,38 @@ export interface ExtensionEvent {
|
|
|
479
530
|
data?: import("./tool-contract.ts").JsonValue;
|
|
480
531
|
}
|
|
481
532
|
|
|
533
|
+
export interface AcceptanceContractEvent {
|
|
534
|
+
t: "acceptance_contract"; id: EventId; at: IsoTime; request: EventId;
|
|
535
|
+
contract: AcceptanceContract;
|
|
536
|
+
}
|
|
537
|
+
export interface AcceptanceDecisionEvent {
|
|
538
|
+
t: "acceptance_decision"; id: EventId; at: IsoTime; request: EventId;
|
|
539
|
+
contract_id: string; criterion: string; actor: "user";
|
|
540
|
+
target_binding?: string; target_observation?: EventId;
|
|
541
|
+
}
|
|
542
|
+
export interface TargetObservationEvent {
|
|
543
|
+
t: "target_observation"; id: EventId; at: IsoTime; observation: RuntimeTargetReceipt;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export interface SkillLoadedEvent {
|
|
547
|
+
t: "skill_loaded"; id: EventId; at: IsoTime; name: string; path: string;
|
|
548
|
+
sha256: Sha256; artifact: Sha256; mode: "inline" | "fork" | "read"; origin: "user" | "model";
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** O.14: owner cleanup is an actual process observation, not a model claim. */
|
|
552
|
+
export interface ProcessExitEvent {
|
|
553
|
+
t: "process_exit"; id: EventId; at: IsoTime; session: number;
|
|
554
|
+
command: string; exit: number; output: ArtifactRef; reason: "disposed";
|
|
555
|
+
receipt?: import("./process-receipt.ts").ProcessReceipt;
|
|
556
|
+
}
|
|
557
|
+
|
|
482
558
|
export type LogEvent =
|
|
559
|
+
| ToolInvocationEvent
|
|
560
|
+
| ProcessExitEvent
|
|
561
|
+
| SkillLoadedEvent
|
|
562
|
+
| AcceptanceContractEvent
|
|
563
|
+
| AcceptanceDecisionEvent
|
|
564
|
+
| TargetObservationEvent
|
|
483
565
|
| ExtensionEvent
|
|
484
566
|
| UserEvent
|
|
485
567
|
| UserBashEvent
|
|
@@ -491,6 +573,8 @@ export type LogEvent =
|
|
|
491
573
|
| EvidenceLinkEvent
|
|
492
574
|
| CutEvent
|
|
493
575
|
| SnapshotEvent
|
|
576
|
+
| CheckpointEvent
|
|
577
|
+
| WorkspaceObservationEvent
|
|
494
578
|
| RewindEvent
|
|
495
579
|
| SpawnEvent
|
|
496
580
|
| JoinedEvent
|
|
@@ -502,12 +586,21 @@ export type LogEvent =
|
|
|
502
586
|
| BlockedEvent
|
|
503
587
|
| QuestionEvent
|
|
504
588
|
| NextActionEvent
|
|
505
|
-
| WorkStatusEvent
|
|
589
|
+
| WorkStatusEvent
|
|
590
|
+
| WorkDependenciesEvent
|
|
591
|
+
| WorkOwnerEvent;
|
|
592
|
+
// Work coordination is intent, never a substitute for command proof.
|
|
506
593
|
|
|
507
594
|
export const HARNESS_EVENTS = [
|
|
595
|
+
"checkpoint", "workspace_observation",
|
|
596
|
+
"target_observation",
|
|
597
|
+
"process_exit",
|
|
598
|
+
"skill_loaded",
|
|
599
|
+
"acceptance_contract", "acceptance_decision",
|
|
508
600
|
"user", "user_bash", "outcome", "guidance", "interrupt", "assistant", "tool_result", "cut", "snapshot", "rewind", "extension",
|
|
509
601
|
] as const;
|
|
510
602
|
export const MODEL_EVENTS = [
|
|
603
|
+
"work_dependencies", "work_owner",
|
|
511
604
|
"claim", "decision", "pitfall", "blocked", "question", "next_action", "work_status", "evidence_link",
|
|
512
605
|
"spawn", "joined", "adopt", "discard",
|
|
513
606
|
] as const;
|
|
@@ -519,6 +612,7 @@ export const MODEL_EVENTS = [
|
|
|
519
612
|
* ancestry or are parent actions the branch should not inherit.
|
|
520
613
|
*/
|
|
521
614
|
export const CARRYABLE_EVENTS = [
|
|
615
|
+
"work_dependencies", "work_owner",
|
|
522
616
|
"claim", "decision", "pitfall", "blocked", "question", "next_action", "work_status", "evidence_link",
|
|
523
617
|
] as const;
|
|
524
618
|
|
|
@@ -36,6 +36,9 @@ export function observedVersion(events: readonly LogEvent[], resource: string):
|
|
|
36
36
|
if (event.removed.map(resourceKey).includes(resource)) version = null;
|
|
37
37
|
for (const [path, value] of Object.entries({ ...event.added, ...event.changed })) if (resourceKey(path) === resource) version = value;
|
|
38
38
|
}
|
|
39
|
+
if (event.t === "checkpoint" && event.phase === "applied") for (const [path, change] of Object.entries(event.changes)) {
|
|
40
|
+
if (resourceKey(path) === resource) version = change.after?.sha256 ?? null;
|
|
41
|
+
}
|
|
39
42
|
}
|
|
40
43
|
return version;
|
|
41
44
|
}
|
|
@@ -65,6 +68,10 @@ export function deriveEvidenceLinks(previous: readonly EvidenceLink[], events: r
|
|
|
65
68
|
if (resourceKey(path) === link.resource) link.freshness = version === link.version ? "current" : "stale";
|
|
66
69
|
}
|
|
67
70
|
}
|
|
71
|
+
if (event.t === "checkpoint" && event.phase === "applied") for (const [path, change] of Object.entries(event.changes)) {
|
|
72
|
+
if (resourceKey(path) === link.resource) link.freshness = change.after?.sha256 === link.version ? "current" : "stale";
|
|
73
|
+
}
|
|
74
|
+
if (event.t === "workspace_observation" && event.changed || event.t === "checkpoint" && event.phase === "unavailable") link.freshness = "unknown";
|
|
68
75
|
}
|
|
69
76
|
}
|
|
70
77
|
return links;
|
package/src/core/evidence.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Shared executed-evidence rules for handoff derivation and the turn watchdog. */
|
|
2
2
|
import type { EvidenceQuery, LogEvent, ToolResultEvent } from "./events.ts";
|
|
3
3
|
import type { Evidence } from "./handoff.ts";
|
|
4
|
+
import { changedPaths } from "./checkpoints.ts";
|
|
4
5
|
|
|
5
6
|
type CommandEffect = Extract<NonNullable<ToolResultEvent["effect"]>, { kind: "command" }>;
|
|
6
7
|
export type CommandResult = ToolResultEvent & { effect: CommandEffect };
|
|
@@ -15,9 +16,14 @@ export function evidenceStale(events: readonly LogEvent[], evidence: Evidence, i
|
|
|
15
16
|
let stale = initially;
|
|
16
17
|
for (const event of events.slice(start + 1)) {
|
|
17
18
|
if (event.t === "tool_result" && event.effect?.kind === "edit") stale = true;
|
|
18
|
-
|
|
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 : event.t === "snapshot" ? event.workspace_sha256
|
|
19
23
|
: event.t === "tool_result" && event.effect?.kind === "command" ? event.effect.workspace_sha256 : undefined;
|
|
20
|
-
|
|
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;
|
|
21
27
|
else if (event.t === "snapshot" && (Object.keys(event.added).length + Object.keys(event.changed).length + event.removed.length > 0)) stale = true;
|
|
22
28
|
}
|
|
23
29
|
return stale;
|
|
@@ -33,7 +39,9 @@ export function resolveEvidence(events: readonly LogEvent[], query: EvidenceQuer
|
|
|
33
39
|
const result = events.slice(start).findLast(event => isCommandResult(event) && matchesQuery(query, event.effect));
|
|
34
40
|
if (!result || !isCommandResult(result)) return { ok: false, reason: "no_matching_event" };
|
|
35
41
|
if (result.effect.exit !== 0) return { ok: false, reason: "nonzero_exit", exit: result.effect.exit };
|
|
42
|
+
if (result.effect.workspace_observed === false) return { ok: false, reason: "stale_workspace" };
|
|
36
43
|
const evidence: Evidence = { command: result.effect.cmd, exit: result.effect.exit, at: result.at, event_id: result.id,
|
|
44
|
+
...(result.effect.workspace_scope ? { workspace_scope: result.effect.workspace_scope } : {}),
|
|
37
45
|
...(result.effect.gate !== undefined ? { gate: result.effect.gate } : {}),
|
|
38
46
|
...(result.effect.workspace_sha256 ? { workspace_sha256: result.effect.workspace_sha256 } : {}) };
|
|
39
47
|
return evidenceStale(events, evidence) ? { ok: false, reason: "stale_workspace" } : { ok: true, evidence };
|
|
@@ -44,8 +52,9 @@ export function eligibleProgressChecks(events: readonly LogEvent[], claimed: Rea
|
|
|
44
52
|
for (const event of events) {
|
|
45
53
|
if (!isCommandResult(event)) continue;
|
|
46
54
|
const gate = event.effect.gate;
|
|
47
|
-
if (event.effect.exit !== 0 || (gate === undefined && !claimed.has(event.id)) || (gate !== undefined && failingGates.has(gate))) continue;
|
|
55
|
+
if (event.effect.exit !== 0 || event.effect.workspace_observed === false || (gate === undefined && !claimed.has(event.id)) || (gate !== undefined && failingGates.has(gate))) continue;
|
|
48
56
|
if (!evidenceStale(events, { command: event.effect.cmd, exit: 0, at: event.at, event_id: event.id,
|
|
57
|
+
...(event.effect.workspace_scope ? { workspace_scope: event.effect.workspace_scope } : {}),
|
|
49
58
|
...(event.effect.workspace_sha256 === undefined ? {} : { workspace_sha256: event.effect.workspace_sha256 }) })) eligible.add(event.id);
|
|
50
59
|
}
|
|
51
60
|
return eligible;
|
|
@@ -74,6 +83,7 @@ export function classifyGate(gates: Readonly<Record<string, string>>, command: s
|
|
|
74
83
|
if (tokens.some((t) => informational.includes(t))) return undefined;
|
|
75
84
|
let i = 0;
|
|
76
85
|
const first = tokens[0];
|
|
86
|
+
if (["bun", "npm", "pnpm", "yarn"].includes(first ?? "") && tokens[1] === "test") return gates["test"];
|
|
77
87
|
if (first === "python" || first === "python3") {
|
|
78
88
|
if (tokens[1] !== "-m") return undefined;
|
|
79
89
|
i = 2;
|
package/src/core/execution.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
/** D5/D11: incomplete execution is unknown, never an instruction to run twice. */
|
|
2
2
|
import type { LogEvent, AssistantBlock } from "./events.ts";
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
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>();
|
|
6
7
|
for (const event of events) {
|
|
7
8
|
if (event.t === "assistant") {
|
|
8
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 } : {}) });
|
|
9
15
|
} else if (event.t === "tool_result") pending.delete(event.call_id);
|
|
10
16
|
}
|
|
11
17
|
return [...pending.values()];
|
package/src/core/experiment.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/** Experimental projections never mutate the durable event stream (D8, L). */
|
|
2
2
|
import type { Message } from "./project.ts";
|
|
3
|
+
import { isTrialLifecycle, type TrialLifecycle } from "./trial-lifecycle.ts";
|
|
4
|
+
import { RECOVERY_TASKS, recoveryCase, type RecoveryTask } from "./recovery-corpus.ts";
|
|
5
|
+
import { isRecoveryMetrics, type RecoveryMetrics } from "./recovery-evaluation.ts";
|
|
6
|
+
import { TOOL_MODE_FACTORS, TOOL_MODE_TASKS, isToolModeTask, isToolModeMetrics, type ToolModeTask, type ToolModeMetrics } from "./tool-mode-protocol.ts";
|
|
3
7
|
|
|
4
8
|
export type ContextArm = "node" | "replay" | "truncate" | "mask";
|
|
5
9
|
|
|
@@ -38,3 +42,132 @@ export function shuffled<T>(items: readonly T[], seed: number): T[] {
|
|
|
38
42
|
}
|
|
39
43
|
return out;
|
|
40
44
|
}
|
|
45
|
+
|
|
46
|
+
/** O.16: each contrast changes exactly one policy, with paired task repeats. */
|
|
47
|
+
export const EVAL_FACTORS = ["profile", "workingSet", "strictVerification"] as const;
|
|
48
|
+
export const EVAL_TASKS = ["short", "boundary-recall", "false-completion", "human-boundary"] as const;
|
|
49
|
+
const supportedFactors = [...EVAL_FACTORS, ...TOOL_MODE_FACTORS] as const;
|
|
50
|
+
export type EvalFactor = typeof supportedFactors[number];
|
|
51
|
+
export type EvalTask = typeof EVAL_TASKS[number] | RecoveryTask | ToolModeTask;
|
|
52
|
+
const supportedTasks:readonly EvalTask[]=[...EVAL_TASKS,...RECOVERY_TASKS,...TOOL_MODE_TASKS];
|
|
53
|
+
export type EvalArm = "control" | "treatment";
|
|
54
|
+
export type EvalPolicy = { profile: "full" | "coding"; workingSet: boolean; strictVerification: boolean; querySyntax?: "json" | "sql" };
|
|
55
|
+
export type EvalJob = { id: string; factor: EvalFactor; task: EvalTask; repeat: number; arm: EvalArm; policy: EvalPolicy };
|
|
56
|
+
export function evalPolicy(factor: EvalFactor, arm: EvalArm): EvalPolicy {
|
|
57
|
+
const policy: EvalPolicy = { profile: "coding", workingSet: true, strictVerification: true };
|
|
58
|
+
if (factor === "querySyntax") return { ...policy, querySyntax: arm === "treatment" ? "sql" : "json" };
|
|
59
|
+
if (arm === "control") {
|
|
60
|
+
if (factor === "profile") policy.profile = "full";
|
|
61
|
+
else policy[factor] = false;
|
|
62
|
+
}
|
|
63
|
+
return policy;
|
|
64
|
+
}
|
|
65
|
+
export function singleFactorSchedule(seed: number, repeats = 1, tasks: readonly EvalTask[] = EVAL_TASKS, factors: readonly EvalFactor[] = EVAL_FACTORS): EvalJob[] {
|
|
66
|
+
if (!Number.isSafeInteger(seed) || !Number.isSafeInteger(repeats) || repeats < 1 || repeats > 20 || !tasks.length ||
|
|
67
|
+
new Set(tasks).size !== tasks.length || tasks.some(task => !supportedTasks.includes(task)) || !factors.length || new Set(factors).size !== factors.length ||
|
|
68
|
+
factors.some(factor => !supportedFactors.includes(factor) || tasks.some(task => isToolModeTask(task) !== (TOOL_MODE_FACTORS as readonly string[]).includes(factor)))) throw new RangeError("invalid eval schedule");
|
|
69
|
+
const pairs = shuffled(factors.flatMap(factor => [...tasks].sort().flatMap(task =>
|
|
70
|
+
Array.from({ length: repeats }, (_, repeat) => ({ factor, task, repeat })))), seed);
|
|
71
|
+
// Alternate first position before shuffling: adjacent LCG seeds are correlated.
|
|
72
|
+
const positions = new Map<EvalFactor, number>();
|
|
73
|
+
const order = shuffled(["control", "treatment"] as const, seed + 1);
|
|
74
|
+
return pairs.flatMap(pair => {
|
|
75
|
+
const offset = positions.get(pair.factor) ?? 0; positions.set(pair.factor, offset + 1);
|
|
76
|
+
return order.map((_, i) => {
|
|
77
|
+
const arm = order[(offset + i) % 2]!;
|
|
78
|
+
return { ...pair, arm, id: `${pair.factor}/${pair.task}/${pair.repeat}/${arm}`, policy: evalPolicy(pair.factor, arm) };
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
export function validateEvalJobs(jobs: readonly EvalJob[]): void {
|
|
83
|
+
const seen = new Set<string>();
|
|
84
|
+
for (const job of jobs) {
|
|
85
|
+
if (!supportedFactors.includes(job.factor) || !supportedTasks.includes(job.task) || !["control", "treatment"].includes(job.arm) ||
|
|
86
|
+
isToolModeTask(job.task) !== (TOOL_MODE_FACTORS as readonly string[]).includes(job.factor) ||
|
|
87
|
+
!Number.isSafeInteger(job.repeat) || job.repeat < 0 || job.id !== `${job.factor}/${job.task}/${job.repeat}/${job.arm}` || seen.has(job.id) ||
|
|
88
|
+
JSON.stringify(job.policy) !== JSON.stringify(evalPolicy(job.factor, job.arm))) throw new Error("invalid or confounded eval job");
|
|
89
|
+
seen.add(job.id);
|
|
90
|
+
}
|
|
91
|
+
for (const job of jobs) if (!seen.has(`${job.factor}/${job.task}/${job.repeat}/${job.arm === "control" ? "treatment" : "control"}`)) throw new Error("unpaired eval job");
|
|
92
|
+
}
|
|
93
|
+
export interface EvalResult {
|
|
94
|
+
toolMode?: ToolModeMetrics;
|
|
95
|
+
recovery?: RecoveryMetrics;
|
|
96
|
+
/** v2 separates an unmet final state from a machine-readable unsupported claim. */
|
|
97
|
+
unverifiedEnding?: boolean;
|
|
98
|
+
/** Absent in the frozen O.16 archives; absence is unknown, not an uninterrupted run. */
|
|
99
|
+
lifecycle?: TrialLifecycle;
|
|
100
|
+
id: string;
|
|
101
|
+
status: "finished" | "error" | "budget" | "stopped";
|
|
102
|
+
scenarioPassed: boolean;
|
|
103
|
+
artifactCorrect: boolean;
|
|
104
|
+
verified: boolean;
|
|
105
|
+
awaitingHuman: boolean;
|
|
106
|
+
falseCompletion: boolean;
|
|
107
|
+
requests: number;
|
|
108
|
+
toolFailures: number;
|
|
109
|
+
repeatedReads: number;
|
|
110
|
+
promptTokens: number | null;
|
|
111
|
+
cacheTokens: number | null;
|
|
112
|
+
outputTokens: number | null;
|
|
113
|
+
costUsd: number | null;
|
|
114
|
+
reservedUnknownUsd: number;
|
|
115
|
+
}
|
|
116
|
+
export function summarizeSingleFactor(jobs: readonly EvalJob[], results: readonly EvalResult[]) {
|
|
117
|
+
validateEvalJobs(jobs);
|
|
118
|
+
const byId = new Map<string, EvalResult>(), planned = new Set(jobs.map(job => job.id));
|
|
119
|
+
for (const row of results) {
|
|
120
|
+
if (row.toolMode !== undefined && (!isToolModeMetrics(row.toolMode) || row.scenarioPassed && !row.toolMode.sourceComplete)) throw Error("invalid tool mode metrics");
|
|
121
|
+
if (row.unverifiedEnding !== undefined && typeof row.unverifiedEnding !== "boolean") throw Error("invalid ending metric");
|
|
122
|
+
if(row.recovery!==undefined&&(!isRecoveryMetrics(row.recovery)||row.recovery.finalTaskSuccess!==row.scenarioPassed||row.recovery.toolFailures!==row.toolFailures||row.falseCompletion!==(row.recovery.unsupportedClaims>0)))throw Error("invalid recovery metrics");
|
|
123
|
+
if (row.lifecycle !== undefined && (!isTrialLifecycle(row.lifecycle) || row.lifecycle.backendRequests > row.requests ||
|
|
124
|
+
row.lifecycle.interruption !== null && row.status === "finished" || row.lifecycle.cleanup === "failed" && row.status !== "error"))
|
|
125
|
+
throw new Error("invalid eval lifecycle result");
|
|
126
|
+
if (!planned.has(row.id) || byId.has(row.id) || !["finished", "error", "budget", "stopped"].includes(row.status) ||
|
|
127
|
+
[row.scenarioPassed, row.artifactCorrect, row.verified, row.awaitingHuman, row.falseCompletion].some(value => typeof value !== "boolean") ||
|
|
128
|
+
row.scenarioPassed && (row.status !== "finished" || !row.verified || row.falseCompletion && row.recovery === undefined) ||
|
|
129
|
+
[row.requests, row.toolFailures, row.repeatedReads].some(n => !Number.isSafeInteger(n) || n < 0) ||
|
|
130
|
+
[row.promptTokens, row.cacheTokens, row.outputTokens].some(n => n !== null && (!Number.isSafeInteger(n) || n < 0)) ||
|
|
131
|
+
[row.costUsd, row.reservedUnknownUsd].some(n => n !== null && (!Number.isFinite(n) || n < 0)) ||
|
|
132
|
+
row.cacheTokens !== null && row.promptTokens !== null && row.cacheTokens > row.promptTokens) throw new Error("invalid or duplicate eval result");
|
|
133
|
+
byId.set(row.id, row);
|
|
134
|
+
}
|
|
135
|
+
const factors = supportedFactors.filter(factor => jobs.some(job => job.factor === factor));
|
|
136
|
+
const arms = factors.flatMap(factor => (["control", "treatment"] as const).map(arm => {
|
|
137
|
+
const selected = jobs.filter(job => job.factor === factor && job.arm === arm), rows = selected.flatMap(job => byId.has(job.id) ? [byId.get(job.id)!] : []);
|
|
138
|
+
const complete = rows.length === selected.length, scenarioPassed = rows.filter(row => row.scenarioPassed).length;
|
|
139
|
+
const knownCostSubtotal = rows.reduce((sum, row) => sum + (row.costUsd ?? 0), 0);
|
|
140
|
+
// Missing jobs and unknown charges cannot become a zero-priced success.
|
|
141
|
+
const costUsd = complete && rows.length > 0 && rows.every(row => row.costUsd !== null) ? knownCostSubtotal : null;
|
|
142
|
+
const total = (field: "promptTokens" | "cacheTokens" | "outputTokens") => complete && rows.length > 0 && rows.every(row => row[field] !== null)
|
|
143
|
+
? rows.reduce((sum, row) => sum + row[field]!, 0) : null;
|
|
144
|
+
const recovery=selected.some(job=>recoveryCase(job.task)||isToolModeTask(job.task))?{observed:rows.filter(row=>row.recovery).length,
|
|
145
|
+
missing:selected.length-rows.filter(row=>row.recovery).length,
|
|
146
|
+
firstToolSuccess:rows.filter(row=>row.recovery?.firstToolSuccess===true).length,
|
|
147
|
+
firstPassTaskSuccess:rows.filter(row=>row.recovery?.firstPassTaskSuccess===true).length,
|
|
148
|
+
...Object.fromEntries((["toolCalls","outerCalls","nestedCalls","unresolvedCalls","recoveryCalls","unchangedFailureRetries","originalEvidenceRetrievals","unsupportedClaims"] as const)
|
|
149
|
+
.map(key=>[key,rows.reduce((n,row)=>n+(row.recovery?.[key]??0),0)]))}:undefined;
|
|
150
|
+
return { factor, arm, ...(recovery?{recovery}:{}), planned: selected.length, attempted: rows.length, missing: selected.length - rows.length,
|
|
151
|
+
failed: rows.filter(row => !row.scenarioPassed).length, scenarioPassed, verified: rows.filter(row => row.verified).length,
|
|
152
|
+
awaitingHuman: rows.filter(row => row.awaitingHuman).length, falseCompletions: rows.filter(row => row.falseCompletion).length,
|
|
153
|
+
plannedPassFraction: selected.length ? scenarioPassed / selected.length : null, complete,
|
|
154
|
+
requests: rows.reduce((n, row) => n + row.requests, 0), toolFailures: rows.reduce((n, row) => n + row.toolFailures, 0),
|
|
155
|
+
repeatedReads: rows.reduce((n, row) => n + row.repeatedReads, 0), promptTokens: total("promptTokens"), cacheTokens: total("cacheTokens"), outputTokens: total("outputTokens"),
|
|
156
|
+
knownCostSubtotal, costUsd, unknownCostJobs: rows.filter(row => row.costUsd === null).length,
|
|
157
|
+
reservedUnknownUsd: rows.reduce((n, row) => n + row.reservedUnknownUsd, 0),
|
|
158
|
+
costPerScenarioSuccess: costUsd !== null && scenarioPassed > 0 ? costUsd / scenarioPassed : null };
|
|
159
|
+
}));
|
|
160
|
+
const comparisons = factors.map(factor => {
|
|
161
|
+
const pairs = jobs.filter(job => job.factor === factor && job.arm === "control");
|
|
162
|
+
const clusters = [...new Set(pairs.map(job => job.task))].sort().map(task => {
|
|
163
|
+
const matched = pairs.filter(job => job.task === task).flatMap(job => {
|
|
164
|
+
const control = byId.get(job.id), treatment = byId.get(`${factor}/${task}/${job.repeat}/treatment`);
|
|
165
|
+
return control && treatment ? [{ delta: Number(treatment.scenarioPassed) - Number(control.scenarioPassed) }] : [];
|
|
166
|
+
});
|
|
167
|
+
return { task, matchedPairs: matched.length, passDelta: matched.length ? matched.reduce((n, pair) => n + pair.delta, 0) / matched.length : null };
|
|
168
|
+
});
|
|
169
|
+
const matchedPairs = clusters.reduce((n, row) => n + row.matchedPairs, 0);
|
|
170
|
+
return { factor, matchedPairs, missingPairs: pairs.length - matchedPairs, clusters };
|
|
171
|
+
});
|
|
172
|
+
return { arms, comparisons };
|
|
173
|
+
}
|