@visual-z/xeno 0.8.3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -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 -2066
- package/src/apps/startup-input.ts +0 -135
- package/src/apps/startup.ts +0 -1677
- package/src/apps/ui/app.ts +0 -1713
- 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 -3001
- 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/canvas.ts +0 -96
- package/src/tui/ui/colors.ts +0 -70
- package/src/tui/ui/composer.ts +0 -200
- 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/screen.ts +0 -548
- 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/history.ts
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
/** D20: bounded carried state, with deterministic pages rebuilt from the log. */
|
|
2
|
-
import { emitHandoff, type Handoff, type UserTurnRecord } from "./handoff.ts";
|
|
3
|
-
import { estimateTokens } from "./tokens.ts";
|
|
4
|
-
import { recoveryWorkingSet, recoveryWorkingSetV3 } from "./working-set.ts";
|
|
5
|
-
import { recoveryInstructions } from "./prompt-policy.ts";
|
|
6
|
-
import type { RecoveryNodes } from "./recovery-sources.ts";
|
|
7
|
-
|
|
8
|
-
// A carried handoff must leave room for another segment's work. This budget is
|
|
9
|
-
// the existing 5k work floor, not a new segment threshold or an accuracy claim.
|
|
10
|
-
export const DEFAULT_HANDOFF_TOKENS = 5_000;
|
|
11
|
-
export const HISTORY_SECTIONS = [
|
|
12
|
-
"user_turns", "state.completed", "state.unverified_claims", "state.in_flight",
|
|
13
|
-
"state.blocked", "decisions", "pitfalls", "file_anchors", "verification",
|
|
14
|
-
"branches", "open_questions", "progress_evidence",
|
|
15
|
-
"state.work_items",
|
|
16
|
-
"evidence_links",
|
|
17
|
-
"acceptance.criteria",
|
|
18
|
-
"acceptance.targets",
|
|
19
|
-
"loaded_skills",
|
|
20
|
-
"processes",
|
|
21
|
-
] as const;
|
|
22
|
-
export type HistorySection = (typeof HISTORY_SECTIONS)[number];
|
|
23
|
-
|
|
24
|
-
export function historyItems(h: Handoff, section: HistorySection): readonly unknown[] {
|
|
25
|
-
switch (section) {
|
|
26
|
-
case "state.completed": return h.state.completed;
|
|
27
|
-
case "state.unverified_claims": return h.state.unverified_claims;
|
|
28
|
-
case "state.in_flight": return h.state.in_flight;
|
|
29
|
-
case "state.blocked": return h.state.blocked;
|
|
30
|
-
case "state.work_items": return h.state.work_items ?? [];
|
|
31
|
-
case "evidence_links": return h.evidence_links ?? [];
|
|
32
|
-
case "acceptance.criteria": return h.acceptance?.criteria ?? [];
|
|
33
|
-
case "acceptance.targets": return h.acceptance?.targets ?? [];
|
|
34
|
-
case "loaded_skills": return h.loaded_skills ?? [];
|
|
35
|
-
case "processes": return h.processes ?? [];
|
|
36
|
-
case "progress_evidence": return h.progress_evidence ?? [];
|
|
37
|
-
default: return h[section];
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function suffixes(h: Handoff, offsets: Record<HistorySection, number>): Handoff {
|
|
42
|
-
return {
|
|
43
|
-
...h,
|
|
44
|
-
user_turns: h.user_turns.slice(offsets.user_turns),
|
|
45
|
-
state: {
|
|
46
|
-
...h.state,
|
|
47
|
-
completed: h.state.completed.slice(offsets["state.completed"]),
|
|
48
|
-
unverified_claims: h.state.unverified_claims.slice(offsets["state.unverified_claims"]),
|
|
49
|
-
in_flight: h.state.in_flight.slice(offsets["state.in_flight"]),
|
|
50
|
-
blocked: h.state.blocked.slice(offsets["state.blocked"]),
|
|
51
|
-
...(h.state.work_items !== undefined ? { work_items: h.state.work_items.slice(offsets["state.work_items"] ?? 0) } : {}),
|
|
52
|
-
},
|
|
53
|
-
decisions: h.decisions.slice(offsets.decisions),
|
|
54
|
-
pitfalls: h.pitfalls.slice(offsets.pitfalls),
|
|
55
|
-
file_anchors: h.file_anchors.slice(offsets.file_anchors),
|
|
56
|
-
verification: h.verification.slice(offsets.verification),
|
|
57
|
-
branches: h.branches.slice(offsets.branches),
|
|
58
|
-
open_questions: h.open_questions.slice(offsets.open_questions),
|
|
59
|
-
progress_evidence: (h.progress_evidence ?? []).slice(offsets.progress_evidence),
|
|
60
|
-
...(h.evidence_links !== undefined ? { evidence_links: h.evidence_links.slice(offsets.evidence_links ?? 0) } : {}),
|
|
61
|
-
...(h.acceptance ? { acceptance: { ...h.acceptance, criteria: h.acceptance.criteria.slice(offsets["acceptance.criteria"] ?? 0),
|
|
62
|
-
...(h.acceptance.targets ? { targets: h.acceptance.targets.slice(offsets["acceptance.targets"] ?? 0) } : {}) } } : {}),
|
|
63
|
-
...(h.loaded_skills ? { loaded_skills: h.loaded_skills.slice(offsets.loaded_skills ?? 0) } : {}),
|
|
64
|
-
...(h.processes ? { processes: h.processes.slice(offsets.processes ?? 0) } : {}),
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Sections a handoff actually has; an absent new section must not change legacy projections or frozen replay. */
|
|
69
|
-
function pageableSections(full: Handoff): HistorySection[] {
|
|
70
|
-
return HISTORY_SECTIONS.filter(section => (section !== "state.work_items" || full.state.work_items !== undefined) &&
|
|
71
|
-
(section !== "evidence_links" || full.evidence_links !== undefined) &&
|
|
72
|
-
(section !== "acceptance.criteria" || full.acceptance !== undefined) &&
|
|
73
|
-
(section !== "acceptance.targets" || full.acceptance?.targets !== undefined) &&
|
|
74
|
-
(section !== "loaded_skills" || full.loaded_skills !== undefined) &&
|
|
75
|
-
(section !== "processes" || full.processes !== undefined));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** The v2 shape carrying `items.length - offset` newest items of every pageable section. */
|
|
79
|
-
function carriedAt(full: Handoff, sections: readonly HistorySection[], offsets: Record<HistorySection, number>): Handoff {
|
|
80
|
-
return {
|
|
81
|
-
...suffixes(full, offsets),
|
|
82
|
-
handoff_version: 2,
|
|
83
|
-
history: {
|
|
84
|
-
source: full.predecessor!,
|
|
85
|
-
sections: Object.fromEntries(sections.map((s) => [s, {
|
|
86
|
-
total: historyItems(full, s).length, offset: offsets[s],
|
|
87
|
-
}])),
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const handoffSize = (h: Handoff): number => estimateTokens.count([], emitHandoff(h));
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* `priority` sections grow to completion, newest first, before any other
|
|
96
|
-
* section gets an item; the rest then share what is left round-robin. The
|
|
97
|
-
* default (none) is the original D20 order, which recorded cuts replay.
|
|
98
|
-
*/
|
|
99
|
-
export function boundHandoff(full: Handoff, budget = DEFAULT_HANDOFF_TOKENS, priority: readonly HistorySection[] = []): Handoff {
|
|
100
|
-
if (!Number.isFinite(budget) || budget <= 0) throw new RangeError("handoff budget must be positive");
|
|
101
|
-
const size = handoffSize;
|
|
102
|
-
if (size(full) <= budget) return full;
|
|
103
|
-
if (full.predecessor === null) throw new Error("history requires an immutable source");
|
|
104
|
-
const sections = pageableSections(full);
|
|
105
|
-
const offsets = Object.fromEntries(sections.map((s) => [s, historyItems(full, s).length])) as Record<HistorySection, number>;
|
|
106
|
-
const build = (): Handoff => carriedAt(full, sections, offsets);
|
|
107
|
-
let carried = build();
|
|
108
|
-
// The objective stays verbatim (D2). An oversized objective is an explicit
|
|
109
|
-
// input problem; repeatedly cutting it would create an infinite cut loop.
|
|
110
|
-
if (size(carried) > budget) throw new RangeError("handoff scalar fields exceed budget; shorten the objective or frozen input");
|
|
111
|
-
const grow = (section: HistorySection): boolean => {
|
|
112
|
-
if (offsets[section] === 0) return false;
|
|
113
|
-
offsets[section]--;
|
|
114
|
-
const candidate = build();
|
|
115
|
-
if (size(candidate) <= budget) { carried = candidate; return true; }
|
|
116
|
-
offsets[section]++;
|
|
117
|
-
return false;
|
|
118
|
-
};
|
|
119
|
-
for (const section of priority) if (sections.includes(section)) while (grow(section)) { /* newest first until full or out of room */ }
|
|
120
|
-
const active = new Set<HistorySection>(sections.filter(section => !priority.includes(section)));
|
|
121
|
-
// Round-robin suffix growth prevents one long section starving every other
|
|
122
|
-
// category. Each retained range is contiguous and independently pageable.
|
|
123
|
-
while (active.size > 0) {
|
|
124
|
-
for (const section of active) if (!grow(section)) active.delete(section);
|
|
125
|
-
}
|
|
126
|
-
return carried;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function historyPage(full: Handoff, section: HistorySection, offset: number, limit: number): {
|
|
130
|
-
total: number; offset: number; next: number | null; items: readonly unknown[];
|
|
131
|
-
} {
|
|
132
|
-
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1) {
|
|
133
|
-
throw new RangeError("history offset and limit must be nonnegative/positive integers");
|
|
134
|
-
}
|
|
135
|
-
const items = historyItems(full, section);
|
|
136
|
-
const end = Math.min(items.length, offset + limit);
|
|
137
|
-
return { total: items.length, offset, next: end < items.length ? end : null, items: items.slice(offset, end) };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** R9 uses the same budget; originals are supplied by the ancestry owner. */
|
|
141
|
-
export function boundRecoveryHandoff(full: Handoff, nodes: RecoveryNodes, budget = DEFAULT_HANDOFF_TOKENS, tools?: readonly string[]): Handoff {
|
|
142
|
-
const legacy = boundHandoff(full, budget);
|
|
143
|
-
const view = recoveryWorkingSet(full, nodes, budget * .4, tools);
|
|
144
|
-
if (!view) return legacy;
|
|
145
|
-
let carried: Handoff;
|
|
146
|
-
try { carried = boundHandoff({ ...full, working_set: view }, budget); }
|
|
147
|
-
catch (error) { if (error instanceof RangeError) return legacy; throw error; }
|
|
148
|
-
// Whether the user turns survived bounding is known only now. The inline
|
|
149
|
-
// wording is shorter, so swapping it in cannot push the handoff over budget.
|
|
150
|
-
const inline = tools?.includes("history") && carried.user_turns_truncated === undefined && (carried.history?.sections["user_turns"]?.offset ?? 0) === 0;
|
|
151
|
-
return inline && carried.working_set ? { ...carried, working_set: { ...carried.working_set, recovery: recoveryInstructions(tools!, true) } } : carried;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* D8: join carried turns to their original user events to name attached images
|
|
156
|
-
* by digest. Turns and events are both the active ancestry in log order; any
|
|
157
|
-
* disagreement leaves the turns undecorated rather than guessing a pairing.
|
|
158
|
-
*/
|
|
159
|
-
export function userTurnImages(turns: readonly UserTurnRecord[], nodes: RecoveryNodes): readonly UserTurnRecord[] {
|
|
160
|
-
const users = nodes.flatMap(part => part.events.flatMap(event => event.t === "user" ? [event] : []));
|
|
161
|
-
if (users.length !== turns.length || users.some((event, i) => event.at !== turns[i]!.at || event.text !== turns[i]!.text)) return turns;
|
|
162
|
-
return turns.map((turn, i) => {
|
|
163
|
-
const event = users[i]!;
|
|
164
|
-
return event.images?.length ? { ...turn, event: event.id,
|
|
165
|
-
images: event.images.map(({ sha256, name, width, height }) => ({ sha256, name, width, height })) } : turn;
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* v3 carries one copy of each work item's prose. `state.in_flight` and
|
|
171
|
-
* `state.blocked` are compatible projections of identified work (O.2), so a
|
|
172
|
-
* leading run of items the carried `state.work_items` or working set already
|
|
173
|
-
* hold moves into the existing page range, which `validateHandoff` accepts for
|
|
174
|
-
* continuation; D20 arrays stay contiguous suffixes. A working-set entry whose
|
|
175
|
-
* prose the carried `state.work_items` holds keeps only its id and status, since
|
|
176
|
-
* `state.work_items` is the canonical identified work. Every change only
|
|
177
|
-
* removes bytes, so the budget still holds.
|
|
178
|
-
*/
|
|
179
|
-
function oneCopyOfWork(h: Handoff): Handoff {
|
|
180
|
-
const items = new Map((h.state.work_items ?? []).map(item => [item.id, item]));
|
|
181
|
-
const viewed = new Map((h.working_set?.work ?? []).map(entry => [entry.id, entry]));
|
|
182
|
-
const history = h.history && { ...h.history, sections: { ...h.history.sections } };
|
|
183
|
-
type Prose = { status: string; what?: string; next_action?: string; reason?: string };
|
|
184
|
-
const holders = (id: string | undefined): Prose[] => {
|
|
185
|
-
const found: Prose[] = [], item = id === undefined ? undefined : items.get(id), entry = id === undefined ? undefined : viewed.get(id);
|
|
186
|
-
if (item) found.push(item);
|
|
187
|
-
if (entry) found.push(entry);
|
|
188
|
-
return found;
|
|
189
|
-
};
|
|
190
|
-
const page = <T extends { work_id?: string }>(section: "state.in_flight" | "state.blocked", list: readonly T[], same: (item: T, holder: Prose) => boolean): readonly T[] => {
|
|
191
|
-
const range = history?.sections[section];
|
|
192
|
-
let skip = 0;
|
|
193
|
-
while (range && skip < list.length && holders(list[skip]!.work_id).some(holder => same(list[skip]!, holder))) skip++;
|
|
194
|
-
if (skip === 0) return list;
|
|
195
|
-
history!.sections[section] = { total: range!.total, offset: range!.offset + skip };
|
|
196
|
-
return list.slice(skip);
|
|
197
|
-
};
|
|
198
|
-
const in_flight = page("state.in_flight", h.state.in_flight, (item, holder) => holder.status === "active" && holder.what === item.what && holder.next_action === item.next_action);
|
|
199
|
-
const blocked = page("state.blocked", h.state.blocked, (item, holder) => holder.status === "blocked" && holder.what === item.what && holder.reason === item.reason);
|
|
200
|
-
const view = h.working_set && { ...h.working_set, work: h.working_set.work.map(entry => {
|
|
201
|
-
const item = items.get(entry.id);
|
|
202
|
-
if (!item || item.what !== entry.what || item.next_action !== entry.next_action || item.reason !== entry.reason) return entry;
|
|
203
|
-
const { what: _what, next_action: _next, reason: _reason, ...rest } = entry;
|
|
204
|
-
return rest;
|
|
205
|
-
}) };
|
|
206
|
-
return { ...h, ...(view ? { working_set: view } : {}), ...(history ? { history } : {}), state: { ...h.state, in_flight, blocked } };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const USERS_FIRST: readonly HistorySection[] = ["user_turns"];
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Working set v3. User turns are the smallest, highest-value carried content
|
|
213
|
-
* (Codex compaction likewise keeps user messages newest first, verbatim, under
|
|
214
|
-
* its own cap), so they are reserved before the working set and before every
|
|
215
|
-
* other page; only turns that alone exceed the budget are cut, newest kept.
|
|
216
|
-
* While every turn is inline, user references would only point at text already
|
|
217
|
-
* present, so the view leaves them out. The view keeps v2's 40% allocation
|
|
218
|
-
* assumption, measured inside the handoff it is carried in.
|
|
219
|
-
*/
|
|
220
|
-
export function boundRecoveryHandoffV3(full: Handoff, nodes: RecoveryNodes, budget = DEFAULT_HANDOFF_TOKENS, tools?: readonly string[]): Handoff {
|
|
221
|
-
if (!Number.isFinite(budget) || budget <= 0) throw new RangeError("handoff budget must be positive");
|
|
222
|
-
const base: Handoff = { ...full, user_turns: userTurnImages(full.user_turns, nodes) };
|
|
223
|
-
const plain = (): Handoff => oneCopyOfWork(boundHandoff(base, budget, USERS_FIRST));
|
|
224
|
-
if (base.predecessor === null) return plain();
|
|
225
|
-
const sections = pageableSections(base);
|
|
226
|
-
const empty = Object.fromEntries(sections.map((s) => [s, historyItems(base, s).length])) as Record<HistorySection, number>;
|
|
227
|
-
const withUsers = carriedAt(base, sections, { ...empty, user_turns: 0 });
|
|
228
|
-
const usersInline = base.user_turns_truncated === undefined && handoffSize(withUsers) <= budget;
|
|
229
|
-
const skeleton = usersInline ? withUsers : carriedAt(base, sections, empty);
|
|
230
|
-
const cap = Math.min(budget, handoffSize(skeleton) + budget * .4);
|
|
231
|
-
const recovery = recoveryInstructions(tools ?? ["history"], usersInline, base.user_turns.some(turn => turn.images !== undefined));
|
|
232
|
-
const view = recoveryWorkingSetV3(base, nodes, { users: !usersInline, recovery,
|
|
233
|
-
fits: candidate => handoffSize({ ...skeleton, working_set: candidate }) <= cap });
|
|
234
|
-
if (!view) return plain();
|
|
235
|
-
try { return oneCopyOfWork(boundHandoff({ ...base, working_set: view }, budget, USERS_FIRST)); }
|
|
236
|
-
catch (error) { if (error instanceof RangeError) return plain(); throw error; }
|
|
237
|
-
}
|
package/src/core/image-limits.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/** Admission, model detail and preview memory are separate budgets (D8). */
|
|
2
|
-
export const MAX_IMAGE_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
3
|
-
/** Matches the native decoder's area cap; inspect headers before WASM allocates. */
|
|
4
|
-
export const MAX_IMAGE_SOURCE_PIXELS = 25_000_000;
|
|
5
|
-
/** More readable screenshots than 2000px, with at most 32 MiB RGBA per preview. */
|
|
6
|
-
export const MAX_MODEL_IMAGE_SIDE = 4096;
|
|
7
|
-
export const MAX_MODEL_IMAGE_PIXELS = 8 * 1024 * 1024;
|
|
8
|
-
export interface ImageDimensions { readonly width: number; readonly height: number }
|
|
9
|
-
|
|
10
|
-
/** Header-only probe. Decoders still validate the complete image afterward. */
|
|
11
|
-
export function imageDimensions(bytes: Uint8Array, mime: string): ImageDimensions | undefined {
|
|
12
|
-
const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
13
|
-
const result = (width: number, height: number): ImageDimensions | undefined =>
|
|
14
|
-
Number.isSafeInteger(width) && Number.isSafeInteger(height) && width > 0 && height > 0 ? { width, height } : undefined;
|
|
15
|
-
if (mime === "image/png" && bytes.length >= 24) return result(v.getUint32(16), v.getUint32(20));
|
|
16
|
-
if (mime === "image/gif" && bytes.length >= 10) return result(v.getUint16(6, true), v.getUint16(8, true));
|
|
17
|
-
if (mime === "image/bmp" && bytes.length >= 26) {
|
|
18
|
-
const header = v.getUint32(14, true);
|
|
19
|
-
if (header === 12) return result(v.getUint16(18, true), v.getUint16(20, true));
|
|
20
|
-
if (header >= 40) return result(v.getInt32(18, true), Math.abs(v.getInt32(22, true)));
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
|
-
if (mime === "image/jpeg") {
|
|
24
|
-
let offset = 2;
|
|
25
|
-
while (offset + 3 < bytes.length) {
|
|
26
|
-
if (bytes[offset++] !== 255) return undefined;
|
|
27
|
-
while (bytes[offset] === 255) offset++;
|
|
28
|
-
const marker = bytes[offset++];
|
|
29
|
-
if (marker === undefined || marker === 0xda || marker === 0xd9) return undefined;
|
|
30
|
-
if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd8)) continue;
|
|
31
|
-
if (offset + 2 > bytes.length) return undefined;
|
|
32
|
-
const size = v.getUint16(offset);
|
|
33
|
-
if (size < 2 || offset + size > bytes.length) return undefined;
|
|
34
|
-
if (marker >= 0xc0 && marker <= 0xcf && ![0xc4, 0xc8, 0xcc].includes(marker)) {
|
|
35
|
-
return size >= 8 ? result(v.getUint16(offset + 5), v.getUint16(offset + 3)) : undefined;
|
|
36
|
-
}
|
|
37
|
-
offset += size;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (mime === "image/webp") {
|
|
41
|
-
for (let offset = 12; offset + 8 <= bytes.length;) {
|
|
42
|
-
const size = v.getUint32(offset + 4, true), start = offset + 8;
|
|
43
|
-
if (start + size > bytes.length) return undefined;
|
|
44
|
-
const kind = String.fromCharCode(...bytes.subarray(offset, offset + 4));
|
|
45
|
-
const u24 = (at: number) => v.getUint8(at) | v.getUint8(at + 1) << 8 | v.getUint8(at + 2) << 16;
|
|
46
|
-
if (kind === "VP8X" && size >= 10) return result(u24(start + 4) + 1, u24(start + 7) + 1);
|
|
47
|
-
if (kind === "VP8L" && size >= 5 && bytes[start] === 0x2f) {
|
|
48
|
-
const packed = v.getUint32(start + 1, true);
|
|
49
|
-
return result((packed & 0x3fff) + 1, ((packed >>> 14) & 0x3fff) + 1);
|
|
50
|
-
}
|
|
51
|
-
if (kind === "VP8 " && size >= 10 && bytes[start + 3] === 0x9d && bytes[start + 4] === 1 && bytes[start + 5] === 0x2a)
|
|
52
|
-
return result(v.getUint16(start + 6, true) & 0x3fff, v.getUint16(start + 8, true) & 0x3fff);
|
|
53
|
-
offset = start + size + size % 2;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Floors keep the area bound exact; small inputs are never upscaled. */
|
|
60
|
-
export function modelImageDimensions(image: ImageDimensions): ImageDimensions {
|
|
61
|
-
const scale = Math.min(1, MAX_MODEL_IMAGE_SIDE / image.width, MAX_MODEL_IMAGE_SIDE / image.height,
|
|
62
|
-
Math.sqrt(MAX_MODEL_IMAGE_PIXELS / image.width / image.height));
|
|
63
|
-
return { width: Math.max(1, Math.floor(image.width * scale)), height: Math.max(1, Math.floor(image.height * scale)) };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Pi rotates EXIF before resizing and rounds the short side; symmetric bounds preserve both orientations. */
|
|
67
|
-
export function modelImageSide(image: ImageDimensions): number {
|
|
68
|
-
const long = Math.max(image.width, image.height), short = Math.min(image.width, image.height);
|
|
69
|
-
let side = Math.max(...Object.values(modelImageDimensions(image)));
|
|
70
|
-
while (side * Math.max(1, Math.round(short * side / long)) > MAX_MODEL_IMAGE_PIXELS) side--;
|
|
71
|
-
return side;
|
|
72
|
-
}
|
package/src/core/images.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/** Immutable attachment metadata; D8 stores bytes once and projects references without I/O. */
|
|
2
|
-
export interface ImageRef {
|
|
3
|
-
readonly sha256: string;
|
|
4
|
-
readonly mimeType: "image/png" | "image/jpeg" | "image/gif" | "image/webp";
|
|
5
|
-
readonly width: number;
|
|
6
|
-
readonly height: number;
|
|
7
|
-
readonly bytes: number;
|
|
8
|
-
readonly name: string;
|
|
9
|
-
/** Submission-time policy; enabling images later cannot rewrite an earlier request prefix. */
|
|
10
|
-
readonly blocked?: true;
|
|
11
|
-
readonly original?: { readonly sha256: string; readonly width: number; readonly height: number };
|
|
12
|
-
}
|
|
13
|
-
export interface ImageContent { readonly type: "image"; readonly image: ImageRef; readonly data?: string }
|
|
14
|
-
export type UserInput = string | { readonly text: string; readonly images?: readonly ImageRef[] };
|
|
15
|
-
export function validImageRef(value: unknown): value is ImageRef {
|
|
16
|
-
if (!value || typeof value !== "object") return false;
|
|
17
|
-
const v = value as Record<string, unknown>;
|
|
18
|
-
const digest = (s: unknown) => typeof s === "string" && /^[a-f0-9]{64}$/.test(s);
|
|
19
|
-
const positive = (n: unknown) => typeof n === "number" && Number.isSafeInteger(n) && n > 0;
|
|
20
|
-
if (v["blocked"] !== undefined && v["blocked"] !== true) return false;
|
|
21
|
-
if (!digest(v["sha256"]) || !positive(v["width"]) || !positive(v["height"]) || !positive(v["bytes"]) ||
|
|
22
|
-
typeof v["name"] !== "string" || !["image/png", "image/jpeg", "image/gif", "image/webp"].includes(String(v["mimeType"]))) return false;
|
|
23
|
-
if (v["original"] === undefined) return true;
|
|
24
|
-
if (!v["original"] || typeof v["original"] !== "object") return false;
|
|
25
|
-
const o = v["original"] as Record<string, unknown>;
|
|
26
|
-
return digest(o["sha256"]) && positive(o["width"]) && positive(o["height"]);
|
|
27
|
-
}
|
|
28
|
-
export function submissionImages(images: readonly ImageRef[], blocked: boolean): ImageRef[] {
|
|
29
|
-
return images.map(image => {
|
|
30
|
-
if (!validImageRef(image)) throw new Error("Invalid image reference");
|
|
31
|
-
const { sha256, mimeType, width, height, bytes, name, original } = image;
|
|
32
|
-
return { sha256, mimeType, width, height, bytes, name, ...(original ? { original } : {}), ...(blocked ? { blocked: true as const } : {}) };
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
export function userInputData(input: UserInput, blocked?: boolean): { text: string; images?: readonly ImageRef[] } {
|
|
36
|
-
return typeof input === "string" ? { text: input } : { text: input.text,
|
|
37
|
-
...(input.images?.length ? { images: blocked === undefined ? input.images : submissionImages(input.images, blocked) } : {}) };
|
|
38
|
-
}
|
|
39
|
-
export function imageDescription(image: ImageRef): string {
|
|
40
|
-
const original = image.original;
|
|
41
|
-
return `[Image: ${image.name}, ${image.width}x${image.height}${original ? `; original ${original.width}x${original.height}, coordinate scale ${(original.width / image.width).toFixed(2)}` : ""}]`;
|
|
42
|
-
}
|
|
43
|
-
/** Assumption, not provider billing: 32px patches plus framing; anchors replace this after the response. */
|
|
44
|
-
export function estimateImageTokens(image: Pick<ImageRef, "width" | "height">): number {
|
|
45
|
-
return 256 + Math.ceil(image.width / 32) * Math.ceil(image.height / 32) * 2;
|
|
46
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/** Q.13: observed connectivity is a cache, never authorization or replay evidence. */
|
|
2
|
-
export type IntegrationFailureCode = "offline" | "rate_limited" | "auth_required" | "protocol_error" | "timeout" | "unknown";
|
|
3
|
-
export type IntegrationTime = { at: number; tick: number };
|
|
4
|
-
export type IntegrationHealth = {
|
|
5
|
-
state: "not_checked" | "ready" | "backoff" | "auth_required" | "protocol_error";
|
|
6
|
-
observed: IntegrationTime | null;
|
|
7
|
-
failures: number;
|
|
8
|
-
retryAt: number | null;
|
|
9
|
-
error: IntegrationFailureCode | null;
|
|
10
|
-
};
|
|
11
|
-
// Assumed bounds, not measured optima: avoid tight failure loops and unbounded server delays.
|
|
12
|
-
export const INTEGRATION_BACKOFF_MS = 1_000;
|
|
13
|
-
export const MAX_INTEGRATION_BACKOFF_MS = 60_000;
|
|
14
|
-
export const INTEGRATION_HEALTH_TTL_MS = 30_000;
|
|
15
|
-
|
|
16
|
-
export function initialIntegrationHealth(): IntegrationHealth {
|
|
17
|
-
return { state: "not_checked", observed: null, failures: 0, retryAt: null, error: null };
|
|
18
|
-
}
|
|
19
|
-
export function readyIntegrationHealth(now: IntegrationTime): IntegrationHealth {
|
|
20
|
-
return { ...initialIntegrationHealth(), state: "ready", observed: { ...now } };
|
|
21
|
-
}
|
|
22
|
-
export function failedIntegrationHealth(previous: IntegrationHealth, error: IntegrationFailureCode, now: IntegrationTime, retryAfterMs?: number): IntegrationHealth {
|
|
23
|
-
const failures = Math.min(16, previous.failures + 1);
|
|
24
|
-
const delay = Math.min(MAX_INTEGRATION_BACKOFF_MS, Math.max(INTEGRATION_BACKOFF_MS * 2 ** (failures - 1),
|
|
25
|
-
Number.isFinite(retryAfterMs) ? Math.max(0, retryAfterMs!) : 0));
|
|
26
|
-
const state = error === "auth_required" || error === "protocol_error" ? error : "backoff";
|
|
27
|
-
return { state, observed: { ...now }, failures, retryAt: state === "backoff" ? now.tick + delay : null, error };
|
|
28
|
-
}
|
|
29
|
-
export function integrationHealthView(health: IntegrationHealth, now: IntegrationTime) {
|
|
30
|
-
const age = health.observed === null ? null : Math.max(0, now.tick - health.observed.tick);
|
|
31
|
-
return { state: health.state, observed_at: health.observed?.at ?? null, age_ms: age,
|
|
32
|
-
stale: age === null || age >= INTEGRATION_HEALTH_TTL_MS, error: health.error,
|
|
33
|
-
failures: health.failures, retry_after_ms: health.retryAt === null ? null : Math.max(0, health.retryAt - now.tick) };
|
|
34
|
-
}
|
package/src/core/json-event.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LoopEvent → one JSON object for `--mode json`.
|
|
3
|
-
*
|
|
4
|
-
* Declared here, not imported from runtime, so core stays below the loop
|
|
5
|
-
* (D13: core does not import a layer above it). The kind union is kept in
|
|
6
|
-
* lockstep with LoopEvent; a new kind makes the call site in apps fail to
|
|
7
|
-
* type-check until this switch grows a case.
|
|
8
|
-
*
|
|
9
|
-
* Deltas return null. A pipe cannot retract a half-turn, and a retry would
|
|
10
|
-
* then emit the same prefix twice — the same reason `-p` waits for the
|
|
11
|
-
* complete block. The caller still sees `assistant_text` / `thinking` once
|
|
12
|
-
* the turn has a final block.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { assertNever } from "./exhaustive.ts";
|
|
16
|
-
import type { ExecutionOutcome } from "./outcome.ts";
|
|
17
|
-
|
|
18
|
-
export type JsonLoopKind =
|
|
19
|
-
| "turn_start"
|
|
20
|
-
| "assistant_text"
|
|
21
|
-
| "thinking"
|
|
22
|
-
| "text_delta"
|
|
23
|
-
| "thinking_delta"
|
|
24
|
-
| "discard"
|
|
25
|
-
| "progress"
|
|
26
|
-
| "tool_call"
|
|
27
|
-
| "tool_result"
|
|
28
|
-
| "usage"
|
|
29
|
-
| "cut"
|
|
30
|
-
| "stop_and_ask"
|
|
31
|
-
| "branch_start"
|
|
32
|
-
| "branch_done"
|
|
33
|
-
| "done";
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Structural match for runtime LoopEvent. Copied rather than imported so
|
|
37
|
-
* this module cannot grow an upward dependency.
|
|
38
|
-
*/
|
|
39
|
-
export interface JsonLoopEvent {
|
|
40
|
-
kind: JsonLoopKind;
|
|
41
|
-
text?: string;
|
|
42
|
-
tool?: { name: string; input: unknown };
|
|
43
|
-
ok?: boolean;
|
|
44
|
-
usage?: {
|
|
45
|
-
input_tokens: number;
|
|
46
|
-
output_tokens: number;
|
|
47
|
-
cache_creation_input_tokens: number;
|
|
48
|
-
cache_read_input_tokens: number;
|
|
49
|
-
node: number;
|
|
50
|
-
ms: number;
|
|
51
|
-
};
|
|
52
|
-
prefix?: string;
|
|
53
|
-
node?: number;
|
|
54
|
-
reason?: string;
|
|
55
|
-
at?: number;
|
|
56
|
-
outcome?: ExecutionOutcome;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* `null` means an internal event that must not go on the wire.
|
|
61
|
-
* `at` is supplied by the caller (ISO from the session clock) so this
|
|
62
|
-
* function stays free of Date.
|
|
63
|
-
*/
|
|
64
|
-
export function toJsonEvent(e: JsonLoopEvent, at: string): Record<string, unknown> | null {
|
|
65
|
-
switch (e.kind) {
|
|
66
|
-
case "text_delta":
|
|
67
|
-
case "thinking_delta":
|
|
68
|
-
case "discard":
|
|
69
|
-
case "progress":
|
|
70
|
-
return null;
|
|
71
|
-
case "assistant_text":
|
|
72
|
-
return { type: "assistant_text", text: e.text ?? "", at };
|
|
73
|
-
case "thinking":
|
|
74
|
-
return { type: "thinking", text: e.text ?? "", at };
|
|
75
|
-
case "tool_call":
|
|
76
|
-
return { type: "tool_call", tool: e.tool ?? { name: "", input: {} }, at };
|
|
77
|
-
case "tool_result":
|
|
78
|
-
return { type: "tool_result", text: e.text ?? "", ok: e.ok !== false,
|
|
79
|
-
...(e.outcome ? { outcome: e.outcome } : {}), at };
|
|
80
|
-
case "usage":
|
|
81
|
-
return {
|
|
82
|
-
type: "usage",
|
|
83
|
-
...(e.usage !== undefined
|
|
84
|
-
? {
|
|
85
|
-
usage: {
|
|
86
|
-
input_tokens: e.usage.input_tokens,
|
|
87
|
-
output_tokens: e.usage.output_tokens,
|
|
88
|
-
cache_creation_input_tokens: e.usage.cache_creation_input_tokens,
|
|
89
|
-
cache_read_input_tokens: e.usage.cache_read_input_tokens,
|
|
90
|
-
node: e.usage.node,
|
|
91
|
-
ms: e.usage.ms,
|
|
92
|
-
},
|
|
93
|
-
}
|
|
94
|
-
: {}),
|
|
95
|
-
...(e.prefix !== undefined ? { prefix: e.prefix } : {}),
|
|
96
|
-
ok: e.ok !== false,
|
|
97
|
-
at,
|
|
98
|
-
};
|
|
99
|
-
case "turn_start":
|
|
100
|
-
return { type: "turn_start", ...(e.node !== undefined ? { node: e.node } : {}), at };
|
|
101
|
-
case "cut":
|
|
102
|
-
return {
|
|
103
|
-
type: "cut",
|
|
104
|
-
...(e.node !== undefined ? { node: e.node } : {}),
|
|
105
|
-
...(e.reason !== undefined ? { reason: e.reason } : {}),
|
|
106
|
-
at,
|
|
107
|
-
};
|
|
108
|
-
case "stop_and_ask":
|
|
109
|
-
return { type: "stop_and_ask", ...(e.reason !== undefined ? { reason: e.reason } : {}), at };
|
|
110
|
-
case "done":
|
|
111
|
-
return { type: "done", ...(e.reason !== undefined ? { reason: e.reason } : {}),
|
|
112
|
-
...(e.outcome ? { outcome: e.outcome } : {}), at };
|
|
113
|
-
case "branch_start":
|
|
114
|
-
return {
|
|
115
|
-
type: "branch_start",
|
|
116
|
-
...(e.node !== undefined ? { node: e.node } : {}),
|
|
117
|
-
...(e.reason !== undefined ? { reason: e.reason } : {}),
|
|
118
|
-
at,
|
|
119
|
-
};
|
|
120
|
-
case "branch_done":
|
|
121
|
-
return {
|
|
122
|
-
type: "branch_done",
|
|
123
|
-
...(e.outcome ? { outcome: e.outcome } : {}),
|
|
124
|
-
...(e.node !== undefined ? { node: e.node } : {}),
|
|
125
|
-
...(e.text !== undefined ? { text: e.text } : {}),
|
|
126
|
-
at,
|
|
127
|
-
};
|
|
128
|
-
default:
|
|
129
|
-
return assertNever(e.kind, "toJsonEvent");
|
|
130
|
-
}
|
|
131
|
-
}
|
package/src/core/json-pointer.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/** Parsed-JSON traversal and recoverable diagnostics (research N.5, RFC 6901). */
|
|
2
|
-
// Assumed diagnostic budget, not a data limit: show only complete short keys.
|
|
3
|
-
// Twelve forty-unit keys keep even JSON-escaped control characters below 4 KiB.
|
|
4
|
-
const HINT_KEYS = 12;
|
|
5
|
-
const HINT_KEY_UNITS = 40;
|
|
6
|
-
|
|
7
|
-
export function validateJsonPointer(pointer: string): void {
|
|
8
|
-
if ((pointer !== "" && !pointer.startsWith("/")) || /~(?:[^01]|$)/.test(pointer)) {
|
|
9
|
-
throw new Error('invalid JSON pointer: use "" for the root, / between keys, ~0 for ~ and ~1 for /');
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function arrayIndex(key: string, length: number): boolean {
|
|
14
|
-
const index = Number(key);
|
|
15
|
-
return /^(0|[1-9][0-9]*)$/.test(key) && Number.isSafeInteger(index) && index < length;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function missingPointer(parent: unknown, segment: number): Error {
|
|
19
|
-
const array = Array.isArray(parent);
|
|
20
|
-
const kind = parent === null ? "null" : array ? "array" : typeof parent;
|
|
21
|
-
const keys = parent !== null && typeof parent === "object"
|
|
22
|
-
? Object.keys(parent).filter((key) => !array || arrayIndex(key, parent.length)) : [];
|
|
23
|
-
const shown = keys.filter((key) => key.length <= HINT_KEY_UNITS).sort().slice(0, HINT_KEYS);
|
|
24
|
-
// Segment ordinal identifies the valid prefix without echoing unbounded input
|
|
25
|
-
// paths or source values. Omitted names remain readable through that prefix.
|
|
26
|
-
return new Error(`JSON pointer does not exist at segment ${segment}; parent type: ${kind}. ` +
|
|
27
|
-
`Available keys (${shown.length} of ${keys.length}): ${JSON.stringify(shown)}; ` +
|
|
28
|
-
`${keys.length - shown.length} omitted (long names or diagnostic budget). ` +
|
|
29
|
-
`Inspect the pointer prefix before segment ${segment}; use pointer="" for the document root. ` +
|
|
30
|
-
"The pointer / selects an empty key, not the root.");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function resolveJsonPointer(document: unknown, pointer: string): unknown {
|
|
34
|
-
validateJsonPointer(pointer);
|
|
35
|
-
if (pointer === "") return document;
|
|
36
|
-
let value = document;
|
|
37
|
-
for (const [index, raw] of pointer.slice(1).split("/").entries()) {
|
|
38
|
-
const key = raw.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
39
|
-
if (value === null || typeof value !== "object" ||
|
|
40
|
-
(Array.isArray(value) && !arrayIndex(key, value.length)) || !Object.hasOwn(value, key)) {
|
|
41
|
-
throw missingPointer(value, index + 1);
|
|
42
|
-
}
|
|
43
|
-
value = (value as Record<string, unknown>)[key];
|
|
44
|
-
}
|
|
45
|
-
return value;
|
|
46
|
-
}
|