paseo-plugin-zcode-provider 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/NOTICE.md +29 -0
- package/README.md +112 -2
- package/client/account.tsx +827 -0
- package/client/diagnostics.tsx +379 -0
- package/client/runtime.tsx +296 -0
- package/client/settings.tsx +84 -0
- package/client/value.tsx +20 -0
- package/icon.svg +1 -0
- package/index.client.tsx +22 -0
- package/index.server.ts +39 -0
- package/package.json +77 -4
- package/paseo-plugin.json +6 -0
- package/server/account.ts +781 -0
- package/server/attachments.ts +150 -0
- package/server/build-info.ts +2 -0
- package/server/conversation.ts +319 -0
- package/server/diagnostics.ts +252 -0
- package/server/discovery/discover.ts +225 -0
- package/server/discovery/manifest.ts +36 -0
- package/server/discovery/types.ts +30 -0
- package/server/errors.ts +36 -0
- package/server/host/bridge.ts +490 -0
- package/server/host/schemas.ts +119 -0
- package/server/logger.ts +19 -0
- package/server/mapping.ts +270 -0
- package/server/models.ts +68 -0
- package/server/persistence.ts +146 -0
- package/server/presentation.ts +470 -0
- package/server/provider-status.ts +81 -0
- package/server/provider.ts +838 -0
- package/server/runtime/managed.ts +501 -0
- package/server/runtime/pins.ts +72 -0
- package/server/runtime-setup.ts +77 -0
- package/server/session-types.ts +49 -0
- package/server/session.ts +866 -0
- package/server/status.ts +89 -0
- package/server/timeline.ts +18 -0
- package/server/usage.ts +362 -0
- package/server/vendor/zcode/LICENSE +202 -0
- package/server/vendor/zcode/NOTICE.md +70 -0
- package/server/vendor/zcode/THIRD-PARTY-NOTICES.md +37 -0
- package/server/vendor/zcode/packages/model-option-map/src/compiler.ts +92 -0
- package/server/vendor/zcode/packages/model-option-map/src/evaluator.ts +214 -0
- package/server/vendor/zcode/packages/model-option-map/src/index.ts +5 -0
- package/server/vendor/zcode/packages/model-option-map/src/merge-patch.ts +97 -0
- package/server/vendor/zcode/packages/model-option-map/src/option-maps.ts +42 -0
- package/server/vendor/zcode/packages/model-option-map/src/parser.ts +240 -0
- package/server/vendor/zcode/packages/model-option-map/src/tokenizer.ts +141 -0
- package/server/vendor/zcode/packages/model-option-map/src/types.ts +36 -0
- package/server/vendor/zcode/packages/rpc/src/buffer.ts +88 -0
- package/server/vendor/zcode/packages/rpc/src/channelClient.ts +275 -0
- package/server/vendor/zcode/packages/rpc/src/channelServer.ts +283 -0
- package/server/vendor/zcode/packages/rpc/src/channels.shared.ts +66 -0
- package/server/vendor/zcode/packages/rpc/src/channels.ts +4 -0
- package/server/vendor/zcode/packages/rpc/src/delayedChannel.ts +17 -0
- package/server/vendor/zcode/packages/rpc/src/foundation.ts +260 -0
- package/server/vendor/zcode/packages/rpc/src/protocol.ts +397 -0
- package/server/vendor/zcode/packages/rpc/src/proxy-channel.ts +213 -0
- package/server/vendor/zcode/packages/rpc/src/serialization.ts +291 -0
- package/server/vendor/zcode/packages/shared/src/account-provider-state.ts +13 -0
- package/server/vendor/zcode/packages/shared/src/background-bash-output.ts +24 -0
- package/server/vendor/zcode/packages/shared/src/background-task-controls.ts +404 -0
- package/server/vendor/zcode/packages/shared/src/bash-output-display.ts +11 -0
- package/server/vendor/zcode/packages/shared/src/bots.ts +558 -0
- package/server/vendor/zcode/packages/shared/src/browser-use/backend.ts +80 -0
- package/server/vendor/zcode/packages/shared/src/browser-use/command-metadata.ts +170 -0
- package/server/vendor/zcode/packages/shared/src/browser-use/commands.ts +588 -0
- package/server/vendor/zcode/packages/shared/src/browser-use/result.ts +130 -0
- package/server/vendor/zcode/packages/shared/src/browser-use/snapshot.ts +77 -0
- package/server/vendor/zcode/packages/shared/src/command-types.ts +96 -0
- package/server/vendor/zcode/packages/shared/src/config-schema.ts +8 -0
- package/server/vendor/zcode/packages/shared/src/cuaAccessibilitySettings.ts +41 -0
- package/server/vendor/zcode/packages/shared/src/database-startup.ts +178 -0
- package/server/vendor/zcode/packages/shared/src/env.ts +63 -0
- package/server/vendor/zcode/packages/shared/src/execution-output-preview.ts +14 -0
- package/server/vendor/zcode/packages/shared/src/localTtft.ts +192 -0
- package/server/vendor/zcode/packages/shared/src/model-config.ts +123 -0
- package/server/vendor/zcode/packages/shared/src/model-execution.ts +23 -0
- package/server/vendor/zcode/packages/shared/src/model-provider-family.ts +171 -0
- package/server/vendor/zcode/packages/shared/src/model-provider-types.ts +81 -0
- package/server/vendor/zcode/packages/shared/src/model-selection.ts +60 -0
- package/server/vendor/zcode/packages/shared/src/oauth.ts +203 -0
- package/server/vendor/zcode/packages/shared/src/official-mcp-auth.ts +272 -0
- package/server/vendor/zcode/packages/shared/src/official-mcp-tool-error.ts +68 -0
- package/server/vendor/zcode/packages/shared/src/process-diagnostic.ts +42 -0
- package/server/vendor/zcode/packages/shared/src/protocol.ts +369 -0
- package/server/vendor/zcode/packages/shared/src/provider-family-connection-selection.ts +32 -0
- package/server/vendor/zcode/packages/shared/src/remoteAssetInstallMode.ts +11 -0
- package/server/vendor/zcode/packages/shared/src/remoteResourcePackages.ts +91 -0
- package/server/vendor/zcode/packages/shared/src/runtimeEnv.ts +315 -0
- package/server/vendor/zcode/packages/shared/src/settings-source.ts +10 -0
- package/server/vendor/zcode/packages/shared/src/usage-quota.ts +74 -0
- package/server/vendor/zcode/packages/shared/src/usage-stats.ts +462 -0
- package/server/vendor/zcode/packages/shared/src/validationAppSettings.ts +562 -0
- package/server/vendor/zcode/packages/shared/src/workspacePurpose.ts +2 -0
- package/server/vendor/zcode/packages/shared/src/wslUserValidation.ts +33 -0
- package/server/vendor/zcode/packages/shared/src/zcode-agent-policy.ts +25 -0
- package/server/vendor/zcode/packages/shared/src/zcode-media-policy.ts +10 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol/index.ts +3720 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-legacy-types.ts +529 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/apply.ts +218 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/attachment-ref.ts +14 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/command.ts +482 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/core.ts +100 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/create-workflow-display.ts +156 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/cuaPermission.ts +41 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/delta.ts +147 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/input-intent.ts +69 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/rows.ts +432 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/session-config.ts +43 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/sessions-index-workflow-activity.ts +195 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/sessions-index.ts +85 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/shared-context-import.ts +38 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/shared-context-ref.ts +10 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/snapshot.ts +509 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/submission.ts +5 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/toolDisplay.ts +264 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/transport.ts +1152 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-assembler.ts +574 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-binary.ts +65 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-codec.ts +240 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-fault.ts +39 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire.ts +159 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-artifacts.ts +291 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-observation-display.ts +281 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-row-meta.ts +163 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-run-settings-command.ts +62 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-runs-delta.ts +517 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-runs.ts +512 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workspace-config.ts +81 -0
- package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workspace-hook-review.ts +210 -0
- package/server/vendor/zcode/packages/shared/src/zcode-task-mode-schema.ts +6 -0
- package/server/vendor/zcode/packages/shared/src/zcode-task-types-core.ts +1187 -0
- package/server/vendor/zcode/packages/shared/src/zcodeEndpoint.ts +296 -0
- package/server/vendor/zcode/provenance.json +405 -0
- package/shared/account.ts +201 -0
- package/shared/diagnostics.ts +52 -0
- package/shared/runtime-setup.ts +73 -0
- package/vendor/acp-registry/LICENSE +176 -0
- package/vendor/paseo/LICENSE +211 -0
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { isDeepStrictEqual } from "node:util";
|
|
3
|
+
import { isAbsolute } from "node:path";
|
|
4
|
+
import { realpath, stat } from "node:fs/promises";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type {
|
|
7
|
+
ProviderSessionConfig,
|
|
8
|
+
ProviderConfigState,
|
|
9
|
+
ProviderPermissionRequest,
|
|
10
|
+
ProviderPermissionResponse,
|
|
11
|
+
ProviderCommand,
|
|
12
|
+
ProviderTimelineItem,
|
|
13
|
+
} from "@getpaseo/plugin/server/provider";
|
|
14
|
+
import type {
|
|
15
|
+
ConversationRow,
|
|
16
|
+
TurnHeaderRow,
|
|
17
|
+
} from "./vendor/zcode/packages/shared/src/zcode-protocol-v4/rows.js";
|
|
18
|
+
import { Conversation, conversationCommand } from "./conversation.js";
|
|
19
|
+
import {
|
|
20
|
+
rowTimeline,
|
|
21
|
+
usage,
|
|
22
|
+
presentInteraction,
|
|
23
|
+
type PresentedInteraction,
|
|
24
|
+
} from "./presentation.js";
|
|
25
|
+
import { readModelSelection, resolveModelSelection } from "./models.js";
|
|
26
|
+
import { uploadAttachments } from "./attachments.js";
|
|
27
|
+
import {
|
|
28
|
+
catalogModels,
|
|
29
|
+
decodeModel,
|
|
30
|
+
encodeModel,
|
|
31
|
+
mapPrompt,
|
|
32
|
+
requireMode,
|
|
33
|
+
ZCODE_MODES,
|
|
34
|
+
} from "./mapping.js";
|
|
35
|
+
import {
|
|
36
|
+
InitializeResultSchema,
|
|
37
|
+
WorkspacePresentationSchema,
|
|
38
|
+
type SessionSnapshot,
|
|
39
|
+
type ModelOption,
|
|
40
|
+
type ModelSelectionView,
|
|
41
|
+
} from "./host/schemas.js";
|
|
42
|
+
import type { HostBridge } from "./host/bridge.js";
|
|
43
|
+
import type { Logger } from "./logger.js";
|
|
44
|
+
import type {
|
|
45
|
+
NativePromptInput,
|
|
46
|
+
NativeSessionEvent,
|
|
47
|
+
NativeTimelineEntry,
|
|
48
|
+
} from "./session-types.js";
|
|
49
|
+
import { AdapterError } from "./errors.js";
|
|
50
|
+
import { diagnosticError, formatDiagnostic } from "./diagnostics.js";
|
|
51
|
+
interface Admission {
|
|
52
|
+
clientMessageId?: string;
|
|
53
|
+
accepted: boolean;
|
|
54
|
+
consumed: boolean;
|
|
55
|
+
productTurnId?: string;
|
|
56
|
+
}
|
|
57
|
+
interface Run {
|
|
58
|
+
id: string;
|
|
59
|
+
inputs: Map<string, Admission>;
|
|
60
|
+
productTurns: Set<string>;
|
|
61
|
+
announced: boolean;
|
|
62
|
+
inFlight: number;
|
|
63
|
+
cancelled: boolean;
|
|
64
|
+
stopping?: Promise<void>;
|
|
65
|
+
buffered: NativeSessionEvent[];
|
|
66
|
+
}
|
|
67
|
+
export class ZCodeSession {
|
|
68
|
+
readonly id: string;
|
|
69
|
+
private readonly conversation: Conversation;
|
|
70
|
+
private readonly listeners = new Set<(event: NativeSessionEvent) => void>();
|
|
71
|
+
private readonly admissions = new Map<string, Admission>();
|
|
72
|
+
private readonly seenTurns = new Set<string>();
|
|
73
|
+
private readonly publicTurns = new Map<string, string>();
|
|
74
|
+
private readonly presented = new Map<string, NativeTimelineEntry>();
|
|
75
|
+
private readonly pending = new Map<string, PresentedInteraction>();
|
|
76
|
+
private readonly answering = new Set<string>();
|
|
77
|
+
private readonly resolvedInteractions = new Set<string>();
|
|
78
|
+
private active?: Run;
|
|
79
|
+
private pendingAdmissions = 0;
|
|
80
|
+
private failed = false;
|
|
81
|
+
private closed = false;
|
|
82
|
+
private initialized = false;
|
|
83
|
+
private unlistenFailure?: () => void;
|
|
84
|
+
private previousConfig?: unknown;
|
|
85
|
+
private previousUsage?: unknown;
|
|
86
|
+
private readonly idleListeners = new Set<() => void>();
|
|
87
|
+
private constructor(
|
|
88
|
+
private readonly bridge: HostBridge,
|
|
89
|
+
private readonly logger: Logger,
|
|
90
|
+
private readonly workspace: string,
|
|
91
|
+
private readonly snapshot: SessionSnapshot,
|
|
92
|
+
private modelCatalog: readonly ModelOption[],
|
|
93
|
+
private readonly onClose: () => void,
|
|
94
|
+
) {
|
|
95
|
+
assertSnapshotWorkspace(snapshot, workspace);
|
|
96
|
+
this.id = snapshot.session.sessionId;
|
|
97
|
+
this.conversation = new Conversation(bridge, workspace, this.id, (error) =>
|
|
98
|
+
this.runtimeFailed(diagnosticError(error, bridge.diagnostic)),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
static async create(options: {
|
|
102
|
+
bridge: HostBridge;
|
|
103
|
+
logger: Logger;
|
|
104
|
+
workspace: string;
|
|
105
|
+
snapshot: SessionSnapshot;
|
|
106
|
+
modelCatalog: readonly ModelOption[];
|
|
107
|
+
onClose: () => void;
|
|
108
|
+
}): Promise<ZCodeSession> {
|
|
109
|
+
const session = new ZCodeSession(
|
|
110
|
+
options.bridge,
|
|
111
|
+
options.logger,
|
|
112
|
+
options.workspace,
|
|
113
|
+
options.snapshot,
|
|
114
|
+
options.modelCatalog,
|
|
115
|
+
options.onClose,
|
|
116
|
+
);
|
|
117
|
+
session.unlistenFailure = options.bridge.onFailure((error) =>
|
|
118
|
+
session.runtimeFailed(error),
|
|
119
|
+
);
|
|
120
|
+
try {
|
|
121
|
+
await session.conversation.open();
|
|
122
|
+
session.assertOpen();
|
|
123
|
+
if (!session.conversation.idle)
|
|
124
|
+
throw new AdapterError(
|
|
125
|
+
"SESSION_BUSY",
|
|
126
|
+
"ZCode resumed a conversation with active foreground work",
|
|
127
|
+
);
|
|
128
|
+
for (const row of session.state.rows.window) {
|
|
129
|
+
if (row.kind === "turnHeader")
|
|
130
|
+
session.seenTurns.add(session.productTurn(row));
|
|
131
|
+
const item = rowTimeline(row);
|
|
132
|
+
if (item)
|
|
133
|
+
session.presented.set(item.id, {
|
|
134
|
+
item,
|
|
135
|
+
timestamp: new Date(row.createdAt).toISOString(),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
session.conversation.subscribe(() => {
|
|
139
|
+
try {
|
|
140
|
+
session.observe();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
session.runtimeFailed(
|
|
143
|
+
diagnosticError(error, session.bridge.diagnostic),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
session.initialized = true;
|
|
148
|
+
session.observe();
|
|
149
|
+
return session;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
session.unlistenFailure();
|
|
152
|
+
await session.conversation.close();
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
private get state() {
|
|
157
|
+
return this.conversation.state!;
|
|
158
|
+
}
|
|
159
|
+
private productTurn(row: ConversationRow): string {
|
|
160
|
+
if (!row.productTurnId)
|
|
161
|
+
throw new AdapterError(
|
|
162
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
163
|
+
"ZCode V4 row has no product turn ID",
|
|
164
|
+
);
|
|
165
|
+
return row.productTurnId;
|
|
166
|
+
}
|
|
167
|
+
private newRun(): Run {
|
|
168
|
+
return {
|
|
169
|
+
id: randomUUID(),
|
|
170
|
+
inputs: new Map(),
|
|
171
|
+
productTurns: new Set(),
|
|
172
|
+
announced: false,
|
|
173
|
+
inFlight: 0,
|
|
174
|
+
cancelled: false,
|
|
175
|
+
buffered: [],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
private announce(run: Run): void {
|
|
179
|
+
if (run.announced) return;
|
|
180
|
+
run.announced = true;
|
|
181
|
+
this.emit({ type: "turn_started", turnId: run.id });
|
|
182
|
+
for (const e of run.buffered.splice(0)) this.emit(e);
|
|
183
|
+
}
|
|
184
|
+
private runEvent(event: NativeSessionEvent, run = this.active): void {
|
|
185
|
+
if (run && !run.announced) run.buffered.push(event);
|
|
186
|
+
else this.emit(event);
|
|
187
|
+
}
|
|
188
|
+
private observe(): void {
|
|
189
|
+
if (!this.initialized || this.failed || this.closed) return;
|
|
190
|
+
for (const row of this.state.rows.window) {
|
|
191
|
+
if (row.kind === "userInput" && row.sourceCommandId) {
|
|
192
|
+
const input = this.admissions.get(row.sourceCommandId);
|
|
193
|
+
if (input) {
|
|
194
|
+
input.consumed = true;
|
|
195
|
+
input.productTurnId = this.productTurn(row);
|
|
196
|
+
if (this.active?.inputs.has(row.sourceCommandId)) {
|
|
197
|
+
this.active.productTurns.add(input.productTurnId);
|
|
198
|
+
this.publicTurns.set(input.productTurnId, this.active.id);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (row.kind !== "turnHeader") continue;
|
|
203
|
+
const turn = this.productTurn(row);
|
|
204
|
+
if (!this.seenTurns.has(turn)) {
|
|
205
|
+
this.seenTurns.add(turn);
|
|
206
|
+
if (!this.active && row.origin !== "userInput") {
|
|
207
|
+
this.active = this.newRun();
|
|
208
|
+
this.announce(this.active);
|
|
209
|
+
}
|
|
210
|
+
if (this.active) {
|
|
211
|
+
this.active.productTurns.add(turn);
|
|
212
|
+
this.publicTurns.set(turn, this.active.id);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const row of this.state.rows.window) {
|
|
217
|
+
const clientMessageId =
|
|
218
|
+
row.kind === "userInput" && row.sourceCommandId
|
|
219
|
+
? this.admissions.get(row.sourceCommandId)?.clientMessageId
|
|
220
|
+
: undefined;
|
|
221
|
+
const item = rowTimeline(row, clientMessageId);
|
|
222
|
+
if (!item) continue;
|
|
223
|
+
const entry = { item, timestamp: new Date(row.createdAt).toISOString() };
|
|
224
|
+
if (isDeepStrictEqual(entry, this.presented.get(item.id))) continue;
|
|
225
|
+
this.presented.set(item.id, entry);
|
|
226
|
+
this.runEvent({
|
|
227
|
+
type: "timeline",
|
|
228
|
+
...entry,
|
|
229
|
+
turnId: row.productTurnId
|
|
230
|
+
? this.publicTurns.get(row.productTurnId)
|
|
231
|
+
: undefined,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
if (this.state.plan) {
|
|
235
|
+
const item: ProviderTimelineItem = {
|
|
236
|
+
id: "zcode:plan",
|
|
237
|
+
type: "todo",
|
|
238
|
+
items: this.state.plan.items.map((i) => ({
|
|
239
|
+
text: i.content,
|
|
240
|
+
completed: i.status === "completed",
|
|
241
|
+
status: i.status === "inProgress" ? "in_progress" : i.status,
|
|
242
|
+
})),
|
|
243
|
+
};
|
|
244
|
+
if (!isDeepStrictEqual(item, this.presented.get(item.id)?.item)) {
|
|
245
|
+
this.presented.set(item.id, { item });
|
|
246
|
+
this.runEvent({ type: "timeline", item, turnId: this.active?.id });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (!isDeepStrictEqual(this.previousConfig, this.state.config)) {
|
|
250
|
+
this.previousConfig = this.state.config;
|
|
251
|
+
this.emit({ type: "config_changed" });
|
|
252
|
+
}
|
|
253
|
+
const nextUsage = usage(this.state);
|
|
254
|
+
if (!isDeepStrictEqual(nextUsage, this.previousUsage)) {
|
|
255
|
+
this.previousUsage = nextUsage;
|
|
256
|
+
this.runEvent({
|
|
257
|
+
type: "usage_updated",
|
|
258
|
+
usage: nextUsage,
|
|
259
|
+
turnId: this.active?.id,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
const ids = new Set(
|
|
263
|
+
this.state.pendingInteractions.map((i) => i.interactionId),
|
|
264
|
+
);
|
|
265
|
+
for (const id of this.pending.keys())
|
|
266
|
+
if (!ids.has(id)) {
|
|
267
|
+
this.pending.delete(id);
|
|
268
|
+
this.resolvedInteractions.add(id);
|
|
269
|
+
this.runEvent({
|
|
270
|
+
type: "permission_resolved",
|
|
271
|
+
requestId: id,
|
|
272
|
+
resolution: { behavior: "deny" },
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
for (const interaction of this.state.pendingInteractions) {
|
|
276
|
+
if (this.pending.has(interaction.interactionId)) continue;
|
|
277
|
+
const p = presentInteraction(interaction);
|
|
278
|
+
if (p) {
|
|
279
|
+
this.pending.set(interaction.interactionId, p);
|
|
280
|
+
this.runEvent({
|
|
281
|
+
type: "permission_requested",
|
|
282
|
+
request: p.request,
|
|
283
|
+
turnId: this.active?.id,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (
|
|
288
|
+
this.state.workspaceHookAdmission ||
|
|
289
|
+
this.state.pendingInteractions.some(
|
|
290
|
+
(i) => i.kind === "workspaceHookReview",
|
|
291
|
+
)
|
|
292
|
+
) {
|
|
293
|
+
const item: ProviderTimelineItem = {
|
|
294
|
+
id: "zcode:hook-trust",
|
|
295
|
+
type: "notification",
|
|
296
|
+
level: "warning",
|
|
297
|
+
message:
|
|
298
|
+
"ZCode workspace hooks require trust review. Review them with the official zcode hooks trust command.",
|
|
299
|
+
};
|
|
300
|
+
if (!this.presented.has(item.id)) {
|
|
301
|
+
this.presented.set(item.id, { item });
|
|
302
|
+
this.runEvent({ type: "timeline", item });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
this.changed();
|
|
306
|
+
const run = this.active;
|
|
307
|
+
if (
|
|
308
|
+
run &&
|
|
309
|
+
!run.stopping &&
|
|
310
|
+
!run.cancelled &&
|
|
311
|
+
this.state.queue.items.length &&
|
|
312
|
+
(this.state.control.phase === "error" ||
|
|
313
|
+
this.state.rows.window.some(
|
|
314
|
+
(row) =>
|
|
315
|
+
row.kind === "turnHeader" &&
|
|
316
|
+
row.state === "failed" &&
|
|
317
|
+
run.productTurns.has(this.productTurn(row)),
|
|
318
|
+
))
|
|
319
|
+
) {
|
|
320
|
+
void this.interrupt().catch(() => {}); // interrupt reports terminal transport failures.
|
|
321
|
+
}
|
|
322
|
+
this.maybeComplete();
|
|
323
|
+
}
|
|
324
|
+
private maybeComplete(): void {
|
|
325
|
+
const run = this.active;
|
|
326
|
+
if (
|
|
327
|
+
!run ||
|
|
328
|
+
!run.announced ||
|
|
329
|
+
run.inFlight ||
|
|
330
|
+
this.pendingAdmissions ||
|
|
331
|
+
run.stopping ||
|
|
332
|
+
!this.conversation.idle
|
|
333
|
+
)
|
|
334
|
+
return;
|
|
335
|
+
if (
|
|
336
|
+
!run.cancelled &&
|
|
337
|
+
[...run.inputs.values()].some((i) => !i.accepted || !i.consumed)
|
|
338
|
+
)
|
|
339
|
+
return;
|
|
340
|
+
const headers = this.state.rows.window.filter(
|
|
341
|
+
(r): r is TurnHeaderRow =>
|
|
342
|
+
r.kind === "turnHeader" && run.productTurns.has(this.productTurn(r)),
|
|
343
|
+
);
|
|
344
|
+
if (
|
|
345
|
+
!run.cancelled &&
|
|
346
|
+
(!headers.length || headers.some((h) => h.state === "running"))
|
|
347
|
+
)
|
|
348
|
+
return;
|
|
349
|
+
const failed =
|
|
350
|
+
headers.some((h) => h.state === "failed") ||
|
|
351
|
+
this.state.control.phase === "error";
|
|
352
|
+
this.active = undefined;
|
|
353
|
+
this.emit(
|
|
354
|
+
failed
|
|
355
|
+
? {
|
|
356
|
+
type: "turn_failed",
|
|
357
|
+
turnId: run.id,
|
|
358
|
+
error: "ZCode execution failed",
|
|
359
|
+
code: "NATIVE_PROTOCOL_ERROR",
|
|
360
|
+
}
|
|
361
|
+
: {
|
|
362
|
+
type:
|
|
363
|
+
run.cancelled ||
|
|
364
|
+
headers.some((h) => h.state === "completedInterrupted")
|
|
365
|
+
? "turn_canceled"
|
|
366
|
+
: "turn_completed",
|
|
367
|
+
turnId: run.id,
|
|
368
|
+
},
|
|
369
|
+
);
|
|
370
|
+
this.changed();
|
|
371
|
+
}
|
|
372
|
+
private changed(): void {
|
|
373
|
+
for (const listener of this.idleListeners) listener();
|
|
374
|
+
}
|
|
375
|
+
private waitFor(predicate: () => boolean): Promise<void> {
|
|
376
|
+
if (predicate()) return Promise.resolve();
|
|
377
|
+
return new Promise((resolve, reject) => {
|
|
378
|
+
const timer = setTimeout(() => {
|
|
379
|
+
this.idleListeners.delete(check);
|
|
380
|
+
reject(
|
|
381
|
+
new AdapterError(
|
|
382
|
+
"NATIVE_TIMEOUT",
|
|
383
|
+
"ZCode did not settle input admission",
|
|
384
|
+
),
|
|
385
|
+
);
|
|
386
|
+
}, 30_000);
|
|
387
|
+
const check = () => {
|
|
388
|
+
if (this.failed || predicate()) {
|
|
389
|
+
clearTimeout(timer);
|
|
390
|
+
this.idleListeners.delete(check);
|
|
391
|
+
this.failed
|
|
392
|
+
? reject(new AdapterError("NATIVE_EXITED", "ZCode disconnected"))
|
|
393
|
+
: resolve();
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
this.idleListeners.add(check);
|
|
397
|
+
check();
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
reserveInput(): () => void {
|
|
401
|
+
this.pendingAdmissions++;
|
|
402
|
+
let released = false;
|
|
403
|
+
return () => {
|
|
404
|
+
if (released) return;
|
|
405
|
+
released = true;
|
|
406
|
+
this.pendingAdmissions--;
|
|
407
|
+
this.maybeComplete();
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
hasActiveTurn(): boolean {
|
|
411
|
+
return !!this.active;
|
|
412
|
+
}
|
|
413
|
+
subscribe(listener: (event: NativeSessionEvent) => void): () => void {
|
|
414
|
+
this.listeners.add(listener);
|
|
415
|
+
listener({ type: "usage_updated", usage: usage(this.state) });
|
|
416
|
+
return () => this.listeners.delete(listener);
|
|
417
|
+
}
|
|
418
|
+
historyItems(): readonly NativeTimelineEntry[] {
|
|
419
|
+
return [...this.presented.values()];
|
|
420
|
+
}
|
|
421
|
+
getPendingPermissions(): ProviderPermissionRequest[] {
|
|
422
|
+
return [...this.pending.values()].map((p) => p.request);
|
|
423
|
+
}
|
|
424
|
+
listCommands(): ProviderCommand[] {
|
|
425
|
+
return this.snapshot.slashCommands.map((c) => ({
|
|
426
|
+
name: c.name,
|
|
427
|
+
description: c.description,
|
|
428
|
+
argumentHint: c.inputHint ?? "",
|
|
429
|
+
}));
|
|
430
|
+
}
|
|
431
|
+
getConfig(): ProviderConfigState {
|
|
432
|
+
const c = this.state.config;
|
|
433
|
+
return {
|
|
434
|
+
model: c.modelSelection ? encodeModel(c.modelSelection) : undefined,
|
|
435
|
+
mode: c.mode,
|
|
436
|
+
thinkingOption: c.thought,
|
|
437
|
+
models: catalogModels(this.modelCatalog, c.modelSelection),
|
|
438
|
+
modes: ZCODE_MODES,
|
|
439
|
+
thinkingOptions: c.thoughtLevels.map((id) => ({ id, label: id })),
|
|
440
|
+
settings: [
|
|
441
|
+
{
|
|
442
|
+
type: "toggle",
|
|
443
|
+
id: "plan_mode",
|
|
444
|
+
label: "Toggle plan mode",
|
|
445
|
+
value: c.planEnabled === true,
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
async applyInitialConfig(
|
|
451
|
+
config: Partial<ProviderSessionConfig>,
|
|
452
|
+
): Promise<void> {
|
|
453
|
+
if (config.model !== undefined) await this.setModel(config.model);
|
|
454
|
+
if (config.thinkingOption !== undefined)
|
|
455
|
+
await this.setThinkingOption(config.thinkingOption);
|
|
456
|
+
await this.configureMode(
|
|
457
|
+
config.mode,
|
|
458
|
+
config.settings?.plan_mode as boolean | undefined,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
async configureMode(mode?: string | null, plan?: boolean): Promise<void> {
|
|
462
|
+
this.assertIdle();
|
|
463
|
+
if (
|
|
464
|
+
mode === null ||
|
|
465
|
+
(mode !== undefined && !ZCODE_MODES.some((m) => m.id === mode))
|
|
466
|
+
)
|
|
467
|
+
throw new AdapterError(
|
|
468
|
+
"INVALID_CONFIGURATION",
|
|
469
|
+
"Mode must be build, edit, or yolo",
|
|
470
|
+
);
|
|
471
|
+
const nextMode = mode ?? this.state.config.mode,
|
|
472
|
+
nextPlan = plan ?? this.state.config.planEnabled === true;
|
|
473
|
+
if (
|
|
474
|
+
nextMode === this.state.config.mode &&
|
|
475
|
+
nextPlan === (this.state.config.planEnabled === true)
|
|
476
|
+
)
|
|
477
|
+
return;
|
|
478
|
+
if (
|
|
479
|
+
nextMode !== this.state.config.mode ||
|
|
480
|
+
(!nextPlan && this.state.config.planEnabled)
|
|
481
|
+
) {
|
|
482
|
+
await this.control("switchCollaborationMode", { mode: nextMode });
|
|
483
|
+
await this.conversation.waitFor(
|
|
484
|
+
() =>
|
|
485
|
+
this.state.config.mode === nextMode && !this.state.config.planEnabled,
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
if (nextPlan && !this.state.config.planEnabled)
|
|
489
|
+
await this.control("switchCollaborationMode", { mode: "plan" });
|
|
490
|
+
await this.conversation.waitFor(
|
|
491
|
+
() =>
|
|
492
|
+
this.state.config.mode === nextMode &&
|
|
493
|
+
(this.state.config.planEnabled === true) === nextPlan,
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
async setModel(value: string | null): Promise<void> {
|
|
497
|
+
this.assertIdle();
|
|
498
|
+
if (value === null)
|
|
499
|
+
throw new AdapterError(
|
|
500
|
+
"INVALID_CONFIGURATION",
|
|
501
|
+
"ZCode model cannot be cleared",
|
|
502
|
+
);
|
|
503
|
+
const view = await readModelSelection(this.bridge);
|
|
504
|
+
this.modelCatalog = view.models;
|
|
505
|
+
const selection = await resolveModelSelection(
|
|
506
|
+
this.bridge,
|
|
507
|
+
view,
|
|
508
|
+
decodeModel(value),
|
|
509
|
+
);
|
|
510
|
+
this.assertIdle();
|
|
511
|
+
await this.control("switchModelConfig", {
|
|
512
|
+
provider: selection.providerId,
|
|
513
|
+
model: selection.modelId,
|
|
514
|
+
thought: selection.options!.reasoningLevel,
|
|
515
|
+
});
|
|
516
|
+
await this.conversation.waitFor(
|
|
517
|
+
() =>
|
|
518
|
+
this.state.config.modelSelection !== undefined &&
|
|
519
|
+
encodeModel(this.state.config.modelSelection) === value &&
|
|
520
|
+
this.state.config.thought === selection.options!.reasoningLevel,
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
async setThinkingOption(value: string | null): Promise<void> {
|
|
524
|
+
this.assertIdle();
|
|
525
|
+
if (value === null || !this.state.config.thoughtLevels.includes(value))
|
|
526
|
+
throw new AdapterError(
|
|
527
|
+
"INVALID_CONFIGURATION",
|
|
528
|
+
"Unknown reasoning level",
|
|
529
|
+
);
|
|
530
|
+
await this.control("switchModelConfig", {
|
|
531
|
+
provider: this.state.config.provider,
|
|
532
|
+
model: this.state.config.model,
|
|
533
|
+
thought: value,
|
|
534
|
+
});
|
|
535
|
+
await this.conversation.waitFor(() => this.state.config.thought === value);
|
|
536
|
+
}
|
|
537
|
+
async startTurn(
|
|
538
|
+
prompt: NativePromptInput,
|
|
539
|
+
options?: {
|
|
540
|
+
clientMessageId: string;
|
|
541
|
+
delivery?: "auto" | "steer";
|
|
542
|
+
beforeSend?: () => Promise<void>;
|
|
543
|
+
},
|
|
544
|
+
): Promise<{ turnId: string }> {
|
|
545
|
+
this.assertOpen();
|
|
546
|
+
const previous = this.active;
|
|
547
|
+
if (previous?.cancelled || (options?.delivery === "steer" && !previous))
|
|
548
|
+
throw new AdapterError(
|
|
549
|
+
"SESSION_BUSY",
|
|
550
|
+
"ZCode cannot accept this input in its current execution state",
|
|
551
|
+
);
|
|
552
|
+
const run = previous ?? this.newRun();
|
|
553
|
+
this.active = run;
|
|
554
|
+
run.inFlight++;
|
|
555
|
+
const commandId = randomUUID(),
|
|
556
|
+
input: Admission = {
|
|
557
|
+
clientMessageId: options?.clientMessageId,
|
|
558
|
+
accepted: false,
|
|
559
|
+
consumed: false,
|
|
560
|
+
};
|
|
561
|
+
run.inputs.set(commandId, input);
|
|
562
|
+
this.admissions.set(commandId, input);
|
|
563
|
+
let sent = false;
|
|
564
|
+
const assertSending = () => {
|
|
565
|
+
this.assertOpen();
|
|
566
|
+
if (run.cancelled)
|
|
567
|
+
throw new AdapterError(
|
|
568
|
+
"SESSION_BUSY",
|
|
569
|
+
"Input was cancelled before sending",
|
|
570
|
+
);
|
|
571
|
+
};
|
|
572
|
+
try {
|
|
573
|
+
const native = await mapPrompt(prompt, this.workspace);
|
|
574
|
+
if (!native.content.trim() && !native.attachments.length)
|
|
575
|
+
throw new AdapterError(
|
|
576
|
+
"UNSUPPORTED_CONTENT",
|
|
577
|
+
"ZCode requires nonempty input",
|
|
578
|
+
);
|
|
579
|
+
if (previous && native.content.trimStart().startsWith("/"))
|
|
580
|
+
throw new AdapterError(
|
|
581
|
+
"SESSION_BUSY",
|
|
582
|
+
"ZCode commands require an idle session",
|
|
583
|
+
);
|
|
584
|
+
await options?.beforeSend?.();
|
|
585
|
+
assertSending();
|
|
586
|
+
if (!previous && !this.state.queue.autoDrain)
|
|
587
|
+
await this.control("setAutoDrain", { autoDrain: true });
|
|
588
|
+
const attachments = await uploadAttachments(
|
|
589
|
+
this.bridge,
|
|
590
|
+
this.workspace,
|
|
591
|
+
this.id,
|
|
592
|
+
native.attachments,
|
|
593
|
+
assertSending,
|
|
594
|
+
);
|
|
595
|
+
assertSending();
|
|
596
|
+
const config = this.state.config;
|
|
597
|
+
if (!config.modelSelection?.options?.reasoningLevel)
|
|
598
|
+
throw new AdapterError(
|
|
599
|
+
"INVALID_CONFIGURATION",
|
|
600
|
+
"Select a ZCode model and reasoning level before sending",
|
|
601
|
+
);
|
|
602
|
+
sent = true;
|
|
603
|
+
const ack = await conversationCommand(
|
|
604
|
+
this.bridge,
|
|
605
|
+
this.workspace,
|
|
606
|
+
this.id,
|
|
607
|
+
"sendText",
|
|
608
|
+
{
|
|
609
|
+
text: native.content,
|
|
610
|
+
attachments,
|
|
611
|
+
modelSelection: config.modelSelection,
|
|
612
|
+
mode: config.mode,
|
|
613
|
+
planEnabled: config.planEnabled === true,
|
|
614
|
+
requestedDelivery: previous
|
|
615
|
+
? attachments.length
|
|
616
|
+
? "queue"
|
|
617
|
+
: "guide"
|
|
618
|
+
: "startNow",
|
|
619
|
+
},
|
|
620
|
+
{ commandId },
|
|
621
|
+
);
|
|
622
|
+
if (["failed", "rejected", "stale", "noop"].includes(ack.status))
|
|
623
|
+
throw new AdapterError(
|
|
624
|
+
"NATIVE_INPUT_REJECTED",
|
|
625
|
+
"ZCode rejected the input",
|
|
626
|
+
);
|
|
627
|
+
if (
|
|
628
|
+
ack.status !== "accepted" ||
|
|
629
|
+
ack.result?.type !== "inputAccepted" ||
|
|
630
|
+
ack.result.inputId !== commandId
|
|
631
|
+
)
|
|
632
|
+
throw new AdapterError(
|
|
633
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
634
|
+
"ZCode input acknowledgement is inconsistent",
|
|
635
|
+
);
|
|
636
|
+
this.assertOpen();
|
|
637
|
+
input.accepted = true;
|
|
638
|
+
if (options?.clientMessageId)
|
|
639
|
+
this.emit({
|
|
640
|
+
type: "prompt_accepted",
|
|
641
|
+
clientMessageId: options.clientMessageId,
|
|
642
|
+
turnId: run.id,
|
|
643
|
+
delivery: previous ? "steer" : "turn",
|
|
644
|
+
});
|
|
645
|
+
this.announce(run);
|
|
646
|
+
this.observe();
|
|
647
|
+
return { turnId: run.id };
|
|
648
|
+
} catch (error) {
|
|
649
|
+
if (
|
|
650
|
+
!sent ||
|
|
651
|
+
(error instanceof AdapterError &&
|
|
652
|
+
error.code === "NATIVE_INPUT_REJECTED")
|
|
653
|
+
) {
|
|
654
|
+
run.inputs.delete(commandId);
|
|
655
|
+
this.admissions.delete(commandId);
|
|
656
|
+
if (!previous && run.inputs.size === 0) this.active = undefined;
|
|
657
|
+
} else this.runtimeFailed(diagnosticError(error, this.bridge.diagnostic));
|
|
658
|
+
throw error;
|
|
659
|
+
} finally {
|
|
660
|
+
run.inFlight--;
|
|
661
|
+
this.changed();
|
|
662
|
+
this.maybeComplete();
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
private async control(type: string, payload: unknown): Promise<void> {
|
|
666
|
+
this.assertOpen();
|
|
667
|
+
// A stale CAS proves no mutation occurred. No input is retried here.
|
|
668
|
+
let revision = this.state.revision;
|
|
669
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
670
|
+
const ack = await conversationCommand(
|
|
671
|
+
this.bridge,
|
|
672
|
+
this.workspace,
|
|
673
|
+
this.id,
|
|
674
|
+
type,
|
|
675
|
+
payload,
|
|
676
|
+
{ revision },
|
|
677
|
+
);
|
|
678
|
+
if (ack.status === "accepted" || ack.status === "noop") return;
|
|
679
|
+
if (ack.status !== "stale")
|
|
680
|
+
throw new AdapterError(
|
|
681
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
682
|
+
`ZCode ${type} was rejected`,
|
|
683
|
+
);
|
|
684
|
+
revision = ack.revisionAtDecision;
|
|
685
|
+
}
|
|
686
|
+
throw new AdapterError(
|
|
687
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
688
|
+
"ZCode configuration remained stale",
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
async respondToPermission(
|
|
692
|
+
id: string,
|
|
693
|
+
response: ProviderPermissionResponse,
|
|
694
|
+
): Promise<void> {
|
|
695
|
+
this.assertOpen();
|
|
696
|
+
// An observed terminal interaction is immutable. A delayed UI response cannot
|
|
697
|
+
// resolve it again or interrupt a later turn.
|
|
698
|
+
if (this.resolvedInteractions.has(id) || this.answering.has(id)) return;
|
|
699
|
+
const pending = this.pending.get(id);
|
|
700
|
+
if (!pending)
|
|
701
|
+
throw new AdapterError(
|
|
702
|
+
"SESSION_NOT_FOUND",
|
|
703
|
+
"ZCode interaction is no longer pending",
|
|
704
|
+
);
|
|
705
|
+
const answer = pending.answer(response);
|
|
706
|
+
this.answering.add(id);
|
|
707
|
+
try {
|
|
708
|
+
await this.control("resolveInteraction", { interactionId: id, answer });
|
|
709
|
+
await this.conversation.waitFor(
|
|
710
|
+
() =>
|
|
711
|
+
!this.state.pendingInteractions.some((i) => i.interactionId === id),
|
|
712
|
+
);
|
|
713
|
+
} finally {
|
|
714
|
+
this.answering.delete(id);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
async interrupt(): Promise<void> {
|
|
718
|
+
const run = this.active;
|
|
719
|
+
if (!run) return;
|
|
720
|
+
run.cancelled = true;
|
|
721
|
+
run.stopping ??= this.stop(run);
|
|
722
|
+
try {
|
|
723
|
+
await run.stopping;
|
|
724
|
+
run.stopping = undefined;
|
|
725
|
+
this.maybeComplete();
|
|
726
|
+
} catch (error) {
|
|
727
|
+
this.runtimeFailed(diagnosticError(error, this.bridge.diagnostic));
|
|
728
|
+
throw error;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
private async stop(run: Run): Promise<void> {
|
|
732
|
+
await this.control("setAutoDrain", { autoDrain: false });
|
|
733
|
+
await this.waitFor(() => run.inFlight === 0);
|
|
734
|
+
if (this.active !== run) return;
|
|
735
|
+
const target = this.state.control.activeWorks.find(
|
|
736
|
+
(w) => w.foregroundExecutionId,
|
|
737
|
+
)?.foregroundExecutionId;
|
|
738
|
+
if (this.state.control.canStop && !target)
|
|
739
|
+
throw new AdapterError(
|
|
740
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
741
|
+
"ZCode did not identify its stoppable foreground execution",
|
|
742
|
+
);
|
|
743
|
+
if (target)
|
|
744
|
+
await this.control("stop", { expectedForegroundExecutionId: target });
|
|
745
|
+
await this.conversation.waitFor(
|
|
746
|
+
() =>
|
|
747
|
+
!this.state.control.canStop &&
|
|
748
|
+
this.state.control.activeWorks.length === 0 &&
|
|
749
|
+
this.state.control.phase !== "running" &&
|
|
750
|
+
this.state.control.phase !== "prewarming",
|
|
751
|
+
);
|
|
752
|
+
for (const item of [...this.state.queue.items])
|
|
753
|
+
await this.control("deleteQueueItem", { queueItemId: item.queueItemId });
|
|
754
|
+
await this.conversation.waitFor(() => this.conversation.idle);
|
|
755
|
+
}
|
|
756
|
+
runtimeFailed(error?: AdapterError): void {
|
|
757
|
+
if (this.failed) return;
|
|
758
|
+
this.failed = true;
|
|
759
|
+
const failure = diagnosticError(
|
|
760
|
+
error ?? new AdapterError("NATIVE_EXITED", "ZCode Server disconnected"),
|
|
761
|
+
{ ...this.bridge.diagnostic, stage: "session" },
|
|
762
|
+
);
|
|
763
|
+
this.active = undefined;
|
|
764
|
+
this.changed();
|
|
765
|
+
this.emit({
|
|
766
|
+
type: "runtime_failed",
|
|
767
|
+
error: failure.message,
|
|
768
|
+
code: failure.code,
|
|
769
|
+
diagnostic: formatDiagnostic(failure),
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
async close(): Promise<void> {
|
|
773
|
+
if (this.closed) return;
|
|
774
|
+
try {
|
|
775
|
+
if (!this.failed) {
|
|
776
|
+
await this.interrupt();
|
|
777
|
+
await this.conversation.close();
|
|
778
|
+
await this.bridge.request(
|
|
779
|
+
"closeSession",
|
|
780
|
+
{ workspacePath: this.workspace, sessionId: this.id },
|
|
781
|
+
z.boolean(),
|
|
782
|
+
);
|
|
783
|
+
} else await this.conversation.close();
|
|
784
|
+
} finally {
|
|
785
|
+
this.closed = true;
|
|
786
|
+
this.unlistenFailure?.();
|
|
787
|
+
this.listeners.clear();
|
|
788
|
+
this.changed();
|
|
789
|
+
this.onClose();
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
private assertOpen(): void {
|
|
793
|
+
if (this.closed || this.failed)
|
|
794
|
+
throw new AdapterError("NATIVE_EXITED", "ZCode session is closed");
|
|
795
|
+
}
|
|
796
|
+
private assertIdle(): void {
|
|
797
|
+
this.assertOpen();
|
|
798
|
+
if (this.active)
|
|
799
|
+
throw new AdapterError("SESSION_BUSY", "ZCode session is running");
|
|
800
|
+
}
|
|
801
|
+
private emit(event: NativeSessionEvent): void {
|
|
802
|
+
for (const listener of this.listeners) listener(event);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
export async function resolveWorkspace(cwd: string): Promise<string> {
|
|
806
|
+
if (!isAbsolute(cwd)) {
|
|
807
|
+
throw new AdapterError(
|
|
808
|
+
"INVALID_WORKSPACE",
|
|
809
|
+
"Workspace path must be absolute",
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
const info = await stat(cwd);
|
|
813
|
+
if (!info.isDirectory()) {
|
|
814
|
+
throw new AdapterError(
|
|
815
|
+
"INVALID_WORKSPACE",
|
|
816
|
+
"Workspace path must be a directory",
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
return await realpath(cwd);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
export function assertSnapshotWorkspace(
|
|
823
|
+
snapshot: SessionSnapshot,
|
|
824
|
+
workspace: string,
|
|
825
|
+
): void {
|
|
826
|
+
if (snapshot.session.workspace.workspacePath !== workspace) {
|
|
827
|
+
throw new AdapterError(
|
|
828
|
+
"INVALID_WORKSPACE",
|
|
829
|
+
"ZCode session belongs to another workspace",
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
export async function initializeWorkspace(
|
|
835
|
+
bridge: HostBridge,
|
|
836
|
+
workspace: string,
|
|
837
|
+
): Promise<{ presentation: { mode: string }; selection: ModelSelectionView }> {
|
|
838
|
+
const initialized = await bridge.request(
|
|
839
|
+
"initialize",
|
|
840
|
+
{ workspacePath: workspace },
|
|
841
|
+
InitializeResultSchema,
|
|
842
|
+
60_000,
|
|
843
|
+
);
|
|
844
|
+
if (!initialized.available) {
|
|
845
|
+
throw new AdapterError(
|
|
846
|
+
initialized.reasonCode === "provider_not_ready"
|
|
847
|
+
? "AUTH_REQUIRED"
|
|
848
|
+
: "NATIVE_PROTOCOL_ERROR",
|
|
849
|
+
initialized.reason ?? "ZCode host is unavailable",
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
const presentation = await bridge.request(
|
|
853
|
+
"readWorkspacePresentation",
|
|
854
|
+
{ workspacePath: workspace },
|
|
855
|
+
WorkspacePresentationSchema,
|
|
856
|
+
60_000,
|
|
857
|
+
);
|
|
858
|
+
if (presentation.workspace.workspacePath !== workspace)
|
|
859
|
+
throw new AdapterError(
|
|
860
|
+
"INVALID_WORKSPACE",
|
|
861
|
+
"ZCode returned a different workspace",
|
|
862
|
+
);
|
|
863
|
+
requireMode(presentation.mode);
|
|
864
|
+
const selection = await readModelSelection(bridge);
|
|
865
|
+
return { presentation, selection };
|
|
866
|
+
}
|