@visual-z/xeno 0.8.4 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +31 -746
- package/bin/xeno.exe +26 -0
- package/install.cjs +88 -0
- package/package.json +18 -48
- package/docs/app-server.md +0 -115
- package/docs/foundations.md +0 -88
- package/docs/install.md +0 -244
- package/docs/providers.md +0 -172
- package/docs/research/foundation-clients-2026-10-09.md +0 -27
- package/docs/research/foundation-editing-2026-10-09.md +0 -56
- package/docs/research/foundation-network-2026-10-09.md +0 -37
- package/docs/research/foundation-workflow-2026-10-09.md +0 -37
- package/src/apps/acp-server.ts +0 -41
- package/src/apps/agent-profiles.ts +0 -19
- package/src/apps/app-server.ts +0 -30
- package/src/apps/approve.ts +0 -49
- package/src/apps/cli.ts +0 -99
- package/src/apps/commands.ts +0 -319
- package/src/apps/completions.ts +0 -87
- package/src/apps/control-transport.ts +0 -90
- package/src/apps/export-cli.ts +0 -24
- package/src/apps/goal-flow.ts +0 -70
- package/src/apps/image-draft.ts +0 -46
- package/src/apps/input.ts +0 -312
- package/src/apps/model-catalog.ts +0 -69
- package/src/apps/preferences.ts +0 -20
- package/src/apps/project-trust.ts +0 -9
- package/src/apps/provider-connect.ts +0 -346
- package/src/apps/question-ui.ts +0 -27
- package/src/apps/render.ts +0 -181
- package/src/apps/resources.ts +0 -66
- package/src/apps/review.ts +0 -109
- package/src/apps/session-hub.ts +0 -96
- package/src/apps/session-lifecycle.ts +0 -64
- package/src/apps/shell.ts +0 -2071
- package/src/apps/startup-input.ts +0 -135
- package/src/apps/startup.ts +0 -1677
- package/src/apps/ui/app.ts +0 -1818
- package/src/apps/ui/control-model.ts +0 -43
- package/src/apps/ui/extension-host.ts +0 -194
- package/src/apps/ui/image-cache.ts +0 -121
- package/src/apps/ui/palette.ts +0 -88
- package/src/apps/ui/project.ts +0 -127
- package/src/apps/ui/standalone.ts +0 -148
- package/src/apps/ui/status-model.ts +0 -68
- package/src/apps/ui/surface.ts +0 -91
- package/src/apps/ui/theme.ts +0 -86
- package/src/apps/update-check.ts +0 -112
- package/src/apps/workflow.ts +0 -58
- package/src/core/acceptance.ts +0 -139
- package/src/core/action-defaults.ts +0 -58
- package/src/core/activity-status.ts +0 -52
- package/src/core/agent-profile.ts +0 -130
- package/src/core/attachment-input.ts +0 -17
- package/src/core/blind-checks.ts +0 -72
- package/src/core/boundary.ts +0 -174
- package/src/core/branch-receipt.ts +0 -26
- package/src/core/catalog-merge.ts +0 -19
- package/src/core/checkpoints.ts +0 -85
- package/src/core/churn.ts +0 -61
- package/src/core/command-artifacts.ts +0 -94
- package/src/core/command-contract.ts +0 -151
- package/src/core/completion-action.ts +0 -20
- package/src/core/completion-review.ts +0 -98
- package/src/core/context-detail.ts +0 -37
- package/src/core/context-policy.ts +0 -133
- package/src/core/context-window.ts +0 -54
- package/src/core/continuation.ts +0 -130
- package/src/core/control-protocol.ts +0 -69
- package/src/core/control-view.ts +0 -39
- package/src/core/cycle.ts +0 -17
- package/src/core/data-query.ts +0 -201
- package/src/core/deadline.ts +0 -40
- package/src/core/derive.ts +0 -516
- package/src/core/draft-images.ts +0 -56
- package/src/core/edit-plan.ts +0 -200
- package/src/core/environment.ts +0 -181
- package/src/core/event-validation.ts +0 -220
- package/src/core/events.ts +0 -670
- package/src/core/evidence-ladder.ts +0 -62
- package/src/core/evidence-links.ts +0 -68
- package/src/core/evidence.ts +0 -240
- package/src/core/execution.ts +0 -18
- package/src/core/exhaustive.ts +0 -18
- package/src/core/export.ts +0 -179
- package/src/core/extension-flags.ts +0 -14
- package/src/core/external-effects.ts +0 -488
- package/src/core/failure-recovery.ts +0 -173
- package/src/core/file-precondition.ts +0 -78
- package/src/core/file-references.ts +0 -35
- package/src/core/frontmatter.ts +0 -213
- package/src/core/git-status.ts +0 -29
- package/src/core/goal-view.ts +0 -64
- package/src/core/goal.ts +0 -200
- package/src/core/handoff.ts +0 -479
- package/src/core/history-query.ts +0 -139
- package/src/core/history.ts +0 -237
- package/src/core/image-limits.ts +0 -72
- package/src/core/images.ts +0 -46
- package/src/core/integration-health.ts +0 -34
- package/src/core/json-event.ts +0 -131
- package/src/core/json-pointer.ts +0 -46
- package/src/core/keymap.ts +0 -153
- package/src/core/lsp-framing.ts +0 -34
- package/src/core/mcp-config.ts +0 -68
- package/src/core/model-picker.ts +0 -64
- package/src/core/model-preferences.ts +0 -28
- package/src/core/model-replay.ts +0 -22
- package/src/core/model-scope.ts +0 -53
- package/src/core/note-guidance.ts +0 -34
- package/src/core/oauth-fields.ts +0 -44
- package/src/core/observation-view.ts +0 -152
- package/src/core/outcome.ts +0 -44
- package/src/core/overflow.ts +0 -59
- package/src/core/packages.ts +0 -47
- package/src/core/patch.ts +0 -130
- package/src/core/path-suggestions.ts +0 -59
- package/src/core/permissions.ts +0 -410
- package/src/core/process-receipt.ts +0 -105
- package/src/core/process-state.ts +0 -37
- package/src/core/program-status.ts +0 -40
- package/src/core/progress.ts +0 -150
- package/src/core/project.ts +0 -431
- package/src/core/prompt-policy.ts +0 -46
- package/src/core/prompts.ts +0 -111
- package/src/core/question.ts +0 -94
- package/src/core/queue.ts +0 -78
- package/src/core/read-bounds.ts +0 -24
- package/src/core/read-observation.ts +0 -106
- package/src/core/recovery-sources.ts +0 -74
- package/src/core/regex-dialect.ts +0 -25
- package/src/core/request-budget.ts +0 -23
- package/src/core/request-progress.ts +0 -87
- package/src/core/request-state.ts +0 -273
- package/src/core/resource-selection.ts +0 -36
- package/src/core/response-recovery.ts +0 -59
- package/src/core/resume.ts +0 -59
- package/src/core/review.ts +0 -68
- package/src/core/ripgrep-release.ts +0 -64
- package/src/core/runtime-target.ts +0 -94
- package/src/core/scheduling.ts +0 -8
- package/src/core/search-observation.ts +0 -97
- package/src/core/secrets.ts +0 -188
- package/src/core/session-archive.ts +0 -4
- package/src/core/session-browser.ts +0 -82
- package/src/core/session-entry.ts +0 -61
- package/src/core/session-exit.ts +0 -42
- package/src/core/session-format.ts +0 -169
- package/src/core/session-position.ts +0 -26
- package/src/core/session-tree.ts +0 -147
- package/src/core/settings.ts +0 -192
- package/src/core/skills.ts +0 -165
- package/src/core/sql-query.ts +0 -211
- package/src/core/stall.ts +0 -128
- package/src/core/stop-report.ts +0 -68
- package/src/core/text-normalize.ts +0 -81
- package/src/core/theme.ts +0 -166
- package/src/core/thinking.ts +0 -26
- package/src/core/tokens.ts +0 -151
- package/src/core/tool-contract.ts +0 -153
- package/src/core/tool-discovery.ts +0 -87
- package/src/core/tool-failure.ts +0 -81
- package/src/core/tool-input.ts +0 -44
- package/src/core/transcript.ts +0 -108
- package/src/core/tui-mode.ts +0 -29
- package/src/core/update-check.ts +0 -157
- package/src/core/version.ts +0 -55
- package/src/core/web-content.ts +0 -80
- package/src/core/work-state.ts +0 -154
- package/src/core/workflow-mode.ts +0 -34
- package/src/core/working-set.ts +0 -119
- package/src/core/yaml.ts +0 -282
- package/src/runtime/acp-agent.ts +0 -289
- package/src/runtime/agent-profiles.ts +0 -21
- package/src/runtime/auth.ts +0 -367
- package/src/runtime/blind-check.ts +0 -36
- package/src/runtime/branch-jobs.ts +0 -36
- package/src/runtime/catalog.ts +0 -285
- package/src/runtime/clipboard.ts +0 -125
- package/src/runtime/command-artifacts.ts +0 -64
- package/src/runtime/connection-reuse.ts +0 -40
- package/src/runtime/control-client.ts +0 -149
- package/src/runtime/control-connection.ts +0 -139
- package/src/runtime/counter-check.ts +0 -74
- package/src/runtime/environment-tools.ts +0 -60
- package/src/runtime/evidence-notes.ts +0 -35
- package/src/runtime/exec-sessions.ts +0 -456
- package/src/runtime/execution-control.ts +0 -40
- package/src/runtime/extension-loader.ts +0 -37
- package/src/runtime/extension-ui.ts +0 -47
- package/src/runtime/extensions.ts +0 -370
- package/src/runtime/formatter-tool.ts +0 -38
- package/src/runtime/formatter.ts +0 -50
- package/src/runtime/foundation.ts +0 -116
- package/src/runtime/history-cache.ts +0 -57
- package/src/runtime/history-tools.ts +0 -168
- package/src/runtime/images.ts +0 -80
- package/src/runtime/integrations.ts +0 -216
- package/src/runtime/loop.ts +0 -3108
- package/src/runtime/lsp-client.ts +0 -201
- package/src/runtime/mcp-client.ts +0 -264
- package/src/runtime/mcp-oauth.ts +0 -168
- package/src/runtime/mcp-schema.ts +0 -51
- package/src/runtime/model-config.ts +0 -232
- package/src/runtime/model-data.LICENSE +0 -27
- package/src/runtime/model-data.ts +0 -21
- package/src/runtime/model-discovery.ts +0 -208
- package/src/runtime/model-replay.ts +0 -10
- package/src/runtime/model-scope.ts +0 -20
- package/src/runtime/model-sources.ts +0 -151
- package/src/runtime/model.ts +0 -686
- package/src/runtime/models-fetch.ts +0 -57
- package/src/runtime/oauth-device.ts +0 -86
- package/src/runtime/oauth.ts +0 -853
- package/src/runtime/open-image.ts +0 -42
- package/src/runtime/openai-responses.ts +0 -304
- package/src/runtime/openai.ts +0 -697
- package/src/runtime/output-schema.ts +0 -26
- package/src/runtime/package-manager.ts +0 -125
- package/src/runtime/patch-tool.ts +0 -38
- package/src/runtime/permission-resources.ts +0 -18
- package/src/runtime/pi-provider.ts +0 -423
- package/src/runtime/program-status.ts +0 -38
- package/src/runtime/progress.ts +0 -26
- package/src/runtime/provider-access.ts +0 -21
- package/src/runtime/provider-connect.ts +0 -109
- package/src/runtime/provider-presets.ts +0 -98
- package/src/runtime/provider-protocols.ts +0 -29
- package/src/runtime/provider-request.ts +0 -87
- package/src/runtime/provider-tool-policy.ts +0 -33
- package/src/runtime/provider.ts +0 -580
- package/src/runtime/question-tool.ts +0 -62
- package/src/runtime/retry.ts +0 -447
- package/src/runtime/review-tool.ts +0 -48
- package/src/runtime/rich-text.ts +0 -36
- package/src/runtime/runtime-target.ts +0 -149
- package/src/runtime/secrets.ts +0 -79
- package/src/runtime/self-update.ts +0 -74
- package/src/runtime/session-control.ts +0 -265
- package/src/runtime/session-hub.ts +0 -85
- package/src/runtime/stream-idle.ts +0 -80
- package/src/runtime/tool-broker.ts +0 -137
- package/src/runtime/tool-discovery.ts +0 -58
- package/src/runtime/tool-queue.ts +0 -77
- package/src/runtime/tools.ts +0 -1820
- package/src/runtime/turn-effects.ts +0 -31
- package/src/runtime/update-check.ts +0 -48
- package/src/runtime/usage.ts +0 -253
- package/src/runtime/version.ts +0 -12
- package/src/runtime/web-http.ts +0 -56
- package/src/runtime/web-tools.ts +0 -88
- package/src/runtime/working-notes.ts +0 -60
- package/src/runtime/workspace-lifecycle.ts +0 -141
- package/src/store/acceptance.ts +0 -15
- package/src/store/agent-profile-files.ts +0 -13
- package/src/store/atomic.ts +0 -31
- package/src/store/auth-store.ts +0 -50
- package/src/store/blind-checks.ts +0 -32
- package/src/store/blobs.ts +0 -143
- package/src/store/checkpoint-history.ts +0 -32
- package/src/store/command-artifacts.ts +0 -96
- package/src/store/command.ts +0 -111
- package/src/store/config.ts +0 -69
- package/src/store/context-files.ts +0 -436
- package/src/store/file-checkpoints.ts +0 -108
- package/src/store/file-index.ts +0 -174
- package/src/store/foundation-config.ts +0 -24
- package/src/store/git-checkpoints.ts +0 -67
- package/src/store/git-status.ts +0 -15
- package/src/store/history-search.ts +0 -19
- package/src/store/log.ts +0 -725
- package/src/store/lsp-document.ts +0 -30
- package/src/store/mcp-credentials.ts +0 -85
- package/src/store/models.ts +0 -60
- package/src/store/observations.ts +0 -224
- package/src/store/packages.ts +0 -98
- package/src/store/patch.ts +0 -92
- package/src/store/permission-paths.ts +0 -34
- package/src/store/process-receipts.ts +0 -34
- package/src/store/query-source.ts +0 -49
- package/src/store/recovery.ts +0 -44
- package/src/store/replay.ts +0 -76
- package/src/store/resource-index.ts +0 -170
- package/src/store/resources.ts +0 -285
- package/src/store/restore.ts +0 -110
- package/src/store/review.ts +0 -109
- package/src/store/runtime-target.ts +0 -64
- package/src/store/search.ts +0 -728
- package/src/store/secrets.ts +0 -44
- package/src/store/serial.ts +0 -13
- package/src/store/session-archive.ts +0 -165
- package/src/store/session-browser.ts +0 -68
- package/src/store/session-position.ts +0 -27
- package/src/store/sessions.ts +0 -525
- package/src/store/source-cancellation.ts +0 -6
- package/src/store/tool-binaries.ts +0 -90
- package/src/store/trust.ts +0 -82
- package/src/store/update-cache.ts +0 -36
- package/src/store/user-settings.ts +0 -30
- package/src/store/workspace-clone.ts +0 -42
- package/src/store/workspace-observation.ts +0 -113
- package/src/store/workspace.ts +0 -115
- package/src/store/worktree.ts +0 -278
- package/src/tui/ansi-spans.ts +0 -126
- package/src/tui/diff.ts +0 -160
- package/src/tui/exhaustive.ts +0 -14
- package/src/tui/fuzzy.ts +0 -75
- package/src/tui/style.ts +0 -208
- package/src/tui/ui/atomic-draft.ts +0 -121
- package/src/tui/ui/branch-panel.ts +0 -47
- package/src/tui/ui/canvas.ts +0 -96
- package/src/tui/ui/colors.ts +0 -70
- package/src/tui/ui/composer.ts +0 -199
- package/src/tui/ui/control-panel.ts +0 -25
- package/src/tui/ui/control-sidebar.ts +0 -49
- package/src/tui/ui/dialog.ts +0 -398
- package/src/tui/ui/draft.ts +0 -23
- package/src/tui/ui/duration.ts +0 -16
- package/src/tui/ui/edge.ts +0 -186
- package/src/tui/ui/image-size.ts +0 -44
- package/src/tui/ui/ink.ts +0 -210
- package/src/tui/ui/items.ts +0 -42
- package/src/tui/ui/keys.ts +0 -40
- package/src/tui/ui/layout.ts +0 -74
- package/src/tui/ui/list-model.ts +0 -86
- package/src/tui/ui/list-view.ts +0 -172
- package/src/tui/ui/mascot.ts +0 -67
- package/src/tui/ui/messages.ts +0 -518
- package/src/tui/ui/motion.ts +0 -28
- package/src/tui/ui/paths.ts +0 -10
- package/src/tui/ui/pictures.ts +0 -108
- package/src/tui/ui/prompt-history.ts +0 -60
- package/src/tui/ui/screen.ts +0 -579
- package/src/tui/ui/status.ts +0 -403
- package/src/tui/ui/styled.ts +0 -85
- package/src/tui/ui/tool-card.ts +0 -150
- package/src/tui/width.ts +0 -567
- package/src/vendor/pi-rich/LICENSE.upstream +0 -21
- package/src/vendor/pi-rich/UPSTREAM.json +0 -17
- package/src/vendor/pi-rich/clipboard-command.ts +0 -44
- package/src/vendor/pi-rich/exif-orientation.ts +0 -182
- package/src/vendor/pi-rich/html.ts +0 -51
- package/src/vendor/pi-rich/image-convert.ts +0 -49
- package/src/vendor/pi-rich/image-resize-core.ts +0 -164
- package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
- package/src/vendor/pi-rich/image-resize.ts +0 -123
- package/src/vendor/pi-rich/mime.ts +0 -116
- package/src/vendor/pi-rich/photon.ts +0 -139
- package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* How a bounded observation is presented to the model.
|
|
3
|
-
*
|
|
4
|
-
* The complete bytes always live in a blob (D8: reference, never inline), so
|
|
5
|
-
* this is only a view -- but it is the view the model reasons from. Keeping
|
|
6
|
-
* only the head, as this module's predecessor did, cuts exactly the part a
|
|
7
|
-
* failing build or test run prints last: the error and the summary. Codex
|
|
8
|
-
* keeps half of each end (`utils/string/src/truncate.rs`); so does this.
|
|
9
|
-
*/
|
|
10
|
-
import type { ArtifactRef } from "./events.ts";
|
|
11
|
-
|
|
12
|
-
export interface BytePreview {
|
|
13
|
-
unit: "utf8_bytes";
|
|
14
|
-
complete: boolean;
|
|
15
|
-
visible: readonly (readonly [number, number])[];
|
|
16
|
-
next_offset: number | null;
|
|
17
|
-
}
|
|
18
|
-
const object = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19
|
-
const natural = (value: unknown): value is number => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
20
|
-
export function isObservationArtifact(value: unknown): value is ArtifactRef {
|
|
21
|
-
return object(value) && typeof value["sha256"] === "string" && /^[a-f0-9]{64}$/.test(value["sha256"]) && typeof value["path"] === "string" && natural(value["bytes"]);
|
|
22
|
-
}
|
|
23
|
-
/** The same coverage invariant serves file reads and search projections. */
|
|
24
|
-
export function isBytePreview(value: unknown, total: number): value is BytePreview {
|
|
25
|
-
if (!object(value) || value["unit"] !== "utf8_bytes" || typeof value["complete"] !== "boolean" || !Array.isArray(value["visible"])) return false;
|
|
26
|
-
let last = 0, prefix = 0;
|
|
27
|
-
for (const range of value["visible"]) {
|
|
28
|
-
if (!Array.isArray(range) || range.length !== 2 || !natural(range[0]) || !natural(range[1]) || range[0] < last || range[1] <= range[0] || range[1] > total) return false;
|
|
29
|
-
if (prefix === range[0]) prefix = range[1];
|
|
30
|
-
last = range[1];
|
|
31
|
-
}
|
|
32
|
-
return value["complete"] === (prefix === total) && value["next_offset"] === (value["complete"] ? null : prefix);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** A UTF-8 continuation byte: cutting before one would split a character. */
|
|
36
|
-
const continuation = (byte: number | undefined) => byte !== undefined && (byte & 0xc0) === 0x80;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Head and tail of an observation within `limit` bytes.
|
|
40
|
-
*
|
|
41
|
-
* `head` is the first bytes and `tail` the last bytes of an observation of
|
|
42
|
-
* `total` bytes; each need only be as long as `limit`. When the whole
|
|
43
|
-
* observation fits, it is returned unchanged. Otherwise the marker, which is
|
|
44
|
-
* given the omitted byte count and the byte offset where the gap starts (so
|
|
45
|
-
* the model can page the missing middle back), sits between the two ends.
|
|
46
|
-
* Cuts land on character boundaries, which is why a view can come out a few
|
|
47
|
-
* bytes under budget but never over it for valid UTF-8 input.
|
|
48
|
-
*/
|
|
49
|
-
export function headTailView(
|
|
50
|
-
head: Uint8Array,
|
|
51
|
-
tail: Uint8Array,
|
|
52
|
-
total: number,
|
|
53
|
-
limit: number,
|
|
54
|
-
marker: (omitted: number, offset: number) => string,
|
|
55
|
-
): string {
|
|
56
|
-
// Legacy command callers require the recovery locator even below its minimum display budget.
|
|
57
|
-
return headTailSelection(head, tail, total, limit, marker, true).text;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Half-open byte ranges in the original observation, excluding presentation markers. */
|
|
61
|
-
export function headTailSelection(
|
|
62
|
-
head: Uint8Array, tail: Uint8Array, total: number, limit: number,
|
|
63
|
-
marker: (omitted: number, offset: number) => string, allowOversizeMarker = false,
|
|
64
|
-
): { text: string; visible: [number, number][] } {
|
|
65
|
-
const decoder = new TextDecoder();
|
|
66
|
-
if (total <= limit && head.length >= total) return { text: decoder.decode(head.subarray(0, total)), visible: total ? [[0, total]] : [] };
|
|
67
|
-
// Digits of the real numbers never exceed digits of `total`, so this bounds
|
|
68
|
-
// the marker before the cut points that decide its numbers are known.
|
|
69
|
-
const reserve = new TextEncoder().encode(marker(total, total)).length;
|
|
70
|
-
if (reserve > limit && !allowOversizeMarker) {
|
|
71
|
-
const bytes = new TextEncoder().encode(marker(total, 0));
|
|
72
|
-
let end = Math.max(0, Math.min(Math.floor(limit), bytes.length));
|
|
73
|
-
while (end > 0 && continuation(bytes[end])) end--;
|
|
74
|
-
return { text: decoder.decode(bytes.subarray(0, end)), visible: [] };
|
|
75
|
-
}
|
|
76
|
-
const budget = Math.max(0, limit - reserve);
|
|
77
|
-
let headEnd = Math.min(Math.floor(budget / 2), head.length);
|
|
78
|
-
while (headEnd > 0 && continuation(head[headEnd])) headEnd--;
|
|
79
|
-
let tailStart = tail.length - Math.min(budget - headEnd, tail.length);
|
|
80
|
-
while (tailStart < tail.length && continuation(tail[tailStart])) tailStart++;
|
|
81
|
-
const kept = tail.length - tailStart;
|
|
82
|
-
return { text: decoder.decode(head.subarray(0, headEnd)) +
|
|
83
|
-
marker(total - headEnd - kept, headEnd) +
|
|
84
|
-
decoder.decode(tail.subarray(tailStart)),
|
|
85
|
-
visible: [...(headEnd ? [[0, headEnd] as [number, number]] : []), ...(kept ? [[total - kept, total] as [number, number]] : [])] };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* The text a terminal would finally show for output that redraws lines with
|
|
90
|
-
* `\r` (progress bars). Within each `\n`-separated line, a carriage return
|
|
91
|
-
* moves back to column 0 and the following characters overwrite, one code
|
|
92
|
-
* point each; characters not overwritten stay. `\r\n` is thus a plain line
|
|
93
|
-
* break. Other control sequences (`ESC[K` and the like) are left as they are.
|
|
94
|
-
*
|
|
95
|
-
* A view only: the archived bytes keep every frame (D8). Without this a 6.3KB
|
|
96
|
-
* brew download of 48 frames reached the model whole (carriage-return-view
|
|
97
|
-
* card), though a terminal showed one line.
|
|
98
|
-
*/
|
|
99
|
-
export function foldCarriageReturns(text: string): string {
|
|
100
|
-
if (!text.includes("\r")) return text;
|
|
101
|
-
return text.split("\n").map(line => {
|
|
102
|
-
if (!line.includes("\r")) return line;
|
|
103
|
-
let screen: string[] = [];
|
|
104
|
-
for (const frame of line.split("\r")) {
|
|
105
|
-
const chars = Array.from(frame);
|
|
106
|
-
screen = chars.concat(screen.slice(chars.length));
|
|
107
|
-
}
|
|
108
|
-
return screen.join("");
|
|
109
|
-
}).join("\n");
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** Signals a shell reports as 128+n; the common ones a model can act on. */
|
|
113
|
-
const SIGNALS: Readonly<Record<number, string>> = {
|
|
114
|
-
1: "SIGHUP", 2: "SIGINT", 6: "SIGABRT", 9: "SIGKILL", 11: "SIGSEGV", 13: "SIGPIPE", 15: "SIGTERM",
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const seconds = (ms: number) => `${Number((ms / 1000).toFixed(1))}s`;
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* First line of a command result.
|
|
121
|
-
*
|
|
122
|
-
* `exit N` stays first because both the TUI and the log readers key on it.
|
|
123
|
-
* What follows states facts the exit code alone hides: a timeout reads as
|
|
124
|
-
* `exit 143`, which a model otherwise takes for the program's own failure and
|
|
125
|
-
* reruns unchanged (codex puts wall time and exit in the same header,
|
|
126
|
-
* `core/src/tools/context.rs`).
|
|
127
|
-
*/
|
|
128
|
-
export function commandHeader(input: {
|
|
129
|
-
exit: number;
|
|
130
|
-
wallMs: number;
|
|
131
|
-
stopped?: "timeout" | "cancelled";
|
|
132
|
-
/** The timeout actually applied. */
|
|
133
|
-
timeoutMs: number;
|
|
134
|
-
/** What the model asked for, when that differs from what was applied. */
|
|
135
|
-
requestedMs?: number;
|
|
136
|
-
managed?: boolean;
|
|
137
|
-
}): string {
|
|
138
|
-
const parts = [`exit ${input.exit}`, `${(input.wallMs / 1000).toFixed(1)}s`];
|
|
139
|
-
if (input.stopped === "timeout") {
|
|
140
|
-
const requested = input.requestedMs !== undefined && input.requestedMs !== input.timeoutMs
|
|
141
|
-
? ` (requested ${seconds(input.requestedMs)}, capped)` : "";
|
|
142
|
-
// A deadline requests cleanup, but cannot by itself prove absence of
|
|
143
|
-
// partial side effects or successful cleanup (R3 / R10).
|
|
144
|
-
parts.push(`timed out after ${seconds(input.timeoutMs)}${requested}; process group cleanup requested; ` +
|
|
145
|
-
"inspect partial effects and cleanup before deciding on a new invocation or a larger timeout");
|
|
146
|
-
} else if (input.stopped === "cancelled") {
|
|
147
|
-
parts.push("cancelled; process group cleanup requested");
|
|
148
|
-
} else if (input.exit > 128 && SIGNALS[input.exit - 128] !== undefined) {
|
|
149
|
-
parts.push(`128+${input.exit - 128}: likely terminated by ${SIGNALS[input.exit - 128]}`);
|
|
150
|
-
}
|
|
151
|
-
return parts.join(" · ");
|
|
152
|
-
}
|
package/src/core/outcome.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/** O.1 / D11: execution termination is a fact, distinct from verified work. */
|
|
2
|
-
const STATUS = {
|
|
3
|
-
model_end: "completed",
|
|
4
|
-
tool_completed: "completed",
|
|
5
|
-
cancelled: "cancelled",
|
|
6
|
-
not_started: "cancelled",
|
|
7
|
-
max_turns: "stopped",
|
|
8
|
-
context_budget: "stopped",
|
|
9
|
-
stalled: "stopped",
|
|
10
|
-
no_progress: "stopped",
|
|
11
|
-
output_limit: "stopped",
|
|
12
|
-
timeout: "stopped",
|
|
13
|
-
model_refusal: "failed",
|
|
14
|
-
provider_error: "failed",
|
|
15
|
-
execution_error: "failed",
|
|
16
|
-
projection_invalid: "failed",
|
|
17
|
-
tool_failed: "failed",
|
|
18
|
-
unknown: "unknown",
|
|
19
|
-
} as const;
|
|
20
|
-
|
|
21
|
-
export type ExecutionReason = keyof typeof STATUS;
|
|
22
|
-
export type ExecutionOutcome = {
|
|
23
|
-
status: (typeof STATUS)[ExecutionReason];
|
|
24
|
-
reason: ExecutionReason;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export function executionOutcome(reason: ExecutionReason): ExecutionOutcome {
|
|
28
|
-
return { status: STATUS[reason], reason };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Unknown/legacy provider reasons never silently turn into success. */
|
|
32
|
-
export function modelOutcome(reason: string | null): ExecutionOutcome {
|
|
33
|
-
if (reason === "end_turn" || reason === "stop" || reason === "stop_sequence") return executionOutcome("model_end");
|
|
34
|
-
if (reason === "max_tokens" || reason === "length") return executionOutcome("output_limit");
|
|
35
|
-
if (reason === "refusal" || reason === "content_filter") return executionOutcome("model_refusal");
|
|
36
|
-
if (reason === "error") return executionOutcome("provider_error");
|
|
37
|
-
return executionOutcome("unknown");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function isExecutionOutcome(value: unknown): value is ExecutionOutcome {
|
|
41
|
-
if (value === null || typeof value !== "object" || !("reason" in value) || !("status" in value)) return false;
|
|
42
|
-
return typeof value.reason === "string" && Object.hasOwn(STATUS, value.reason) &&
|
|
43
|
-
value.status === STATUS[value.reason as ExecutionReason];
|
|
44
|
-
}
|
package/src/core/overflow.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Whether a provider error means "the prompt does not fit the window".
|
|
3
|
-
*
|
|
4
|
-
* That failure needs a different remedy from every other one: retrying the
|
|
5
|
-
* same request fails the same way (so it must not be retried even when a
|
|
6
|
-
* relay reports it as a 5xx), and the only thing that makes it succeed is a
|
|
7
|
-
* smaller projection. Providers word it differently and none shares a code,
|
|
8
|
-
* so recognition is by message text.
|
|
9
|
-
*
|
|
10
|
-
* Patterns ported from pi-mono `packages/ai/src/utils/overflow.ts` (MIT,
|
|
11
|
-
* commit 6f75515); the provider each one came from is kept beside it. A
|
|
12
|
-
* provider not listed here surfaces as an ordinary error, which is the safe
|
|
13
|
-
* direction: a missed overflow ends the turn visibly, a false one would cut
|
|
14
|
-
* a healthy context.
|
|
15
|
-
*/
|
|
16
|
-
const OVERFLOW_PATTERNS: readonly RegExp[] = [
|
|
17
|
-
/prompt (?:is )?too long/i, // Anthropic, z.ai
|
|
18
|
-
/request_too_large/i, // Anthropic byte-size overflow (413)
|
|
19
|
-
/input is too long for requested model/i, // Amazon Bedrock
|
|
20
|
-
/exceeds the context window/i, // OpenAI (Completions & Responses)
|
|
21
|
-
/exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\d,]+ tokens?|\s*\([\d,]+\))/i, // OpenAI-compatible proxies
|
|
22
|
-
/input token count.*exceeds the maximum/i, // Google Gemini
|
|
23
|
-
/maximum prompt length is \d+/i, // xAI
|
|
24
|
-
/reduce the length of the messages/i, // Groq
|
|
25
|
-
/maximum context length is \d+ tokens/i, // OpenRouter
|
|
26
|
-
/exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i, // OpenRouter / Poolside
|
|
27
|
-
/input \(\d+ tokens\) is longer than the model'?s context length \(\d+ tokens\)/i, // Together AI
|
|
28
|
-
/exceeds the limit of \d+/i, // GitHub Copilot
|
|
29
|
-
/exceeds the available context size/i, // llama.cpp
|
|
30
|
-
/greater than the context length/i, // LM Studio
|
|
31
|
-
/context window exceeds limit/i, // MiniMax
|
|
32
|
-
/exceeded model token limit/i, // Kimi
|
|
33
|
-
/too large for model with \d+ maximum context length/i, // Mistral
|
|
34
|
-
/prompt has [\d,]+ tokens?, but the configured context size is [\d,]+ tokens?/i, // DS4
|
|
35
|
-
/model_context_window_exceeded/i, // z.ai finish reason surfaced as error text
|
|
36
|
-
/prompt too long; exceeded (?:max )?context length/i, // Ollama
|
|
37
|
-
/range of input length should be/i, // DashScope / Qwen
|
|
38
|
-
/context[_ ]length[_ ]exceeded/i, // generic
|
|
39
|
-
/too many tokens/i, // generic
|
|
40
|
-
/token limit exceeded/i, // generic
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
/** Throttling that happens to mention tokens; excluded even when a pattern above matches. */
|
|
44
|
-
const NOT_OVERFLOW: readonly RegExp[] = [
|
|
45
|
-
/^(Throttling error|Service unavailable):/i,
|
|
46
|
-
/throttl/i,
|
|
47
|
-
/rate limit/i,
|
|
48
|
-
/too many requests/i,
|
|
49
|
-
/please wait/i,
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
/** Cerebras answers an oversized request with a bodiless 400 / 413. */
|
|
53
|
-
const BODYLESS = /^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i;
|
|
54
|
-
|
|
55
|
-
export function isContextOverflow(message: string, status?: number): boolean {
|
|
56
|
-
if (status === 429) return false;
|
|
57
|
-
if (NOT_OVERFLOW.some((pattern) => pattern.test(message))) return false;
|
|
58
|
-
return OVERFLOW_PATTERNS.some((pattern) => pattern.test(message)) || BODYLESS.test(message.trim());
|
|
59
|
-
}
|
package/src/core/packages.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/** Pi package identities and filters, independent of installation and I/O (D13). */
|
|
2
|
-
import { parseResourcePattern, type ResourceKind } from "./resource-selection.ts";
|
|
3
|
-
|
|
4
|
-
export type PackageDeclaration = string | ({ source: string; autoload?: boolean } & Partial<Record<ResourceKind, readonly string[]>>);
|
|
5
|
-
export type PackageSource =
|
|
6
|
-
| { kind: "local"; id: string; path: string; source: string; pinned: true }
|
|
7
|
-
| { kind: "npm"; id: string; name: string; spec: string; version?: string; source: string; pinned: boolean }
|
|
8
|
-
| { kind: "git"; id: string; repo: string; ref?: string; source: string; pinned: boolean };
|
|
9
|
-
export function packageDeclaration(value: unknown): value is PackageDeclaration {
|
|
10
|
-
if (typeof value === "string") return value.trim().length > 0;
|
|
11
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
12
|
-
const entry = value as Record<string, unknown>;
|
|
13
|
-
return typeof entry["source"] === "string" && entry["source"].trim().length > 0 &&
|
|
14
|
-
(entry["autoload"] === undefined || typeof entry["autoload"] === "boolean") &&
|
|
15
|
-
["extensions", "skills", "prompts", "themes"].every(key => entry[key] === undefined || Array.isArray(entry[key]) && entry[key].every(p => typeof p === "string"));
|
|
16
|
-
}
|
|
17
|
-
export function packageConfig(value: PackageDeclaration): Exclude<PackageDeclaration, string> { return typeof value === "string" ? { source: value } : value; }
|
|
18
|
-
export function parsePackageSource(raw: string, localPath: (path: string) => string): PackageSource {
|
|
19
|
-
const source = raw.trim(); if (!source || source.startsWith("-") || /[\x00-\x1f]/.test(source)) throw Error("Invalid package source");
|
|
20
|
-
if (source.startsWith("npm:")) {
|
|
21
|
-
const spec = source.slice(4), at = spec.lastIndexOf("@"), name = at > 0 ? spec.slice(0, at) : spec, version = at > 0 ? spec.slice(at + 1) : undefined;
|
|
22
|
-
if (!/^(?:@[a-zA-Z0-9][a-zA-Z0-9._-]*\/)?[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(name) || version === "" || version?.startsWith("-")) throw Error("Invalid npm package source");
|
|
23
|
-
return { kind: "npm", id: "npm:" + name, source, name, spec, ...(version === undefined ? {} : { version }), pinned: !!version && /^v?\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/.test(version) };
|
|
24
|
-
}
|
|
25
|
-
if (/^(git:|git\+|https?:\/\/|ssh:\/\/|git@)/.test(source)) {
|
|
26
|
-
let repo = source.replace(/^git:(?!\/\/)/, "").replace(/^git\+/, ""), ref: string | undefined;
|
|
27
|
-
const hash = repo.lastIndexOf("#"), at = repo.lastIndexOf("@");
|
|
28
|
-
const authorityEnd = /^\w+:\/\//.test(repo) ? repo.indexOf("/",repo.indexOf("://")+3) : repo.startsWith("git@") ? repo.indexOf(":") : repo.indexOf("/");
|
|
29
|
-
const split = hash >= 0 ? hash : at > authorityEnd && authorityEnd >= 0 ? at : -1;
|
|
30
|
-
if (split >= 0) { ref = repo.slice(split + 1); repo = repo.slice(0, split); if (!ref || ref.startsWith("-")) throw Error("Invalid git ref"); }
|
|
31
|
-
if (!/^(\w+:\/\/|git@|\/|\.\.?\/)/.test(repo)) repo = "https://" + repo;
|
|
32
|
-
const remote = /^\w+:\/\//.test(repo) ? new URL(repo) : undefined;
|
|
33
|
-
const canonical = (remote ? remote.host.toLowerCase()+remote.pathname : repo.replace(/^git@([^:]+):/, "$1/")).replace(/\.git\/?$/, "").replace(/\/$/, "");
|
|
34
|
-
if (!canonical.includes("/")) throw Error("Invalid git repository");
|
|
35
|
-
return { kind: "git", source, repo, id: "git:" + canonical, ...(ref === undefined ? {} : { ref }), pinned: ref !== undefined };
|
|
36
|
-
}
|
|
37
|
-
const path = localPath(source); return { kind: "local", id: "local:" + path, source, path, pinned: true };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Missing filters inherit; [] excludes all; ordinary positive patterns form a whitelist. */
|
|
41
|
-
export function packageResourceEnabled(patterns: readonly string[] | undefined, matches: (pattern: string, exact: boolean) => boolean, inherited = true): boolean {
|
|
42
|
-
if (patterns === undefined) return inherited;
|
|
43
|
-
if (patterns.length === 0) return false;
|
|
44
|
-
let enabled = patterns.some(pattern => !/^[!+-]/.test(pattern)) ? false : inherited;
|
|
45
|
-
for (const raw of patterns) { const pattern = parseResourcePattern(raw); if (matches(pattern.pattern,pattern.explicit)) enabled = pattern.enabled; }
|
|
46
|
-
return enabled;
|
|
47
|
-
}
|
package/src/core/patch.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/** Codex patch grammar with exact, unambiguous matching (D9 / R4). No I/O. */
|
|
2
|
-
import { assertNever } from "./exhaustive.ts";
|
|
3
|
-
|
|
4
|
-
// Assumed resource ceilings, not measured: reject oversized plans before retaining hunks.
|
|
5
|
-
export const MAX_PATCH_CHARS = 2 * 1024 * 1024;
|
|
6
|
-
export const MAX_PATCH_FILES = 100;
|
|
7
|
-
export interface PatchChunk {
|
|
8
|
-
oldLines: readonly string[];
|
|
9
|
-
newLines: readonly string[];
|
|
10
|
-
anchor?: string;
|
|
11
|
-
endOfFile: boolean;
|
|
12
|
-
}
|
|
13
|
-
export type PatchOperation =
|
|
14
|
-
| { kind: "add"; path: string; content: string }
|
|
15
|
-
| { kind: "delete"; path: string }
|
|
16
|
-
| { kind: "update"; path: string; movePath?: string; chunks: readonly PatchChunk[] };
|
|
17
|
-
export class PatchError extends Error {
|
|
18
|
-
readonly code: "invalid_input" | "not_found" | "ambiguous_edit";
|
|
19
|
-
constructor(code: PatchError["code"], message: string) { super(message); this.code = code; }
|
|
20
|
-
}
|
|
21
|
-
const invalid = (message: string): never => { throw new PatchError("invalid_input", message); };
|
|
22
|
-
const pathValue = (text: string): string => {
|
|
23
|
-
if (!text.trim() || text.includes("\0")) return invalid("patch paths must be nonempty and contain no NUL");
|
|
24
|
-
return text;
|
|
25
|
-
};
|
|
26
|
-
const fileHeader = (line: string) => /^\*\*\* (?:Add|Delete|Update) File: /.test(line);
|
|
27
|
-
|
|
28
|
-
/** Parse all operations before the store resolves any target or changes a byte. */
|
|
29
|
-
export function parsePatch(text: string): PatchOperation[] {
|
|
30
|
-
if (text.length > MAX_PATCH_CHARS) return invalid("patch exceeds the character budget");
|
|
31
|
-
const lines = text.replaceAll("\r\n", "\n").trim().split("\n");
|
|
32
|
-
if (lines[0] !== "*** Begin Patch" || lines.at(-1) !== "*** End Patch")
|
|
33
|
-
return invalid("patch must begin with *** Begin Patch and end with *** End Patch");
|
|
34
|
-
const operations: PatchOperation[] = [];
|
|
35
|
-
let index = 1;
|
|
36
|
-
while (index < lines.length - 1) {
|
|
37
|
-
if (operations.length >= MAX_PATCH_FILES) return invalid("patch exceeds the file budget");
|
|
38
|
-
const header = lines[index++]!;
|
|
39
|
-
if (header.startsWith("*** Add File: ")) {
|
|
40
|
-
const path = pathValue(header.slice("*** Add File: ".length));
|
|
41
|
-
const added: string[] = [];
|
|
42
|
-
while (index < lines.length - 1 && !fileHeader(lines[index]!)) {
|
|
43
|
-
const line = lines[index++]!;
|
|
44
|
-
if (!line.startsWith("+")) return invalid(`added lines must start with +: ${path}`);
|
|
45
|
-
added.push(line.slice(1));
|
|
46
|
-
}
|
|
47
|
-
operations.push({ kind: "add", path, content: added.length ? added.join("\n") + "\n" : "" });
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (header.startsWith("*** Delete File: ")) {
|
|
51
|
-
operations.push({ kind: "delete", path: pathValue(header.slice("*** Delete File: ".length)) });
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (!header.startsWith("*** Update File: ")) return invalid(`unexpected patch line ${index}: ${header}`);
|
|
55
|
-
const path = pathValue(header.slice("*** Update File: ".length));
|
|
56
|
-
const moveLine = lines[index];
|
|
57
|
-
const movePath = moveLine?.startsWith("*** Move to: ") ? pathValue(moveLine.slice("*** Move to: ".length)) : undefined;
|
|
58
|
-
if (movePath !== undefined) index++;
|
|
59
|
-
const chunks: PatchChunk[] = [];
|
|
60
|
-
while (index < lines.length - 1 && !fileHeader(lines[index]!)) {
|
|
61
|
-
const marker = lines[index++]!;
|
|
62
|
-
if (marker !== "@@" && !marker.startsWith("@@ ")) return invalid(`update hunks must start with @@: ${path}`);
|
|
63
|
-
const oldLines: string[] = [], newLines: string[] = [];
|
|
64
|
-
let endOfFile = false, changed = false;
|
|
65
|
-
while (index < lines.length - 1 && !fileHeader(lines[index]!) && !lines[index]!.startsWith("@@")) {
|
|
66
|
-
const line = lines[index++]!;
|
|
67
|
-
if (line === "*** End of File") { endOfFile = true; break; }
|
|
68
|
-
const prefix = line[0];
|
|
69
|
-
if (prefix !== " " && prefix !== "+" && prefix !== "-") return invalid(`invalid update line: ${path}`);
|
|
70
|
-
if (prefix !== "+") oldLines.push(line.slice(1));
|
|
71
|
-
if (prefix !== "-") newLines.push(line.slice(1));
|
|
72
|
-
if (prefix !== " ") changed = true;
|
|
73
|
-
}
|
|
74
|
-
if (!changed) return invalid(`update hunk makes no change: ${path}`);
|
|
75
|
-
chunks.push({ oldLines, newLines, endOfFile, ...(marker === "@@" ? {} : { anchor: marker.slice(3) }) });
|
|
76
|
-
if (endOfFile && index < lines.length - 1 && !fileHeader(lines[index]!)) return invalid(`End of File must finish the update: ${path}`);
|
|
77
|
-
}
|
|
78
|
-
if (!chunks.length && movePath === undefined) return invalid(`update has no hunks: ${path}`);
|
|
79
|
-
operations.push({ kind: "update", path, chunks, ...(movePath === undefined ? {} : { movePath }) });
|
|
80
|
-
}
|
|
81
|
-
if (!operations.length) return invalid("patch is empty");
|
|
82
|
-
return operations;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Keep BOM, line endings and final-newline status; ambiguous contexts require a reread. */
|
|
86
|
-
export function applyPatchChunks(before: string, chunks: readonly PatchChunk[]): string {
|
|
87
|
-
const bom = before.startsWith("\uFEFF") ? "\uFEFF" : "";
|
|
88
|
-
const body = before.slice(bom.length);
|
|
89
|
-
const ending = body.includes("\r\n") ? "\r\n" : "\n";
|
|
90
|
-
const normalized = body.replaceAll("\r\n", "\n");
|
|
91
|
-
if (normalized.includes("\r")) return invalid("patch updates do not support bare CR line endings");
|
|
92
|
-
if (ending === "\r\n" && body.replaceAll("\r\n", "").includes("\n"))
|
|
93
|
-
return invalid("patch updates require a consistent line ending; mixed LF/CRLF bytes were preserved");
|
|
94
|
-
const finalNewline = normalized.endsWith("\n");
|
|
95
|
-
const lines = normalized ? normalized.split("\n") : [];
|
|
96
|
-
if (finalNewline) lines.pop();
|
|
97
|
-
let cursor = 0;
|
|
98
|
-
for (const chunk of chunks) {
|
|
99
|
-
let start = cursor;
|
|
100
|
-
if (chunk.anchor !== undefined) {
|
|
101
|
-
const anchors = lines.flatMap((line, at) => at >= cursor && line === chunk.anchor ? [at] : []);
|
|
102
|
-
if (anchors.length !== 1) throw new PatchError(anchors.length ? "ambiguous_edit" : "not_found", "patch anchor must match exactly once after the preceding hunk");
|
|
103
|
-
start = anchors[0]! + 1;
|
|
104
|
-
}
|
|
105
|
-
const matches: number[] = [];
|
|
106
|
-
for (let at = start; at <= lines.length - chunk.oldLines.length; at++) {
|
|
107
|
-
if (chunk.endOfFile && at + chunk.oldLines.length !== lines.length) continue;
|
|
108
|
-
if (chunk.oldLines.every((line, offset) => lines[at + offset] === line)) matches.push(at);
|
|
109
|
-
}
|
|
110
|
-
if (!chunk.oldLines.length && !chunk.endOfFile) {
|
|
111
|
-
if (chunk.anchor !== undefined || lines.length === 0) matches.splice(0, matches.length, start);
|
|
112
|
-
else throw new PatchError("ambiguous_edit", "insertion needs an anchor, End of File, or an empty source");
|
|
113
|
-
}
|
|
114
|
-
if (matches.length !== 1) throw new PatchError(matches.length ? "ambiguous_edit" : "not_found", "patch context must match exactly once; read current content and add context");
|
|
115
|
-
const at = matches[0]!;
|
|
116
|
-
lines.splice(at, chunk.oldLines.length, ...chunk.newLines);
|
|
117
|
-
cursor = at + chunk.newLines.length;
|
|
118
|
-
}
|
|
119
|
-
return bom + lines.join(ending) + (finalNewline && lines.length ? ending : "");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function patchPaths(operations: readonly PatchOperation[]): string[] {
|
|
123
|
-
return operations.flatMap(operation => {
|
|
124
|
-
switch (operation.kind) {
|
|
125
|
-
case "add": case "delete": return [operation.path];
|
|
126
|
-
case "update": return operation.movePath === undefined ? [operation.path] : [operation.path, operation.movePath];
|
|
127
|
-
default: return assertNever(operation, "patchPaths");
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Existing paths that a missing one probably meant.
|
|
3
|
-
*
|
|
4
|
-
* Models guess paths, and most guesses in a reviewed session (flotil: 11 of
|
|
5
|
-
* 11 ENOENT) had the right file name in the wrong directory or a near
|
|
6
|
-
* spelling. OpenCode's read tool suggests up to three entries of the same
|
|
7
|
-
* directory whose name contains, or is contained in, the requested one
|
|
8
|
-
* (`packages/opencode/src/tool/read.ts`); that misses a wrong directory, so
|
|
9
|
-
* this ranks the whole workspace listing, file name first.
|
|
10
|
-
*
|
|
11
|
-
* Pure and deterministic: the result depends on the set of candidates, never
|
|
12
|
-
* their order, so the same failure always names the same paths.
|
|
13
|
-
*/
|
|
14
|
-
import { editDistance } from "./text-normalize.ts";
|
|
15
|
-
|
|
16
|
-
/** OpenCode's count: enough to cover a wrong directory, short enough to read at a glance. */
|
|
17
|
-
export const PATH_SUGGESTION_LIMIT = 3;
|
|
18
|
-
|
|
19
|
-
/** Containment of a shorter name is too weak a signal below this length (assumed: "a" is in everything). */
|
|
20
|
-
const MIN_CONTAINED = 3;
|
|
21
|
-
|
|
22
|
-
const baseName = (path: string) => path.slice(path.lastIndexOf("/") + 1);
|
|
23
|
-
const stem = (name: string) => { const dot = name.lastIndexOf("."); return dot > 0 ? name.slice(0, dot) : name; };
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Rank tier of `name` against the wanted file name, or null when unrelated.
|
|
27
|
-
* 0: same name; 1: same name without extension; 2: one name contains the
|
|
28
|
-
* other; 3: a near spelling (edit distance within a quarter of the length).
|
|
29
|
-
*/
|
|
30
|
-
function tier(name: string, wanted: string, distance: number): number | null {
|
|
31
|
-
if (name === wanted) return 0;
|
|
32
|
-
if (stem(name) === stem(wanted) && stem(name).length > 0) return 1;
|
|
33
|
-
const shorter = name.length < wanted.length ? name : wanted, longer = shorter === name ? wanted : name;
|
|
34
|
-
if (shorter.length >= MIN_CONTAINED && longer.includes(shorter)) return 2;
|
|
35
|
-
if (distance <= Math.max(1, Math.floor(wanted.length / 4))) return 3;
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Up to `limit` workspace-relative paths from `candidates` closest to the
|
|
41
|
-
* missing `target`, best first. Paths use "/" separators; comparison ignores
|
|
42
|
-
* case. The target itself is never suggested.
|
|
43
|
-
*/
|
|
44
|
-
export function nearestPaths(target: string, candidates: readonly string[], limit = PATH_SUGGESTION_LIMIT): string[] {
|
|
45
|
-
const wantedPath = target.replace(/^\.\//, "").toLowerCase();
|
|
46
|
-
const wanted = baseName(wantedPath);
|
|
47
|
-
if (wanted.length === 0 || limit <= 0) return [];
|
|
48
|
-
const ranked: { path: string; tier: number; name: number; whole: number }[] = [];
|
|
49
|
-
for (const path of new Set(candidates)) {
|
|
50
|
-
const lower = path.toLowerCase();
|
|
51
|
-
if (lower === wantedPath) continue;
|
|
52
|
-
const name = baseName(lower), distance = editDistance(name, wanted);
|
|
53
|
-
const rank = tier(name, wanted, distance);
|
|
54
|
-
if (rank === null) continue;
|
|
55
|
-
ranked.push({ path, tier: rank, name: distance, whole: editDistance(lower, wantedPath) });
|
|
56
|
-
}
|
|
57
|
-
ranked.sort((a, b) => a.tier - b.tier || a.name - b.name || a.whole - b.whole || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
58
|
-
return ranked.slice(0, limit).map(item => item.path);
|
|
59
|
-
}
|