@visual-z/xeno 0.8.4 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +31 -746
- package/bin/xeno.exe +26 -0
- package/install.cjs +88 -0
- package/package.json +18 -48
- package/docs/app-server.md +0 -115
- package/docs/foundations.md +0 -88
- package/docs/install.md +0 -244
- package/docs/providers.md +0 -172
- package/docs/research/foundation-clients-2026-10-09.md +0 -27
- package/docs/research/foundation-editing-2026-10-09.md +0 -56
- package/docs/research/foundation-network-2026-10-09.md +0 -37
- package/docs/research/foundation-workflow-2026-10-09.md +0 -37
- package/src/apps/acp-server.ts +0 -41
- package/src/apps/agent-profiles.ts +0 -19
- package/src/apps/app-server.ts +0 -30
- package/src/apps/approve.ts +0 -49
- package/src/apps/cli.ts +0 -99
- package/src/apps/commands.ts +0 -319
- package/src/apps/completions.ts +0 -87
- package/src/apps/control-transport.ts +0 -90
- package/src/apps/export-cli.ts +0 -24
- package/src/apps/goal-flow.ts +0 -70
- package/src/apps/image-draft.ts +0 -46
- package/src/apps/input.ts +0 -312
- package/src/apps/model-catalog.ts +0 -69
- package/src/apps/preferences.ts +0 -20
- package/src/apps/project-trust.ts +0 -9
- package/src/apps/provider-connect.ts +0 -346
- package/src/apps/question-ui.ts +0 -27
- package/src/apps/render.ts +0 -181
- package/src/apps/resources.ts +0 -66
- package/src/apps/review.ts +0 -109
- package/src/apps/session-hub.ts +0 -96
- package/src/apps/session-lifecycle.ts +0 -64
- package/src/apps/shell.ts +0 -2071
- package/src/apps/startup-input.ts +0 -135
- package/src/apps/startup.ts +0 -1677
- package/src/apps/ui/app.ts +0 -1818
- package/src/apps/ui/control-model.ts +0 -43
- package/src/apps/ui/extension-host.ts +0 -194
- package/src/apps/ui/image-cache.ts +0 -121
- package/src/apps/ui/palette.ts +0 -88
- package/src/apps/ui/project.ts +0 -127
- package/src/apps/ui/standalone.ts +0 -148
- package/src/apps/ui/status-model.ts +0 -68
- package/src/apps/ui/surface.ts +0 -91
- package/src/apps/ui/theme.ts +0 -86
- package/src/apps/update-check.ts +0 -112
- package/src/apps/workflow.ts +0 -58
- package/src/core/acceptance.ts +0 -139
- package/src/core/action-defaults.ts +0 -58
- package/src/core/activity-status.ts +0 -52
- package/src/core/agent-profile.ts +0 -130
- package/src/core/attachment-input.ts +0 -17
- package/src/core/blind-checks.ts +0 -72
- package/src/core/boundary.ts +0 -174
- package/src/core/branch-receipt.ts +0 -26
- package/src/core/catalog-merge.ts +0 -19
- package/src/core/checkpoints.ts +0 -85
- package/src/core/churn.ts +0 -61
- package/src/core/command-artifacts.ts +0 -94
- package/src/core/command-contract.ts +0 -151
- package/src/core/completion-action.ts +0 -20
- package/src/core/completion-review.ts +0 -98
- package/src/core/context-detail.ts +0 -37
- package/src/core/context-policy.ts +0 -133
- package/src/core/context-window.ts +0 -54
- package/src/core/continuation.ts +0 -130
- package/src/core/control-protocol.ts +0 -69
- package/src/core/control-view.ts +0 -39
- package/src/core/cycle.ts +0 -17
- package/src/core/data-query.ts +0 -201
- package/src/core/deadline.ts +0 -40
- package/src/core/derive.ts +0 -516
- package/src/core/draft-images.ts +0 -56
- package/src/core/edit-plan.ts +0 -200
- package/src/core/environment.ts +0 -181
- package/src/core/event-validation.ts +0 -220
- package/src/core/events.ts +0 -670
- package/src/core/evidence-ladder.ts +0 -62
- package/src/core/evidence-links.ts +0 -68
- package/src/core/evidence.ts +0 -240
- package/src/core/execution.ts +0 -18
- package/src/core/exhaustive.ts +0 -18
- package/src/core/export.ts +0 -179
- package/src/core/extension-flags.ts +0 -14
- package/src/core/external-effects.ts +0 -488
- package/src/core/failure-recovery.ts +0 -173
- package/src/core/file-precondition.ts +0 -78
- package/src/core/file-references.ts +0 -35
- package/src/core/frontmatter.ts +0 -213
- package/src/core/git-status.ts +0 -29
- package/src/core/goal-view.ts +0 -64
- package/src/core/goal.ts +0 -200
- package/src/core/handoff.ts +0 -479
- package/src/core/history-query.ts +0 -139
- package/src/core/history.ts +0 -237
- package/src/core/image-limits.ts +0 -72
- package/src/core/images.ts +0 -46
- package/src/core/integration-health.ts +0 -34
- package/src/core/json-event.ts +0 -131
- package/src/core/json-pointer.ts +0 -46
- package/src/core/keymap.ts +0 -153
- package/src/core/lsp-framing.ts +0 -34
- package/src/core/mcp-config.ts +0 -68
- package/src/core/model-picker.ts +0 -64
- package/src/core/model-preferences.ts +0 -28
- package/src/core/model-replay.ts +0 -22
- package/src/core/model-scope.ts +0 -53
- package/src/core/note-guidance.ts +0 -34
- package/src/core/oauth-fields.ts +0 -44
- package/src/core/observation-view.ts +0 -152
- package/src/core/outcome.ts +0 -44
- package/src/core/overflow.ts +0 -59
- package/src/core/packages.ts +0 -47
- package/src/core/patch.ts +0 -130
- package/src/core/path-suggestions.ts +0 -59
- package/src/core/permissions.ts +0 -410
- package/src/core/process-receipt.ts +0 -105
- package/src/core/process-state.ts +0 -37
- package/src/core/program-status.ts +0 -40
- package/src/core/progress.ts +0 -150
- package/src/core/project.ts +0 -431
- package/src/core/prompt-policy.ts +0 -46
- package/src/core/prompts.ts +0 -111
- package/src/core/question.ts +0 -94
- package/src/core/queue.ts +0 -78
- package/src/core/read-bounds.ts +0 -24
- package/src/core/read-observation.ts +0 -106
- package/src/core/recovery-sources.ts +0 -74
- package/src/core/regex-dialect.ts +0 -25
- package/src/core/request-budget.ts +0 -23
- package/src/core/request-progress.ts +0 -87
- package/src/core/request-state.ts +0 -273
- package/src/core/resource-selection.ts +0 -36
- package/src/core/response-recovery.ts +0 -59
- package/src/core/resume.ts +0 -59
- package/src/core/review.ts +0 -68
- package/src/core/ripgrep-release.ts +0 -64
- package/src/core/runtime-target.ts +0 -94
- package/src/core/scheduling.ts +0 -8
- package/src/core/search-observation.ts +0 -97
- package/src/core/secrets.ts +0 -188
- package/src/core/session-archive.ts +0 -4
- package/src/core/session-browser.ts +0 -82
- package/src/core/session-entry.ts +0 -61
- package/src/core/session-exit.ts +0 -42
- package/src/core/session-format.ts +0 -169
- package/src/core/session-position.ts +0 -26
- package/src/core/session-tree.ts +0 -147
- package/src/core/settings.ts +0 -192
- package/src/core/skills.ts +0 -165
- package/src/core/sql-query.ts +0 -211
- package/src/core/stall.ts +0 -128
- package/src/core/stop-report.ts +0 -68
- package/src/core/text-normalize.ts +0 -81
- package/src/core/theme.ts +0 -166
- package/src/core/thinking.ts +0 -26
- package/src/core/tokens.ts +0 -151
- package/src/core/tool-contract.ts +0 -153
- package/src/core/tool-discovery.ts +0 -87
- package/src/core/tool-failure.ts +0 -81
- package/src/core/tool-input.ts +0 -44
- package/src/core/transcript.ts +0 -108
- package/src/core/tui-mode.ts +0 -29
- package/src/core/update-check.ts +0 -157
- package/src/core/version.ts +0 -55
- package/src/core/web-content.ts +0 -80
- package/src/core/work-state.ts +0 -154
- package/src/core/workflow-mode.ts +0 -34
- package/src/core/working-set.ts +0 -119
- package/src/core/yaml.ts +0 -282
- package/src/runtime/acp-agent.ts +0 -289
- package/src/runtime/agent-profiles.ts +0 -21
- package/src/runtime/auth.ts +0 -367
- package/src/runtime/blind-check.ts +0 -36
- package/src/runtime/branch-jobs.ts +0 -36
- package/src/runtime/catalog.ts +0 -285
- package/src/runtime/clipboard.ts +0 -125
- package/src/runtime/command-artifacts.ts +0 -64
- package/src/runtime/connection-reuse.ts +0 -40
- package/src/runtime/control-client.ts +0 -149
- package/src/runtime/control-connection.ts +0 -139
- package/src/runtime/counter-check.ts +0 -74
- package/src/runtime/environment-tools.ts +0 -60
- package/src/runtime/evidence-notes.ts +0 -35
- package/src/runtime/exec-sessions.ts +0 -456
- package/src/runtime/execution-control.ts +0 -40
- package/src/runtime/extension-loader.ts +0 -37
- package/src/runtime/extension-ui.ts +0 -47
- package/src/runtime/extensions.ts +0 -370
- package/src/runtime/formatter-tool.ts +0 -38
- package/src/runtime/formatter.ts +0 -50
- package/src/runtime/foundation.ts +0 -116
- package/src/runtime/history-cache.ts +0 -57
- package/src/runtime/history-tools.ts +0 -168
- package/src/runtime/images.ts +0 -80
- package/src/runtime/integrations.ts +0 -216
- package/src/runtime/loop.ts +0 -3108
- package/src/runtime/lsp-client.ts +0 -201
- package/src/runtime/mcp-client.ts +0 -264
- package/src/runtime/mcp-oauth.ts +0 -168
- package/src/runtime/mcp-schema.ts +0 -51
- package/src/runtime/model-config.ts +0 -232
- package/src/runtime/model-data.LICENSE +0 -27
- package/src/runtime/model-data.ts +0 -21
- package/src/runtime/model-discovery.ts +0 -208
- package/src/runtime/model-replay.ts +0 -10
- package/src/runtime/model-scope.ts +0 -20
- package/src/runtime/model-sources.ts +0 -151
- package/src/runtime/model.ts +0 -686
- package/src/runtime/models-fetch.ts +0 -57
- package/src/runtime/oauth-device.ts +0 -86
- package/src/runtime/oauth.ts +0 -853
- package/src/runtime/open-image.ts +0 -42
- package/src/runtime/openai-responses.ts +0 -304
- package/src/runtime/openai.ts +0 -697
- package/src/runtime/output-schema.ts +0 -26
- package/src/runtime/package-manager.ts +0 -125
- package/src/runtime/patch-tool.ts +0 -38
- package/src/runtime/permission-resources.ts +0 -18
- package/src/runtime/pi-provider.ts +0 -423
- package/src/runtime/program-status.ts +0 -38
- package/src/runtime/progress.ts +0 -26
- package/src/runtime/provider-access.ts +0 -21
- package/src/runtime/provider-connect.ts +0 -109
- package/src/runtime/provider-presets.ts +0 -98
- package/src/runtime/provider-protocols.ts +0 -29
- package/src/runtime/provider-request.ts +0 -87
- package/src/runtime/provider-tool-policy.ts +0 -33
- package/src/runtime/provider.ts +0 -580
- package/src/runtime/question-tool.ts +0 -62
- package/src/runtime/retry.ts +0 -447
- package/src/runtime/review-tool.ts +0 -48
- package/src/runtime/rich-text.ts +0 -36
- package/src/runtime/runtime-target.ts +0 -149
- package/src/runtime/secrets.ts +0 -79
- package/src/runtime/self-update.ts +0 -74
- package/src/runtime/session-control.ts +0 -265
- package/src/runtime/session-hub.ts +0 -85
- package/src/runtime/stream-idle.ts +0 -80
- package/src/runtime/tool-broker.ts +0 -137
- package/src/runtime/tool-discovery.ts +0 -58
- package/src/runtime/tool-queue.ts +0 -77
- package/src/runtime/tools.ts +0 -1820
- package/src/runtime/turn-effects.ts +0 -31
- package/src/runtime/update-check.ts +0 -48
- package/src/runtime/usage.ts +0 -253
- package/src/runtime/version.ts +0 -12
- package/src/runtime/web-http.ts +0 -56
- package/src/runtime/web-tools.ts +0 -88
- package/src/runtime/working-notes.ts +0 -60
- package/src/runtime/workspace-lifecycle.ts +0 -141
- package/src/store/acceptance.ts +0 -15
- package/src/store/agent-profile-files.ts +0 -13
- package/src/store/atomic.ts +0 -31
- package/src/store/auth-store.ts +0 -50
- package/src/store/blind-checks.ts +0 -32
- package/src/store/blobs.ts +0 -143
- package/src/store/checkpoint-history.ts +0 -32
- package/src/store/command-artifacts.ts +0 -96
- package/src/store/command.ts +0 -111
- package/src/store/config.ts +0 -69
- package/src/store/context-files.ts +0 -436
- package/src/store/file-checkpoints.ts +0 -108
- package/src/store/file-index.ts +0 -174
- package/src/store/foundation-config.ts +0 -24
- package/src/store/git-checkpoints.ts +0 -67
- package/src/store/git-status.ts +0 -15
- package/src/store/history-search.ts +0 -19
- package/src/store/log.ts +0 -725
- package/src/store/lsp-document.ts +0 -30
- package/src/store/mcp-credentials.ts +0 -85
- package/src/store/models.ts +0 -60
- package/src/store/observations.ts +0 -224
- package/src/store/packages.ts +0 -98
- package/src/store/patch.ts +0 -92
- package/src/store/permission-paths.ts +0 -34
- package/src/store/process-receipts.ts +0 -34
- package/src/store/query-source.ts +0 -49
- package/src/store/recovery.ts +0 -44
- package/src/store/replay.ts +0 -76
- package/src/store/resource-index.ts +0 -170
- package/src/store/resources.ts +0 -285
- package/src/store/restore.ts +0 -110
- package/src/store/review.ts +0 -109
- package/src/store/runtime-target.ts +0 -64
- package/src/store/search.ts +0 -728
- package/src/store/secrets.ts +0 -44
- package/src/store/serial.ts +0 -13
- package/src/store/session-archive.ts +0 -165
- package/src/store/session-browser.ts +0 -68
- package/src/store/session-position.ts +0 -27
- package/src/store/sessions.ts +0 -525
- package/src/store/source-cancellation.ts +0 -6
- package/src/store/tool-binaries.ts +0 -90
- package/src/store/trust.ts +0 -82
- package/src/store/update-cache.ts +0 -36
- package/src/store/user-settings.ts +0 -30
- package/src/store/workspace-clone.ts +0 -42
- package/src/store/workspace-observation.ts +0 -113
- package/src/store/workspace.ts +0 -115
- package/src/store/worktree.ts +0 -278
- package/src/tui/ansi-spans.ts +0 -126
- package/src/tui/diff.ts +0 -160
- package/src/tui/exhaustive.ts +0 -14
- package/src/tui/fuzzy.ts +0 -75
- package/src/tui/style.ts +0 -208
- package/src/tui/ui/atomic-draft.ts +0 -121
- package/src/tui/ui/branch-panel.ts +0 -47
- package/src/tui/ui/canvas.ts +0 -96
- package/src/tui/ui/colors.ts +0 -70
- package/src/tui/ui/composer.ts +0 -199
- package/src/tui/ui/control-panel.ts +0 -25
- package/src/tui/ui/control-sidebar.ts +0 -49
- package/src/tui/ui/dialog.ts +0 -398
- package/src/tui/ui/draft.ts +0 -23
- package/src/tui/ui/duration.ts +0 -16
- package/src/tui/ui/edge.ts +0 -186
- package/src/tui/ui/image-size.ts +0 -44
- package/src/tui/ui/ink.ts +0 -210
- package/src/tui/ui/items.ts +0 -42
- package/src/tui/ui/keys.ts +0 -40
- package/src/tui/ui/layout.ts +0 -74
- package/src/tui/ui/list-model.ts +0 -86
- package/src/tui/ui/list-view.ts +0 -172
- package/src/tui/ui/mascot.ts +0 -67
- package/src/tui/ui/messages.ts +0 -518
- package/src/tui/ui/motion.ts +0 -28
- package/src/tui/ui/paths.ts +0 -10
- package/src/tui/ui/pictures.ts +0 -108
- package/src/tui/ui/prompt-history.ts +0 -60
- package/src/tui/ui/screen.ts +0 -579
- package/src/tui/ui/status.ts +0 -403
- package/src/tui/ui/styled.ts +0 -85
- package/src/tui/ui/tool-card.ts +0 -150
- package/src/tui/width.ts +0 -567
- package/src/vendor/pi-rich/LICENSE.upstream +0 -21
- package/src/vendor/pi-rich/UPSTREAM.json +0 -17
- package/src/vendor/pi-rich/clipboard-command.ts +0 -44
- package/src/vendor/pi-rich/exif-orientation.ts +0 -182
- package/src/vendor/pi-rich/html.ts +0 -51
- package/src/vendor/pi-rich/image-convert.ts +0 -49
- package/src/vendor/pi-rich/image-resize-core.ts +0 -164
- package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
- package/src/vendor/pi-rich/image-resize.ts +0 -123
- package/src/vendor/pi-rich/mime.ts +0 -116
- package/src/vendor/pi-rich/photon.ts +0 -139
- package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
package/src/core/acceptance.ts
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/** O.11: declared criteria, command evidence and human acceptance are separate. */
|
|
2
|
-
import type { LogEvent } from "./events.ts";
|
|
3
|
-
import type { Evidence } from "./handoff.ts";
|
|
4
|
-
import { evidenceStale } from "./evidence.ts";
|
|
5
|
-
import { workspaceChanged } from "./checkpoints.ts";
|
|
6
|
-
import { parseRuntimeTarget, type RuntimeTarget, type TargetAcceptance } from "./runtime-target.ts";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* A command criterion either tests the change -- it fails before the work and
|
|
10
|
-
* passes after it (SWE-bench's FAIL_TO_PASS) -- or, with `guard`, protects
|
|
11
|
-
* what the work must not break: it passes before and after (PASS_TO_PASS),
|
|
12
|
-
* such as "the test file is unchanged" (S.7).
|
|
13
|
-
*/
|
|
14
|
-
export type AcceptanceCriterion = { id: string; description: string } &
|
|
15
|
-
({ kind: "command"; command: string; guard?: true } | { kind: "human"; target?: string });
|
|
16
|
-
export type AcceptanceContract = { version: 1; id: string; scope?: "request" | "changes"; criteria: AcceptanceCriterion[];
|
|
17
|
-
source?: { path: string; sha256: string }; targets?: RuntimeTarget[] };
|
|
18
|
-
export type AcceptanceState = {
|
|
19
|
-
contract_id: string; contract_event: string; request: string; scope: "request" | "changes"; changed: boolean;
|
|
20
|
-
criteria: (AcceptanceCriterion & { status: "not_run" | "pass" | "fail" | "stale" | "awaiting_human" | "accepted"; evidence?: Evidence; accepted_event?: string; accepted_binding?: string })[];
|
|
21
|
-
targets?: TargetAcceptance[];
|
|
22
|
-
status: "needs_work" | "awaiting_human" | "verified" | "not_applicable";
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* The longest check command a contract accepts. Drafted checks are often one
|
|
26
|
-
* inline script (`python3 -c '...'`); a real 599-character structure check
|
|
27
|
-
* was refused at the earlier 500 (fix-goal-read-only-draft). Bounded so a
|
|
28
|
-
* frozen contract stays small.
|
|
29
|
-
*/
|
|
30
|
-
export const ACCEPTANCE_COMMAND_LIMIT = 2000;
|
|
31
|
-
const record = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
32
|
-
const label = (value: unknown, max: number) => typeof value === "string" && value.trim().length > 0 && value.length <= max;
|
|
33
|
-
|
|
34
|
-
export function parseAcceptanceContract(value: unknown): AcceptanceContract {
|
|
35
|
-
if (!record(value) || value["version"] !== 1 || !label(value["id"], 80) ||
|
|
36
|
-
(value["scope"] !== undefined && value["scope"] !== "request" && value["scope"] !== "changes") ||
|
|
37
|
-
!Array.isArray(value["criteria"]) || value["criteria"].length < 1 || value["criteria"].length > 20) throw new Error("invalid acceptance contract v1");
|
|
38
|
-
if (Object.keys(value).some(key => !["version", "id", "scope", "criteria", "source", "targets"].includes(key))) throw new Error("unsupported acceptance contract field");
|
|
39
|
-
const source = value["source"];
|
|
40
|
-
if (source !== undefined && (!record(source) || !label(source["path"], 4096) || typeof source["sha256"] !== "string" || !/^[a-f0-9]{64}$/.test(source["sha256"]))) throw new Error("invalid acceptance source");
|
|
41
|
-
const criteria: AcceptanceCriterion[] = value["criteria"].map((item: unknown) => {
|
|
42
|
-
if (!record(item) || !label(item["id"], 80) || !label(item["description"], 300)) throw new Error("invalid acceptance criterion");
|
|
43
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$/.test(String(item["id"])) || Object.keys(item).some(key => !["id", "description", "kind", "command", "target", "guard"].includes(key))) throw new Error("unsupported acceptance criterion field or ID");
|
|
44
|
-
const fields = { id: String(item["id"]), description: String(item["description"]) };
|
|
45
|
-
if (item["kind"] === "human" && item["command"] === undefined && item["guard"] === undefined && (item["target"] === undefined || label(item["target"], 80))) return { ...fields, kind: "human", ...(item["target"] ? { target: String(item["target"]) } : {}) };
|
|
46
|
-
if (item["kind"] === "command" && item["target"] === undefined && label(item["command"], ACCEPTANCE_COMMAND_LIMIT) && (item["guard"] === undefined || item["guard"] === true)) {
|
|
47
|
-
return { ...fields, kind: "command", command: String(item["command"]), ...(item["guard"] === true ? { guard: true as const } : {}) };
|
|
48
|
-
}
|
|
49
|
-
throw new Error("acceptance criterion must declare a command or human decision");
|
|
50
|
-
});
|
|
51
|
-
if (new Set(criteria.map(item => item.id)).size !== criteria.length) throw new Error("duplicate acceptance criterion IDs");
|
|
52
|
-
const declarations = value["targets"];
|
|
53
|
-
if (declarations !== undefined && (!Array.isArray(declarations) || declarations.length < 1 || declarations.length > 8)) throw Error("targets requires 1..8 declared runtimes");
|
|
54
|
-
const targets = Array.isArray(declarations) ? declarations.map(parseRuntimeTarget) : undefined;
|
|
55
|
-
if (targets && (new Set(targets.map(target => target.id)).size !== targets.length || targets.some(target =>
|
|
56
|
-
!criteria.some(item => item.id === target.build && item.kind === "command") || !criteria.some(item => item.kind === "human" && item.target === target.id)))) throw Error("targets require a build condition and a bound human condition");
|
|
57
|
-
if (criteria.some(item => item.kind === "human" && item.target && !targets?.some(target => target.id === item.target))) throw Error("unknown human acceptance target");
|
|
58
|
-
return { version: 1, id: String(value["id"]), criteria,
|
|
59
|
-
...(targets ? { targets } : {}),
|
|
60
|
-
...(record(source) ? { source: { path: String(source["path"]), sha256: String(source["sha256"]) } } : {}),
|
|
61
|
-
...(value["scope"] ? { scope: value["scope"] as "request" | "changes" } : {}) };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* What one counter-check observation means (S.1, S.7): the check run once more
|
|
66
|
-
* in a clone with the request's changes undone. A check that tests the change
|
|
67
|
-
* must fail there; a guard must still pass there. The log records only the
|
|
68
|
-
* exit code and the role it ran under, so every view derives the verdict here
|
|
69
|
-
* and none can disagree with another.
|
|
70
|
-
*
|
|
71
|
-
* tests the change fails there: discriminating passes there: vacuous
|
|
72
|
-
* guard passes there: held fails there: not_held
|
|
73
|
-
*
|
|
74
|
-
* A guard that did not hold is not a defect in the evidence: the check does
|
|
75
|
-
* test the change, only its declaration was wrong.
|
|
76
|
-
*/
|
|
77
|
-
export type CounterVerdict = "discriminating" | "vacuous" | "held" | "not_held" | "unavailable";
|
|
78
|
-
export function counterVerdict(observation: { guard?: true } & ({ exit: number } | { reason: string })): CounterVerdict {
|
|
79
|
-
if (!("exit" in observation)) return "unavailable";
|
|
80
|
-
if (observation.guard) return observation.exit === 0 ? "held" : "not_held";
|
|
81
|
-
return observation.exit === 0 ? "vacuous" : "discriminating";
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function deriveAcceptance(previous: AcceptanceState | undefined, events: readonly LogEvent[]): AcceptanceState | undefined {
|
|
85
|
-
let state = previous ? { ...previous, criteria: previous.criteria.map(item => ({ ...item })), ...(previous.targets ? { targets: previous.targets.map(target => ({ ...target })) } : {}) } : undefined;
|
|
86
|
-
for (const event of events) {
|
|
87
|
-
if (event.t === "acceptance_contract") {
|
|
88
|
-
state = { contract_id: event.contract.id, contract_event: event.id, request: event.request, scope: event.contract.scope ?? "request", changed: false,
|
|
89
|
-
criteria: event.contract.criteria.map(item => ({ ...item, status: item.kind === "human" ? "awaiting_human" : "not_run" })), status: "needs_work" };
|
|
90
|
-
if (event.contract.targets) state.targets = event.contract.targets.map(target => ({ id: target.id, build: target.build, status: "not_run" }));
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (!state) continue;
|
|
94
|
-
const mutation = workspaceChanged(event);
|
|
95
|
-
if (mutation) { state.changed = true; for (const target of state.targets ?? []) target.status = "stale"; }
|
|
96
|
-
if (event.t === "target_observation" && event.observation.request === state.request && event.observation.contract_event === state.contract_event) {
|
|
97
|
-
const target = state.targets?.find(item => item.id === event.observation.target), receipt = event.observation;
|
|
98
|
-
if (target) {
|
|
99
|
-
const build = state.criteria.find(item => item.id === target.build);
|
|
100
|
-
target.status = receipt.state === "ready" && (build?.status !== "pass" || build.evidence?.event_id !== receipt.build?.event) ? "stale" : receipt.state;
|
|
101
|
-
target.observation = event.id; target.reason = receipt.reason;
|
|
102
|
-
if (receipt.build) target.build_event = receipt.build.event;
|
|
103
|
-
if (receipt.instance) { target.binding = receipt.instance.id; target.session = receipt.instance.session; target.pid = receipt.instance.pid; target.entry = receipt.instance.entry.path; }
|
|
104
|
-
else { delete target.binding; delete target.session; delete target.pid; delete target.entry; }
|
|
105
|
-
if (receipt.port !== undefined) target.port = receipt.port;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
for (const target of state.targets ?? []) if (event.t === "process_exit" && event.session === target.session ||
|
|
109
|
-
event.t === "tool_result" && record(event.data) && event.data["session_id"] === target.session && event.data["running"] === false) target.status = "stale";
|
|
110
|
-
for (const item of state.criteria) {
|
|
111
|
-
if (item.evidence && evidenceStale([event], item.evidence, item.status === "stale")) item.status = "stale";
|
|
112
|
-
if (mutation && item.kind === "human" && item.status === "accepted") { item.status = "awaiting_human"; delete item.accepted_event; delete item.accepted_binding; }
|
|
113
|
-
if (item.kind === "command" && event.t === "tool_result" && event.effect?.kind === "command" && event.effect.cmd === item.command) {
|
|
114
|
-
item.status = event.effect.exit === 0 && event.ok ? event.effect.workspace_observed === false ? "stale" : "pass" : "fail";
|
|
115
|
-
item.evidence = { command: event.effect.cmd, exit: event.effect.exit, event_id: event.id, at: event.at,
|
|
116
|
-
...(event.effect.workspace_scope ? { workspace_scope: event.effect.workspace_scope } : {}),
|
|
117
|
-
...(event.effect.workspace_sha256 ? { workspace_sha256: event.effect.workspace_sha256 } : {}) };
|
|
118
|
-
for (const target of state.targets ?? []) if (target.build === item.id && target.build_event !== event.id) target.status = "stale";
|
|
119
|
-
}
|
|
120
|
-
if (item.kind === "human" && event.t === "acceptance_decision" && event.request === state.request &&
|
|
121
|
-
event.contract_id === state.contract_id && event.criterion === item.id && event.actor === "user") {
|
|
122
|
-
const target = item.target ? state.targets?.find(target => target.id === item.target) : undefined;
|
|
123
|
-
if (!item.target || target?.status === "ready" && target.binding === event.target_binding && target.observation === event.target_observation) {
|
|
124
|
-
item.status = "accepted"; item.accepted_event = event.id;
|
|
125
|
-
if (event.target_binding) item.accepted_binding = event.target_binding;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
for (const target of state.targets ?? []) if (target.status === "ready" && !state.criteria.some(item => item.id === target.build && item.status === "pass" && item.evidence?.event_id === target.build_event)) target.status = "stale";
|
|
130
|
-
for (const item of state.criteria) if (item.kind === "human" && item.target && item.status === "accepted") {
|
|
131
|
-
const target = state.targets?.find(target => target.id === item.target);
|
|
132
|
-
if (target?.status !== "ready" || target.binding !== item.accepted_binding) { item.status = "awaiting_human"; delete item.accepted_event; delete item.accepted_binding; }
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
if (state) state.status = state.scope === "changes" && !state.changed ? "not_applicable" :
|
|
136
|
-
state.criteria.some(item => item.kind === "command" && item.status !== "pass") || state.targets?.some(target => target.status !== "ready") ? "needs_work" :
|
|
137
|
-
state.criteria.some(item => item.kind === "human" && item.status !== "accepted") ? "awaiting_human" : "verified";
|
|
138
|
-
return state;
|
|
139
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* xeno's key bindings (D20). One flat namespace:
|
|
3
|
-
* app.* global, handled before the focused component
|
|
4
|
-
* editor.* the composer (submit / newline / history / completion)
|
|
5
|
-
* list.* every list dialog (palette, model, login, resume, tree, …)
|
|
6
|
-
* Shortcuts that only exist inside one dialog are shown in that dialog's
|
|
7
|
-
* hint row instead of being configurable globals; the palette (Ctrl+P)
|
|
8
|
-
* reaches every command, so rarely used actions need no chord at all.
|
|
9
|
-
*/
|
|
10
|
-
export const ACTION_DEFAULTS = {
|
|
11
|
-
"app.palette": ["ctrl+p"],
|
|
12
|
-
"app.model": ["ctrl+l"],
|
|
13
|
-
"app.cycleModel": ["alt+m"],
|
|
14
|
-
"app.cycleModelReverse": ["alt+shift+m"],
|
|
15
|
-
"app.cycleThinking": ["shift+tab"],
|
|
16
|
-
"app.toggleTools": ["ctrl+o"],
|
|
17
|
-
"app.toggleThinking": ["ctrl+t"],
|
|
18
|
-
"app.toggleSidebar": ["ctrl+b"],
|
|
19
|
-
"app.interrupt": ["escape"],
|
|
20
|
-
"app.clear": ["ctrl+c"],
|
|
21
|
-
"app.exit": ["ctrl+d"],
|
|
22
|
-
"app.suspend": ["ctrl+z"],
|
|
23
|
-
"app.externalEditor": ["ctrl+g"],
|
|
24
|
-
"app.message.copy": ["ctrl+x"],
|
|
25
|
-
"app.message.followUp": ["alt+enter"],
|
|
26
|
-
"app.message.dequeue": ["alt+up"],
|
|
27
|
-
"app.clipboard.pasteImage": ["ctrl+v"],
|
|
28
|
-
"app.scroll.pageUp": ["pageup"],
|
|
29
|
-
"app.scroll.pageDown": ["pagedown"],
|
|
30
|
-
"app.scroll.top": ["ctrl+home"],
|
|
31
|
-
"app.scroll.bottom": ["ctrl+end"],
|
|
32
|
-
"app.session.new": [],
|
|
33
|
-
"app.session.tree": [],
|
|
34
|
-
"app.session.fork": [],
|
|
35
|
-
"app.session.resume": [],
|
|
36
|
-
"app.saveDefault": ["ctrl+s"],
|
|
37
|
-
"editor.submit": ["enter"],
|
|
38
|
-
"editor.newLine": ["shift+enter", "ctrl+j"],
|
|
39
|
-
"editor.complete": ["tab"],
|
|
40
|
-
"editor.historyPrevious": ["up"],
|
|
41
|
-
"editor.historyNext": ["down"],
|
|
42
|
-
"list.up": ["up", "ctrl+k"],
|
|
43
|
-
"list.down": ["down", "ctrl+j"],
|
|
44
|
-
"list.pageUp": ["pageup"],
|
|
45
|
-
"list.pageDown": ["pagedown"],
|
|
46
|
-
"list.confirm": ["enter"],
|
|
47
|
-
"list.cancel": ["escape"],
|
|
48
|
-
"list.toggle": ["space"],
|
|
49
|
-
} as const satisfies Record<string, readonly string[]>;
|
|
50
|
-
|
|
51
|
-
/** Renamed ids still read from a user's keybindings file. */
|
|
52
|
-
export const ACTION_ALIASES: Readonly<Record<string, string>> = {
|
|
53
|
-
"app.model.select": "app.model",
|
|
54
|
-
"app.thinking.cycle": "app.cycleThinking",
|
|
55
|
-
"app.thinking.toggle": "app.toggleThinking",
|
|
56
|
-
"app.tools.expand": "app.toggleTools",
|
|
57
|
-
"app.editor.external": "app.externalEditor",
|
|
58
|
-
};
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/** A finer view of OSC status, derived from the same runtime facts (D11). */
|
|
2
|
-
import { programStatus, type ProgramStatus } from "./program-status.ts";
|
|
3
|
-
import type { ExecutionOutcome } from "./outcome.ts";
|
|
4
|
-
|
|
5
|
-
export interface ActivityFacts {
|
|
6
|
-
readonly running: boolean;
|
|
7
|
-
readonly waiting?: "permission" | "question" | "auth" | null;
|
|
8
|
-
readonly outcome?: ExecutionOutcome;
|
|
9
|
-
readonly goal?: string;
|
|
10
|
-
readonly phase?: "connecting" | "thinking" | "streaming" | "tool";
|
|
11
|
-
readonly tool?: string;
|
|
12
|
-
readonly retry?: RetryFacts;
|
|
13
|
-
readonly queued: number;
|
|
14
|
-
}
|
|
15
|
-
/** One pending retry: which attempt, out of how many, how long until it, and why. */
|
|
16
|
-
export interface RetryFacts { readonly attempt: number; readonly max?: number; readonly seconds: number; readonly reason?: string }
|
|
17
|
-
|
|
18
|
-
const attemptText = (retry: RetryFacts): string => retry.max === undefined ? `${retry.attempt}` : `${retry.attempt}/${retry.max}`;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* The stream-area retry line. The cause appears from the third attempt (or
|
|
22
|
-
* the last, for a smaller budget), as in Claude Code: one or two retries are
|
|
23
|
-
* noise, and naming them would send the user to check a network that is fine.
|
|
24
|
-
*/
|
|
25
|
-
export function retryLine(retry: RetryFacts): string {
|
|
26
|
-
const named = retry.reason !== undefined && retry.attempt >= Math.min(3, retry.max ?? 3);
|
|
27
|
-
return `retry ${attemptText(retry)} in ${Math.max(0, retry.seconds).toFixed(1)}s · ${named ? `${retry.reason} · ` : ""}esc to cancel`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface ActivityStatus { readonly state: ProgramStatus["state"]; readonly label: string; readonly detail: string; readonly queued: number }
|
|
31
|
-
|
|
32
|
-
const STOP_CAUSE: Partial<Record<ExecutionOutcome["reason"], string>> = {
|
|
33
|
-
stalled: "no progress", no_progress: "no progress", max_turns: "request limit", context_budget: "context budget",
|
|
34
|
-
output_limit: "output limit", timeout: "time limit",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export function activityStatus(facts: ActivityFacts): ActivityStatus {
|
|
38
|
-
const status = programStatus(facts), queued = Math.max(0, Math.floor(facts.queued));
|
|
39
|
-
const view = (label: string, detail = status.msg ?? ""): ActivityStatus => ({ state: status.state, label, detail, queued });
|
|
40
|
-
// A harness stop is not a question: saying "Awaiting input" left the user
|
|
41
|
-
// guessing what to answer (fix-finished-turn-status). The OSC state stays blocked.
|
|
42
|
-
if (!facts.running && !facts.waiting && facts.outcome?.status === "stopped") return view("Stopped", STOP_CAUSE[facts.outcome.reason] ?? facts.outcome.reason.replaceAll("_", " "));
|
|
43
|
-
if (status.state === "blocked") return view(status.kind === "auth" ? "Sign in required" : status.kind === "permission" ? "Awaiting approval"
|
|
44
|
-
: !facts.waiting && facts.goal === "awaiting_human" ? "Awaiting review" : "Awaiting input");
|
|
45
|
-
if (status.state === "error") return view("Failed");
|
|
46
|
-
if (!facts.running) return view(status.state === "done" ? "Turn finished" : facts.outcome?.status === "cancelled" ? "Cancelled" : "Ready");
|
|
47
|
-
if (facts.retry) return view("Retrying", `attempt ${attemptText(facts.retry)} · ${Math.max(0, Math.ceil(facts.retry.seconds))}s`);
|
|
48
|
-
if (facts.phase === "tool") return view("Running tool", facts.tool ?? "Tool execution");
|
|
49
|
-
if (facts.phase === "thinking") return view("Thinking");
|
|
50
|
-
if (facts.phase === "streaming") return view("Responding");
|
|
51
|
-
return view("Connecting", "Waiting for the first response");
|
|
52
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/** R13 / D13: explicit role layers share tool, model, workflow and budget ownership. */
|
|
2
|
-
import { isWorkflowMode, type WorkflowMode } from "./workflow-mode.ts";
|
|
3
|
-
import { isToolProfile, parsePermissionRule, type PermissionRule, type ToolSelection } from "./permissions.ts";
|
|
4
|
-
import { asThinkingLevel, type ThinkingLevel } from "./thinking.ts";
|
|
5
|
-
|
|
6
|
-
export interface AgentProfile {
|
|
7
|
-
readonly name: string;
|
|
8
|
-
readonly description?: string;
|
|
9
|
-
readonly mode?: WorkflowMode;
|
|
10
|
-
readonly instructions?: string;
|
|
11
|
-
readonly model?: { readonly provider: string; readonly id: string; readonly thinking?: ThinkingLevel };
|
|
12
|
-
readonly tools?: ToolSelection;
|
|
13
|
-
readonly permissions?: readonly PermissionRule[];
|
|
14
|
-
/** maxTurns is a hard cap; timeBudgetMs retains the existing advisory semantics. */
|
|
15
|
-
readonly budget?: { readonly maxTurns?: number; readonly timeBudgetMs?: number };
|
|
16
|
-
}
|
|
17
|
-
export type AgentProfileField = Exclude<keyof AgentProfile, "name">;
|
|
18
|
-
export interface AgentProfileSource { readonly kind: "builtin" | "user" | "project" | "cli"; readonly path?: string }
|
|
19
|
-
export interface AgentProfileLayer { readonly source: AgentProfileSource; readonly profiles: readonly AgentProfile[] }
|
|
20
|
-
export interface ResolvedAgentProfile {
|
|
21
|
-
readonly profile: AgentProfile;
|
|
22
|
-
readonly sources: readonly AgentProfileSource[];
|
|
23
|
-
readonly provenance: Readonly<Partial<Record<AgentProfileField, AgentProfileSource>>>;
|
|
24
|
-
}
|
|
25
|
-
export interface AgentProfileParse { readonly profiles: readonly AgentProfile[]; readonly diagnostics: readonly string[] }
|
|
26
|
-
|
|
27
|
-
/** Named builtins are explicit selectable presets; they never fill missing fields in another role. */
|
|
28
|
-
export function builtinAgentProfileLayer(): AgentProfileLayer {
|
|
29
|
-
return { source: { kind: "builtin" }, profiles: [{ name: "build", mode: "build", tools: { profile: "foundation" } }, { name: "plan", mode: "plan", tools: { profile: "foundation" } },
|
|
30
|
-
{ name: "reviewer", mode: "plan", tools: { profile: "readonly" },
|
|
31
|
-
instructions: "Review current code for actionable defects. Cite concrete evidence and paths; do not edit the workspace." }] };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const FIELDS: readonly AgentProfileField[] = ["description", "mode", "instructions", "model", "tools", "permissions", "budget"];
|
|
35
|
-
export function isAgentProfileName(value: string): boolean { return /^[a-z][a-z0-9_-]{0,63}$/.test(value); }
|
|
36
|
-
function record(value: unknown, label: string, allowed: readonly string[]): Record<string, unknown> {
|
|
37
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw Error(`${label} must be an object`);
|
|
38
|
-
const obj = value as Record<string, unknown>;
|
|
39
|
-
const unknown = Object.keys(obj).find(key => !allowed.includes(key));
|
|
40
|
-
if (unknown) throw Error(`${label} has unknown field ${unknown}`);
|
|
41
|
-
return obj;
|
|
42
|
-
}
|
|
43
|
-
function text(value: unknown, label: string): string {
|
|
44
|
-
if (typeof value !== "string" || !value.trim() || value.includes("\0")) throw Error(`${label} must be a nonempty string`);
|
|
45
|
-
return value;
|
|
46
|
-
}
|
|
47
|
-
function strings(value: unknown, label: string): string[] {
|
|
48
|
-
if (!Array.isArray(value) || value.some(item => typeof item !== "string" || !item.trim()) || new Set(value).size !== value.length) throw Error(`${label} must contain unique nonempty names`);
|
|
49
|
-
return [...value] as string[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function parseAgentProfile(value: unknown): AgentProfile {
|
|
53
|
-
const obj = record(value, "agent profile", ["name", ...FIELDS]);
|
|
54
|
-
const name = text(obj["name"], "agent name");
|
|
55
|
-
if (!isAgentProfileName(name)) throw Error("agent name must begin with a lowercase letter and contain only letters, digits, _ or - (max 64)");
|
|
56
|
-
const profile: { name: string; description?: string; mode?: WorkflowMode; instructions?: string;
|
|
57
|
-
model?: NonNullable<AgentProfile["model"]>; tools?: ToolSelection; permissions?: PermissionRule[]; budget?: NonNullable<AgentProfile["budget"]> } = { name };
|
|
58
|
-
if (obj["description"] !== undefined) profile.description = text(obj["description"], "agent description");
|
|
59
|
-
if (obj["instructions"] !== undefined) profile.instructions = text(obj["instructions"], "agent instructions");
|
|
60
|
-
if (obj["mode"] !== undefined) {
|
|
61
|
-
if (typeof obj["mode"] !== "string" || !isWorkflowMode(obj["mode"])) throw Error("agent mode must be build or plan");
|
|
62
|
-
profile.mode = obj["mode"];
|
|
63
|
-
}
|
|
64
|
-
if (obj["model"] !== undefined) {
|
|
65
|
-
const model = record(obj["model"], "agent model", ["provider", "id", "thinking"]);
|
|
66
|
-
const provider = text(model["provider"], "agent model provider"), id = text(model["id"], "agent model id");
|
|
67
|
-
const thinking = asThinkingLevel(model["thinking"]);
|
|
68
|
-
if (model["thinking"] !== undefined && thinking === undefined) throw Error("agent model thinking is invalid");
|
|
69
|
-
profile.model = { provider, id, ...(thinking !== undefined ? { thinking } : {}) };
|
|
70
|
-
}
|
|
71
|
-
if (obj["tools"] !== undefined) {
|
|
72
|
-
const tools = record(obj["tools"], "agent tools", ["profile", "only", "exclude", "none", "noBuiltin"]);
|
|
73
|
-
const selected: { profile?: NonNullable<ToolSelection["profile"]>; only?: string[]; exclude?: string[]; none?: boolean; noBuiltin?: boolean } = {};
|
|
74
|
-
if (tools["profile"] !== undefined) {
|
|
75
|
-
if (typeof tools["profile"] !== "string" || !isToolProfile(tools["profile"])) throw Error("agent tool profile is invalid");
|
|
76
|
-
selected.profile = tools["profile"];
|
|
77
|
-
}
|
|
78
|
-
if (tools["only"] !== undefined) selected.only = strings(tools["only"], "agent tools.only");
|
|
79
|
-
if (tools["exclude"] !== undefined) selected.exclude = strings(tools["exclude"], "agent tools.exclude");
|
|
80
|
-
for (const key of ["none", "noBuiltin"] as const) {
|
|
81
|
-
if (tools[key] !== undefined) { if (typeof tools[key] !== "boolean") throw Error(`agent tools.${key} must be boolean`); selected[key] = tools[key]; }
|
|
82
|
-
}
|
|
83
|
-
profile.tools = selected;
|
|
84
|
-
}
|
|
85
|
-
if (obj["permissions"] !== undefined) {
|
|
86
|
-
if (!Array.isArray(obj["permissions"])) throw Error("agent permissions must be an array");
|
|
87
|
-
profile.permissions = obj["permissions"].map(parsePermissionRule);
|
|
88
|
-
}
|
|
89
|
-
if (obj["budget"] !== undefined) {
|
|
90
|
-
const budget = record(obj["budget"], "agent budget", ["maxTurns", "timeBudgetMs"]);
|
|
91
|
-
const selected: { maxTurns?: number; timeBudgetMs?: number } = {};
|
|
92
|
-
for (const key of ["maxTurns", "timeBudgetMs"] as const) {
|
|
93
|
-
if (budget[key] !== undefined) {
|
|
94
|
-
if (!Number.isSafeInteger(budget[key]) || Number(budget[key]) < 1) throw Error(`agent budget.${key} must be a positive safe integer`);
|
|
95
|
-
selected[key] = Number(budget[key]);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
profile.budget = selected;
|
|
99
|
-
}
|
|
100
|
-
return profile;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** A malformed document contributes no partial authority. */
|
|
104
|
-
export function parseAgentProfiles(value: unknown): AgentProfileParse {
|
|
105
|
-
try {
|
|
106
|
-
const obj = record(value, "agents document", ["profiles"]);
|
|
107
|
-
if (!Array.isArray(obj["profiles"])) throw Error("agents document profiles must be an array");
|
|
108
|
-
const profiles = obj["profiles"].map(parseAgentProfile);
|
|
109
|
-
if (new Set(profiles.map(profile => profile.name)).size !== profiles.length) throw Error("agents document contains duplicate profile names");
|
|
110
|
-
return { profiles, diagnostics: [] };
|
|
111
|
-
} catch (error) { return { profiles: [], diagnostics: [error instanceof Error ? error.message : "Invalid agents document"] }; }
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Caller supplies low-to-high layers; whole-field replacement stays explainable. */
|
|
115
|
-
export function resolveAgentProfile(name: string, layers: readonly AgentProfileLayer[]): ResolvedAgentProfile | null {
|
|
116
|
-
let profile: AgentProfile | undefined;
|
|
117
|
-
const sources: AgentProfileSource[] = [], provenance: Partial<Record<AgentProfileField, AgentProfileSource>> = {};
|
|
118
|
-
for (const layer of layers) {
|
|
119
|
-
const matching = layer.profiles.find(item => item.name === name);
|
|
120
|
-
if (!matching) continue;
|
|
121
|
-
const source = structuredClone(layer.source);
|
|
122
|
-
sources.push(source); profile = { ...profile, ...structuredClone(matching) };
|
|
123
|
-
for (const field of FIELDS) if (matching[field] !== undefined) provenance[field] = structuredClone(source);
|
|
124
|
-
}
|
|
125
|
-
return profile ? { profile, sources, provenance } : null;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function agentProfileNames(layers: readonly AgentProfileLayer[]): string[] {
|
|
129
|
-
return [...new Set(layers.flatMap(layer => layer.profiles.map(profile => profile.name)))].sort();
|
|
130
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/** Only an entire list of image paths is a drop; ordinary prose and shell syntax remain literal. */
|
|
2
|
-
export function droppedImagePaths(text: string): string[] | null {
|
|
3
|
-
const paths: string[] = [];
|
|
4
|
-
let token = "", quote = "", escaped = false;
|
|
5
|
-
for (const char of text.trim()) {
|
|
6
|
-
if (escaped) { token += char; escaped = false; }
|
|
7
|
-
else if (char === "\\" && quote !== "'") escaped = true;
|
|
8
|
-
else if (quote) { if (char === quote) quote = ""; else token += char; }
|
|
9
|
-
else if (char === "'" || char === '"') quote = char;
|
|
10
|
-
else if (/\s/.test(char)) { if (token) { paths.push(token); token = ""; } }
|
|
11
|
-
else token += char;
|
|
12
|
-
}
|
|
13
|
-
if (escaped || quote) return null;
|
|
14
|
-
if (token) paths.push(token);
|
|
15
|
-
return paths.length > 0 && paths.every(path => !/[\x00-\x1f]/.test(path) && !path.startsWith("@") &&
|
|
16
|
-
/\.(png|jpe?g|gif|webp|bmp)$/i.test(path) && !/[;$|<>`]/.test(path)) ? paths : null;
|
|
17
|
-
}
|
package/src/core/blind-checks.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Blind checks: the independent rung of the evidence ladder (S.1).
|
|
3
|
-
*
|
|
4
|
-
* A check the model can see is a check the model can work toward. Blind
|
|
5
|
-
* checks live outside the workspace, never enter a projection, and run only
|
|
6
|
-
* after the goal ends, against a clone of the final result. Because the log
|
|
7
|
-
* is complete, whether the model ever touched them is a mechanical question
|
|
8
|
-
* over its own tool inputs and read receipts -- not a promise.
|
|
9
|
-
*/
|
|
10
|
-
import type { LogEvent } from "./events.ts";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Assumed, not measured: a hidden test suite of a handful of cases plus their
|
|
14
|
-
* fixtures. Bounded so freezing a blind directory into the session context
|
|
15
|
-
* cannot grow without limit.
|
|
16
|
-
*/
|
|
17
|
-
export const BLIND_CHECK_LIMIT = 8;
|
|
18
|
-
export const BLIND_FILE_LIMIT = 64;
|
|
19
|
-
export const BLIND_BYTES_LIMIT = 8 * 1024 * 1024;
|
|
20
|
-
|
|
21
|
-
export type BlindCheck = { id: string; description: string; command: string };
|
|
22
|
-
export type BlindManifest = { version: 1; checks: BlindCheck[] };
|
|
23
|
-
/**
|
|
24
|
-
* What a session freezes: where the checks live (the real path, plus every
|
|
25
|
-
* spelling of it the user gave, since a symlinked temp directory is a
|
|
26
|
-
* different string), the manifest, and every file's digest.
|
|
27
|
-
*/
|
|
28
|
-
export type BlindChecks = { dir: string; aliases: readonly string[]; manifest: BlindManifest; files: Record<string, string> };
|
|
29
|
-
|
|
30
|
-
const record = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
31
|
-
const label = (value: unknown, max: number): value is string => typeof value === "string" && value.trim().length > 0 && value.length <= max;
|
|
32
|
-
|
|
33
|
-
export function parseBlindManifest(value: unknown): BlindManifest {
|
|
34
|
-
if (!record(value) || value["version"] !== 1 || Object.keys(value).some(key => key !== "version" && key !== "checks")) throw Error("blind.json must be {version: 1, checks: [...]}");
|
|
35
|
-
const checks = value["checks"];
|
|
36
|
-
if (!Array.isArray(checks) || checks.length < 1 || checks.length > BLIND_CHECK_LIMIT) throw Error(`blind.json needs 1..${BLIND_CHECK_LIMIT} checks`);
|
|
37
|
-
const parsed = checks.map((check: unknown, index): BlindCheck => {
|
|
38
|
-
if (!record(check) || Object.keys(check).some(key => !["id", "description", "command"].includes(key))) throw Error(`blind check ${index + 1}: only id, description and command are allowed`);
|
|
39
|
-
const { id, description, command } = check;
|
|
40
|
-
if (!label(id, 80) || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$/.test(id)) throw Error(`blind check ${index + 1}: invalid id`);
|
|
41
|
-
if (!label(description, 300) || !label(command, 500)) throw Error(`blind check ${index + 1}: description and command are required`);
|
|
42
|
-
return { id, description, command };
|
|
43
|
-
});
|
|
44
|
-
if (new Set(parsed.map(check => check.id)).size !== parsed.length) throw Error("blind.json has duplicate check ids");
|
|
45
|
-
return { version: 1, checks: parsed };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function isBlindChecks(value: unknown): value is BlindChecks {
|
|
49
|
-
if (!record(value) || typeof value["dir"] !== "string" || !record(value["files"]) ||
|
|
50
|
-
!Array.isArray(value["aliases"]) || !value["aliases"].every(alias => typeof alias === "string" && alias.length > 0)) return false;
|
|
51
|
-
try { parseBlindManifest(value["manifest"]); } catch { return false; }
|
|
52
|
-
return Object.values(value["files"]).every(sha => typeof sha === "string" && /^[a-f0-9]{64}$/.test(sha));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Whether the model could have seen the blind checks: a tool input naming the
|
|
57
|
-
* blind directory, or a read whose content digest matches a blind file. This
|
|
58
|
-
* covers what the harness records for every tool; output a command produced
|
|
59
|
-
* without naming the path (a recursive search, say) is not matched, and the
|
|
60
|
-
* report says only what was found.
|
|
61
|
-
*/
|
|
62
|
-
export function blindExposure(events: readonly LogEvent[], blind: BlindChecks): string | null {
|
|
63
|
-
const digests = new Set(Object.values(blind.files)), paths = [blind.dir, ...blind.aliases];
|
|
64
|
-
for (const event of events) {
|
|
65
|
-
if (event.t === "assistant") for (const block of event.blocks) {
|
|
66
|
-
const input = block.type === "tool_use" ? JSON.stringify(block.input) : "";
|
|
67
|
-
if (block.type === "tool_use" && paths.some(path => input.includes(path))) return `tool call ${block.call_id} names the blind check directory`;
|
|
68
|
-
}
|
|
69
|
-
if (event.t === "tool_result" && event.effect?.kind === "read" && digests.has(event.effect.sha256)) return `tool result ${event.id} read a blind check file`;
|
|
70
|
-
}
|
|
71
|
-
return null;
|
|
72
|
-
}
|
package/src/core/boundary.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Explicit fixed-segment policy (decisions D1, D4, D5; new default in N.8).
|
|
3
|
-
*
|
|
4
|
-
* Pure. Given a token count and where the loop currently stands, decide whether
|
|
5
|
-
* to close the segment.
|
|
6
|
-
*
|
|
7
|
-
* Three rules, each with a reason:
|
|
8
|
-
*
|
|
9
|
-
* ONLY AT A TURN BOUNDARY (D5). A cut between a tool_use and its tool_result
|
|
10
|
-
* would produce an unpairable request. The gap between trigger and hard limit is
|
|
11
|
-
* the headroom that makes waiting for the boundary safe.
|
|
12
|
-
*
|
|
13
|
-
* THE MODEL MAY ADVANCE A CUT BUT NEVER DEFER ONE (D4). A checkpoint request at
|
|
14
|
-
* a clean semantic boundary is usually better than an arbitrary threshold, so it
|
|
15
|
-
* is honoured -- but the hard limit is checked first and cannot be overridden.
|
|
16
|
-
*
|
|
17
|
-
* ABSOLUTE TOKENS, NOT A PERCENTAGE OF THE WINDOW. Cost and quality both argue
|
|
18
|
-
* for tens of thousands of tokens, and neither argument scales with whatever
|
|
19
|
-
* window the model happens to advertise.
|
|
20
|
-
*
|
|
21
|
-
* On the defaults below: the optimum from the cost model is L - H = 5*sqrt(H*d),
|
|
22
|
-
* where H is handoff size and d is per-turn growth. H was originally *assumed* to
|
|
23
|
-
* be ~10k, which put the optimum near 37k and the thresholds at 60k/80k.
|
|
24
|
-
* The original single-segment measurement (prev=null) yielded 366 to 3201
|
|
25
|
-
* tokens, 1.6-7.6% of L. It did not measure accumulation across boundaries.
|
|
26
|
-
* D20 adds bounded carried state and continuous-history measurements; full
|
|
27
|
-
* derivation state still grows with distinct facts. The old local cost optimum
|
|
28
|
-
* is a workload diagnostic, not proof of a globally optimal threshold.
|
|
29
|
-
*
|
|
30
|
-
* The defaults are 30k/40k rather than 15k/25k because the cost model prices only
|
|
31
|
-
* bytes. Two things it does not price argue for longer segments: the re-read a new
|
|
32
|
-
* segment usually needs before it can act (the digest check on file_anchors), and
|
|
33
|
-
* the fact that too-short segments break stateful work -- the documented failure
|
|
34
|
-
* mode of fresh-context loops is that extended debugging loses context that was
|
|
35
|
-
* load-bearing. 30k/40k is within 15% of the cost optimum for every re-read cost
|
|
36
|
-
* from 0 to 24k tokens per boundary, which 60k/80k is not.
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
import type { CutReason } from "./events.ts";
|
|
40
|
-
|
|
41
|
-
export interface BoundaryConfig {
|
|
42
|
-
/** Start looking for a turn boundary at or above this count. */
|
|
43
|
-
triggerTokens: number;
|
|
44
|
-
/** Cut at the next turn boundary regardless. Must exceed triggerTokens. */
|
|
45
|
-
hardTokens: number;
|
|
46
|
-
/**
|
|
47
|
-
* How much of the context a node must have produced itself before cutting is
|
|
48
|
-
* worth it.
|
|
49
|
-
*
|
|
50
|
-
* Every node starts with tokens it did not create: the frozen prefix plus,
|
|
51
|
-
* after the first cut, the handoff. Without this floor, a trigger set below
|
|
52
|
-
* that inherited weight makes the first turn of every node cut -- a real run
|
|
53
|
-
* produced 36 nodes for a five-file task, spending its budget on handoffs.
|
|
54
|
-
*
|
|
55
|
-
* The value follows the crossover: a handoff only pays for itself once the
|
|
56
|
-
* node's own history exceeds it. Measured handoffs ran 366-3201 tokens, so
|
|
57
|
-
* the default sits above the top of that range.
|
|
58
|
-
*
|
|
59
|
-
* This is also the floor for a model-requested checkpoint. A separate knob
|
|
60
|
-
* for that case would be the same idea written twice: a checkpoint on a node
|
|
61
|
-
* that has produced nothing costs a handoff write and discards the node's
|
|
62
|
-
* cache for no gain, which is exactly what this floor exists to prevent.
|
|
63
|
-
*/
|
|
64
|
-
minNodeWorkTokens: number;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const DEFAULT_BOUNDARY: BoundaryConfig = {
|
|
68
|
-
triggerTokens: 30_000,
|
|
69
|
-
hardTokens: 40_000,
|
|
70
|
-
minNodeWorkTokens: 5_000,
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export interface BoundaryInput {
|
|
74
|
-
tokens: number;
|
|
75
|
-
/**
|
|
76
|
-
* Tokens the node did not produce: the frozen prefix, plus the rendered
|
|
77
|
-
* handoff for any node after the first. Cutting cannot reduce these, so they
|
|
78
|
-
* do not count toward the node having earned a boundary.
|
|
79
|
-
*/
|
|
80
|
-
inheritedTokens?: number;
|
|
81
|
-
/** True only between a completed assistant/tool exchange and the next request. */
|
|
82
|
-
atTurnBoundary: boolean;
|
|
83
|
-
/** Set when the model called the checkpoint tool during this turn. */
|
|
84
|
-
checkpointRequested?: boolean;
|
|
85
|
-
config?: Partial<BoundaryConfig>;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export type BoundaryDecision =
|
|
89
|
-
| { cut: true; reason: CutReason }
|
|
90
|
-
| {
|
|
91
|
-
cut: false;
|
|
92
|
-
reason:
|
|
93
|
-
| "below_trigger"
|
|
94
|
-
| "awaiting_turn_boundary"
|
|
95
|
-
| "below_min_node_work"
|
|
96
|
-
/**
|
|
97
|
-
* The inherited floor alone is at or past the hard limit, so a cut
|
|
98
|
-
* would hand the next node a context it cannot work in either. The
|
|
99
|
-
* thresholds need widening or the frozen prefix shrinking; cutting
|
|
100
|
-
* would only thrash.
|
|
101
|
-
*/
|
|
102
|
-
| "inherited_floor_exceeds_limit";
|
|
103
|
-
/**
|
|
104
|
-
* True when the count is already past the hard limit and a cut is not yet
|
|
105
|
-
* possible -- i.e. a single turn consumed more than the headroom. The caller
|
|
106
|
-
* should surface this: the thresholds need retuning, or one tool returned an
|
|
107
|
-
* output large enough to need its own handling.
|
|
108
|
-
*/
|
|
109
|
-
overrun: boolean;
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
export function shouldCut(input: BoundaryInput): BoundaryDecision {
|
|
113
|
-
const cfg = { ...DEFAULT_BOUNDARY, ...input.config };
|
|
114
|
-
if (cfg.hardTokens <= cfg.triggerTokens) {
|
|
115
|
-
throw new RangeError(
|
|
116
|
-
`boundary: hardTokens (${cfg.hardTokens}) must exceed triggerTokens (${cfg.triggerTokens}); ` +
|
|
117
|
-
"the difference is the headroom needed to reach a turn boundary",
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
// The mirror of thrashing, and just as silent: a work floor at or above the
|
|
121
|
-
// trigger means the trigger can never be acted on, so the session grows
|
|
122
|
-
// without bound and nothing says why.
|
|
123
|
-
if (cfg.minNodeWorkTokens >= cfg.triggerTokens) {
|
|
124
|
-
throw new RangeError(
|
|
125
|
-
`boundary: minNodeWorkTokens (${cfg.minNodeWorkTokens}) must be below triggerTokens ` +
|
|
126
|
-
`(${cfg.triggerTokens}); otherwise no node can ever reach a boundary`,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const { tokens, atTurnBoundary, checkpointRequested = false, inheritedTokens = 0 } = input;
|
|
131
|
-
const ownWork = Math.max(0, tokens - inheritedTokens);
|
|
132
|
-
|
|
133
|
-
if (!atTurnBoundary) {
|
|
134
|
-
return {
|
|
135
|
-
cut: false,
|
|
136
|
-
reason: "awaiting_turn_boundary",
|
|
137
|
-
overrun: tokens >= cfg.hardTokens,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Is there any reason to consider a boundary at all?
|
|
142
|
-
const wouldCut =
|
|
143
|
-
tokens >= cfg.hardTokens || checkpointRequested || tokens >= cfg.triggerTokens;
|
|
144
|
-
if (!wouldCut) return { cut: false, reason: "below_trigger", overrun: false };
|
|
145
|
-
|
|
146
|
-
// There is -- but a node that has produced almost nothing cannot be helped
|
|
147
|
-
// by one: the next node inherits the same floor and lands in the same place.
|
|
148
|
-
if (ownWork < cfg.minNodeWorkTokens) {
|
|
149
|
-
if (inheritedTokens >= cfg.hardTokens) {
|
|
150
|
-
return { cut: false, reason: "inherited_floor_exceeds_limit", overrun: true };
|
|
151
|
-
}
|
|
152
|
-
return { cut: false, reason: "below_min_node_work", overrun: tokens >= cfg.hardTokens };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Worth cutting. The hard limit is checked first so the model cannot defer
|
|
156
|
-
// it by asking for a checkpoint.
|
|
157
|
-
if (tokens >= cfg.hardTokens) return { cut: true, reason: "hard_limit" };
|
|
158
|
-
if (checkpointRequested) return { cut: true, reason: "model_checkpoint" };
|
|
159
|
-
return { cut: true, reason: "trigger" };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Stop-and-ask rule (D9 / M.4). New read observations or fresh successful checks
|
|
164
|
-
* reset the counter. Exact repetitions, annotations alone and gate regressions
|
|
165
|
-
* do not. This detects inactivity; it cannot judge relevance to the user's goal.
|
|
166
|
-
*/
|
|
167
|
-
export const DEFAULT_MAX_SEGMENTS_WITHOUT_PROGRESS = 3;
|
|
168
|
-
|
|
169
|
-
export function shouldStopAndAsk(
|
|
170
|
-
segmentsWithoutProgress: number,
|
|
171
|
-
max = DEFAULT_MAX_SEGMENTS_WITHOUT_PROGRESS,
|
|
172
|
-
): boolean {
|
|
173
|
-
return segmentsWithoutProgress >= max;
|
|
174
|
-
}
|