@visual-z/xeno 0.2.1 → 0.4.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 +18 -0
- package/README.md +126 -34
- package/docs/app-server.md +84 -0
- package/docs/install.md +51 -13
- package/docs/providers.md +24 -3
- package/package.json +7 -5
- package/src/apps/app-server.ts +28 -0
- package/src/apps/approve.ts +3 -3
- package/src/apps/cli.ts +25 -10
- package/src/apps/commands.ts +45 -8
- package/src/apps/completions.ts +78 -22
- package/src/apps/control-transport.ts +89 -0
- package/src/apps/image-draft.ts +46 -0
- package/src/apps/model-catalog.ts +2 -1
- package/src/apps/project-trust.ts +2 -42
- package/src/apps/provider-connect.ts +346 -0
- package/src/apps/render.ts +22 -47
- package/src/apps/resources.ts +5 -2
- package/src/apps/shell.ts +333 -1542
- package/src/apps/startup-input.ts +45 -9
- package/src/apps/startup.ts +149 -94
- package/src/apps/ui/app.ts +1553 -0
- package/src/apps/ui/control-model.ts +26 -0
- package/src/apps/ui/extension-host.ts +194 -0
- package/src/apps/ui/image-cache.ts +78 -0
- package/src/apps/ui/palette.ts +88 -0
- package/src/apps/ui/project.ts +127 -0
- package/src/apps/ui/standalone.ts +146 -0
- package/src/apps/ui/status-model.ts +68 -0
- package/src/apps/ui/surface.ts +86 -0
- package/src/apps/ui/theme.ts +86 -0
- package/src/core/acceptance.ts +107 -0
- package/src/core/action-defaults.ts +47 -295
- package/src/core/benchmark.ts +2 -2
- package/src/core/branch-receipt.ts +24 -0
- package/src/core/checkpoints.ts +64 -0
- package/src/core/churn.ts +2 -0
- package/src/core/command-artifacts.ts +94 -0
- package/src/core/command-contract.ts +35 -0
- package/src/core/completion-action.ts +20 -0
- package/src/core/completion-review.ts +37 -21
- package/src/core/context-detail.ts +37 -0
- package/src/core/context-policy.ts +74 -1
- package/src/core/continuation.ts +17 -2
- package/src/core/control-protocol.ts +62 -0
- package/src/core/control-view.ts +42 -0
- package/src/core/derive.ts +29 -3
- package/src/core/draft-images.ts +56 -0
- package/src/core/environment.ts +145 -0
- package/src/core/evaluation-coverage.ts +74 -0
- package/src/core/event-validation.ts +81 -6
- package/src/core/events.ts +99 -3
- package/src/core/evidence-links.ts +7 -0
- package/src/core/evidence.ts +13 -3
- package/src/core/execution.ts +8 -2
- package/src/core/experiment.ts +133 -0
- package/src/core/export.ts +9 -0
- package/src/core/failure-recovery.ts +173 -0
- package/src/core/file-precondition.ts +72 -0
- package/src/core/handoff.ts +65 -4
- package/src/core/harness-diagnostics.ts +206 -0
- package/src/core/history-query.ts +38 -2
- package/src/core/history.ts +38 -1
- package/src/core/integration-health.ts +34 -0
- package/src/core/keymap.ts +8 -20
- package/src/core/model-picker.ts +64 -0
- package/src/core/native-eval.ts +25 -0
- package/src/core/note-guidance.ts +34 -0
- package/src/core/oauth-fields.ts +44 -0
- package/src/core/observation-view.ts +49 -8
- package/src/core/permissions.ts +24 -2
- package/src/core/process-receipt.ts +105 -0
- package/src/core/process-state.ts +37 -0
- package/src/core/program-status.ts +31 -0
- package/src/core/project.ts +70 -19
- package/src/core/prompt-policy.ts +37 -0
- package/src/core/query-eval-contract.ts +14 -0
- package/src/core/read-observation.ts +82 -0
- package/src/core/recovery-corpus.ts +17 -0
- package/src/core/recovery-evaluation.ts +60 -0
- package/src/core/recovery-sources.ts +74 -0
- package/src/core/request-progress.ts +87 -0
- package/src/core/request-state.ts +114 -0
- package/src/core/runtime-target.ts +94 -0
- package/src/core/search-observation.ts +97 -0
- package/src/core/session-tree.ts +147 -0
- package/src/core/settings.ts +33 -10
- package/src/core/skills.ts +44 -0
- package/src/core/sql-query.ts +211 -0
- package/src/core/theme.ts +36 -58
- package/src/core/tool-contract.ts +51 -2
- package/src/core/tool-discovery.ts +87 -0
- package/src/core/tool-failure.ts +60 -0
- package/src/core/tool-input.ts +6 -0
- package/src/core/tool-mode-protocol.ts +25 -0
- package/src/core/transcript.ts +19 -4
- package/src/core/trial-lifecycle.ts +14 -0
- package/src/core/tui-mode.ts +29 -0
- package/src/core/update-check.ts +97 -0
- package/src/core/version.ts +55 -0
- package/src/core/work-state.ts +49 -3
- package/src/core/working-set.ts +91 -0
- package/src/runtime/auth.ts +11 -4
- package/src/runtime/branch-jobs.ts +36 -0
- package/src/runtime/catalog.ts +13 -3
- package/src/runtime/clipboard.ts +15 -4
- package/src/runtime/command-artifacts.ts +64 -0
- package/src/runtime/control-connection.ts +104 -0
- package/src/runtime/environment-tools.ts +60 -0
- package/src/runtime/evidence-notes.ts +4 -2
- package/src/runtime/exec-sessions.ts +206 -61
- package/src/runtime/execution-control.ts +1 -0
- package/src/runtime/extension-ui.ts +22 -30
- package/src/runtime/extensions.ts +95 -16
- package/src/runtime/harness-comparison.ts +27 -7
- package/src/runtime/harness-eval.ts +462 -0
- package/src/runtime/history-cache.ts +57 -0
- package/src/runtime/history-tools.ts +62 -12
- package/src/runtime/integrations.ts +210 -0
- package/src/runtime/loop.ts +1088 -282
- package/src/runtime/model-discovery.ts +208 -0
- package/src/runtime/model.ts +9 -0
- package/src/runtime/native-eval-fixtures.ts +33 -0
- package/src/runtime/native-eval.ts +146 -0
- package/src/runtime/oauth.ts +80 -3
- package/src/runtime/pi-provider.ts +169 -12
- package/src/runtime/program-status.ts +38 -0
- package/src/runtime/provider-connect.ts +109 -0
- package/src/runtime/provider-presets.ts +87 -0
- package/src/runtime/provider-request.ts +13 -2
- package/src/runtime/provider.ts +23 -9
- package/src/runtime/query-eval-verifier.ts +28 -0
- package/src/runtime/recovery-coverage.ts +71 -0
- package/src/runtime/recovery-evidence.ts +76 -0
- package/src/runtime/recovery-fixtures.ts +116 -0
- package/src/runtime/runtime-target.ts +149 -0
- package/src/runtime/session-control.ts +198 -0
- package/src/runtime/tool-broker.ts +137 -0
- package/src/runtime/tool-discovery.ts +57 -0
- package/src/runtime/tool-mode-eval.ts +146 -0
- package/src/runtime/tool-queue.ts +77 -0
- package/src/runtime/tools.ts +628 -183
- package/src/runtime/trial-control.ts +45 -0
- package/src/runtime/update-check.ts +47 -0
- package/src/runtime/window-trial.ts +2 -1
- package/src/runtime/working-notes.ts +33 -3
- package/src/runtime/workspace-lifecycle.ts +97 -0
- package/src/store/acceptance.ts +15 -0
- package/src/store/checkpoint-history.ts +43 -0
- package/src/store/command-artifacts.ts +96 -0
- package/src/store/command.ts +65 -37
- package/src/store/file-checkpoints.ts +104 -0
- package/src/store/file-index.ts +13 -1
- package/src/store/git-checkpoints.ts +67 -0
- package/src/store/observations.ts +61 -20
- package/src/store/process-receipts.ts +34 -0
- package/src/store/query-source.ts +13 -8
- package/src/store/recovery.ts +2 -3
- package/src/store/replay.ts +16 -4
- package/src/store/runtime-target.ts +64 -0
- package/src/store/search.ts +171 -132
- package/src/store/session-archive.ts +4 -0
- package/src/store/source-cancellation.ts +6 -0
- package/src/store/update-cache.ts +36 -0
- package/src/store/workspace-observation.ts +113 -0
- package/src/tui/ansi-spans.ts +126 -0
- package/src/tui/style.ts +19 -16
- package/src/tui/ui/atomic-draft.ts +121 -0
- package/src/tui/ui/canvas.ts +96 -0
- package/src/tui/ui/colors.ts +70 -0
- package/src/tui/ui/composer.ts +200 -0
- package/src/tui/ui/control-panel.ts +23 -0
- package/src/tui/ui/dialog.ts +379 -0
- package/src/tui/ui/draft.ts +23 -0
- package/src/tui/ui/edge.ts +186 -0
- package/src/tui/ui/image-size.ts +40 -0
- package/src/tui/ui/ink.ts +210 -0
- package/src/tui/ui/items.ts +40 -0
- package/src/tui/ui/keys.ts +40 -0
- package/src/tui/ui/layout.ts +74 -0
- package/src/tui/ui/list-model.ts +86 -0
- package/src/tui/ui/list-view.ts +172 -0
- package/src/tui/ui/mascot.ts +67 -0
- package/src/tui/ui/messages.ts +518 -0
- package/src/tui/ui/motion.ts +28 -0
- package/src/tui/ui/paths.ts +10 -0
- package/src/tui/ui/pictures.ts +78 -0
- package/src/tui/ui/screen.ts +421 -0
- package/src/tui/ui/status.ts +279 -0
- package/src/tui/ui/styled.ts +85 -0
- package/src/tui/ui/testing.ts +5 -0
- package/src/tui/ui/tool-card.ts +150 -0
- package/src/apps/extension-ui.ts +0 -330
- package/src/apps/resource-browser.ts +0 -75
- package/src/apps/session-browser.ts +0 -113
- package/src/apps/session-picker.ts +0 -55
- package/src/tui/ansi.ts +0 -67
- package/src/tui/autocomplete.ts +0 -29
- package/src/tui/chat-viewport.ts +0 -47
- package/src/tui/complete.ts +0 -87
- package/src/tui/editor.ts +0 -940
- package/src/tui/host.ts +0 -264
- package/src/tui/input.ts +0 -75
- package/src/tui/keys.ts +0 -378
- package/src/tui/markdown.ts +0 -17
- package/src/tui/render.ts +0 -153
- package/src/tui/select.ts +0 -258
- package/src/tui/settings.ts +0 -44
- package/src/tui/statusline.ts +0 -162
- package/src/tui/system-theme.ts +0 -700
- package/src/tui/terminal.ts +0 -30
- package/src/tui/text.ts +0 -52
- package/src/tui/toolview.ts +0 -386
- package/src/tui/transcript.ts +0 -123
- package/src/tui/tree.ts +0 -471
- package/src/vendor/pi-tui/LICENSE.upstream +0 -21
- package/src/vendor/pi-tui/UPSTREAM.json +0 -50
- package/src/vendor/pi-tui/VENDOR.md +0 -25
- package/src/vendor/pi-tui/alt-screen-search.ts +0 -327
- package/src/vendor/pi-tui/autocomplete.ts +0 -860
- package/src/vendor/pi-tui/colors.ts +0 -367
- package/src/vendor/pi-tui/components/alt-screen-flash.ts +0 -51
- package/src/vendor/pi-tui/components/box.ts +0 -166
- package/src/vendor/pi-tui/components/cancellable-loader.ts +0 -40
- package/src/vendor/pi-tui/components/editor.ts +0 -2472
- package/src/vendor/pi-tui/components/h-stack.ts +0 -44
- package/src/vendor/pi-tui/components/image.ts +0 -127
- package/src/vendor/pi-tui/components/input.ts +0 -494
- package/src/vendor/pi-tui/components/loader.ts +0 -101
- package/src/vendor/pi-tui/components/markdown.ts +0 -1021
- package/src/vendor/pi-tui/components/mouse-region.ts +0 -33
- package/src/vendor/pi-tui/components/scroll-view.ts +0 -224
- package/src/vendor/pi-tui/components/select-list.ts +0 -273
- package/src/vendor/pi-tui/components/settings-list.ts +0 -328
- package/src/vendor/pi-tui/components/spacer.ts +0 -28
- package/src/vendor/pi-tui/components/stack.ts +0 -154
- package/src/vendor/pi-tui/components/text.ts +0 -107
- package/src/vendor/pi-tui/components/truncated-text.ts +0 -65
- package/src/vendor/pi-tui/components/v-stack.ts +0 -33
- package/src/vendor/pi-tui/editor-component.ts +0 -74
- package/src/vendor/pi-tui/fuzzy.ts +0 -138
- package/src/vendor/pi-tui/index.ts +0 -186
- package/src/vendor/pi-tui/keybindings.ts +0 -320
- package/src/vendor/pi-tui/keys.ts +0 -1401
- package/src/vendor/pi-tui/kill-ring.ts +0 -46
- package/src/vendor/pi-tui/latex.ts +0 -1506
- package/src/vendor/pi-tui/layout-node.ts +0 -51
- package/src/vendor/pi-tui/layout.ts +0 -449
- package/src/vendor/pi-tui/native/UPSTREAM.json +0 -23
- package/src/vendor/pi-tui/native/clipboard.h +0 -208
- package/src/vendor/pi-tui/native/darwin/README.md +0 -22
- package/src/vendor/pi-tui/native/darwin/build.sh +0 -69
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +0 -132
- package/src/vendor/pi-tui/native/linux/README.md +0 -23
- package/src/vendor/pi-tui/native/linux/build.sh +0 -65
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +0 -70
- package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +0 -274
- package/src/vendor/pi-tui/native/napi.h +0 -91
- package/src/vendor/pi-tui/native/win32/README.md +0 -31
- package/src/vendor/pi-tui/native/win32/build.mjs +0 -232
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/src/win32-platform.c +0 -241
- package/src/vendor/pi-tui/native-modifiers.ts +0 -13
- package/src/vendor/pi-tui/native-module-path.ts +0 -31
- package/src/vendor/pi-tui/native-platform.ts +0 -65
- package/src/vendor/pi-tui/oklab.ts +0 -233
- package/src/vendor/pi-tui/stdin-buffer.ts +0 -444
- package/src/vendor/pi-tui/terminal-colors.ts +0 -91
- package/src/vendor/pi-tui/terminal-image.ts +0 -731
- package/src/vendor/pi-tui/terminal.ts +0 -554
- package/src/vendor/pi-tui/tui-alt-screen.ts +0 -1750
- package/src/vendor/pi-tui/tui-main-screen.ts +0 -655
- package/src/vendor/pi-tui/tui.ts +0 -1493
- package/src/vendor/pi-tui/undo-stack.ts +0 -28
- package/src/vendor/pi-tui/utils.ts +0 -1385
- package/src/vendor/pi-tui/wheel-scroll.ts +0 -82
- package/src/vendor/pi-tui/word-navigation.ts +0 -117
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/** Pi's factory/registration protocol, attached to xeno's session rather than another AgentSession. */
|
|
2
2
|
import { loadExtensionFactory } from "./extension-loader.ts";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
4
|
import type { ResourceIndex } from "../store/resource-index.ts";
|
|
4
5
|
import type { ExtensionEvent, LogEvent } from "../core/events.ts";
|
|
5
6
|
import { isJsonValue, type JsonValue } from "../core/tool-contract.ts";
|
|
6
|
-
import {
|
|
7
|
+
import type { ToolContext, ToolDefinition } from "./tools.ts";
|
|
8
|
+
import { nativeCommandResult } from "./tools.ts";
|
|
7
9
|
import { attachImageBytes } from "./images.ts";
|
|
8
10
|
import type { ExtensionComponent, ExtensionUI, ExtensionTheme } from "./extension-ui.ts";
|
|
11
|
+
import { DISCOVERY_TOOL, DISCOVERY_TOOL_NAME } from "./tool-discovery.ts";
|
|
12
|
+
import { LazyIntegration, type ExtensionIntegration } from "./integrations.ts";
|
|
9
13
|
|
|
10
14
|
export interface ExtensionContext {
|
|
11
15
|
ui: ExtensionUI; cwd: string; mode: "tui" | "json" | "print"; hasUI: boolean;
|
|
@@ -35,6 +39,8 @@ export interface ExtensionToolRenderContext {
|
|
|
35
39
|
}
|
|
36
40
|
export interface ExtensionTool {
|
|
37
41
|
name: string; label?: string; description: string; parameters: unknown;
|
|
42
|
+
/** R11: omitted remains direct; deferred schemas are activated at a Session checkpoint. */
|
|
43
|
+
exposure?: "direct" | "deferred";
|
|
38
44
|
promptSnippet?: string; promptGuidelines?: string[]; executionMode?: "parallel" | "sequential";
|
|
39
45
|
prepareArguments?(input: unknown): unknown;
|
|
40
46
|
execute(id: string, args: Record<string, unknown>, signal: AbortSignal | undefined,
|
|
@@ -52,6 +58,7 @@ export interface ExtensionActions {
|
|
|
52
58
|
setLabel(id: string, label: string | undefined): Promise<void>;
|
|
53
59
|
setTools(names: string[]): Promise<void>;
|
|
54
60
|
getTools(): { name: string; description: string; parameters: unknown }[];
|
|
61
|
+
getAllTools?(): { name: string; description: string; parameters: unknown }[];
|
|
55
62
|
setModel(model: unknown): Promise<boolean>;
|
|
56
63
|
setThinking(level: string): Promise<void>;
|
|
57
64
|
exec(command: string, args: string[], options?: { cwd?: string; timeout?: number; signal?: AbortSignal }): Promise<{ stdout: string; stderr: string; code: number; killed: boolean }>;
|
|
@@ -60,6 +67,7 @@ export interface ExtensionAPI {
|
|
|
60
67
|
on(event: string, handler: ExtensionHandler): () => void;
|
|
61
68
|
registerCommand(name: string, command: ExtensionCommand): void;
|
|
62
69
|
registerTool(tool: ExtensionTool): void;
|
|
70
|
+
registerIntegration(integration: ExtensionIntegration): void;
|
|
63
71
|
registerShortcut(key: string, shortcut: { description?: string; handler(ctx: ExtensionContext): void | Promise<void> }): void;
|
|
64
72
|
registerFlag(name: string, options: Flag): void;
|
|
65
73
|
getFlag(name: string): string | boolean | undefined;
|
|
@@ -106,11 +114,16 @@ export class ExtensionRegistry {
|
|
|
106
114
|
private handlers = new Map<string, Set<ExtensionHandler>>();
|
|
107
115
|
private bus = new Map<string, Set<(data: unknown) => void>>();
|
|
108
116
|
private pending = new Set<Promise<void>>();
|
|
117
|
+
/** Pi A / D8: void session mutations still have a durable invocation order. */
|
|
118
|
+
private actionTail: Promise<void> = Promise.resolve();
|
|
109
119
|
private disposers: (() => void)[] = [];
|
|
110
120
|
private detachListeners = new Set<() => void>();
|
|
111
121
|
private toolUpdateListeners = new Set<(name: string, id: string, input: unknown, result: ExtensionToolResult) => void>();
|
|
112
122
|
private actions: ExtensionActions | undefined;
|
|
123
|
+
private readonly execution = new AsyncLocalStorage<ToolContext>();
|
|
113
124
|
private disposed = false;
|
|
125
|
+
private readonly integrations = new Map<string, LazyIntegration>();
|
|
126
|
+
private readonly integrationTools = new Map<string, LazyIntegration>();
|
|
114
127
|
private readonly options: ExtensionRegistryOptions;
|
|
115
128
|
constructor(options: ExtensionRegistryOptions = {}) { this.options = options; }
|
|
116
129
|
|
|
@@ -124,8 +137,10 @@ export class ExtensionRegistry {
|
|
|
124
137
|
return this.actions;
|
|
125
138
|
}
|
|
126
139
|
private enqueue(operation: () => Promise<void>): void {
|
|
127
|
-
const pending =
|
|
140
|
+
const pending = this.actionTail.then(operation).catch(error => this.report(String(error))).finally(() => this.pending.delete(pending));
|
|
128
141
|
this.pending.add(pending);
|
|
142
|
+
// A failing observer must not poison later accepted work or create an unhandled rejection.
|
|
143
|
+
this.actionTail = pending.catch(() => {});
|
|
129
144
|
}
|
|
130
145
|
async settle(): Promise<void> { while (this.pending.size) await Promise.all([...this.pending]); }
|
|
131
146
|
private report(message: string): void { this.diagnostics.push(message); this.options.report?.(`Extension: ${message}`); }
|
|
@@ -146,9 +161,42 @@ export class ExtensionRegistry {
|
|
|
146
161
|
},
|
|
147
162
|
registerTool: tool => {
|
|
148
163
|
if (!/^[a-zA-Z0-9_-]+$/.test(tool.name) || typeof tool.execute !== "function") throw new Error("Invalid tool definition");
|
|
149
|
-
if (["note", "history", "branch", "resolve"].includes(tool.name)) throw new Error(`${tool.name} owns the append-only harness and cannot be replaced`);
|
|
164
|
+
if (["note", "history", "branch", "resolve", DISCOVERY_TOOL_NAME].includes(tool.name)) throw new Error(`${tool.name} owns the append-only harness and cannot be replaced`);
|
|
165
|
+
if (tool.exposure !== undefined && tool.exposure !== "direct" && tool.exposure !== "deferred") throw new Error("Unknown tool exposure");
|
|
166
|
+
if (tool.exposure === "deferred" && tool.name.length > 128) throw new Error("Deferred tool names must fit the discovery receipt");
|
|
150
167
|
extensionSchema(tool.parameters); this.insert(this.tools, tool.name, tool, source);
|
|
151
168
|
},
|
|
169
|
+
registerIntegration: integration => {
|
|
170
|
+
if (!/^[a-zA-Z0-9_-]{1,64}$/.test(integration.name) || this.integrations.has(integration.name)
|
|
171
|
+
|| typeof integration.connect !== "function" || !Array.isArray(integration.tools) || !integration.tools.length) throw Error("Invalid or duplicate integration");
|
|
172
|
+
const repair = integration.repair;
|
|
173
|
+
if (repair && (!/^[a-zA-Z0-9_-]{1,64}$/.test(repair.command) || this.commands.has(repair.command)
|
|
174
|
+
|| this.options.reserved?.includes(repair.command) || typeof repair.run !== "function")) throw Error("Invalid or duplicate integration repair command");
|
|
175
|
+
const names = new Set<string>();
|
|
176
|
+
for (const tool of integration.tools) {
|
|
177
|
+
if (names.has(tool.name) || this.tools.has(tool.name)) throw Error("Duplicate integration tool");
|
|
178
|
+
names.add(tool.name);
|
|
179
|
+
}
|
|
180
|
+
// Copy the manifest before publishing. Registrations and live connection ownership share one generation.
|
|
181
|
+
const owner = new LazyIntegration({ ...integration, ...(repair ? { repair: { ...repair } } : {}),
|
|
182
|
+
tools: integration.tools.map(tool => ({ ...tool, parameters: structuredClone(tool.parameters) })) });
|
|
183
|
+
const added: string[] = [];
|
|
184
|
+
try {
|
|
185
|
+
for (const descriptor of owner.definition.tools) {
|
|
186
|
+
this.api(source).registerTool({ ...descriptor, exposure: descriptor.exposure ?? "deferred", execute: async () => { throw Error("Integration tools require their owner"); } });
|
|
187
|
+
added.push(descriptor.name); this.integrationTools.set(descriptor.name, owner);
|
|
188
|
+
}
|
|
189
|
+
if (repair) this.api(source).registerCommand(repair.command, { description: repair.description, handler: async (_args, ctx) => {
|
|
190
|
+
const result = await owner.repair(ctx);
|
|
191
|
+
await this.active().append({ customType: "integration_repair", content: result.content, display: true, inContext: false,
|
|
192
|
+
...(result.data === undefined ? {} : { data: result.data }) });
|
|
193
|
+
} });
|
|
194
|
+
this.integrations.set(integration.name, owner);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
for (const name of added) { this.tools.delete(name); this.integrationTools.delete(name); }
|
|
197
|
+
void owner.dispose(); throw error;
|
|
198
|
+
}
|
|
199
|
+
},
|
|
152
200
|
registerShortcut: (name, value) => this.insert(this.shortcuts, name, value, source),
|
|
153
201
|
registerFlag: (name, value) => {
|
|
154
202
|
if (!/^[a-zA-Z][a-zA-Z0-9-]*$/.test(name) || this.options.reservedFlags?.includes(name)) throw new Error(`CLI flag --${name} is reserved or invalid`);
|
|
@@ -166,13 +214,27 @@ export class ExtensionRegistry {
|
|
|
166
214
|
setSessionName: name => this.enqueue(() => this.active().setName(name)),
|
|
167
215
|
setLabel: (id, label) => this.enqueue(() => this.active().setLabel(id, label)),
|
|
168
216
|
getActiveTools: () => this.active().getTools().map(tool => tool.name),
|
|
169
|
-
getAllTools: () => this.active().getTools(),
|
|
217
|
+
getAllTools: () => this.active().getAllTools?.() ?? this.active().getTools(),
|
|
170
218
|
setActiveTools: names => this.enqueue(() => this.active().setTools(names)),
|
|
171
219
|
setModel: model => this.active().setModel(model),
|
|
172
220
|
getThinkingLevel: () => this.active().context().thinkingLevel,
|
|
173
221
|
setThinkingLevel: level => this.enqueue(() => this.active().setThinking(level)),
|
|
174
222
|
getCommands: () => [...this.commands].map(([name, command]) => ({ name, ...(command.description ? { description: command.description } : {}) })),
|
|
175
|
-
exec: (command, args, options) =>
|
|
223
|
+
exec: (command, args, options) => {
|
|
224
|
+
const pending = (async () => {
|
|
225
|
+
this.active();
|
|
226
|
+
const scope = this.execution.getStore();
|
|
227
|
+
if (!scope) return this.active().exec(command, [...args], options);
|
|
228
|
+
if (!scope.invokeHost || !scope.outputStore) throw new Error("native commands require a live Session host scope");
|
|
229
|
+
const result = await scope.invokeHost({ argv: [command, ...args], ...(options?.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
230
|
+
...(options?.timeout !== undefined ? { timeout: options.timeout } : {}) }, options?.signal);
|
|
231
|
+
return nativeCommandResult(result, scope.outputStore);
|
|
232
|
+
})();
|
|
233
|
+
// The host owns accepted work even if the extension drops this SDK promise.
|
|
234
|
+
// Keep the rejection available to an awaiter while observing it ourselves.
|
|
235
|
+
void pending.catch(error => this.report(`exec: ${String(error)}`));
|
|
236
|
+
return pending;
|
|
237
|
+
},
|
|
176
238
|
events: {
|
|
177
239
|
on: (name, handler) => { const listeners = this.bus.get(name) ?? new Set(); this.bus.set(name, listeners); listeners.add(handler); return () => { listeners.delete(handler); }; },
|
|
178
240
|
emit: (name, data) => { for (const handler of this.bus.get(name) ?? []) try { handler(data); } catch (error) { this.report(String(error)); } },
|
|
@@ -183,7 +245,7 @@ export class ExtensionRegistry {
|
|
|
183
245
|
async load(index: ResourceIndex): Promise<void> {
|
|
184
246
|
const version = ++generation;
|
|
185
247
|
for (const resource of index.entries.filter(r => r.kind === "extensions" && r.enabled)) {
|
|
186
|
-
const maps = [this.commands, this.tools, this.shortcuts, this.flags, this.renderers, this.entryRenderers];
|
|
248
|
+
const maps = [this.commands, this.tools, this.shortcuts, this.flags, this.renderers, this.entryRenderers, this.integrations, this.integrationTools];
|
|
187
249
|
const before = maps.map(map => new Set(map.keys()));
|
|
188
250
|
const oldHandlers = new Map([...this.handlers].map(([name, handlers]) => [name, new Set(handlers)]));
|
|
189
251
|
const oldBus = new Map([...this.bus].map(([name, handlers]) => [name, new Set(handlers)]));
|
|
@@ -218,36 +280,47 @@ export class ExtensionRegistry {
|
|
|
218
280
|
return true;
|
|
219
281
|
}
|
|
220
282
|
definitions(): ReadonlyMap<string, ToolDefinition> {
|
|
221
|
-
|
|
283
|
+
const definitions = new Map<string, ToolDefinition>([...this.tools].map(([name, tool]) => [name, {
|
|
222
284
|
name, description: tool.description, input_schema: extensionSchema(tool.parameters),
|
|
223
285
|
// Arbitrary local code is not provably read-only. Keep admission and effects
|
|
224
286
|
// serial; an extension's annotation cannot grant a parallel write capability.
|
|
225
287
|
access: "write", execution: "exclusive", branch: "none",
|
|
288
|
+
...(this.integrationTools.has(name) ? { integrationHealth: () => this.integrationTools.get(name)!.observe() } : {}),
|
|
226
289
|
...(tool.prepareArguments ? { prepareArguments: tool.prepareArguments } : {}),
|
|
227
290
|
run: async (ctx: ToolContext, raw: Record<string, unknown>) => {
|
|
228
|
-
let nested = 0;
|
|
229
291
|
const context = { ...this.active().context(), signal: ctx.signal, tools: this.active().getTools(),
|
|
230
292
|
executeTool: async (target: string, input: unknown) => {
|
|
231
|
-
if (
|
|
232
|
-
const out = await
|
|
233
|
-
return { isError: !out.ok, content: [{ type: "text", text: out.content }], details: out.data
|
|
293
|
+
if (!ctx.invoke) return { isError: true, content: [{ type: "text", text: "Nested tools require a live Session execution scope" }] };
|
|
294
|
+
const out = await ctx.invoke(target, input);
|
|
295
|
+
return { isError: !out.ok, content: [{ type: "text", text: out.content }], details: out.data,
|
|
296
|
+
outcome: out.outcome, diagnostic: out.diagnostic, warnings: out.warnings, artifact: out.content_ref, observation: out.observation };
|
|
234
297
|
} };
|
|
235
|
-
const
|
|
298
|
+
const update = (update: ExtensionToolResult) => {
|
|
236
299
|
for (const listener of this.toolUpdateListeners) try { listener(name, ctx.callId ?? name, raw, update); } catch (error) { this.report(String(error)); }
|
|
237
300
|
ctx.onOutput?.(new TextEncoder().encode(update.content.filter(c => c.type === "text").map(c => c.text).join("\n")));
|
|
238
|
-
}
|
|
301
|
+
};
|
|
302
|
+
const integration = this.integrationTools.get(name);
|
|
303
|
+
const managed = integration ? await this.execution.run(ctx, () => integration.execute(name, ctx.callId ?? name, raw, ctx.signal, update, context)) : undefined;
|
|
304
|
+
if (managed && !managed.ok) return managed.result;
|
|
305
|
+
const result = managed?.ok ? managed.result : await this.execution.run(ctx, () => tool.execute(ctx.callId ?? name, raw, ctx.signal, update, context));
|
|
239
306
|
const text = result.content.filter(c => c.type === "text").map(c => c.text).join("\n");
|
|
240
307
|
const images = [];
|
|
241
308
|
for (const item of result.content) if (item.type === "image" && ctx.imageStore) images.push(await attachImageBytes(Buffer.from(item.data, "base64"), name + ".png", ctx.imageStore, ctx.imageAutoResize));
|
|
242
309
|
return { ok: result.isError !== true && !ctx.signal?.aborted, content: text,
|
|
243
|
-
...(
|
|
310
|
+
...(managed?.ok ? { data: { kind: "integration_observation", version: 1, phase: "call", health: managed.observation,
|
|
311
|
+
...(isJsonValue(result.details) ? { result: result.details } : {}) } } : isJsonValue(result.details) ? { data: result.details } : {}),
|
|
312
|
+
...(images.length ? { images } : {}) };
|
|
244
313
|
},
|
|
245
314
|
}]));
|
|
315
|
+
if ([...this.tools.values()].some(tool => tool.exposure === "deferred")) definitions.set(DISCOVERY_TOOL_NAME, DISCOVERY_TOOL);
|
|
316
|
+
return definitions;
|
|
246
317
|
}
|
|
247
318
|
toolHooks(): NonNullable<ToolContext["extensionHooks"]> {
|
|
248
319
|
return {
|
|
249
|
-
|
|
250
|
-
|
|
320
|
+
scope: (ctx, action) => this.execution.run(ctx, action),
|
|
321
|
+
serial: () => ["tool_call", "tool_execution_start", "tool_execution_update", "tool_result", "tool_execution_end"].some(type => (this.handlers.get(type)?.size ?? 0) > 0),
|
|
322
|
+
before: async (toolName, input, toolCallId, parentToolCallId) => {
|
|
323
|
+
const blocked = (await this.emit("tool_call", { toolName, input, toolCallId, ...(parentToolCallId ? { parentToolCallId } : {}) })).find(result => result["block"] === true);
|
|
251
324
|
return blocked ? String(blocked["reason"] ?? "Blocked by extension") : undefined;
|
|
252
325
|
},
|
|
253
326
|
event: async (type, fields) => { await this.emit(type, fields); },
|
|
@@ -255,12 +328,18 @@ export class ExtensionRegistry {
|
|
|
255
328
|
}
|
|
256
329
|
async dispose(): Promise<void> {
|
|
257
330
|
if (this.disposed) return;
|
|
331
|
+
await Promise.all([...this.integrations.values()].map(owner => owner.dispose()));
|
|
258
332
|
for (const detach of this.detachListeners) try { detach(); } catch (error) { this.report(String(error)); }
|
|
259
333
|
this.detachListeners.clear(); this.toolUpdateListeners.clear();
|
|
260
334
|
await this.emit("session_shutdown"); await this.settle(); this.disposed = true;
|
|
261
335
|
for (const dispose of this.disposers.reverse()) try { dispose(); } catch (error) { this.report(String(error)); }
|
|
262
336
|
this.handlers.clear(); this.bus.clear(); this.actions = undefined;
|
|
263
337
|
}
|
|
338
|
+
/** Q.13: a new generation must not overlap an unresolved callback from its predecessor. */
|
|
339
|
+
async releaseIntegrations(): Promise<boolean> {
|
|
340
|
+
const released = await Promise.all([...this.integrations.values()].map(owner => owner.releaseForReload()));
|
|
341
|
+
return released.every(Boolean);
|
|
342
|
+
}
|
|
264
343
|
}
|
|
265
344
|
|
|
266
345
|
function extensionSchema(parameters: unknown): ToolDefinition["input_schema"] {
|
|
@@ -8,6 +8,8 @@ import { Session } from "./loop.ts";
|
|
|
8
8
|
import { buildRequest, OpenAICompletionsProvider } from "./openai.ts";
|
|
9
9
|
import type { Provider, ProviderConfig, ToolSchema, Turn } from "./provider.ts";
|
|
10
10
|
import { DEFAULT_TOOL_CONTEXT, TOOL_SCHEMAS, runTool, type ToolResult } from "./tools.ts";
|
|
11
|
+
import { openTrialControl } from "./trial-control.ts";
|
|
12
|
+
import type { TrialLifecycle } from "../core/trial-lifecycle.ts";
|
|
11
13
|
|
|
12
14
|
export const COMPARISON_TOOLS = ["read", "grep", "find", "ls", "write", "edit", "query"] as const;
|
|
13
15
|
export const COMPARISON_SYSTEM = "Use the supplied file tools to solve the user's task. " +
|
|
@@ -202,6 +204,7 @@ export function comparisonGateway(options: {
|
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
export interface ComparisonResult {
|
|
207
|
+
lifecycle?: TrialLifecycle;
|
|
205
208
|
arm: ComparisonArm;
|
|
206
209
|
status: "answered" | "error" | "budget" | "balance" | "stopped";
|
|
207
210
|
response: string;
|
|
@@ -221,20 +224,25 @@ export async function runComparison(options: {
|
|
|
221
224
|
arm: ComparisonArm; directory: string; workspace: string; prompt: string; config: ProviderConfig;
|
|
222
225
|
price: BenchmarkPrice; ledger: ComparisonLedger; backend: ComparisonBackend;
|
|
223
226
|
maxRequests: number; timeoutMs: number; pi: PiIdentity | null; extension: string;
|
|
227
|
+
signal?: AbortSignal;
|
|
224
228
|
onRequest?: (record: GatewayRecord) => Promise<void>;
|
|
225
229
|
/** Synthetic pressure only; live comparisons preserve Pi's default compaction. */
|
|
226
230
|
piCompaction?: { reserveTokens: number; keepRecentTokens: number };
|
|
227
231
|
}): Promise<ComparisonResult> {
|
|
228
232
|
const started = performance.now(), outputStore = join(options.directory, "observations");
|
|
229
233
|
await mkdir(outputStore, { recursive: true });
|
|
230
|
-
const gateway = comparisonGateway({ ...options,
|
|
234
|
+
const gateway = comparisonGateway({ ...options, backend: async (payload, signal) => {
|
|
235
|
+
control.beginBackend();
|
|
236
|
+
return options.backend(payload, AbortSignal.any([signal, control.signal]));
|
|
237
|
+
}, record: async (record) => {
|
|
231
238
|
await appendFile(join(options.directory, "requests.jsonl"), JSON.stringify(record) + "\n");
|
|
232
239
|
await options.onRequest?.(record);
|
|
233
240
|
}, executeTool: (name, input) => runTool({ ...DEFAULT_TOOL_CONTEXT, root: options.workspace, allow: COMPARISON_TOOLS, outputStore }, name, input) });
|
|
234
241
|
let response = "", cuts = 0, toolCalls = 0, toolFailures = 0, reason = "no completion";
|
|
235
242
|
let status: ComparisonResult["status"] = "stopped";
|
|
236
243
|
const config = { ...options.config, spec: { ...options.config.spec, baseUrl: gateway.url } };
|
|
237
|
-
const
|
|
244
|
+
const control = await openTrialControl(options.timeoutMs, options.signal, () => gateway.close()), signal = control.signal;
|
|
245
|
+
let session: Session | undefined, cleanupFailed = false;
|
|
238
246
|
try {
|
|
239
247
|
if (options.arm !== "pi") {
|
|
240
248
|
await mkdir(join(options.directory, "log"));
|
|
@@ -244,10 +252,11 @@ export async function runComparison(options: {
|
|
|
244
252
|
response = turn.blocks.filter((block) => block.type === "text").map((block) => block.text).join("\n");
|
|
245
253
|
return turn;
|
|
246
254
|
} };
|
|
247
|
-
|
|
255
|
+
session = new Session({ logDir: join(options.directory, "log"), frozen: { system: COMPARISON_SYSTEM, tools: comparisonTools() },
|
|
248
256
|
tools: { ...DEFAULT_TOOL_CONTEXT, root: options.workspace, allow: COMPARISON_TOOLS, outputStore },
|
|
249
257
|
contextPolicy: options.arm === "xeno-windowed" ? "windowed" : "continuous", maxTurns: options.maxRequests, harnessReviews: false }, provider);
|
|
250
258
|
await session.start();
|
|
259
|
+
control.enterExecution();
|
|
251
260
|
for await (const event of session.send(options.prompt, signal)) {
|
|
252
261
|
if (event.kind === "cut") cuts++;
|
|
253
262
|
if (event.kind === "tool_call") toolCalls++;
|
|
@@ -257,21 +266,32 @@ export async function runComparison(options: {
|
|
|
257
266
|
if (["stop", "end_turn"].includes(reason)) status = "answered";
|
|
258
267
|
} else {
|
|
259
268
|
if (!options.pi) throw new Error("Pi identity required");
|
|
269
|
+
control.enterExecution();
|
|
260
270
|
const run = await runPiPrompt({ directory: options.directory, workspace: options.workspace, prompt: options.prompt, config,
|
|
261
271
|
gateway, price: options.price, pi: options.pi, extension: options.extension, signal, resume: false,
|
|
262
272
|
...(options.piCompaction ? { piCompaction: options.piCompaction } : {}) });
|
|
263
273
|
({ response, reason, cuts, toolCalls, toolFailures } = run);
|
|
264
274
|
status = run.answered ? "answered" : "error";
|
|
265
275
|
}
|
|
266
|
-
} catch { status = "error"; reason =
|
|
267
|
-
finally {
|
|
276
|
+
} catch { status = "error"; reason = "harness failed; inspect local request and session evidence"; }
|
|
277
|
+
finally {
|
|
278
|
+
control.finishWork();
|
|
279
|
+
try { await session?.dispose(); } catch { cleanupFailed = true; options.ledger.halted = true; }
|
|
280
|
+
try { await gateway.close(); } catch { cleanupFailed = true; options.ledger.halted = true; }
|
|
281
|
+
}
|
|
268
282
|
if (gateway.stopped) {
|
|
269
283
|
status = gateway.stopped === "budget" || gateway.stopped === "balance" ? gateway.stopped : gateway.stopped === "limit" ? "stopped" : "error";
|
|
270
|
-
reason =
|
|
284
|
+
reason = `model gateway: ${gateway.stopped}`;
|
|
285
|
+
}
|
|
286
|
+
const lifecycle = control.observe(cleanupFailed ? "failed" : "complete");
|
|
287
|
+
if (lifecycle.interruption) {
|
|
288
|
+
status = lifecycle.interruption.source === "deadline" ? "error" : "stopped";
|
|
289
|
+
reason = lifecycle.interruption.source === "deadline" ? "timeout" : "cancelled";
|
|
271
290
|
}
|
|
291
|
+
if (cleanupFailed) { status = "error"; reason = "execution_error"; }
|
|
272
292
|
const usage: BenchmarkUsage = { input_tokens: 0, output_tokens: 0, cache_read_input_tokens: 0, cache_creation_input_tokens: 0 };
|
|
273
293
|
for (const row of gateway.records) if (row.usage) for (const key of Object.keys(usage) as (keyof BenchmarkUsage)[]) usage[key] += row.usage[key];
|
|
274
|
-
return { arm: options.arm, status, response, requests: gateway.requests, cuts, toolCalls, toolFailures,
|
|
294
|
+
return { arm: options.arm, status, response, requests: gateway.requests, cuts, toolCalls, toolFailures, lifecycle,
|
|
275
295
|
charged: gateway.records.reduce((n, row) => n + row.charge, 0),
|
|
276
296
|
reservedUnknown: gateway.records.reduce((n, row) => n + (row.measured === null ? row.charge : 0), 0),
|
|
277
297
|
usage, wallMs: performance.now() - started, reason,
|