@visual-z/xeno 0.8.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +31 -746
- package/bin/xeno.exe +26 -0
- package/install.cjs +88 -0
- package/package.json +18 -48
- package/docs/app-server.md +0 -115
- package/docs/foundations.md +0 -88
- package/docs/install.md +0 -244
- package/docs/providers.md +0 -172
- package/docs/research/foundation-clients-2026-10-09.md +0 -27
- package/docs/research/foundation-editing-2026-10-09.md +0 -56
- package/docs/research/foundation-network-2026-10-09.md +0 -37
- package/docs/research/foundation-workflow-2026-10-09.md +0 -37
- package/src/apps/acp-server.ts +0 -41
- package/src/apps/agent-profiles.ts +0 -19
- package/src/apps/app-server.ts +0 -30
- package/src/apps/approve.ts +0 -49
- package/src/apps/cli.ts +0 -99
- package/src/apps/commands.ts +0 -319
- package/src/apps/completions.ts +0 -87
- package/src/apps/control-transport.ts +0 -90
- package/src/apps/export-cli.ts +0 -24
- package/src/apps/goal-flow.ts +0 -70
- package/src/apps/image-draft.ts +0 -46
- package/src/apps/input.ts +0 -312
- package/src/apps/model-catalog.ts +0 -69
- package/src/apps/preferences.ts +0 -20
- package/src/apps/project-trust.ts +0 -9
- package/src/apps/provider-connect.ts +0 -346
- package/src/apps/question-ui.ts +0 -27
- package/src/apps/render.ts +0 -181
- package/src/apps/resources.ts +0 -66
- package/src/apps/review.ts +0 -109
- package/src/apps/session-hub.ts +0 -96
- package/src/apps/session-lifecycle.ts +0 -64
- package/src/apps/shell.ts +0 -2071
- package/src/apps/startup-input.ts +0 -135
- package/src/apps/startup.ts +0 -1677
- package/src/apps/ui/app.ts +0 -1818
- package/src/apps/ui/control-model.ts +0 -43
- package/src/apps/ui/extension-host.ts +0 -194
- package/src/apps/ui/image-cache.ts +0 -121
- package/src/apps/ui/palette.ts +0 -88
- package/src/apps/ui/project.ts +0 -127
- package/src/apps/ui/standalone.ts +0 -148
- package/src/apps/ui/status-model.ts +0 -68
- package/src/apps/ui/surface.ts +0 -91
- package/src/apps/ui/theme.ts +0 -86
- package/src/apps/update-check.ts +0 -112
- package/src/apps/workflow.ts +0 -58
- package/src/core/acceptance.ts +0 -139
- package/src/core/action-defaults.ts +0 -58
- package/src/core/activity-status.ts +0 -52
- package/src/core/agent-profile.ts +0 -130
- package/src/core/attachment-input.ts +0 -17
- package/src/core/blind-checks.ts +0 -72
- package/src/core/boundary.ts +0 -174
- package/src/core/branch-receipt.ts +0 -26
- package/src/core/catalog-merge.ts +0 -19
- package/src/core/checkpoints.ts +0 -85
- package/src/core/churn.ts +0 -61
- package/src/core/command-artifacts.ts +0 -94
- package/src/core/command-contract.ts +0 -151
- package/src/core/completion-action.ts +0 -20
- package/src/core/completion-review.ts +0 -98
- package/src/core/context-detail.ts +0 -37
- package/src/core/context-policy.ts +0 -133
- package/src/core/context-window.ts +0 -54
- package/src/core/continuation.ts +0 -130
- package/src/core/control-protocol.ts +0 -69
- package/src/core/control-view.ts +0 -39
- package/src/core/cycle.ts +0 -17
- package/src/core/data-query.ts +0 -201
- package/src/core/deadline.ts +0 -40
- package/src/core/derive.ts +0 -516
- package/src/core/draft-images.ts +0 -56
- package/src/core/edit-plan.ts +0 -200
- package/src/core/environment.ts +0 -181
- package/src/core/event-validation.ts +0 -220
- package/src/core/events.ts +0 -670
- package/src/core/evidence-ladder.ts +0 -62
- package/src/core/evidence-links.ts +0 -68
- package/src/core/evidence.ts +0 -240
- package/src/core/execution.ts +0 -18
- package/src/core/exhaustive.ts +0 -18
- package/src/core/export.ts +0 -179
- package/src/core/extension-flags.ts +0 -14
- package/src/core/external-effects.ts +0 -488
- package/src/core/failure-recovery.ts +0 -173
- package/src/core/file-precondition.ts +0 -78
- package/src/core/file-references.ts +0 -35
- package/src/core/frontmatter.ts +0 -213
- package/src/core/git-status.ts +0 -29
- package/src/core/goal-view.ts +0 -64
- package/src/core/goal.ts +0 -200
- package/src/core/handoff.ts +0 -479
- package/src/core/history-query.ts +0 -139
- package/src/core/history.ts +0 -237
- package/src/core/image-limits.ts +0 -72
- package/src/core/images.ts +0 -46
- package/src/core/integration-health.ts +0 -34
- package/src/core/json-event.ts +0 -131
- package/src/core/json-pointer.ts +0 -46
- package/src/core/keymap.ts +0 -153
- package/src/core/lsp-framing.ts +0 -34
- package/src/core/mcp-config.ts +0 -68
- package/src/core/model-picker.ts +0 -64
- package/src/core/model-preferences.ts +0 -28
- package/src/core/model-replay.ts +0 -22
- package/src/core/model-scope.ts +0 -53
- package/src/core/note-guidance.ts +0 -34
- package/src/core/oauth-fields.ts +0 -44
- package/src/core/observation-view.ts +0 -152
- package/src/core/outcome.ts +0 -44
- package/src/core/overflow.ts +0 -59
- package/src/core/packages.ts +0 -47
- package/src/core/patch.ts +0 -130
- package/src/core/path-suggestions.ts +0 -59
- package/src/core/permissions.ts +0 -410
- package/src/core/process-receipt.ts +0 -105
- package/src/core/process-state.ts +0 -37
- package/src/core/program-status.ts +0 -40
- package/src/core/progress.ts +0 -150
- package/src/core/project.ts +0 -431
- package/src/core/prompt-policy.ts +0 -46
- package/src/core/prompts.ts +0 -111
- package/src/core/question.ts +0 -94
- package/src/core/queue.ts +0 -78
- package/src/core/read-bounds.ts +0 -24
- package/src/core/read-observation.ts +0 -106
- package/src/core/recovery-sources.ts +0 -74
- package/src/core/regex-dialect.ts +0 -25
- package/src/core/request-budget.ts +0 -23
- package/src/core/request-progress.ts +0 -87
- package/src/core/request-state.ts +0 -273
- package/src/core/resource-selection.ts +0 -36
- package/src/core/response-recovery.ts +0 -59
- package/src/core/resume.ts +0 -59
- package/src/core/review.ts +0 -68
- package/src/core/ripgrep-release.ts +0 -64
- package/src/core/runtime-target.ts +0 -94
- package/src/core/scheduling.ts +0 -8
- package/src/core/search-observation.ts +0 -97
- package/src/core/secrets.ts +0 -188
- package/src/core/session-archive.ts +0 -4
- package/src/core/session-browser.ts +0 -82
- package/src/core/session-entry.ts +0 -61
- package/src/core/session-exit.ts +0 -42
- package/src/core/session-format.ts +0 -169
- package/src/core/session-position.ts +0 -26
- package/src/core/session-tree.ts +0 -147
- package/src/core/settings.ts +0 -192
- package/src/core/skills.ts +0 -165
- package/src/core/sql-query.ts +0 -211
- package/src/core/stall.ts +0 -128
- package/src/core/stop-report.ts +0 -68
- package/src/core/text-normalize.ts +0 -81
- package/src/core/theme.ts +0 -166
- package/src/core/thinking.ts +0 -26
- package/src/core/tokens.ts +0 -151
- package/src/core/tool-contract.ts +0 -153
- package/src/core/tool-discovery.ts +0 -87
- package/src/core/tool-failure.ts +0 -81
- package/src/core/tool-input.ts +0 -44
- package/src/core/transcript.ts +0 -108
- package/src/core/tui-mode.ts +0 -29
- package/src/core/update-check.ts +0 -157
- package/src/core/version.ts +0 -55
- package/src/core/web-content.ts +0 -80
- package/src/core/work-state.ts +0 -154
- package/src/core/workflow-mode.ts +0 -34
- package/src/core/working-set.ts +0 -119
- package/src/core/yaml.ts +0 -282
- package/src/runtime/acp-agent.ts +0 -289
- package/src/runtime/agent-profiles.ts +0 -21
- package/src/runtime/auth.ts +0 -367
- package/src/runtime/blind-check.ts +0 -36
- package/src/runtime/branch-jobs.ts +0 -36
- package/src/runtime/catalog.ts +0 -285
- package/src/runtime/clipboard.ts +0 -125
- package/src/runtime/command-artifacts.ts +0 -64
- package/src/runtime/connection-reuse.ts +0 -40
- package/src/runtime/control-client.ts +0 -149
- package/src/runtime/control-connection.ts +0 -139
- package/src/runtime/counter-check.ts +0 -74
- package/src/runtime/environment-tools.ts +0 -60
- package/src/runtime/evidence-notes.ts +0 -35
- package/src/runtime/exec-sessions.ts +0 -456
- package/src/runtime/execution-control.ts +0 -40
- package/src/runtime/extension-loader.ts +0 -37
- package/src/runtime/extension-ui.ts +0 -47
- package/src/runtime/extensions.ts +0 -370
- package/src/runtime/formatter-tool.ts +0 -38
- package/src/runtime/formatter.ts +0 -50
- package/src/runtime/foundation.ts +0 -116
- package/src/runtime/history-cache.ts +0 -57
- package/src/runtime/history-tools.ts +0 -168
- package/src/runtime/images.ts +0 -80
- package/src/runtime/integrations.ts +0 -216
- package/src/runtime/loop.ts +0 -3108
- package/src/runtime/lsp-client.ts +0 -201
- package/src/runtime/mcp-client.ts +0 -264
- package/src/runtime/mcp-oauth.ts +0 -168
- package/src/runtime/mcp-schema.ts +0 -51
- package/src/runtime/model-config.ts +0 -232
- package/src/runtime/model-data.LICENSE +0 -27
- package/src/runtime/model-data.ts +0 -21
- package/src/runtime/model-discovery.ts +0 -208
- package/src/runtime/model-replay.ts +0 -10
- package/src/runtime/model-scope.ts +0 -20
- package/src/runtime/model-sources.ts +0 -151
- package/src/runtime/model.ts +0 -686
- package/src/runtime/models-fetch.ts +0 -57
- package/src/runtime/oauth-device.ts +0 -86
- package/src/runtime/oauth.ts +0 -853
- package/src/runtime/open-image.ts +0 -42
- package/src/runtime/openai-responses.ts +0 -304
- package/src/runtime/openai.ts +0 -697
- package/src/runtime/output-schema.ts +0 -26
- package/src/runtime/package-manager.ts +0 -125
- package/src/runtime/patch-tool.ts +0 -38
- package/src/runtime/permission-resources.ts +0 -18
- package/src/runtime/pi-provider.ts +0 -423
- package/src/runtime/program-status.ts +0 -38
- package/src/runtime/progress.ts +0 -26
- package/src/runtime/provider-access.ts +0 -21
- package/src/runtime/provider-connect.ts +0 -109
- package/src/runtime/provider-presets.ts +0 -98
- package/src/runtime/provider-protocols.ts +0 -29
- package/src/runtime/provider-request.ts +0 -87
- package/src/runtime/provider-tool-policy.ts +0 -33
- package/src/runtime/provider.ts +0 -580
- package/src/runtime/question-tool.ts +0 -62
- package/src/runtime/retry.ts +0 -447
- package/src/runtime/review-tool.ts +0 -48
- package/src/runtime/rich-text.ts +0 -36
- package/src/runtime/runtime-target.ts +0 -149
- package/src/runtime/secrets.ts +0 -79
- package/src/runtime/self-update.ts +0 -74
- package/src/runtime/session-control.ts +0 -265
- package/src/runtime/session-hub.ts +0 -85
- package/src/runtime/stream-idle.ts +0 -80
- package/src/runtime/tool-broker.ts +0 -137
- package/src/runtime/tool-discovery.ts +0 -58
- package/src/runtime/tool-queue.ts +0 -77
- package/src/runtime/tools.ts +0 -1820
- package/src/runtime/turn-effects.ts +0 -31
- package/src/runtime/update-check.ts +0 -48
- package/src/runtime/usage.ts +0 -253
- package/src/runtime/version.ts +0 -12
- package/src/runtime/web-http.ts +0 -56
- package/src/runtime/web-tools.ts +0 -88
- package/src/runtime/working-notes.ts +0 -60
- package/src/runtime/workspace-lifecycle.ts +0 -141
- package/src/store/acceptance.ts +0 -15
- package/src/store/agent-profile-files.ts +0 -13
- package/src/store/atomic.ts +0 -31
- package/src/store/auth-store.ts +0 -50
- package/src/store/blind-checks.ts +0 -32
- package/src/store/blobs.ts +0 -143
- package/src/store/checkpoint-history.ts +0 -32
- package/src/store/command-artifacts.ts +0 -96
- package/src/store/command.ts +0 -111
- package/src/store/config.ts +0 -69
- package/src/store/context-files.ts +0 -436
- package/src/store/file-checkpoints.ts +0 -108
- package/src/store/file-index.ts +0 -174
- package/src/store/foundation-config.ts +0 -24
- package/src/store/git-checkpoints.ts +0 -67
- package/src/store/git-status.ts +0 -15
- package/src/store/history-search.ts +0 -19
- package/src/store/log.ts +0 -725
- package/src/store/lsp-document.ts +0 -30
- package/src/store/mcp-credentials.ts +0 -85
- package/src/store/models.ts +0 -60
- package/src/store/observations.ts +0 -224
- package/src/store/packages.ts +0 -98
- package/src/store/patch.ts +0 -92
- package/src/store/permission-paths.ts +0 -34
- package/src/store/process-receipts.ts +0 -34
- package/src/store/query-source.ts +0 -49
- package/src/store/recovery.ts +0 -44
- package/src/store/replay.ts +0 -76
- package/src/store/resource-index.ts +0 -170
- package/src/store/resources.ts +0 -285
- package/src/store/restore.ts +0 -110
- package/src/store/review.ts +0 -109
- package/src/store/runtime-target.ts +0 -64
- package/src/store/search.ts +0 -728
- package/src/store/secrets.ts +0 -44
- package/src/store/serial.ts +0 -13
- package/src/store/session-archive.ts +0 -165
- package/src/store/session-browser.ts +0 -68
- package/src/store/session-position.ts +0 -27
- package/src/store/sessions.ts +0 -525
- package/src/store/source-cancellation.ts +0 -6
- package/src/store/tool-binaries.ts +0 -90
- package/src/store/trust.ts +0 -82
- package/src/store/update-cache.ts +0 -36
- package/src/store/user-settings.ts +0 -30
- package/src/store/workspace-clone.ts +0 -42
- package/src/store/workspace-observation.ts +0 -113
- package/src/store/workspace.ts +0 -115
- package/src/store/worktree.ts +0 -278
- package/src/tui/ansi-spans.ts +0 -126
- package/src/tui/diff.ts +0 -160
- package/src/tui/exhaustive.ts +0 -14
- package/src/tui/fuzzy.ts +0 -75
- package/src/tui/style.ts +0 -208
- package/src/tui/ui/atomic-draft.ts +0 -121
- package/src/tui/ui/branch-panel.ts +0 -47
- package/src/tui/ui/canvas.ts +0 -96
- package/src/tui/ui/colors.ts +0 -70
- package/src/tui/ui/composer.ts +0 -199
- package/src/tui/ui/control-panel.ts +0 -25
- package/src/tui/ui/control-sidebar.ts +0 -49
- package/src/tui/ui/dialog.ts +0 -398
- package/src/tui/ui/draft.ts +0 -23
- package/src/tui/ui/duration.ts +0 -16
- package/src/tui/ui/edge.ts +0 -186
- package/src/tui/ui/image-size.ts +0 -44
- package/src/tui/ui/ink.ts +0 -210
- package/src/tui/ui/items.ts +0 -42
- package/src/tui/ui/keys.ts +0 -40
- package/src/tui/ui/layout.ts +0 -74
- package/src/tui/ui/list-model.ts +0 -86
- package/src/tui/ui/list-view.ts +0 -172
- package/src/tui/ui/mascot.ts +0 -67
- package/src/tui/ui/messages.ts +0 -518
- package/src/tui/ui/motion.ts +0 -28
- package/src/tui/ui/paths.ts +0 -10
- package/src/tui/ui/pictures.ts +0 -108
- package/src/tui/ui/prompt-history.ts +0 -60
- package/src/tui/ui/screen.ts +0 -579
- package/src/tui/ui/status.ts +0 -403
- package/src/tui/ui/styled.ts +0 -85
- package/src/tui/ui/tool-card.ts +0 -150
- package/src/tui/width.ts +0 -567
- package/src/vendor/pi-rich/LICENSE.upstream +0 -21
- package/src/vendor/pi-rich/UPSTREAM.json +0 -17
- package/src/vendor/pi-rich/clipboard-command.ts +0 -44
- package/src/vendor/pi-rich/exif-orientation.ts +0 -182
- package/src/vendor/pi-rich/html.ts +0 -51
- package/src/vendor/pi-rich/image-convert.ts +0 -49
- package/src/vendor/pi-rich/image-resize-core.ts +0 -164
- package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
- package/src/vendor/pi-rich/image-resize.ts +0 -123
- package/src/vendor/pi-rich/mime.ts +0 -116
- package/src/vendor/pi-rich/photon.ts +0 -139
- package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
package/src/runtime/retry.ts
DELETED
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* When a failed request is worth making again.
|
|
3
|
-
*
|
|
4
|
-
* Split into a pure decision and a thin loop, because the decision is where
|
|
5
|
-
* all the rules are and none of them need a clock. What to retry, how long to
|
|
6
|
-
* wait, when to stop -- all of it is testable in microseconds, which is the
|
|
7
|
-
* difference between pinning the policy and hoping it works.
|
|
8
|
-
*
|
|
9
|
-
* The rules mirror the OpenAI and Anthropic SDKs' own, by way of pi, which
|
|
10
|
-
* notes the same lineage. That is deliberate: the failure modes were found by
|
|
11
|
-
* people running far more traffic than this, and inventing a different policy
|
|
12
|
-
* would mean rediscovering them.
|
|
13
|
-
*
|
|
14
|
-
* Retry lives at the request, inside the adapter, and not in the loop. A retry
|
|
15
|
-
* in the loop would straddle a log append -- the failed attempt would either
|
|
16
|
-
* be recorded, polluting an append-only history with something that did not
|
|
17
|
-
* happen, or dropped, leaving usage that does not add up. The loop should see
|
|
18
|
-
* a result that has already been retried, or a definite failure.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { isContextOverflow } from "../core/overflow.ts";
|
|
22
|
-
import type { FailureCause } from "../core/events.ts";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Words providers and relays use for "try again": timeouts, unavailable or
|
|
26
|
-
* overloaded upstreams, rate limits, dropped connections, 5xx codes. A
|
|
27
|
-
* pattern, not a list of known providers, because relays forward their
|
|
28
|
-
* upstream's wording.
|
|
29
|
-
*/
|
|
30
|
-
const TRANSIENT_STREAM = /time[d ]?out|timed out|unavailable|overload|rate.?limit|too many requests|connection (?:reset|closed|refused)|ECONNRESET|temporar|\b5\d\d\b|internal.?server|bad gateway|service.?unavailable/i;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Connection failures, named by code rather than guessed from prose.
|
|
34
|
-
*
|
|
35
|
-
* Bun's fetch reports every one of these as a plain Error carrying `code`
|
|
36
|
-
* (measured on 1.3.10: a reset before the headers, one halfway through the
|
|
37
|
-
* body and a refused port all arrive that way), never as the TypeError the
|
|
38
|
-
* WHATWG spec describes -- so the TypeError rule below never sees them. SDKs
|
|
39
|
-
* wrap the same error as `cause`. The two sets follow Claude Code's split:
|
|
40
|
-
* "stale" is a connection that existed and died, "network_down" is one that
|
|
41
|
-
* could never be opened. Both are worth another attempt.
|
|
42
|
-
*/
|
|
43
|
-
const STALE_CONNECTION_CODES = new Set([
|
|
44
|
-
"ECONNRESET", "EPIPE", "ECONNABORTED", "ETIMEDOUT", "ConnectionClosed",
|
|
45
|
-
"ERR_SOCKET_CLOSED", "UND_ERR_SOCKET", "UND_ERR_CLOSED",
|
|
46
|
-
]);
|
|
47
|
-
const NETWORK_DOWN_CODES = new Set([
|
|
48
|
-
"ECONNREFUSED", "ENOTFOUND", "ENETUNREACH", "ENETDOWN", "EHOSTUNREACH", "EHOSTDOWN",
|
|
49
|
-
"EAI_AGAIN", "ConnectionRefused", "FailedToOpenSocket", "ERR_PROXY_TUNNEL",
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* The same failures in the words Bun, Node and undici use, for adapters that
|
|
54
|
-
* forward only the message (pi-ai turns a dropped stream into errorMessage
|
|
55
|
-
* and the code is gone by the time it reaches here).
|
|
56
|
-
*/
|
|
57
|
-
const STALE_CONNECTION_TEXT = /socket connection was closed unexpectedly|socket hang up|other side closed|\bECONNRESET\b|\bEPIPE\b/i;
|
|
58
|
-
const NETWORK_DOWN_TEXT = /unable to connect\. is the computer able to access the url|\bECONNREFUSED\b|\bENOTFOUND\b|\bEAI_AGAIN\b/i;
|
|
59
|
-
|
|
60
|
-
export type ConnectionFailure = "stale" | "network_down";
|
|
61
|
-
|
|
62
|
-
function codeOf(value: unknown): string | undefined {
|
|
63
|
-
if (typeof value !== "object" || value === null) return undefined;
|
|
64
|
-
const code = (value as { code?: unknown }).code;
|
|
65
|
-
return typeof code === "string" ? code : undefined;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** TLS verification codes from Node, Bun and undici; Claude Code also leaves these unretried. */
|
|
69
|
-
const CERTIFICATE_CODE = /^(?:CERT_|UNABLE_TO_VERIFY_|UNABLE_TO_GET_ISSUER_CERT)|^(?:SELF_SIGNED_CERT_IN_CHAIN|DEPTH_ZERO_SELF_SIGNED_CERT|ERR_TLS_CERT_ALTNAME_INVALID)$/;
|
|
70
|
-
|
|
71
|
-
function certificateFailure(error: unknown): boolean {
|
|
72
|
-
const cause = typeof error === "object" && error !== null ? (error as { cause?: unknown }).cause : undefined;
|
|
73
|
-
return [codeOf(error), codeOf(cause)].some((code) => code !== undefined && CERTIFICATE_CODE.test(code));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* A few words for the retry line: what went wrong, not the full message.
|
|
78
|
-
* Bun's text runs to a sentence about `verbose: true`, which the status line
|
|
79
|
-
* cannot hold; the full message still reaches the user if the retries run out.
|
|
80
|
-
*/
|
|
81
|
-
export function retryLabel(error: unknown): string {
|
|
82
|
-
const status = statusOf(error);
|
|
83
|
-
if (status === 429) return "rate limited";
|
|
84
|
-
if (status !== undefined) return `HTTP ${status}`;
|
|
85
|
-
const connection = connectionFailure(error);
|
|
86
|
-
if (connection === "stale") return "connection lost";
|
|
87
|
-
if (connection === "network_down") return "network unreachable";
|
|
88
|
-
if (isStreamIdle(error)) return "stream stalled";
|
|
89
|
-
if (isMalformedResponse(error)) return "malformed response";
|
|
90
|
-
if (isStreamFailure(error)) return "stream error";
|
|
91
|
-
return error instanceof TypeError ? "connection failed" : "request failed";
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* What a failed request leaves in the log. `retries` is present only for a
|
|
96
|
-
* provider failure: it is what the adapter reported, not what the policy allowed.
|
|
97
|
-
* A harness error contributes its class name alone -- the message may quote a
|
|
98
|
-
* path, a command or a credential, and the class is chosen by code.
|
|
99
|
-
*/
|
|
100
|
-
export function failureCause(error: unknown, provider: { retries: number } | undefined): FailureCause {
|
|
101
|
-
if (provider) {
|
|
102
|
-
const status = statusOf(error);
|
|
103
|
-
return { source: "provider", label: retryLabel(error), ...(status === undefined ? {} : { status }), retries: provider.retries };
|
|
104
|
-
}
|
|
105
|
-
const name = error instanceof Error ? error.name : "";
|
|
106
|
-
return { source: "harness", label: /^[A-Za-z][\w.]{0,63}$/.test(name) ? name : "Error" };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** The words a person reads: "HTTP 503 after 3 retries", "connection lost". */
|
|
110
|
-
export function describeFailure(cause: FailureCause): string {
|
|
111
|
-
if (cause.source === "harness") return `xeno internal error (${cause.label})`;
|
|
112
|
-
const retries = cause.retries ?? 0;
|
|
113
|
-
return retries === 0 ? cause.label : `${cause.label} after ${retries} ${retries === 1 ? "retry" : "retries"}`;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Whether this error is a connection that died or one that never opened. */
|
|
117
|
-
export function connectionFailure(error: unknown): ConnectionFailure | undefined {
|
|
118
|
-
const cause = typeof error === "object" && error !== null ? (error as { cause?: unknown }).cause : undefined;
|
|
119
|
-
for (const code of [codeOf(error), codeOf(cause)]) {
|
|
120
|
-
if (code === undefined) continue;
|
|
121
|
-
if (STALE_CONNECTION_CODES.has(code)) return "stale";
|
|
122
|
-
if (NETWORK_DOWN_CODES.has(code)) return "network_down";
|
|
123
|
-
}
|
|
124
|
-
for (const value of [error, cause]) {
|
|
125
|
-
if (!(value instanceof Error)) continue;
|
|
126
|
-
if (STALE_CONNECTION_TEXT.test(value.message)) return "stale";
|
|
127
|
-
if (NETWORK_DOWN_TEXT.test(value.message)) return "network_down";
|
|
128
|
-
}
|
|
129
|
-
return undefined;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** Structural, so retry.ts need not import an adapter to recognise its error. */
|
|
133
|
-
function isStreamFailure(error: unknown): error is Error & { code?: string } {
|
|
134
|
-
return error instanceof Error && error.name === "StreamError";
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** stream-idle.ts StreamIdleError: an attempt abandoned after the stream went silent. */
|
|
138
|
-
function isStreamIdle(error: unknown): boolean {
|
|
139
|
-
return error instanceof Error && error.name === "StreamIdleError";
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Retries a silent stream gets. Fewer than the general budget because each
|
|
144
|
-
* one has already cost the idle limit (300s): ten would be fifty minutes.
|
|
145
|
-
* Codex CLI allows 5 stream retries and Claude Code 1; 2 is an assumption.
|
|
146
|
-
*/
|
|
147
|
-
const STREAM_IDLE_RETRIES = 2;
|
|
148
|
-
|
|
149
|
-
/** provider.ts MalformedResponseError: a self-contradictory 200, structural for the same reason. */
|
|
150
|
-
function isMalformedResponse(error: unknown): boolean {
|
|
151
|
-
return error instanceof Error && error.name === "MalformedResponseError";
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export interface RetryPolicy {
|
|
155
|
-
/**
|
|
156
|
-
* Attempts after the first. Ten, doubling to a 32s ceiling, is Claude
|
|
157
|
-
* Code's budget: about 160s of backoff before failing. Four (~15s) gave up
|
|
158
|
-
* inside an ordinary network blip or proxy reconnect (Horizyn 2026-10-12,
|
|
159
|
-
* feat-retry-budget-and-progress). The wait is visible -- a countdown and
|
|
160
|
-
* attempt n/max -- and Escape ends it, so a longer budget is not a silent
|
|
161
|
-
* hang. The ceiling is an assumption carried over, not measured here.
|
|
162
|
-
*/
|
|
163
|
-
maxRetries: number;
|
|
164
|
-
/**
|
|
165
|
-
* Longest server-requested delay worth honouring. Beyond this, fail. An
|
|
166
|
-
* agent that silently sleeps for twenty minutes cannot be told apart from
|
|
167
|
-
* one that has died, and is harder to diagnose than an error.
|
|
168
|
-
*/
|
|
169
|
-
maxRetryDelayMs: number;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export const DEFAULT_RETRY: RetryPolicy = { maxRetries: 10, maxRetryDelayMs: 60_000 };
|
|
173
|
-
|
|
174
|
-
/** Longest backoff between attempts when the server names no delay. */
|
|
175
|
-
const BACKOFF_CEILING_MS = 32_000;
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* 0.5s doubling to the ceiling, then flat. `jitter` in [0, 1) only shortens
|
|
179
|
-
* the wait, by at most a quarter (see retryDecision).
|
|
180
|
-
*/
|
|
181
|
-
function backoffMs(attempt: number, jitter: number): number {
|
|
182
|
-
return Math.min(500 * 2 ** attempt, BACKOFF_CEILING_MS) * (1 - jitter * 0.25);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export type RetryDecision =
|
|
186
|
-
| { retry: true; delayMs: number }
|
|
187
|
-
| { retry: false; reason: string };
|
|
188
|
-
|
|
189
|
-
/** What this module needs from an error: a status and headers, if it has them. */
|
|
190
|
-
interface HasResponse {
|
|
191
|
-
status?: unknown;
|
|
192
|
-
headers?: unknown;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function statusOf(error: unknown): number | undefined {
|
|
196
|
-
if (typeof error !== "object" || error === null) return undefined;
|
|
197
|
-
const status = (error as HasResponse).status;
|
|
198
|
-
return typeof status === "number" ? status : undefined;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function headersOf(error: unknown): Headers | undefined {
|
|
202
|
-
if (typeof error !== "object" || error === null) return undefined;
|
|
203
|
-
const headers = (error as HasResponse).headers;
|
|
204
|
-
return headers instanceof Headers ? headers : undefined;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Whether this status is about timing rather than about the request.
|
|
209
|
-
*
|
|
210
|
-
* 400 and 401 are the two worth naming: a malformed request and a bad key are
|
|
211
|
-
* deterministic, so retrying converts one clear failure into several slow ones
|
|
212
|
-
* and delays the only message that would have helped.
|
|
213
|
-
*/
|
|
214
|
-
function statusIsTransient(status: number): boolean {
|
|
215
|
-
return status === 408 || status === 409 || status === 429 || status >= 500;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function serverDelayMs(headers: Headers | undefined): number | undefined {
|
|
219
|
-
if (headers === undefined) return undefined;
|
|
220
|
-
|
|
221
|
-
// Milliseconds first: it is the more precise of the two, and a provider
|
|
222
|
-
// that sends both means the finer one.
|
|
223
|
-
const ms = headers.get("retry-after-ms");
|
|
224
|
-
if (ms !== null) {
|
|
225
|
-
const value = Number.parseFloat(ms);
|
|
226
|
-
if (!Number.isNaN(value)) return value;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const after = headers.get("retry-after");
|
|
230
|
-
if (after !== null) {
|
|
231
|
-
const seconds = Number.parseFloat(after);
|
|
232
|
-
if (!Number.isNaN(seconds)) return seconds * 1000;
|
|
233
|
-
// The header is also allowed to be an HTTP date.
|
|
234
|
-
const at = Date.parse(after);
|
|
235
|
-
if (!Number.isNaN(at)) return Math.max(0, at - Date.now());
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return undefined;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Whether to try again, and how long to wait first.
|
|
243
|
-
*
|
|
244
|
-
* `jitter` is a value in [0, 1) supplied by the caller rather than drawn here,
|
|
245
|
-
* so the backoff arithmetic is deterministic under test. It only ever
|
|
246
|
-
* shortens the wait: lengthening it could push past the window the server
|
|
247
|
-
* implied, while shortening is what breaks up the thundering herd of clients
|
|
248
|
-
* that were all rate-limited at the same instant and would otherwise wake
|
|
249
|
-
* together.
|
|
250
|
-
*/
|
|
251
|
-
export function retryDecision(
|
|
252
|
-
error: unknown,
|
|
253
|
-
attempt: number,
|
|
254
|
-
policy: RetryPolicy,
|
|
255
|
-
jitter: number,
|
|
256
|
-
): RetryDecision {
|
|
257
|
-
if (attempt >= policy.maxRetries) {
|
|
258
|
-
return { retry: false, reason: `giving up after ${policy.maxRetries} retries` };
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const headers = headersOf(error);
|
|
262
|
-
const status = statusOf(error);
|
|
263
|
-
|
|
264
|
-
// The same prompt overflows the same way however often it is sent, even
|
|
265
|
-
// when a relay dresses the rejection up as a 5xx. The loop owns the remedy.
|
|
266
|
-
if (error instanceof Error && isContextOverflow(error.message, status)) {
|
|
267
|
-
return { retry: false, reason: "the prompt exceeds the model's context window" };
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// A certificate is the same on every attempt; retrying only delays the one
|
|
271
|
-
// message that sends the user to their proxy or CA bundle. Checked before
|
|
272
|
-
// the TypeError rule, which undici uses to wrap exactly this failure.
|
|
273
|
-
if (status === undefined && certificateFailure(error)) {
|
|
274
|
-
return { retry: false, reason: "the server's certificate could not be verified" };
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (isStreamIdle(error)) {
|
|
278
|
-
return attempt < STREAM_IDLE_RETRIES
|
|
279
|
-
? { retry: true, delayMs: backoffMs(attempt, jitter) }
|
|
280
|
-
: { retry: false, reason: `the stream went silent ${attempt + 1} times` };
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// A connection that died or never opened says nothing about the request,
|
|
284
|
-
// whichever wrapper carried it here.
|
|
285
|
-
if (status === undefined && connectionFailure(error) !== undefined) {
|
|
286
|
-
return { retry: true, delayMs: backoffMs(attempt, jitter) };
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// A provider that knows whether its own error is worth retrying gets the
|
|
290
|
-
// last word, in both directions.
|
|
291
|
-
// A failure reported inside a 200 stream carries no status. Relays and
|
|
292
|
-
// Anthropic's own `overloaded_error` use it for exactly the conditions a
|
|
293
|
-
// 5xx would signal; anything else in a stream is a real rejection.
|
|
294
|
-
if (status === undefined && (isStreamFailure(error) || isMalformedResponse(error))) {
|
|
295
|
-
// A contradictory response is a bad sample, not a rejection of the request:
|
|
296
|
-
// resend it like a dropped stream, and let the bound turn it into an error.
|
|
297
|
-
if (isStreamFailure(error) && !TRANSIENT_STREAM.test(`${error.code ?? ""} ${error.message}`)) {
|
|
298
|
-
return { retry: false, reason: "the stream reported a non-transient error" };
|
|
299
|
-
}
|
|
300
|
-
return { retry: true, delayMs: backoffMs(attempt, jitter) };
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const advice = headers?.get("x-should-retry");
|
|
304
|
-
let worthRetrying: boolean;
|
|
305
|
-
if (advice === "true") worthRetrying = true;
|
|
306
|
-
else if (advice === "false") worthRetrying = false;
|
|
307
|
-
else if (status !== undefined) worthRetrying = statusIsTransient(status);
|
|
308
|
-
else {
|
|
309
|
-
// No status at all. fetch reports a refused, reset or dropped connection
|
|
310
|
-
// as a TypeError, which is exactly the transient case. Anything else --
|
|
311
|
-
// a TypeError from our own code, a RangeError -- is a bug, and retrying a
|
|
312
|
-
// bug just runs it again.
|
|
313
|
-
worthRetrying = error instanceof TypeError;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (!worthRetrying) {
|
|
317
|
-
return {
|
|
318
|
-
retry: false,
|
|
319
|
-
reason: status === undefined ? "not a retryable failure" : `HTTP ${status} is not retryable`,
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const requested = serverDelayMs(headers);
|
|
324
|
-
if (requested !== undefined) {
|
|
325
|
-
if (requested > policy.maxRetryDelayMs) {
|
|
326
|
-
return {
|
|
327
|
-
retry: false,
|
|
328
|
-
reason:
|
|
329
|
-
`server asked to wait ${Math.ceil(requested / 1000)}s, ` +
|
|
330
|
-
`longer than the ${Math.ceil(policy.maxRetryDelayMs / 1000)}s cap`,
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
return { retry: true, delayMs: requested };
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return { retry: true, delayMs: backoffMs(attempt, jitter) };
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
export interface RetryEffects {
|
|
340
|
-
sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
341
|
-
jitter(): number;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function abortReason(signal: AbortSignal): Error {
|
|
345
|
-
if (signal.reason instanceof Error) return signal.reason;
|
|
346
|
-
return new DOMException("The operation was aborted.", "AbortError");
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* A wait the caller can cancel.
|
|
351
|
-
*
|
|
352
|
-
* The SDK's own retry timers ignore AbortSignal, which is why the adapters
|
|
353
|
-
* retry with `maxRetries: 0` and sleep here. A sleep that runs to the end
|
|
354
|
-
* after Escape is the hang the user pressed it to get out of.
|
|
355
|
-
*/
|
|
356
|
-
function interruptibleSleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
357
|
-
if (signal === undefined) {
|
|
358
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
359
|
-
}
|
|
360
|
-
return new Promise((resolve, reject) => {
|
|
361
|
-
if (signal.aborted) {
|
|
362
|
-
reject(abortReason(signal));
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
const timer = setTimeout(() => {
|
|
366
|
-
signal.removeEventListener("abort", onAbort);
|
|
367
|
-
resolve();
|
|
368
|
-
}, ms);
|
|
369
|
-
const onAbort = () => {
|
|
370
|
-
clearTimeout(timer);
|
|
371
|
-
reject(abortReason(signal));
|
|
372
|
-
};
|
|
373
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
export const DEFAULT_RETRY_EFFECTS: RetryEffects = {
|
|
378
|
-
sleep: interruptibleSleep,
|
|
379
|
-
jitter: () => Math.random(),
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Resolves when `waiting` does, or rejects as soon as the signal fires.
|
|
384
|
-
*
|
|
385
|
-
* Injected sleeps are allowed to ignore the signal -- tests do, to prove
|
|
386
|
-
* this wrapper is what stops the wait. Swallowing the abandoned sleep
|
|
387
|
-
* avoids an unhandled rejection when it later settles.
|
|
388
|
-
*/
|
|
389
|
-
function waitOrAbort(waiting: Promise<void>, signal?: AbortSignal): Promise<void> {
|
|
390
|
-
if (signal === undefined) return waiting;
|
|
391
|
-
if (signal.aborted) {
|
|
392
|
-
void waiting.catch(() => {});
|
|
393
|
-
return Promise.reject(abortReason(signal));
|
|
394
|
-
}
|
|
395
|
-
return new Promise<void>((resolve, reject) => {
|
|
396
|
-
const onAbort = () => {
|
|
397
|
-
cleanup();
|
|
398
|
-
void waiting.catch(() => {});
|
|
399
|
-
reject(abortReason(signal));
|
|
400
|
-
};
|
|
401
|
-
const onSettled = (error?: unknown) => {
|
|
402
|
-
cleanup();
|
|
403
|
-
if (error === undefined) resolve();
|
|
404
|
-
else reject(error);
|
|
405
|
-
};
|
|
406
|
-
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
407
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
408
|
-
waiting.then(() => onSettled(), (error: unknown) => onSettled(error));
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Runs a request, retrying per the policy.
|
|
414
|
-
*
|
|
415
|
-
* `onRetry` exists so the caller can say something. A retry that produces no
|
|
416
|
-
* output is indistinguishable from a hang, which is the problem this was
|
|
417
|
-
* supposed to solve rather than one to introduce.
|
|
418
|
-
*
|
|
419
|
-
* `signal` is per-request, not per-policy: aborting one send must not
|
|
420
|
-
* poison the adapter. An already-aborted signal does not start an attempt,
|
|
421
|
-
* and an abort during backoff does not wait out the delay -- both would
|
|
422
|
-
* turn Escape into "wait, then maybe stop".
|
|
423
|
-
*/
|
|
424
|
-
export async function withRetry<T>(
|
|
425
|
-
request: () => Promise<T>,
|
|
426
|
-
policy: RetryPolicy = DEFAULT_RETRY,
|
|
427
|
-
fx: RetryEffects = DEFAULT_RETRY_EFFECTS,
|
|
428
|
-
onRetry?: (attempt: number, delayMs: number, error: unknown) => void,
|
|
429
|
-
signal?: AbortSignal,
|
|
430
|
-
): Promise<T> {
|
|
431
|
-
const effects = fx ?? DEFAULT_RETRY_EFFECTS;
|
|
432
|
-
for (let attempt = 0; ; attempt++) {
|
|
433
|
-
if (signal?.aborted) throw abortReason(signal);
|
|
434
|
-
try {
|
|
435
|
-
return await request();
|
|
436
|
-
} catch (error) {
|
|
437
|
-
// Checked here rather than inside retryDecision: abort is a user
|
|
438
|
-
// decision, not a status code, and a fetch abort that surfaces as
|
|
439
|
-
// TypeError must not start another wait.
|
|
440
|
-
if (signal?.aborted) throw abortReason(signal);
|
|
441
|
-
const decision = retryDecision(error, attempt, policy, effects.jitter());
|
|
442
|
-
if (!decision.retry) throw error;
|
|
443
|
-
onRetry?.(attempt + 1, decision.delayMs, error);
|
|
444
|
-
await waitOrAbort(effects.sleep(decision.delayMs, signal), signal);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/** Shared diff preparation for model tools and user review commands; no provider calls. */
|
|
2
|
-
import type { FileChanges } from "../core/checkpoints.ts";
|
|
3
|
-
import type { ReviewScope } from "../core/review.ts";
|
|
4
|
-
import { failureFromError, READ_ATTEMPTED } from "../core/tool-failure.ts";
|
|
5
|
-
import { readReview, readCheckpointReview } from "../store/review.ts";
|
|
6
|
-
import { presentObservation, type ToolContext, type ToolDefinition } from "./tools.ts";
|
|
7
|
-
|
|
8
|
-
export interface LastTurnReview { blobs: string; changes: FileChanges; limitations: string[] }
|
|
9
|
-
export interface DiffToolOptions {
|
|
10
|
-
lastTurn?: (context: ToolContext) => Promise<LastTurnReview>;
|
|
11
|
-
/** Current resource policy must be checked before diff bytes enter an artifact or model output. */
|
|
12
|
-
canRead?: (context: ToolContext, path: string) => Promise<boolean>;
|
|
13
|
-
}
|
|
14
|
-
export function parseReviewScope(input: Record<string, unknown>): ReviewScope {
|
|
15
|
-
const kind = input["scope"] ?? "working";
|
|
16
|
-
if (kind === "working" || kind === "last-turn") {
|
|
17
|
-
if (input["base"] !== undefined || input["commit"] !== undefined) throw new Error(`${kind} scope accepts no revision`);
|
|
18
|
-
return { kind };
|
|
19
|
-
}
|
|
20
|
-
if (kind === "branch" && typeof input["base"] === "string" && input["base"].trim() && input["commit"] === undefined) return { kind, base: input["base"] };
|
|
21
|
-
if (kind === "commit" && typeof input["commit"] === "string" && input["commit"].trim() && input["base"] === undefined) return { kind, commit: input["commit"] };
|
|
22
|
-
throw new Error("review scope must be working, branch with base, commit with commit, or last-turn");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function createDiffTool(options: DiffToolOptions = {}): ToolDefinition {
|
|
26
|
-
return {
|
|
27
|
-
name: "diff", access: "read", execution: "parallel", branch: "read",
|
|
28
|
-
description: "Read a unified diff for current staged/unstaged/untracked changes, a branch merge base, a selected commit, or durable checkpoints in the last user turn. Fixed Git argv disables external diff/textconv drivers and confines paths to the workspace. Reports uncaptured or unavailable data explicitly. This observes changes; it does not change files or run a model review.",
|
|
29
|
-
input_schema: { type: "object", required: [], properties: { scope: { type: "string", enum: ["working", "branch", "commit", "last-turn"] }, base: { type: "string" }, commit: { type: "string" } } },
|
|
30
|
-
run: async (ctx, input) => {
|
|
31
|
-
try {
|
|
32
|
-
const scope = parseReviewScope(input);
|
|
33
|
-
const canRead = options.canRead === undefined ? undefined : (path: string) => options.canRead!(ctx, path);
|
|
34
|
-
const snapshot = scope.kind === "last-turn" ? await (async () => {
|
|
35
|
-
if (!options.lastTurn) throw new Error("last-turn diff requires original session checkpoint history");
|
|
36
|
-
const original = await options.lastTurn(ctx);
|
|
37
|
-
return readCheckpointReview(original.blobs, original.changes, original.limitations, canRead);
|
|
38
|
-
})() : await readReview(ctx.root, scope, ctx.signal, canRead);
|
|
39
|
-
const notices = snapshot.limitations.length ? "\nLimitations:\n" + snapshot.limitations.map(line => "- " + line).join("\n") : "";
|
|
40
|
-
return { ...await presentObservation(ctx, `${snapshot.label}\n${snapshot.diff || "(no captured changes)"}${notices}`), ok: true,
|
|
41
|
-
data: { scope: scope.kind, paths: snapshot.paths, limitations: snapshot.limitations, ...(snapshot.base === undefined ? {} : { base: snapshot.base }) } };
|
|
42
|
-
} catch (error) {
|
|
43
|
-
const diagnostic = failureFromError(error, READ_ATTEMPTED);
|
|
44
|
-
return { ok: false, content: diagnostic.message, diagnostic };
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
}
|
package/src/runtime/rich-text.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/** Ported from Pi (MIT), theme.ts and components/mermaid.ts at 02eed88; only presentation, never log rewriting. */
|
|
2
|
-
import { Marked } from "marked";
|
|
3
|
-
import { render } from "grok-mermaid";
|
|
4
|
-
import { highlight, supportsLanguage, loadAllHighlightLanguages } from "../vendor/pi-rich/syntax-highlight.ts";
|
|
5
|
-
|
|
6
|
-
type Paint = (token: string, text: string) => string;
|
|
7
|
-
export const warmSyntaxLanguages = loadAllHighlightLanguages;
|
|
8
|
-
export function highlightCode(code: string, language: string | undefined, paint: Paint): string[] {
|
|
9
|
-
if (!language || !supportsLanguage(language)) return code.split("\n").map(line => paint("mdCodeBlock", line));
|
|
10
|
-
const tokens: Record<string, string> = { keyword: "Keyword", built_in: "Function", title: "Function", function: "Function",
|
|
11
|
-
string: "String", regexp: "String", number: "Number", literal: "Number", type: "Type", class: "Type",
|
|
12
|
-
variable: "Variable", params: "Variable", attr: "Variable", attribute: "Variable", symbol: "Variable",
|
|
13
|
-
comment: "Comment", meta: "Comment", operator: "Operator", punctuation: "Punctuation" };
|
|
14
|
-
try { return highlight(code, { language, ignoreIllegals: true,
|
|
15
|
-
theme: Object.fromEntries(Object.entries(tokens).map(([scope, token]) => [scope, (text: string) => paint("syntax" + token, text)])),
|
|
16
|
-
}).split("\n"); } catch { return code.split("\n"); }
|
|
17
|
-
}
|
|
18
|
-
const parser = new Marked();
|
|
19
|
-
function codeSpan(line: string): string {
|
|
20
|
-
const content = line || "\u00a0";
|
|
21
|
-
const longest = Math.max(0, ...Array.from(content.matchAll(/`+/g), match => match[0].length));
|
|
22
|
-
const fence = "`".repeat(longest + 1), padding = content.startsWith("`") || content.endsWith("`") ? " " : "";
|
|
23
|
-
return `${fence}${padding}${content}${padding}${fence}`;
|
|
24
|
-
}
|
|
25
|
-
export function transformMermaid(markdown: string, width: number, mode: "off" | "final" | "streaming", streaming: boolean, paint: Paint): string {
|
|
26
|
-
if (mode === "off" || streaming && mode !== "streaming") return markdown;
|
|
27
|
-
try { return parser.lexer(markdown).map(token => {
|
|
28
|
-
if (token.type !== "code" || token.lang?.trim().split(/\s+/, 1)[0]?.toLowerCase() !== "mermaid") return token.raw;
|
|
29
|
-
// Incomplete stream fences remain source; rendering half a graph would flicker and lose error context.
|
|
30
|
-
if (streaming && !/\n\s*(`{3,}|~{3,})\s*$/.test(token.raw.trimEnd())) return token.raw;
|
|
31
|
-
const art = render(token.text);
|
|
32
|
-
if (!art || art.width > width || art.warnings.length) return token.raw;
|
|
33
|
-
const tokens: Record<string, string> = { border: "borderMuted", text: "text", edge: "accent", edgeLabel: "muted", title: "accent" };
|
|
34
|
-
return art.styled.map(row => codeSpan(row.map(span => paint(tokens[span.cls] ?? "text", span.text)).join(""))).join(" \n") + "\n";
|
|
35
|
-
}).join(""); } catch { return markdown; }
|
|
36
|
-
}
|