@camerontaylor/paseo-client 0.8.0-fork.1 → 0.8.0-fork.3
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/dist/connection/index.d.ts +27 -21
- package/dist/connection/index.js +89 -85
- package/dist/connection/legacy-workspaces.d.ts +19 -0
- package/dist/connection/legacy-workspaces.js +160 -0
- package/dist/connection/legacy.d.ts +31 -0
- package/dist/connection/legacy.js +245 -0
- package/dist/connection/owned.d.ts +36 -0
- package/dist/connection/owned.js +210 -0
- package/dist/daemon-client.d.ts +88 -57
- package/dist/daemon-client.js +259 -392
- package/dist/index.d.ts +39 -10
- package/dist/index.js +254 -125
- package/dist/provider-snapshots/index.d.ts +1 -3
- package/dist/provider-snapshots/index.js +23 -27
- package/dist/terminal-stream-router.d.ts +7 -10
- package/dist/terminal-stream-router.js +41 -88
- package/dist/timeline-subscription/index.d.ts +25 -0
- package/dist/timeline-subscription/index.js +56 -0
- package/package.json +7 -3
- package/dist/creation/index.d.ts +0 -47
- package/dist/creation/index.js +0 -216
|
@@ -3,57 +3,53 @@ export class ProviderSnapshotUpdates {
|
|
|
3
3
|
constructor(host) {
|
|
4
4
|
this.host = host;
|
|
5
5
|
this.pending = new Map();
|
|
6
|
-
this.paused = false;
|
|
7
6
|
}
|
|
8
7
|
clear() {
|
|
9
8
|
this.pending.clear();
|
|
10
9
|
}
|
|
11
|
-
pause() {
|
|
12
|
-
this.paused = true;
|
|
13
|
-
// Invalidate old responses while retaining the announcements they would resolve.
|
|
14
|
-
for (const [cwd, pending] of this.pending) {
|
|
15
|
-
this.pending.set(cwd, { latest: pending.latest, running: false });
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
resume() {
|
|
19
|
-
this.paused = false;
|
|
20
|
-
for (const pending of this.pending.values())
|
|
21
|
-
this.receive(pending.latest);
|
|
22
|
-
}
|
|
23
10
|
receive(update) {
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
if (!this.host.active(update))
|
|
12
|
+
return;
|
|
13
|
+
const key = JSON.stringify([update.payload.subscriptionId, update.payload.cwd]);
|
|
14
|
+
const pending = this.pending.get(key) ?? { latest: update, running: false };
|
|
26
15
|
pending.latest = update;
|
|
27
|
-
this.pending.set(
|
|
28
|
-
if (
|
|
16
|
+
this.pending.set(key, pending);
|
|
17
|
+
if (pending.running)
|
|
29
18
|
return;
|
|
30
19
|
pending.running = true;
|
|
31
|
-
void this.resolve(
|
|
20
|
+
void this.resolve(key, pending);
|
|
32
21
|
}
|
|
33
|
-
async resolve(
|
|
22
|
+
async resolve(key, pending) {
|
|
34
23
|
try {
|
|
35
|
-
while (this.pending.get(
|
|
24
|
+
while (this.pending.get(key) === pending) {
|
|
36
25
|
const announced = pending.latest;
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
if (!this.host.active(announced))
|
|
27
|
+
return;
|
|
28
|
+
const snapshot = await this.host.fetch(announced.payload.cwd);
|
|
29
|
+
if (!this.host.active(announced))
|
|
30
|
+
return;
|
|
31
|
+
if (this.pending.get(key) !== pending)
|
|
39
32
|
return;
|
|
40
33
|
// A change arriving while the body was in flight needs the current body.
|
|
41
34
|
// If that response already covers it, no second fetch is necessary.
|
|
42
35
|
if (pending.latest !== announced &&
|
|
43
36
|
pending.latest.payload.snapshotHash !== snapshot.snapshotHash)
|
|
44
37
|
continue;
|
|
45
|
-
this.pending.delete(
|
|
46
|
-
this.host.emit({
|
|
38
|
+
this.pending.delete(key);
|
|
39
|
+
this.host.emit({
|
|
40
|
+
type: "providers_snapshot_update",
|
|
41
|
+
payload: { ...snapshot, subscriptionId: announced.payload.subscriptionId },
|
|
42
|
+
});
|
|
47
43
|
return;
|
|
48
44
|
}
|
|
49
45
|
}
|
|
50
46
|
catch (error) {
|
|
51
|
-
if (this.pending.get(
|
|
47
|
+
if (this.pending.get(key) === pending)
|
|
52
48
|
this.host.failed(error);
|
|
53
49
|
}
|
|
54
50
|
finally {
|
|
55
|
-
if (this.pending.get(
|
|
56
|
-
this.pending.delete(
|
|
51
|
+
if (this.pending.get(key) === pending)
|
|
52
|
+
this.pending.delete(key);
|
|
57
53
|
}
|
|
58
54
|
}
|
|
59
55
|
}
|
|
@@ -1,28 +1,25 @@
|
|
|
1
1
|
import { type TerminalStreamFrame } from "@camerontaylor/paseo-protocol/binary-frames/index";
|
|
2
|
-
import type {
|
|
2
|
+
import type { TerminalState } from "@camerontaylor/paseo-protocol/messages";
|
|
3
3
|
export type TerminalStreamEvent = {
|
|
4
4
|
terminalId: string;
|
|
5
|
+
subscriptionId: string;
|
|
6
|
+
} & ({
|
|
5
7
|
type: "output";
|
|
6
8
|
data: Uint8Array;
|
|
7
9
|
} | {
|
|
8
|
-
terminalId: string;
|
|
9
10
|
type: "snapshot";
|
|
10
11
|
state: TerminalState;
|
|
11
12
|
} | {
|
|
12
|
-
terminalId: string;
|
|
13
13
|
type: "restore";
|
|
14
14
|
data: Uint8Array;
|
|
15
|
-
};
|
|
15
|
+
});
|
|
16
16
|
export declare class TerminalStreamRouter {
|
|
17
|
-
private readonly
|
|
18
|
-
private readonly slotTerminals;
|
|
17
|
+
private readonly slots;
|
|
19
18
|
private readonly listeners;
|
|
19
|
+
/** Passive wire diagnostics. App state consumes the registration's callback. */
|
|
20
20
|
onEvent(handler: (event: TerminalStreamEvent) => void): () => void;
|
|
21
|
-
|
|
22
|
-
removeTerminal(terminalId: string): void;
|
|
21
|
+
attach(subscriptionId: string, terminalId: string, slot: number, receive: (event: TerminalStreamEvent) => void): () => void;
|
|
23
22
|
clearSlots(): void;
|
|
24
|
-
encodeInput(terminalId: string, message: TerminalInput["message"]): Uint8Array | null;
|
|
25
23
|
handleFrame(frame: TerminalStreamFrame): void;
|
|
26
|
-
private emit;
|
|
27
24
|
}
|
|
28
25
|
//# sourceMappingURL=terminal-stream-router.d.ts.map
|
|
@@ -1,107 +1,60 @@
|
|
|
1
|
-
import { decodeTerminalSnapshotPayload,
|
|
1
|
+
import { decodeTerminalSnapshotPayload, TerminalStreamOpcode, } from "@camerontaylor/paseo-protocol/binary-frames/index";
|
|
2
2
|
export class TerminalStreamRouter {
|
|
3
3
|
constructor() {
|
|
4
|
-
this.
|
|
5
|
-
this.slotTerminals = new Map();
|
|
4
|
+
this.slots = new Map();
|
|
6
5
|
this.listeners = new Set();
|
|
7
6
|
}
|
|
7
|
+
/** Passive wire diagnostics. App state consumes the registration's callback. */
|
|
8
8
|
onEvent(handler) {
|
|
9
9
|
this.listeners.add(handler);
|
|
10
10
|
return () => {
|
|
11
11
|
this.listeners.delete(handler);
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.slotTerminals.set(slot, terminalId);
|
|
25
|
-
}
|
|
26
|
-
removeTerminal(terminalId) {
|
|
27
|
-
const slot = this.terminalSlots.get(terminalId);
|
|
28
|
-
if (typeof slot !== "number") {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
this.terminalSlots.delete(terminalId);
|
|
32
|
-
if (this.slotTerminals.get(slot) === terminalId) {
|
|
33
|
-
this.slotTerminals.delete(slot);
|
|
34
|
-
}
|
|
14
|
+
attach(subscriptionId, terminalId, slot, receive) {
|
|
15
|
+
const registration = { subscriptionId, terminalId, receive };
|
|
16
|
+
const registrations = this.slots.get(slot) ?? new Set();
|
|
17
|
+
registrations.add(registration);
|
|
18
|
+
this.slots.set(slot, registrations);
|
|
19
|
+
return () => {
|
|
20
|
+
registrations.delete(registration);
|
|
21
|
+
if (registrations.size === 0)
|
|
22
|
+
this.slots.delete(slot);
|
|
23
|
+
};
|
|
35
24
|
}
|
|
36
25
|
clearSlots() {
|
|
37
|
-
this.
|
|
38
|
-
this.slotTerminals.clear();
|
|
39
|
-
}
|
|
40
|
-
encodeInput(terminalId, message) {
|
|
41
|
-
const slot = this.terminalSlots.get(terminalId);
|
|
42
|
-
if (typeof slot !== "number") {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
if (message.type === "input") {
|
|
46
|
-
return encodeTerminalStreamFrame({
|
|
47
|
-
opcode: TerminalStreamOpcode.Input,
|
|
48
|
-
slot,
|
|
49
|
-
payload: message.data,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
if (message.type === "resize") {
|
|
53
|
-
return encodeTerminalStreamFrame({
|
|
54
|
-
opcode: TerminalStreamOpcode.Resize,
|
|
55
|
-
slot,
|
|
56
|
-
payload: encodeTerminalResizePayload({
|
|
57
|
-
rows: message.rows,
|
|
58
|
-
cols: message.cols,
|
|
59
|
-
...(message.intent ? { intent: message.intent } : {}),
|
|
60
|
-
}),
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
return null;
|
|
26
|
+
this.slots.clear();
|
|
64
27
|
}
|
|
65
28
|
handleFrame(frame) {
|
|
66
|
-
const
|
|
67
|
-
if (!
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (frame.opcode === TerminalStreamOpcode.Output) {
|
|
71
|
-
this.emit({
|
|
72
|
-
terminalId,
|
|
73
|
-
type: "output",
|
|
74
|
-
data: frame.payload,
|
|
75
|
-
});
|
|
29
|
+
const registrations = this.slots.get(frame.slot);
|
|
30
|
+
if (!registrations)
|
|
76
31
|
return;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
terminalId,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
32
|
+
for (const registration of registrations) {
|
|
33
|
+
const identity = {
|
|
34
|
+
subscriptionId: registration.subscriptionId,
|
|
35
|
+
terminalId: registration.terminalId,
|
|
36
|
+
};
|
|
37
|
+
let event;
|
|
38
|
+
if (frame.opcode === TerminalStreamOpcode.Output)
|
|
39
|
+
event = { ...identity, type: "output", data: frame.payload };
|
|
40
|
+
else if (frame.opcode === TerminalStreamOpcode.Restore)
|
|
41
|
+
event = { ...identity, type: "restore", data: frame.payload };
|
|
42
|
+
else if (frame.opcode === TerminalStreamOpcode.Snapshot) {
|
|
43
|
+
const state = decodeTerminalSnapshotPayload(frame.payload);
|
|
44
|
+
if (!state)
|
|
45
|
+
return;
|
|
46
|
+
event = { ...identity, type: "snapshot", state };
|
|
90
47
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
listener(event);
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
// no-op
|
|
48
|
+
else
|
|
49
|
+
return;
|
|
50
|
+
registration.receive(event);
|
|
51
|
+
for (const listener of this.listeners) {
|
|
52
|
+
try {
|
|
53
|
+
listener(event);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
/* Passive diagnostics cannot interrupt delivery. */
|
|
57
|
+
}
|
|
105
58
|
}
|
|
106
59
|
}
|
|
107
60
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SessionOutboundMessage } from "@camerontaylor/paseo-protocol/messages";
|
|
2
|
+
import type { OwnedSubscription, TimelineSubscription } from "../connection/index.js";
|
|
3
|
+
type TimelineUpdate = Extract<SessionOutboundMessage, {
|
|
4
|
+
type: "agent_stream" | "agent.timeline.replacement";
|
|
5
|
+
}>;
|
|
6
|
+
/** Local SDK lifecycle messages; these are not additional wire RPCs. */
|
|
7
|
+
export type TimelineMessage = TimelineUpdate | {
|
|
8
|
+
type: "agent.timeline.subscription_restored";
|
|
9
|
+
payload: {
|
|
10
|
+
agentId: string;
|
|
11
|
+
subscriptionId: string;
|
|
12
|
+
};
|
|
13
|
+
} | {
|
|
14
|
+
type: "agent.timeline.error";
|
|
15
|
+
payload: {
|
|
16
|
+
agentId: string;
|
|
17
|
+
error: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/** Restores live delivery; the consumer owns history reads and gap recovery. */
|
|
21
|
+
export declare function subscribeTimeline(agentId: string, observation: OwnedSubscription<{
|
|
22
|
+
agentIds: string[];
|
|
23
|
+
}>, handler: (message: TimelineMessage) => void, reportError: (error: unknown) => void): TimelineSubscription;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Restores live delivery; the consumer owns history reads and gap recovery. */
|
|
2
|
+
export function subscribeTimeline(agentId, observation, handler, reportError) {
|
|
3
|
+
let established = false;
|
|
4
|
+
let released = false;
|
|
5
|
+
const notify = (message) => {
|
|
6
|
+
if (released)
|
|
7
|
+
return;
|
|
8
|
+
try {
|
|
9
|
+
handler(message);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
reportError(error);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const release = () => {
|
|
16
|
+
released = true;
|
|
17
|
+
return observation.release();
|
|
18
|
+
};
|
|
19
|
+
const stop = () => {
|
|
20
|
+
// Use the public release entry point so the owning API also forgets this handle.
|
|
21
|
+
void subscription.release().catch(reportError);
|
|
22
|
+
};
|
|
23
|
+
const fail = (error) => {
|
|
24
|
+
notify({
|
|
25
|
+
type: "agent.timeline.error",
|
|
26
|
+
payload: { agentId, error: error instanceof Error ? error.message : String(error) },
|
|
27
|
+
});
|
|
28
|
+
stop();
|
|
29
|
+
};
|
|
30
|
+
observation.subscribe({
|
|
31
|
+
snapshot: ({ subscriptionId }) => {
|
|
32
|
+
if (!established) {
|
|
33
|
+
established = true;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
notify({
|
|
37
|
+
type: "agent.timeline.subscription_restored",
|
|
38
|
+
payload: { agentId, subscriptionId },
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
update: (message) => {
|
|
42
|
+
if (message.type === "agent_stream" || message.type === "agent.timeline.replacement")
|
|
43
|
+
notify(message);
|
|
44
|
+
},
|
|
45
|
+
error: fail,
|
|
46
|
+
});
|
|
47
|
+
const subscription = Object.assign(stop, {
|
|
48
|
+
ready: observation.ready.then(() => undefined),
|
|
49
|
+
release,
|
|
50
|
+
subscriptionId: null,
|
|
51
|
+
});
|
|
52
|
+
Object.defineProperty(subscription, "subscriptionId", { get: () => observation.subscriptionId });
|
|
53
|
+
void subscription.ready.catch(fail);
|
|
54
|
+
return subscription;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camerontaylor/paseo-client",
|
|
3
|
-
"version": "0.8.0-fork.
|
|
3
|
+
"version": "0.8.0-fork.3",
|
|
4
4
|
"description": "Paseo client SDK package",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -39,13 +39,17 @@
|
|
|
39
39
|
"test": "vitest run"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@camerontaylor/paseo-protocol": "0.8.0-fork.
|
|
43
|
-
"@camerontaylor/paseo-relay": "0.8.0-fork.
|
|
42
|
+
"@camerontaylor/paseo-protocol": "0.8.0-fork.3",
|
|
43
|
+
"@camerontaylor/paseo-relay": "0.8.0-fork.3",
|
|
44
44
|
"zod": "^4.4.3"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^20.9.0",
|
|
48
48
|
"typescript": "^5.2.2",
|
|
49
49
|
"vitest": "^4.1.6"
|
|
50
|
+
},
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "https://github.com/camerontaylor/paseo"
|
|
50
54
|
}
|
|
51
55
|
}
|
package/dist/creation/index.d.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import type { AgentSnapshotPayload, CreationSnapshot, WorkspaceCreateResponse } from "@camerontaylor/paseo-protocol/messages";
|
|
2
|
-
import type { CreateAgentRequestOptions, CreateWorkspaceRequestOptions } from "../daemon-client.js";
|
|
3
|
-
type Kind = CreationSnapshot["kind"];
|
|
4
|
-
export interface CreationResult {
|
|
5
|
-
agent?: AgentSnapshotPayload | null;
|
|
6
|
-
workspace?: WorkspaceCreateResponse["payload"]["workspace"];
|
|
7
|
-
creation?: CreationSnapshot;
|
|
8
|
-
error: string | null;
|
|
9
|
-
errorCode?: string;
|
|
10
|
-
setupTerminalId?: string | null;
|
|
11
|
-
setupSkippedReason?: string;
|
|
12
|
-
requestId?: string;
|
|
13
|
-
}
|
|
14
|
-
interface Dependencies {
|
|
15
|
-
supports: () => boolean;
|
|
16
|
-
connected: () => boolean;
|
|
17
|
-
requestId: () => string;
|
|
18
|
-
request: (kind: Kind, input: Record<string, unknown>) => Promise<CreationResult>;
|
|
19
|
-
subscribe: (kind: Kind, key: string, enabled?: boolean) => Promise<CreationSnapshot | null>;
|
|
20
|
-
legacyAgent: (input: CreateAgentRequestOptions) => Promise<AgentSnapshotPayload>;
|
|
21
|
-
legacyWorkspace: (input: CreateWorkspaceRequestOptions) => Promise<WorkspaceCreateResponse["payload"]>;
|
|
22
|
-
sendMessage: (id: string, text: string, options: {
|
|
23
|
-
messageId: string;
|
|
24
|
-
images?: CreateAgentRequestOptions["images"];
|
|
25
|
-
attachments?: CreateAgentRequestOptions["attachments"];
|
|
26
|
-
}) => Promise<unknown>;
|
|
27
|
-
}
|
|
28
|
-
/** Client-owned observation and compatibility. UI callbacks never advance creation. */
|
|
29
|
-
export declare class CreationClient {
|
|
30
|
-
private readonly deps;
|
|
31
|
-
private readonly operations;
|
|
32
|
-
constructor(deps: Dependencies);
|
|
33
|
-
createAgent(input: CreateAgentRequestOptions): Promise<CreationResult>;
|
|
34
|
-
createWorkspace(input: CreateWorkspaceRequestOptions): Promise<CreationResult>;
|
|
35
|
-
private legacyAgent;
|
|
36
|
-
private start;
|
|
37
|
-
private submit;
|
|
38
|
-
receive(snapshot: CreationSnapshot): void;
|
|
39
|
-
private recover;
|
|
40
|
-
reconnect(): void;
|
|
41
|
-
close(): void;
|
|
42
|
-
private finish;
|
|
43
|
-
private fail;
|
|
44
|
-
private notify;
|
|
45
|
-
}
|
|
46
|
-
export {};
|
|
47
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/creation/index.js
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
/** Client-owned observation and compatibility. UI callbacks never advance creation. */
|
|
2
|
-
export class CreationClient {
|
|
3
|
-
constructor(deps) {
|
|
4
|
-
this.deps = deps;
|
|
5
|
-
this.operations = new Map();
|
|
6
|
-
}
|
|
7
|
-
createAgent(input) {
|
|
8
|
-
const { onEvent, ...request } = input;
|
|
9
|
-
return this.start("agent", request, onEvent, () => this.legacyAgent(request));
|
|
10
|
-
}
|
|
11
|
-
createWorkspace(input) {
|
|
12
|
-
const { onEvent, ...request } = input;
|
|
13
|
-
return this.start("workspace", request, onEvent, async (operation) => {
|
|
14
|
-
if (request.workspaceId || request.agent?.agentId)
|
|
15
|
-
throw new Error("Update the host to use caller-selected creation IDs.");
|
|
16
|
-
const { agent, ...workspaceInput } = request;
|
|
17
|
-
const workspace = await this.deps.legacyWorkspace(workspaceInput);
|
|
18
|
-
if (workspace.error || !workspace.workspace)
|
|
19
|
-
return workspace;
|
|
20
|
-
this.receive({
|
|
21
|
-
kind: "workspace",
|
|
22
|
-
idempotencyKey: operation.key,
|
|
23
|
-
revision: 1,
|
|
24
|
-
phase: "workspace_ready",
|
|
25
|
-
workspaceId: workspace.workspace.id,
|
|
26
|
-
agentId: null,
|
|
27
|
-
workspace: workspace.workspace,
|
|
28
|
-
setupSkippedReason: workspace.setupSkippedReason,
|
|
29
|
-
error: null,
|
|
30
|
-
});
|
|
31
|
-
if (!agent)
|
|
32
|
-
return workspace;
|
|
33
|
-
const sourceCwd = request.source.kind === "directory" ? request.source.path : request.source.cwd;
|
|
34
|
-
const cwd = sourceCwd
|
|
35
|
-
? remapDirectory(agent.config.cwd, sourceCwd, workspace.workspace.workspaceDirectory)
|
|
36
|
-
: workspace.workspace.workspaceDirectory;
|
|
37
|
-
const created = await this.legacyAgent({
|
|
38
|
-
...agent,
|
|
39
|
-
config: { ...agent.config, cwd },
|
|
40
|
-
workspaceId: workspace.workspace.id,
|
|
41
|
-
idempotencyKey: `${operation.key}:agent`,
|
|
42
|
-
});
|
|
43
|
-
return { ...workspace, agent: created.agent };
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
async legacyAgent(input) {
|
|
47
|
-
if (input.agentId)
|
|
48
|
-
throw new Error("Update the host to use caller-selected creation IDs.");
|
|
49
|
-
// COMPAT(creationLifecycle): added in v0.8.0, remove after 2027-03-11 once the daemon floor supports creationLifecycle.
|
|
50
|
-
// Old keyed creates reject initialPrompt. Keep this adaptation private to the SDK.
|
|
51
|
-
if (input.idempotencyKey) {
|
|
52
|
-
const { initialPrompt = "", images, attachments, ...creation } = input;
|
|
53
|
-
const hasPrompt = Boolean(initialPrompt.trim() || images?.length || attachments?.length);
|
|
54
|
-
if (hasPrompt && input.outputSchema)
|
|
55
|
-
throw new Error("Update the host to create a keyed agent with structured initial output.");
|
|
56
|
-
const agent = await this.deps.legacyAgent(creation);
|
|
57
|
-
if (hasPrompt)
|
|
58
|
-
await this.deps.sendMessage(agent.id, initialPrompt, {
|
|
59
|
-
messageId: input.clientMessageId ?? `${input.idempotencyKey}:initial-message`,
|
|
60
|
-
images,
|
|
61
|
-
attachments,
|
|
62
|
-
});
|
|
63
|
-
return { agent, error: null };
|
|
64
|
-
}
|
|
65
|
-
return { agent: await this.deps.legacyAgent(input), error: null };
|
|
66
|
-
}
|
|
67
|
-
start(kind, input, observer, legacy) {
|
|
68
|
-
const key = typeof input.idempotencyKey === "string" ? input.idempotencyKey : this.deps.requestId();
|
|
69
|
-
const identity = JSON.stringify([kind, key]);
|
|
70
|
-
const { requestId: _requestId, ...intent } = input;
|
|
71
|
-
const fingerprint = JSON.stringify(intent, (_key, value) => value && typeof value === "object" && !Array.isArray(value)
|
|
72
|
-
? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)))
|
|
73
|
-
: value);
|
|
74
|
-
const previous = this.operations.get(identity);
|
|
75
|
-
if (previous) {
|
|
76
|
-
if (previous.fingerprint !== fingerprint)
|
|
77
|
-
return Promise.reject(new Error(`${kind}_request_key_conflict`));
|
|
78
|
-
if (observer) {
|
|
79
|
-
previous.observers.add(observer);
|
|
80
|
-
if (previous.snapshot)
|
|
81
|
-
this.notify(observer, previous.snapshot);
|
|
82
|
-
}
|
|
83
|
-
return previous.result;
|
|
84
|
-
}
|
|
85
|
-
let resolve;
|
|
86
|
-
let reject;
|
|
87
|
-
const result = new Promise((done, fail) => {
|
|
88
|
-
resolve = done;
|
|
89
|
-
reject = fail;
|
|
90
|
-
});
|
|
91
|
-
const operation = {
|
|
92
|
-
kind,
|
|
93
|
-
key,
|
|
94
|
-
input: { ...input, idempotencyKey: key },
|
|
95
|
-
fingerprint,
|
|
96
|
-
observers: new Set(observer ? [observer] : []),
|
|
97
|
-
result,
|
|
98
|
-
resolve,
|
|
99
|
-
reject,
|
|
100
|
-
settled: false,
|
|
101
|
-
modern: this.deps.supports(),
|
|
102
|
-
recovering: false,
|
|
103
|
-
};
|
|
104
|
-
this.operations.set(identity, operation);
|
|
105
|
-
if (operation.modern)
|
|
106
|
-
void this.submit(operation);
|
|
107
|
-
else
|
|
108
|
-
void legacy(operation).then((value) => this.finish(operation, value), (error) => this.fail(operation, error));
|
|
109
|
-
return result;
|
|
110
|
-
}
|
|
111
|
-
async submit(operation) {
|
|
112
|
-
try {
|
|
113
|
-
const result = await this.deps.request(operation.kind, {
|
|
114
|
-
...operation.input,
|
|
115
|
-
subscribe: true,
|
|
116
|
-
});
|
|
117
|
-
if (result.creation)
|
|
118
|
-
this.receive(result.creation);
|
|
119
|
-
this.finish(operation, result);
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
if (operation.settled || !this.deps.connected())
|
|
123
|
-
return;
|
|
124
|
-
this.fail(operation, error);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
receive(snapshot) {
|
|
128
|
-
const operation = this.operations.get(JSON.stringify([snapshot.kind, snapshot.idempotencyKey]));
|
|
129
|
-
if (!operation || (operation.snapshot && operation.snapshot.revision >= snapshot.revision))
|
|
130
|
-
return;
|
|
131
|
-
operation.snapshot = snapshot;
|
|
132
|
-
for (const observer of operation.observers)
|
|
133
|
-
this.notify(observer, snapshot);
|
|
134
|
-
if (operation.recovering && (snapshot.phase === "completed" || snapshot.phase === "failed")) {
|
|
135
|
-
this.finish(operation, {
|
|
136
|
-
agent: snapshot.agent,
|
|
137
|
-
workspace: snapshot.workspace,
|
|
138
|
-
creation: snapshot,
|
|
139
|
-
setupSkippedReason: snapshot.setupSkippedReason,
|
|
140
|
-
error: snapshot.error,
|
|
141
|
-
errorCode: snapshot.errorCode,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
async recover(operation) {
|
|
146
|
-
operation.recovering = true;
|
|
147
|
-
const snapshot = await this.deps.subscribe(operation.kind, operation.key);
|
|
148
|
-
if (!snapshot) {
|
|
149
|
-
await this.submit(operation);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
this.receive(snapshot);
|
|
153
|
-
if (snapshot.phase === "completed" || snapshot.phase === "failed") {
|
|
154
|
-
this.finish(operation, {
|
|
155
|
-
agent: snapshot.agent,
|
|
156
|
-
workspace: snapshot.workspace,
|
|
157
|
-
creation: snapshot,
|
|
158
|
-
setupSkippedReason: snapshot.setupSkippedReason,
|
|
159
|
-
error: snapshot.error,
|
|
160
|
-
errorCode: snapshot.errorCode,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
reconnect() {
|
|
165
|
-
for (const operation of this.operations.values()) {
|
|
166
|
-
if (operation.settled || !operation.modern)
|
|
167
|
-
continue;
|
|
168
|
-
void this.recover(operation).catch((error) => {
|
|
169
|
-
if (this.deps.connected())
|
|
170
|
-
this.fail(operation, error);
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
close() {
|
|
175
|
-
for (const operation of this.operations.values())
|
|
176
|
-
if (!operation.settled)
|
|
177
|
-
this.fail(operation, new Error("Daemon client closed"));
|
|
178
|
-
this.operations.clear();
|
|
179
|
-
}
|
|
180
|
-
finish(operation, result) {
|
|
181
|
-
if (operation.settled)
|
|
182
|
-
return;
|
|
183
|
-
operation.settled = true;
|
|
184
|
-
if (operation.recovering)
|
|
185
|
-
void this.deps.subscribe(operation.kind, operation.key, false).catch(() => undefined);
|
|
186
|
-
operation.resolve(result);
|
|
187
|
-
operation.observers.clear();
|
|
188
|
-
this.operations.delete(JSON.stringify([operation.kind, operation.key]));
|
|
189
|
-
}
|
|
190
|
-
fail(operation, error) {
|
|
191
|
-
if (operation.settled)
|
|
192
|
-
return;
|
|
193
|
-
operation.settled = true;
|
|
194
|
-
operation.reject(error instanceof Error ? error : new Error(String(error)));
|
|
195
|
-
operation.observers.clear();
|
|
196
|
-
this.operations.delete(JSON.stringify([operation.kind, operation.key]));
|
|
197
|
-
}
|
|
198
|
-
notify(observer, snapshot) {
|
|
199
|
-
try {
|
|
200
|
-
observer(snapshot);
|
|
201
|
-
}
|
|
202
|
-
catch {
|
|
203
|
-
/* Observation does not control execution. */
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
function remapDirectory(cwd, source, target) {
|
|
208
|
-
const normalize = (value) => value.replace(/\\/g, "/").replace(/\/$/, "");
|
|
209
|
-
const normalized = normalize(cwd), root = normalize(source);
|
|
210
|
-
if (normalized === root)
|
|
211
|
-
return target;
|
|
212
|
-
if (!normalized.startsWith(`${root}/`))
|
|
213
|
-
throw new Error("Agent directory must be inside the workspace source");
|
|
214
|
-
return `${normalize(target)}${normalized.slice(root.length)}`;
|
|
215
|
-
}
|
|
216
|
-
//# sourceMappingURL=index.js.map
|