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,150 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import type { HostBridge } from "./host/bridge.js";
|
|
5
|
+
import type { NativePrompt } from "./mapping.js";
|
|
6
|
+
import { AdapterError } from "./errors.js";
|
|
7
|
+
|
|
8
|
+
const progress = z.object({
|
|
9
|
+
uploadId: z.string(),
|
|
10
|
+
nextChunkIndex: z.number().int().nonnegative(),
|
|
11
|
+
});
|
|
12
|
+
const begun = z.discriminatedUnion("state", [
|
|
13
|
+
progress.extend({ state: z.literal("staging") }),
|
|
14
|
+
progress.extend({ state: z.literal("committed"), ref: z.string().min(1) }),
|
|
15
|
+
]);
|
|
16
|
+
const reference = z.object({ ref: z.string().min(1) }).strict();
|
|
17
|
+
const metadata = z.object({
|
|
18
|
+
fileName: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1)
|
|
21
|
+
.max(255)
|
|
22
|
+
.regex(/^[^\0\r\n]+$/),
|
|
23
|
+
mime: z
|
|
24
|
+
.string()
|
|
25
|
+
.min(3)
|
|
26
|
+
.max(255)
|
|
27
|
+
.regex(
|
|
28
|
+
/^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$/,
|
|
29
|
+
),
|
|
30
|
+
bytes: z
|
|
31
|
+
.number()
|
|
32
|
+
.int()
|
|
33
|
+
.min(0)
|
|
34
|
+
.max(20 * 1024 * 1024),
|
|
35
|
+
});
|
|
36
|
+
const chunkBytes = 512 * 1024;
|
|
37
|
+
|
|
38
|
+
/** Transfer validated input to ZCode's persisted attachment store before admission. */
|
|
39
|
+
export async function uploadAttachments(
|
|
40
|
+
bridge: HostBridge,
|
|
41
|
+
workspacePath: string,
|
|
42
|
+
sessionId: string,
|
|
43
|
+
attachments: NativePrompt["attachments"],
|
|
44
|
+
assertSending: () => void,
|
|
45
|
+
) {
|
|
46
|
+
const refs: Array<z.infer<typeof metadata> & { ref: string }> = [];
|
|
47
|
+
for (const attachment of attachments) {
|
|
48
|
+
assertSending();
|
|
49
|
+
const info = metadata.safeParse({
|
|
50
|
+
fileName: attachment.filename,
|
|
51
|
+
mime: attachment.mimeType,
|
|
52
|
+
bytes: attachment.sizeBytes,
|
|
53
|
+
});
|
|
54
|
+
if (!info.success)
|
|
55
|
+
throw new AdapterError(
|
|
56
|
+
"UNSUPPORTED_CONTENT",
|
|
57
|
+
"Attachment exceeds ZCode's size limit or has invalid metadata",
|
|
58
|
+
);
|
|
59
|
+
if (
|
|
60
|
+
!attachment.localPath &&
|
|
61
|
+
(attachment.dataBase64 === undefined ||
|
|
62
|
+
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(
|
|
63
|
+
attachment.dataBase64,
|
|
64
|
+
))
|
|
65
|
+
)
|
|
66
|
+
throw new AdapterError(
|
|
67
|
+
"UNSUPPORTED_CONTENT",
|
|
68
|
+
"Attachment has invalid base64 data",
|
|
69
|
+
);
|
|
70
|
+
const data = attachment.localPath
|
|
71
|
+
? await readFile(attachment.localPath)
|
|
72
|
+
: Buffer.from(attachment.dataBase64!, "base64");
|
|
73
|
+
if (data.length !== info.data.bytes)
|
|
74
|
+
throw new AdapterError(
|
|
75
|
+
"UNSUPPORTED_CONTENT",
|
|
76
|
+
"Attachment changed after validation",
|
|
77
|
+
);
|
|
78
|
+
const uploadId = randomUUID();
|
|
79
|
+
const target = { workspacePath, sessionId, uploadId };
|
|
80
|
+
try {
|
|
81
|
+
const start = await bridge.request(
|
|
82
|
+
"attachmentBeginV4",
|
|
83
|
+
{
|
|
84
|
+
...target,
|
|
85
|
+
fileName: info.data.fileName,
|
|
86
|
+
mime: info.data.mime,
|
|
87
|
+
totalBytes: data.length,
|
|
88
|
+
totalChunks: Math.ceil(data.length / chunkBytes),
|
|
89
|
+
checksum: `sha256:${createHash("sha256").update(data).digest("hex")}`,
|
|
90
|
+
},
|
|
91
|
+
begun,
|
|
92
|
+
);
|
|
93
|
+
if (
|
|
94
|
+
start.uploadId !== uploadId ||
|
|
95
|
+
start.state !== "staging" ||
|
|
96
|
+
start.nextChunkIndex !== 0
|
|
97
|
+
)
|
|
98
|
+
throw new AdapterError(
|
|
99
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
100
|
+
"Unexpected attachment upload acknowledgement",
|
|
101
|
+
);
|
|
102
|
+
for (
|
|
103
|
+
let offset = 0, chunkIndex = 0;
|
|
104
|
+
offset < data.length;
|
|
105
|
+
offset += chunkBytes, chunkIndex++
|
|
106
|
+
) {
|
|
107
|
+
assertSending();
|
|
108
|
+
const chunk = await bridge.request(
|
|
109
|
+
"attachmentChunkV4",
|
|
110
|
+
{
|
|
111
|
+
...target,
|
|
112
|
+
chunkIndex,
|
|
113
|
+
dataBase64: data
|
|
114
|
+
.subarray(offset, offset + chunkBytes)
|
|
115
|
+
.toString("base64"),
|
|
116
|
+
},
|
|
117
|
+
progress,
|
|
118
|
+
);
|
|
119
|
+
if (
|
|
120
|
+
chunk.uploadId !== uploadId ||
|
|
121
|
+
chunk.nextChunkIndex !== chunkIndex + 1
|
|
122
|
+
)
|
|
123
|
+
throw new AdapterError(
|
|
124
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
125
|
+
"Unexpected attachment chunk acknowledgement",
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
assertSending();
|
|
129
|
+
const committed = await bridge.request(
|
|
130
|
+
"attachmentCommitV4",
|
|
131
|
+
target,
|
|
132
|
+
reference,
|
|
133
|
+
);
|
|
134
|
+
refs.push({ ...info.data, ref: committed.ref });
|
|
135
|
+
} catch (error) {
|
|
136
|
+
// Abort staged bytes; never retry a command whose result is unknown.
|
|
137
|
+
try {
|
|
138
|
+
await bridge.request(
|
|
139
|
+
"attachmentAbortV4",
|
|
140
|
+
target,
|
|
141
|
+
z.object({}).strict(),
|
|
142
|
+
);
|
|
143
|
+
} catch {
|
|
144
|
+
/* The host expires staging storage after disconnection. */
|
|
145
|
+
}
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return refs;
|
|
150
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { applyConversationDeltas } from "./vendor/zcode/packages/shared/src/zcode-protocol-v4/apply.js";
|
|
3
|
+
import { TopicWireFrameAssembler } from "./vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-assembler.js";
|
|
4
|
+
import {
|
|
5
|
+
commandAckSchema,
|
|
6
|
+
parseCommandEnvelope,
|
|
7
|
+
} from "./vendor/zcode/packages/shared/src/zcode-protocol-v4/command.js";
|
|
8
|
+
import {
|
|
9
|
+
conversationTopicFrameSchema,
|
|
10
|
+
v4ConversationResyncResultSchema,
|
|
11
|
+
v4ConversationRowsRangeResultSchema,
|
|
12
|
+
type ConversationTopicFrame,
|
|
13
|
+
type ConversationTopicWireCandidate,
|
|
14
|
+
} from "./vendor/zcode/packages/shared/src/zcode-protocol-v4/transport.js";
|
|
15
|
+
import type { ConversationSnapshot } from "./vendor/zcode/packages/shared/src/zcode-protocol-v4/snapshot.js";
|
|
16
|
+
import type { ConversationRow } from "./vendor/zcode/packages/shared/src/zcode-protocol-v4/rows.js";
|
|
17
|
+
import type { HostBridge, HostSubscription } from "./host/bridge.js";
|
|
18
|
+
import { AdapterError } from "./errors.js";
|
|
19
|
+
export type ConversationState = ConversationSnapshot;
|
|
20
|
+
|
|
21
|
+
/** One authoritative V4 state. No legacy session events participate in it. */
|
|
22
|
+
export class Conversation {
|
|
23
|
+
state?: ConversationSnapshot;
|
|
24
|
+
private subscription?: HostSubscription;
|
|
25
|
+
private readonly assembler = new TopicWireFrameAssembler(
|
|
26
|
+
conversationTopicFrameSchema,
|
|
27
|
+
);
|
|
28
|
+
private expiry?: ReturnType<typeof setTimeout>;
|
|
29
|
+
private recovery?: Promise<void>;
|
|
30
|
+
private recovering = false;
|
|
31
|
+
private hydrating = false;
|
|
32
|
+
private readonly waiters = new Set<() => void>();
|
|
33
|
+
private closed = false;
|
|
34
|
+
private readonly listeners = new Set<() => void>();
|
|
35
|
+
private failure?: unknown;
|
|
36
|
+
constructor(
|
|
37
|
+
private readonly bridge: HostBridge,
|
|
38
|
+
private readonly workspacePath: string,
|
|
39
|
+
private readonly sessionId: string,
|
|
40
|
+
private readonly onFailure: (error: unknown) => void,
|
|
41
|
+
) {}
|
|
42
|
+
async open(): Promise<void> {
|
|
43
|
+
const early: ConversationTopicWireCandidate[] = [];
|
|
44
|
+
this.subscription = await this.bridge.subscribe(
|
|
45
|
+
{ workspacePath: this.workspacePath, sessionId: this.sessionId },
|
|
46
|
+
(frame) => {
|
|
47
|
+
if (this.subscription) this.accept(frame);
|
|
48
|
+
else early.push(frame);
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
for (const frame of early) this.accept(frame);
|
|
52
|
+
await this.waitFor(() => this.state !== undefined && !this.recovering);
|
|
53
|
+
await this.loadHistory();
|
|
54
|
+
}
|
|
55
|
+
subscribe(listener: () => void): () => void {
|
|
56
|
+
this.listeners.add(listener);
|
|
57
|
+
return () => this.listeners.delete(listener);
|
|
58
|
+
}
|
|
59
|
+
private changed(): void {
|
|
60
|
+
for (const waiter of this.waiters) waiter();
|
|
61
|
+
if (!this.hydrating) for (const listener of this.listeners) listener();
|
|
62
|
+
}
|
|
63
|
+
private fail(error: unknown): void {
|
|
64
|
+
if (this.failure || this.closed) return;
|
|
65
|
+
this.failure = error;
|
|
66
|
+
this.changed();
|
|
67
|
+
this.onFailure(error);
|
|
68
|
+
}
|
|
69
|
+
accept(wire: ConversationTopicWireCandidate): void {
|
|
70
|
+
if (
|
|
71
|
+
this.closed ||
|
|
72
|
+
wire.subscriptionId !== this.subscription?.id ||
|
|
73
|
+
wire.topic !== `conversation/${this.sessionId}`
|
|
74
|
+
)
|
|
75
|
+
return;
|
|
76
|
+
for (const event of this.assembler.accept(wire)) {
|
|
77
|
+
if (event.kind === "fault") {
|
|
78
|
+
if (event.fault.deliveryKind === "recovery")
|
|
79
|
+
this.fail(
|
|
80
|
+
new AdapterError(
|
|
81
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
82
|
+
"ZCode recovery frame is invalid",
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
else this.resync();
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (this.recovering && event.deliveryKind !== "recovery") continue;
|
|
89
|
+
this.apply(event.frame, event.deliveryKind === "recovery");
|
|
90
|
+
}
|
|
91
|
+
this.scheduleExpiry();
|
|
92
|
+
}
|
|
93
|
+
private apply(frame: ConversationTopicFrame, recovery: boolean): void {
|
|
94
|
+
if (frame.payload.kind === "snapshot") {
|
|
95
|
+
const snapshot = frame.payload.snapshot;
|
|
96
|
+
if (
|
|
97
|
+
snapshot.sessionId !== this.sessionId ||
|
|
98
|
+
snapshot.seq !== frame.toSeq ||
|
|
99
|
+
frame.fromSeq !== 0
|
|
100
|
+
) {
|
|
101
|
+
this.resync();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (
|
|
105
|
+
this.state &&
|
|
106
|
+
!recovery &&
|
|
107
|
+
(snapshot.logEpoch !== this.state.logEpoch ||
|
|
108
|
+
snapshot.seq < this.state.seq)
|
|
109
|
+
) {
|
|
110
|
+
this.resync();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.state = snapshot;
|
|
114
|
+
this.recovering = false;
|
|
115
|
+
} else {
|
|
116
|
+
if (!this.state || frame.fromSeq !== this.state.seq) {
|
|
117
|
+
this.resync();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (frame.toSeq < frame.fromSeq) {
|
|
121
|
+
this.resync();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.state = {
|
|
125
|
+
...applyConversationDeltas(this.state, frame.payload.deltas),
|
|
126
|
+
seq: frame.toSeq,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
this.changed();
|
|
130
|
+
}
|
|
131
|
+
private scheduleExpiry(): void {
|
|
132
|
+
clearTimeout(this.expiry);
|
|
133
|
+
const expiry = this.assembler.nextExpiryAt;
|
|
134
|
+
if (expiry === null) return;
|
|
135
|
+
this.expiry = setTimeout(
|
|
136
|
+
() => {
|
|
137
|
+
if (this.assembler.expire().length) this.resync();
|
|
138
|
+
this.scheduleExpiry();
|
|
139
|
+
},
|
|
140
|
+
Math.max(1, expiry - Date.now()),
|
|
141
|
+
);
|
|
142
|
+
this.expiry.unref();
|
|
143
|
+
}
|
|
144
|
+
private resync(): void {
|
|
145
|
+
if (this.closed || this.recovery) return;
|
|
146
|
+
this.recovering = true;
|
|
147
|
+
this.hydrating = true;
|
|
148
|
+
this.recovery = (async () => {
|
|
149
|
+
await this.bridge.request(
|
|
150
|
+
"resyncConversationV4",
|
|
151
|
+
{
|
|
152
|
+
workspacePath: this.workspacePath,
|
|
153
|
+
subscriptionId: this.subscription!.id,
|
|
154
|
+
base: null,
|
|
155
|
+
forceSnapshot: true,
|
|
156
|
+
},
|
|
157
|
+
v4ConversationResyncResultSchema,
|
|
158
|
+
);
|
|
159
|
+
await this.waitFor(() => !this.recovering);
|
|
160
|
+
await this.loadHistory();
|
|
161
|
+
})()
|
|
162
|
+
.catch((error) => this.fail(error))
|
|
163
|
+
.finally(() => {
|
|
164
|
+
this.recovery = undefined;
|
|
165
|
+
this.hydrating = false;
|
|
166
|
+
this.changed();
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
async loadHistory(): Promise<void> {
|
|
170
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
171
|
+
const base = this.state!;
|
|
172
|
+
let beforeRowId: number | undefined;
|
|
173
|
+
const rows = new Map<number, ConversationRow>();
|
|
174
|
+
let consistent = true;
|
|
175
|
+
do {
|
|
176
|
+
const page = await this.bridge.request(
|
|
177
|
+
"conversationRowsRangeV4",
|
|
178
|
+
{
|
|
179
|
+
workspacePath: this.workspacePath,
|
|
180
|
+
sessionId: this.sessionId,
|
|
181
|
+
limit: 200,
|
|
182
|
+
...(beforeRowId === undefined ? {} : { beforeRowId }),
|
|
183
|
+
},
|
|
184
|
+
v4ConversationRowsRangeResultSchema,
|
|
185
|
+
);
|
|
186
|
+
if (
|
|
187
|
+
page.atLogEpoch !== base.logEpoch ||
|
|
188
|
+
page.atRevision !== base.revision ||
|
|
189
|
+
page.atSeq !== base.seq ||
|
|
190
|
+
this.state?.logEpoch !== base.logEpoch ||
|
|
191
|
+
this.state?.seq !== base.seq
|
|
192
|
+
) {
|
|
193
|
+
consistent = false;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
for (const row of page.rows) rows.set(row.rowId, row);
|
|
197
|
+
if (!page.hasMore) break;
|
|
198
|
+
const next = page.rows[0]?.rowId;
|
|
199
|
+
if (
|
|
200
|
+
next === undefined ||
|
|
201
|
+
(beforeRowId !== undefined && next >= beforeRowId)
|
|
202
|
+
)
|
|
203
|
+
throw new AdapterError(
|
|
204
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
205
|
+
"ZCode history cursor did not advance",
|
|
206
|
+
);
|
|
207
|
+
beforeRowId = next;
|
|
208
|
+
} while (!this.closed);
|
|
209
|
+
if (this.closed) return;
|
|
210
|
+
if (consistent) {
|
|
211
|
+
this.state = {
|
|
212
|
+
...base,
|
|
213
|
+
rows: {
|
|
214
|
+
...base.rows,
|
|
215
|
+
window: [...rows.values()].sort((a, b) => a.rowId - b.rowId),
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
this.changed();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
throw new AdapterError(
|
|
223
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
224
|
+
"ZCode history changed during pagination; no mixed history was published",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
get idle(): boolean {
|
|
228
|
+
const c = this.state?.control;
|
|
229
|
+
return (
|
|
230
|
+
!!c &&
|
|
231
|
+
!this.recovering &&
|
|
232
|
+
!this.hydrating &&
|
|
233
|
+
!c.canStop &&
|
|
234
|
+
c.activeWorks.length === 0 &&
|
|
235
|
+
c.phase !== "running" &&
|
|
236
|
+
c.phase !== "prewarming" &&
|
|
237
|
+
this.state!.queue.items.length === 0
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
waitFor(predicate: () => boolean): Promise<void> {
|
|
241
|
+
if (this.failure) return Promise.reject(this.failure);
|
|
242
|
+
if (predicate()) return Promise.resolve();
|
|
243
|
+
return new Promise((resolve, reject) => {
|
|
244
|
+
const timer = setTimeout(() => {
|
|
245
|
+
off();
|
|
246
|
+
reject(
|
|
247
|
+
new AdapterError(
|
|
248
|
+
"NATIVE_TIMEOUT",
|
|
249
|
+
"ZCode did not confirm the requested state",
|
|
250
|
+
),
|
|
251
|
+
);
|
|
252
|
+
}, 30_000);
|
|
253
|
+
const off = () => this.waiters.delete(check);
|
|
254
|
+
const check = () => {
|
|
255
|
+
if (this.failure || this.closed || predicate()) {
|
|
256
|
+
clearTimeout(timer);
|
|
257
|
+
off();
|
|
258
|
+
this.failure
|
|
259
|
+
? reject(this.failure)
|
|
260
|
+
: this.closed
|
|
261
|
+
? reject(new AdapterError("NATIVE_EXITED", "Conversation closed"))
|
|
262
|
+
: resolve();
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
this.waiters.add(check);
|
|
266
|
+
check();
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
async close(): Promise<void> {
|
|
270
|
+
this.closed = true;
|
|
271
|
+
clearTimeout(this.expiry);
|
|
272
|
+
this.assembler.clear();
|
|
273
|
+
this.changed();
|
|
274
|
+
await this.subscription?.dispose();
|
|
275
|
+
this.listeners.clear();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
export async function conversationCommand(
|
|
279
|
+
bridge: HostBridge,
|
|
280
|
+
workspacePath: string,
|
|
281
|
+
sessionId: string,
|
|
282
|
+
type: string,
|
|
283
|
+
payload: unknown,
|
|
284
|
+
options: { commandId?: string; revision?: number } = {},
|
|
285
|
+
) {
|
|
286
|
+
const commandId = options.commandId ?? randomUUID();
|
|
287
|
+
const envelope = {
|
|
288
|
+
commandId,
|
|
289
|
+
clientId: "paseo-zcode-provider",
|
|
290
|
+
sessionId,
|
|
291
|
+
type,
|
|
292
|
+
payload,
|
|
293
|
+
issuedAt: Date.now(),
|
|
294
|
+
...(options.revision === undefined
|
|
295
|
+
? {}
|
|
296
|
+
: { baseRevision: options.revision }),
|
|
297
|
+
};
|
|
298
|
+
const validated = parseCommandEnvelope(envelope);
|
|
299
|
+
if (!validated.ok)
|
|
300
|
+
throw new AdapterError(
|
|
301
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
302
|
+
"Invalid ZCode command",
|
|
303
|
+
{},
|
|
304
|
+
{ cause: validated.error },
|
|
305
|
+
);
|
|
306
|
+
const ack = await bridge.request(
|
|
307
|
+
"sendConversationCommandV4",
|
|
308
|
+
{ workspacePath, envelope },
|
|
309
|
+
commandAckSchema,
|
|
310
|
+
60_000,
|
|
311
|
+
{ sessionId, inputId: commandId },
|
|
312
|
+
);
|
|
313
|
+
if (ack.commandId !== commandId)
|
|
314
|
+
throw new AdapterError(
|
|
315
|
+
"NATIVE_PROTOCOL_ERROR",
|
|
316
|
+
"ZCode acknowledged a different command",
|
|
317
|
+
);
|
|
318
|
+
return ack;
|
|
319
|
+
}
|