@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/runtime/catalog.ts
DELETED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Which model to run, and which adapter to run it through.
|
|
3
|
-
*
|
|
4
|
-
* Selection is a pure function of the catalog, the settings file, the flags
|
|
5
|
-
* and one predicate saying whether a model has a credential. Keeping it pure
|
|
6
|
-
* matters because the interesting case is the one nobody configures on
|
|
7
|
-
* purpose: no flag, no settings, two providers installed, one of them
|
|
8
|
-
* authenticated. What happens then is a decision, and a decision should be
|
|
9
|
-
* testable without a filesystem.
|
|
10
|
-
*
|
|
11
|
-
* The built-in list is a typed constant. The live list is that constant
|
|
12
|
-
* overlaid with `~/.xeno/models.json`. Overlay is also pure: the file is
|
|
13
|
-
* read elsewhere, this module only parses and merges. Fetch is accepted on
|
|
14
|
-
* `loadCatalog` so a test can prove startup never calls it (D16).
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { assertNever } from "../core/exhaustive.ts";
|
|
18
|
-
import { createHash } from "node:crypto";
|
|
19
|
-
import { resolve } from "node:path";
|
|
20
|
-
import { modelKey } from "../core/session-position.ts";
|
|
21
|
-
import { mergeCatalogs } from "../core/catalog-merge.ts";
|
|
22
|
-
import { parseModelConfiguration } from "./model-config.ts";
|
|
23
|
-
import type { Settings } from "../core/settings.ts";
|
|
24
|
-
import { CATALOG, CURATED_CATALOG, type ModelSpec } from "./model.ts";
|
|
25
|
-
import { AnthropicProvider, type Provider, type ProviderConfig } from "./provider.ts";
|
|
26
|
-
import { OpenAICompletionsProvider } from "./openai.ts";
|
|
27
|
-
import { OpenAIResponsesProvider } from "./openai-responses.ts";
|
|
28
|
-
import { PiProvider } from "./pi-provider.ts";
|
|
29
|
-
import { copyProviderAccess, hasProviderAccess } from "./provider-access.ts";
|
|
30
|
-
import type { ResolveEffects } from "./auth.ts";
|
|
31
|
-
|
|
32
|
-
export type { Settings };
|
|
33
|
-
|
|
34
|
-
export interface LoadedCatalog {
|
|
35
|
-
models: ModelSpec[];
|
|
36
|
-
warnings: string[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Call signature only — `typeof fetch` also has `preconnect`, which a test spy does not. */
|
|
40
|
-
export type CatalogFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Built-in catalog plus a user file. `fetch` is part of the signature so the
|
|
44
|
-
* startup-path test can inject one and assert it was never called — a default
|
|
45
|
-
* `refresh: false` flag would not prove that.
|
|
46
|
-
*/
|
|
47
|
-
export function loadCatalog(opts: { userText: string | null; cacheText?: string | null; readWarnings?: readonly string[]; fetch?: CatalogFetch }): LoadedCatalog {
|
|
48
|
-
void opts.fetch;
|
|
49
|
-
const warnings: string[] = [...(opts.readWarnings ?? [])];
|
|
50
|
-
let models = [...CATALOG];
|
|
51
|
-
if (opts.cacheText?.trim()) {
|
|
52
|
-
try {
|
|
53
|
-
const parsed = parseModelConfiguration(JSON.parse(opts.cacheText));
|
|
54
|
-
const curated = new Map(CURATED_CATALOG.map(spec => [modelKey(spec), spec]));
|
|
55
|
-
const cached = parsed.models.map(spec => {
|
|
56
|
-
const known = curated.get(modelKey(spec));
|
|
57
|
-
if (!known) return spec;
|
|
58
|
-
// Refresh metadata without replacing already verified wire dialects for existing sessions.
|
|
59
|
-
const next = { ...spec, api: known.api, reasoning: known.reasoning };
|
|
60
|
-
delete next.driver; delete next.compat; delete next.nativeCompat; delete next.thinkingLevelMap;
|
|
61
|
-
if (known.driver) next.driver = known.driver;
|
|
62
|
-
if (known.nativeCompat) next.nativeCompat = known.nativeCompat;
|
|
63
|
-
if (known.compat) next.compat = known.compat;
|
|
64
|
-
if (known.thinkingLevelMap) next.thinkingLevelMap = known.thinkingLevelMap;
|
|
65
|
-
if (known.baseUrl !== undefined) next.baseUrl = known.baseUrl;
|
|
66
|
-
copyProviderAccess(spec, next);
|
|
67
|
-
return next;
|
|
68
|
-
});
|
|
69
|
-
models = mergeCatalogs(models, cached);
|
|
70
|
-
warnings.push(...parsed.errors.map(message => "catalog cache: " + message));
|
|
71
|
-
} catch { warnings.push("catalog cache is not valid JSON; using bundled models"); }
|
|
72
|
-
}
|
|
73
|
-
if (opts.userText?.trim()) {
|
|
74
|
-
try { const parsed = parseModelConfiguration(JSON.parse(opts.userText), models); models = parsed.models; warnings.push(...parsed.errors); }
|
|
75
|
-
catch { warnings.push("user catalog is not valid JSON; using bundled/cache models"); }
|
|
76
|
-
}
|
|
77
|
-
return { models, warnings };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function overlayCatalog(builtin: readonly ModelSpec[], user: readonly ModelSpec[]): ModelSpec[] { return mergeCatalogs(builtin, user); }
|
|
81
|
-
export function parseUserCatalog(json: unknown): { models: ModelSpec[]; errors: string[] } { return parseModelConfiguration(json); }
|
|
82
|
-
|
|
83
|
-
export function findModel(id: string, catalog: readonly ModelSpec[] = CATALOG): ModelSpec | undefined {
|
|
84
|
-
return catalog.find((m) => m.id === id);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Resolve a typed pattern to one catalog entry.
|
|
89
|
-
*
|
|
90
|
-
* Order is `provider/id`, then exact id, then substring, then subsequence
|
|
91
|
-
* ("fuzzy"). Each tier is tried only if the one above it matched nothing,
|
|
92
|
-
* so a unique exact id cannot be displaced by a looser match. Several
|
|
93
|
-
* hits at the winning tier is an error that lists the candidates --
|
|
94
|
-
* picking the first would make `--model sonnet` silently follow catalog
|
|
95
|
-
* order after a refresh, and bill, cost, and behaviour would all change
|
|
96
|
-
* with nothing in the log saying why.
|
|
97
|
-
*/
|
|
98
|
-
export type ModelPatternMatch =
|
|
99
|
-
| { ok: true; spec: ModelSpec }
|
|
100
|
-
| { ok: false; reason: string; candidates: readonly ModelSpec[] };
|
|
101
|
-
|
|
102
|
-
export function findModelByPattern(
|
|
103
|
-
pattern: string,
|
|
104
|
-
catalog: readonly ModelSpec[] = CATALOG,
|
|
105
|
-
): ModelPatternMatch {
|
|
106
|
-
const needle = pattern.trim().toLowerCase();
|
|
107
|
-
if (needle.length === 0) {
|
|
108
|
-
return { ok: false, reason: "empty model pattern", candidates: [] };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const byRef = catalog.filter((m) => `${m.provider}/${m.id}`.toLowerCase() === needle);
|
|
112
|
-
if (byRef.length > 0) return reportPattern(pattern, byRef);
|
|
113
|
-
|
|
114
|
-
const exactId = catalog.filter((m) => m.id.toLowerCase() === needle);
|
|
115
|
-
if (exactId.length > 0) return reportPattern(pattern, exactId);
|
|
116
|
-
|
|
117
|
-
const substring = catalog.filter((m) => haystacks(m).some((h) => h.includes(needle)));
|
|
118
|
-
if (substring.length > 0) return reportPattern(pattern, substring);
|
|
119
|
-
|
|
120
|
-
const fuzzy = catalog.filter((m) => haystacks(m).some((h) => isSubsequence(needle, h)));
|
|
121
|
-
return reportPattern(pattern, fuzzy);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function haystacks(m: ModelSpec): string[] {
|
|
125
|
-
return [m.id.toLowerCase(), m.name.toLowerCase(), `${m.provider}/${m.id}`.toLowerCase()];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Characters of `needle` appear in order in `haystack`, not necessarily
|
|
130
|
-
* adjacent. A typed abbreviation ("dflash") and a dropped letter both
|
|
131
|
-
* match; an edit-distance threshold would be a guessed constant, and
|
|
132
|
-
* this function is the same rule a picker uses without one.
|
|
133
|
-
*/
|
|
134
|
-
function isSubsequence(needle: string, haystack: string): boolean {
|
|
135
|
-
let i = 0;
|
|
136
|
-
for (const ch of haystack) {
|
|
137
|
-
if (ch === needle[i]) i += 1;
|
|
138
|
-
if (i === needle.length) return true;
|
|
139
|
-
}
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function reportPattern(pattern: string, matches: readonly ModelSpec[]): ModelPatternMatch {
|
|
144
|
-
if (matches.length === 1) return { ok: true, spec: matches[0]! };
|
|
145
|
-
if (matches.length === 0) {
|
|
146
|
-
return { ok: false, reason: `unknown model: ${pattern}. Try --list-models`, candidates: [] };
|
|
147
|
-
}
|
|
148
|
-
const ids = matches.map(modelKey);
|
|
149
|
-
return {
|
|
150
|
-
ok: false,
|
|
151
|
-
reason:
|
|
152
|
-
`ambiguous model pattern ${JSON.stringify(pattern)}: matches ${ids.join(", ")}. ` +
|
|
153
|
-
"Be more specific rather than guessing",
|
|
154
|
-
candidates: matches,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function modelsOfProvider(
|
|
159
|
-
provider: string,
|
|
160
|
-
catalog: readonly ModelSpec[] = CATALOG,
|
|
161
|
-
): readonly ModelSpec[] {
|
|
162
|
-
return catalog.filter((m) => m.provider === provider);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** First-seen provider order, which is catalog order, not alphabetical. */
|
|
166
|
-
export function groupedModels(
|
|
167
|
-
catalog: readonly ModelSpec[] = CATALOG,
|
|
168
|
-
): { provider: string; models: readonly ModelSpec[] }[] {
|
|
169
|
-
const order: string[] = [];
|
|
170
|
-
const map = new Map<string, ModelSpec[]>();
|
|
171
|
-
for (const spec of catalog) {
|
|
172
|
-
let list = map.get(spec.provider);
|
|
173
|
-
if (list === undefined) {
|
|
174
|
-
list = [];
|
|
175
|
-
map.set(spec.provider, list);
|
|
176
|
-
order.push(spec.provider);
|
|
177
|
-
}
|
|
178
|
-
list.push(spec);
|
|
179
|
-
}
|
|
180
|
-
return order.map((provider) => ({ provider, models: map.get(provider)! }));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export type Selection =
|
|
184
|
-
| { ok: true; spec: ModelSpec }
|
|
185
|
-
| { ok: false; reason: string };
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* `--model` wins, then `--provider`'s first model, then the settings file,
|
|
189
|
-
* then a usable recent selection from older settings, then the first model
|
|
190
|
-
* in the catalog that has a credential.
|
|
191
|
-
*
|
|
192
|
-
* The last step is what makes a fresh install work: someone who exported one
|
|
193
|
-
* API key should get a working session without also having to learn the name
|
|
194
|
-
* of a model. It is deliberately last, so it can never override something the
|
|
195
|
-
* person actually asked for -- an explicit `--model` that has no credential
|
|
196
|
-
* must fail loudly rather than quietly running a different model, which would
|
|
197
|
-
* bill the wrong account and invalidate any comparison between runs.
|
|
198
|
-
*/
|
|
199
|
-
export function selectModel(
|
|
200
|
-
settings: Settings,
|
|
201
|
-
flags: { model?: string | undefined; provider?: string | undefined },
|
|
202
|
-
hasCredential: (spec: ModelSpec) => boolean,
|
|
203
|
-
catalog: readonly ModelSpec[] = CATALOG,
|
|
204
|
-
): Selection {
|
|
205
|
-
if (flags.model !== undefined && flags.model.length > 0) {
|
|
206
|
-
const candidates = flags.provider ? modelsOfProvider(flags.provider, catalog) : catalog;
|
|
207
|
-
const match = findModelByPattern(flags.model, candidates);
|
|
208
|
-
return match.ok ? { ok: true, spec: match.spec } : { ok: false, reason: `${match.reason}. Try --list-models` };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (flags.provider !== undefined && flags.provider.length > 0) {
|
|
212
|
-
const models = modelsOfProvider(flags.provider, catalog);
|
|
213
|
-
if (models.length === 0) {
|
|
214
|
-
return { ok: false, reason: `unknown provider: ${flags.provider}. Try --list-models` };
|
|
215
|
-
}
|
|
216
|
-
return { ok: true, spec: models.find(hasCredential) ?? models[0]! };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (settings.defaultModel !== undefined) {
|
|
220
|
-
const candidates = settings.defaultProvider ? catalog.filter(model => model.provider === settings.defaultProvider) : catalog;
|
|
221
|
-
const found = findModelByPattern(settings.defaultModel, candidates);
|
|
222
|
-
const spec = found.ok ? found.spec : undefined;
|
|
223
|
-
if (spec !== undefined) return { ok: true, spec };
|
|
224
|
-
// Not fatal: a settings file naming a model this build dropped should
|
|
225
|
-
// degrade to the normal search, not refuse to start.
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (settings.defaultProvider !== undefined) {
|
|
229
|
-
const preferred = modelsOfProvider(settings.defaultProvider, catalog).find(hasCredential);
|
|
230
|
-
if (preferred !== undefined) return { ok: true, spec: preferred };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Old pickers only persisted recent identities. Reuse one exactly, without fuzzy guesses (Q.25).
|
|
234
|
-
for (const key of settings.recentModels ?? []) {
|
|
235
|
-
const recent = catalog.find(model => modelKey(model) === key);
|
|
236
|
-
if (recent !== undefined && hasCredential(recent)) return { ok: true, spec: recent };
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const authenticated = catalog.find(hasCredential);
|
|
240
|
-
if (authenticated !== undefined) return { ok: true, spec: authenticated };
|
|
241
|
-
|
|
242
|
-
return {
|
|
243
|
-
ok: false,
|
|
244
|
-
reason:
|
|
245
|
-
"no model has a credential. Set an API key for one of: " +
|
|
246
|
-
[...new Set(catalog.map((m) => m.provider))].join(", ") +
|
|
247
|
-
" -- export its environment variable, or run /provider",
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** One adapter per wire format. A new API means a new case here, never a flag. */
|
|
252
|
-
export function createProvider(
|
|
253
|
-
config: ProviderConfig,
|
|
254
|
-
apiKey: string,
|
|
255
|
-
onRetry?: (attempt: number, delayMs: number, error: unknown) => void,
|
|
256
|
-
opts?: { oauth?: boolean; fetch?: typeof fetch; effects?: ResolveEffects; sessionId?: string },
|
|
257
|
-
): Provider {
|
|
258
|
-
const oauth = opts?.oauth === true;
|
|
259
|
-
if (config.spec.driver === "pi" || hasProviderAccess(config.spec)) return new PiProvider(config, apiKey, {
|
|
260
|
-
oauth,
|
|
261
|
-
...(opts?.sessionId ? { sessionId: opts.sessionId } : {}),
|
|
262
|
-
...(onRetry ? { onRetry } : {}), ...(opts?.fetch ? { fetch: opts.fetch } : {}), ...(opts?.effects ? { effects: opts.effects } : {}),
|
|
263
|
-
});
|
|
264
|
-
switch (config.spec.api) {
|
|
265
|
-
case "anthropic-messages":
|
|
266
|
-
return new AnthropicProvider(config, apiKey.length > 0 ? apiKey : undefined, {
|
|
267
|
-
...(onRetry ? { onRetry } : {}),
|
|
268
|
-
...(oauth ? { oauth: true } : {}),
|
|
269
|
-
});
|
|
270
|
-
case "openai-completions":
|
|
271
|
-
return new OpenAICompletionsProvider(config, apiKey, { ...(onRetry ? { onRetry } : {}) });
|
|
272
|
-
case "openai-responses":
|
|
273
|
-
return new OpenAIResponsesProvider(config, apiKey, { ...(onRetry ? { onRetry } : {}) });
|
|
274
|
-
case "azure-openai-responses": case "openai-codex-responses": case "google-generative-ai": case "google-vertex":
|
|
275
|
-
case "bedrock-converse-stream": case "mistral-conversations": case "pi-messages":
|
|
276
|
-
return new PiProvider(config, apiKey, { ...(onRetry ? { onRetry } : {}), ...(opts?.fetch ? { fetch: opts.fetch } : {}), ...(opts?.effects ? { effects: opts.effects } : {}), ...(opts?.sessionId ? { sessionId: opts.sessionId } : {}) });
|
|
277
|
-
default:
|
|
278
|
-
return assertNever(config.spec.api, "createProvider");
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/** D6/D8: routing affinity survives reload and cold resume without server-owned history or path disclosure. */
|
|
283
|
-
export function providerSessionId(logDir: string): string {
|
|
284
|
-
return createHash("sha256").update(resolve(logDir)).digest("hex");
|
|
285
|
-
}
|
package/src/runtime/clipboard.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pi's files → image → text precedence (MIT, 02eed88) over the platform's own
|
|
3
|
-
* clipboard commands. The optional native helper went with the vendored TUI
|
|
4
|
-
* (D20); a caller may still inject one.
|
|
5
|
-
*/
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
|
|
8
|
-
export interface NativeClipboard {
|
|
9
|
-
setText?(text: string): Promise<void>;
|
|
10
|
-
getText?(): Promise<string | null | undefined>;
|
|
11
|
-
getImage?(): Promise<Uint8Array | null | undefined>;
|
|
12
|
-
getFilePaths?(): Promise<string[] | null | undefined>;
|
|
13
|
-
}
|
|
14
|
-
function getNativeClipboard(): NativeClipboard | null { return null; }
|
|
15
|
-
import { runClipboardCommand } from "../vendor/pi-rich/clipboard-command.ts";
|
|
16
|
-
|
|
17
|
-
export type ClipboardContent = { files: string[] } | { image: Uint8Array } | { text: string } | null;
|
|
18
|
-
type Run = (command: string, args: readonly string[], options?: { input?: string; timeoutMs?: number }) => Promise<Buffer | undefined>;
|
|
19
|
-
export interface ClipboardOptions {
|
|
20
|
-
platform?: NodeJS.Platform;
|
|
21
|
-
env?: NodeJS.ProcessEnv;
|
|
22
|
-
native?: NativeClipboard | null;
|
|
23
|
-
run?: Run;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Text is transported as data. Clipboard contents must never become PowerShell or shell source. */
|
|
27
|
-
export async function writeClipboard(text: string, options: ClipboardOptions = {}): Promise<boolean> {
|
|
28
|
-
const platform = options.platform ?? process.platform, env = options.env ?? process.env;
|
|
29
|
-
const native = options.native === undefined ? getNativeClipboard() : options.native, run = options.run ?? runClipboardCommand;
|
|
30
|
-
if (native?.setText) {
|
|
31
|
-
try { await native.setText(text); return true; } catch { /* Continue to the same platform's command transport. */ }
|
|
32
|
-
}
|
|
33
|
-
const windows = platform === "win32" || !!(env["WSL_DISTRO_NAME"] || env["WSL_INTEROP"]);
|
|
34
|
-
const commands: readonly (readonly [string, readonly string[]])[] = windows
|
|
35
|
-
? [["powershell.exe", ["-NoProfile", "-STA", "-Command", "Add-Type -AssemblyName System.Windows.Forms; [Console]::InputEncoding = [System.Text.Encoding]::UTF8; $t = [Console]::In.ReadToEnd(); if ($t.Length) { [System.Windows.Forms.Clipboard]::SetText($t) } else { [System.Windows.Forms.Clipboard]::Clear() }"]]]
|
|
36
|
-
: platform === "darwin" ? [["pbcopy", []]]
|
|
37
|
-
: env["TERMUX_VERSION"] ? [["termux-clipboard-set", []]]
|
|
38
|
-
: platform === "linux" ? [
|
|
39
|
-
...(env["WAYLAND_DISPLAY"] || env["XDG_SESSION_TYPE"] === "wayland" ? [["wl-copy", ["--type", "text/plain;charset=utf-8"]] as const] : []),
|
|
40
|
-
...(env["DISPLAY"] ? [["xclip", ["-selection", "clipboard"]] as const, ["xsel", ["--clipboard", "--input"]] as const] : []),
|
|
41
|
-
] : [];
|
|
42
|
-
for (const [command, args] of commands) {
|
|
43
|
-
try { if (await run(command, args, { input: text, timeoutMs: 5000 }) !== undefined) return true; } catch { /* Try a same-platform backend. */ }
|
|
44
|
-
}
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Fixed programs only. Clipboard content is data, never interpolated into a shell or script.
|
|
49
|
-
const MAC_READ = `ObjC.import('AppKit');
|
|
50
|
-
const p = $.NSPasteboard.generalPasteboard;
|
|
51
|
-
const urls = p.readObjectsForClassesOptions([$.NSURL], $({NSPasteboardURLReadingFileURLsOnlyKey: true}));
|
|
52
|
-
const files = [];
|
|
53
|
-
if (urls) for (let i = 0; i < urls.count; i++) files.push(ObjC.unwrap(urls.objectAtIndex(i).path));
|
|
54
|
-
if (files.length) JSON.stringify({files});
|
|
55
|
-
else {
|
|
56
|
-
const png = p.dataForType('public.png');
|
|
57
|
-
const tiff = png ? null : p.dataForType('public.tiff');
|
|
58
|
-
const data = png || (tiff ? $.NSBitmapImageRep.imageRepWithData(tiff).representationUsingTypeProperties($.NSPNGFileType, $({})) : null);
|
|
59
|
-
if (data) JSON.stringify({image: ObjC.unwrap(data.base64EncodedStringWithOptions(0))});
|
|
60
|
-
else JSON.stringify({text: ObjC.unwrap(p.stringForType($.NSPasteboardTypeString)) || ''});
|
|
61
|
-
}`;
|
|
62
|
-
const WINDOWS_READ = `Add-Type -AssemblyName System.Windows.Forms; Add-Type -AssemblyName System.Drawing;
|
|
63
|
-
$c = [System.Windows.Forms.Clipboard];
|
|
64
|
-
if ($c::ContainsFileDropList()) { @{files = @($c::GetFileDropList())} | ConvertTo-Json -Compress }
|
|
65
|
-
elseif ($c::ContainsImage()) { $i = $c::GetImage(); $s = New-Object System.IO.MemoryStream; try { $i.Save($s, [System.Drawing.Imaging.ImageFormat]::Png); @{image = [Convert]::ToBase64String($s.ToArray())} | ConvertTo-Json -Compress } finally { $i.Dispose(); $s.Dispose() } }
|
|
66
|
-
else { @{text = $c::GetText()} | ConvertTo-Json -Compress }`;
|
|
67
|
-
|
|
68
|
-
function decode(bytes: Buffer | undefined): ClipboardContent | undefined {
|
|
69
|
-
if (bytes === undefined) return undefined;
|
|
70
|
-
const value = JSON.parse(bytes.toString("utf8")) as { files?: unknown; image?: unknown; text?: unknown };
|
|
71
|
-
if (Array.isArray(value.files) && value.files.every(p => typeof p === "string") && value.files.length) return { files: value.files };
|
|
72
|
-
if (typeof value.image === "string" && value.image) return { image: Buffer.from(value.image, "base64") };
|
|
73
|
-
return typeof value.text === "string" && value.text ? { text: value.text } : null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export async function readClipboard(options: ClipboardOptions = {}): Promise<ClipboardContent> {
|
|
77
|
-
const platform = options.platform ?? process.platform, env = options.env ?? process.env;
|
|
78
|
-
const run = options.run ?? runClipboardCommand;
|
|
79
|
-
const native = options.native === undefined ? getNativeClipboard() : options.native;
|
|
80
|
-
const files = await native?.getFilePaths?.();
|
|
81
|
-
if (files?.length) return { files };
|
|
82
|
-
if (platform === "darwin" && !native) {
|
|
83
|
-
const result = decode(await run("osascript", ["-l", "JavaScript", "-e", MAC_READ]));
|
|
84
|
-
if (result !== undefined) return result;
|
|
85
|
-
}
|
|
86
|
-
const wsl = !!(env["WSL_DISTRO_NAME"] || env["WSL_INTEROP"]);
|
|
87
|
-
if (platform === "win32" || wsl) {
|
|
88
|
-
const result = decode(await run("powershell.exe", ["-NoProfile", "-STA", "-Command", WINDOWS_READ], { timeoutMs: 5000 }));
|
|
89
|
-
if (result !== undefined) return result;
|
|
90
|
-
}
|
|
91
|
-
if (platform === "linux" && !env["TERMUX_VERSION"]) {
|
|
92
|
-
const wayland = !!env["WAYLAND_DISPLAY"] || env["XDG_SESSION_TYPE"] === "wayland";
|
|
93
|
-
const backends = [...(wayland ? ["wayland"] : []), ...(env["DISPLAY"] ? ["x11"] : [])];
|
|
94
|
-
for (const backend of backends) {
|
|
95
|
-
const read = (type: string) => backend === "wayland" ? run("wl-paste", ["--type", type, "--no-newline"]) : run("xclip", ["-selection", "clipboard", "-t", type, "-o"]);
|
|
96
|
-
const available = backend === "wayland" ? await run("wl-paste", ["--list-types"], { timeoutMs: 1000 }) : await read("TARGETS");
|
|
97
|
-
if (available === undefined) continue;
|
|
98
|
-
const types = available.toString("utf8").trim().split(/\r?\n/);
|
|
99
|
-
if (types.includes("text/uri-list")) {
|
|
100
|
-
const bytes = await read("text/uri-list");
|
|
101
|
-
if (bytes === undefined) throw new Error("Clipboard file transfer failed");
|
|
102
|
-
const paths = bytes.toString("utf8").split(/\r?\n/).filter(p => p.startsWith("file://")).map(p => fileURLToPath(p));
|
|
103
|
-
if (paths.length) return { files: paths };
|
|
104
|
-
}
|
|
105
|
-
const type = ["image/png", "image/jpeg", "image/webp", "image/gif", "image/bmp"].find(t => types.includes(t));
|
|
106
|
-
if (type) {
|
|
107
|
-
const bytes = await read(type);
|
|
108
|
-
if (bytes === undefined) throw new Error("Clipboard image transfer failed");
|
|
109
|
-
return bytes.length ? { image: bytes } : null;
|
|
110
|
-
}
|
|
111
|
-
const textType = ["text/plain;charset=utf-8", "UTF8_STRING", "text/plain", "text"].find(t => types.includes(t));
|
|
112
|
-
if (!textType) return null; // An empty Wayland selection must not reveal stale X11 contents.
|
|
113
|
-
const bytes = await read(textType);
|
|
114
|
-
if (bytes === undefined) throw new Error("Clipboard text transfer failed");
|
|
115
|
-
return bytes.length ? { text: bytes.toString("utf8") } : null;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
const image = await native?.getImage?.();
|
|
119
|
-
if (image?.length) return { image };
|
|
120
|
-
const text = await native?.getText?.();
|
|
121
|
-
if (text !== undefined) return text ? { text } : null;
|
|
122
|
-
const fallback = env["TERMUX_VERSION"] ? await run("termux-clipboard-get", []) : platform === "darwin" ? await run("pbpaste", []) : undefined;
|
|
123
|
-
if (fallback === undefined) throw new Error("Clipboard unavailable for this terminal session");
|
|
124
|
-
return fallback.length ? { text: fallback.toString("utf8") } : null;
|
|
125
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/** R8: one owner observes an admitted command once, including after a managed call yields. */
|
|
2
|
-
import { hostname } from "node:os";
|
|
3
|
-
import { resolve, join } from "node:path";
|
|
4
|
-
import { artifactChange, commandArtifactState, parseCommandArtifactPlan, COMMAND_ARTIFACT_SCOPE, type CommandArtifactReceipt } from "../core/command-artifacts.ts";
|
|
5
|
-
import { observeArtifactInputs, observeArtifactFiles, artifactExclusions, archiveCommandArtifacts } from "../store/command-artifacts.ts";
|
|
6
|
-
import type { ToolContext } from "./tools.ts";
|
|
7
|
-
|
|
8
|
-
export type ArtifactCommandEnd = { exit: number; stopped?: "cancelled" | "timeout"; capture?: "complete" | "partial"; cleanup?: unknown };
|
|
9
|
-
export type CommandArtifactObserver = {
|
|
10
|
-
pending: CommandArtifactReceipt;
|
|
11
|
-
finish(end: ArtifactCommandEnd): Promise<CommandArtifactReceipt>;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
/** Syntactic validation precedes admission; no preflight disk probing is smuggled through a denied tool. */
|
|
15
|
-
export function prepareArtifactArguments(input: unknown): unknown {
|
|
16
|
-
if (input === null || typeof input !== "object" || Array.isArray(input) || !("artifacts" in input)) return input;
|
|
17
|
-
return { ...input, artifacts: parseCommandArtifactPlan(input.artifacts) };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function beginCommandArtifacts(ctx: Pick<ToolContext, "root" | "hostId" | "ownedStatePaths" | "outputStore" | "signal">,
|
|
21
|
-
command: string, cwd: string, requested: unknown): Promise<CommandArtifactObserver | undefined> {
|
|
22
|
-
if (requested === undefined) return undefined;
|
|
23
|
-
const plan = parseCommandArtifactPlan(requested), exclusions = artifactExclusions(ctx.root, plan,
|
|
24
|
-
[...(ctx.ownedStatePaths ?? []), ctx.outputStore ?? join(ctx.root, ".xeno", "observations")]);
|
|
25
|
-
const beforeInputs = await observeArtifactInputs(ctx.root, exclusions, ctx.signal);
|
|
26
|
-
const before = await observeArtifactFiles(ctx.root, plan.paths, ctx.signal);
|
|
27
|
-
const pending: CommandArtifactReceipt = {
|
|
28
|
-
kind: "command_artifacts", version: 1, state: "pending", plan,
|
|
29
|
-
source: { host: ctx.hostId ?? hostname(), workspace: resolve(ctx.root), cwd: resolve(cwd), runtime: `bun/${Bun.version}` },
|
|
30
|
-
command, exit: null, started_at: new Date().toISOString(), observed_at: null,
|
|
31
|
-
inputs: { scope: COMMAND_ARTIFACT_SCOPE, exclusions, before: beforeInputs.sha256, after: null },
|
|
32
|
-
outputs: [], reasons: beforeInputs.reason ? [beforeInputs.reason] : [],
|
|
33
|
-
};
|
|
34
|
-
let completed: Promise<CommandArtifactReceipt> | undefined;
|
|
35
|
-
return { pending, finish(end) {
|
|
36
|
-
completed ??= (async () => {
|
|
37
|
-
// Observation failures retain the actual exit. Never retry a post-exit read against later files.
|
|
38
|
-
const reasons = [...pending.reasons];
|
|
39
|
-
if (end.exit !== 0) reasons.push("command exited unsuccessfully");
|
|
40
|
-
if (end.stopped) reasons.push(`command ${end.stopped}`);
|
|
41
|
-
if (end.capture === "partial") reasons.push("command output capture is partial");
|
|
42
|
-
if (end.cleanup) reasons.push("command cleanup is unconfirmed");
|
|
43
|
-
const after = await observeArtifactFiles(ctx.root, plan.paths, ctx.signal);
|
|
44
|
-
const afterInputs = await observeArtifactInputs(ctx.root, exclusions, ctx.signal);
|
|
45
|
-
if (afterInputs.reason) reasons.push(afterInputs.reason);
|
|
46
|
-
const receipt: CommandArtifactReceipt = { ...pending, exit: end.exit, observed_at: new Date().toISOString(),
|
|
47
|
-
inputs: { ...pending.inputs, after: afterInputs.sha256 },
|
|
48
|
-
outputs: plan.paths.map((path, i) => ({ path, before: before[i]!, after: after[i]!, change: artifactChange(before[i]!, after[i]!) })), reasons };
|
|
49
|
-
receipt.state = commandArtifactState(receipt);
|
|
50
|
-
try { receipt.manifest = await archiveCommandArtifacts(ctx.outputStore ?? join(ctx.root, ".xeno", "observations"), receipt); }
|
|
51
|
-
catch { receipt.reasons.push("artifact observation archive unavailable"); receipt.state = commandArtifactState(receipt); }
|
|
52
|
-
return receipt;
|
|
53
|
-
})();
|
|
54
|
-
return completed;
|
|
55
|
-
} };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function commandArtifactSummary(receipt: CommandArtifactReceipt | undefined): string {
|
|
59
|
-
if (!receipt) return "";
|
|
60
|
-
const changes = ["created", "modified", "unchanged", "unknown"].map(change => `${change}=${receipt.outputs.filter(output => output.change === change).length}`).join(", ");
|
|
61
|
-
return `\n[artifacts ${receipt.state}; ${changes}; reasons=${receipt.reasons.length}; ` +
|
|
62
|
-
(receipt.manifest ? `history artifact=${receipt.manifest.sha256}; ` : "") +
|
|
63
|
-
"command association only, not build causality or runtime acceptance]";
|
|
64
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Whether a provider may keep reusing pooled connections.
|
|
3
|
-
*
|
|
4
|
-
* A socket that a proxy, NAT or server dropped while it sat in the pool fails
|
|
5
|
-
* the next request the same way -- Horizyn 2026-10-12 lost the first request
|
|
6
|
-
* after "continue" to it. Claude Code answers by turning keep-alive off for
|
|
7
|
-
* the rest of the process once a connection dies; this does the same per
|
|
8
|
-
* provider instance, so tests sharing a process cannot leak state into each
|
|
9
|
-
* other and a fresh session starts from the cheap path again
|
|
10
|
-
* (feat-fresh-connection-after-drop).
|
|
11
|
-
*
|
|
12
|
-
* Bun honours `keepalive: false` by opening a new socket per request
|
|
13
|
-
* (measured on 1.3.10: three default fetches share one socket, three with
|
|
14
|
-
* keepalive:false open three). The cost is a TLS handshake per request,
|
|
15
|
-
* negligible next to a model call. Google, Vertex and Bedrock go through
|
|
16
|
-
* pi-ai's native HTTP with no fetch hook, so this cannot reach them.
|
|
17
|
-
*/
|
|
18
|
-
import { connectionFailure } from "./retry.ts";
|
|
19
|
-
|
|
20
|
-
export class ConnectionReuse {
|
|
21
|
-
private fresh = false;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Called with every failed attempt, retried or final. Only a connection
|
|
25
|
-
* that existed and died counts: one that never opened left nothing stale
|
|
26
|
-
* in the pool.
|
|
27
|
-
*/
|
|
28
|
-
note(error: unknown): void {
|
|
29
|
-
if (connectionFailure(error) === "stale") this.fresh = true;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
init(init: RequestInit | undefined): RequestInit | undefined {
|
|
33
|
-
return this.fresh ? { ...init, keepalive: false } : init;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** A fetch that opens fresh connections once one has died. */
|
|
37
|
-
wrap(fetchImpl: typeof fetch): typeof fetch {
|
|
38
|
-
return Object.assign((input: RequestInfo | URL, init?: RequestInit) => fetchImpl(input, this.init(init)), { preconnect: fetchImpl.preconnect });
|
|
39
|
-
}
|
|
40
|
-
}
|