@pstdio/pocketcoder-remote 0.2.0 → 0.2.1
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/bin.js +17 -4
- package/dist/extension.js +17366 -0
- package/package.json +3 -5
- package/src/agentapi-events.ts +0 -104
- package/src/attachments.ts +0 -158
- package/src/bin.ts +0 -26
- package/src/client.ts +0 -344
- package/src/commands.ts +0 -149
- package/src/control-plane.ts +0 -30
- package/src/environment.ts +0 -19
- package/src/extension.ts +0 -200
- package/src/history.ts +0 -112
- package/src/launch.ts +0 -85
- package/src/renderers.ts +0 -76
- package/src/response-stream.ts +0 -43
- package/src/session-target.ts +0 -66
- package/src/status.ts +0 -121
package/src/response-stream.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { AssistantMessage, AssistantMessageEventStream } from "@earendil-works/pi-ai";
|
|
2
|
-
|
|
3
|
-
type AssistantEvent = Parameters<AssistantMessageEventStream["push"]>[0];
|
|
4
|
-
|
|
5
|
-
export interface AssistantEventSink {
|
|
6
|
-
push(event: AssistantEvent): void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function textOf(message: AssistantMessage): string {
|
|
10
|
-
const content = message.content[0];
|
|
11
|
-
return content?.type === "text" ? content.text : "";
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function emitRemoteResponse(
|
|
15
|
-
stream: AssistantEventSink,
|
|
16
|
-
output: AssistantMessage,
|
|
17
|
-
send: (onSnapshot: (snapshot: string) => void) => Promise<string>,
|
|
18
|
-
onOutputStart: () => void = () => {},
|
|
19
|
-
): Promise<void> {
|
|
20
|
-
let previous = "";
|
|
21
|
-
let started = false;
|
|
22
|
-
const update = (snapshot: string) => {
|
|
23
|
-
if (snapshot === previous) return;
|
|
24
|
-
const delta = snapshot.startsWith(previous) ? snapshot.slice(previous.length) : "";
|
|
25
|
-
if (!started) {
|
|
26
|
-
output.content.push({ type: "text", text: "" });
|
|
27
|
-
started = true;
|
|
28
|
-
onOutputStart();
|
|
29
|
-
stream.push({ type: "text_start", contentIndex: 0, partial: output });
|
|
30
|
-
}
|
|
31
|
-
const content = output.content[0];
|
|
32
|
-
if (content?.type === "text") content.text = snapshot;
|
|
33
|
-
previous = snapshot;
|
|
34
|
-
stream.push({ type: "text_delta", contentIndex: 0, delta, partial: output });
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const final = await send(update);
|
|
38
|
-
update(final);
|
|
39
|
-
if (!started) update("");
|
|
40
|
-
stream.push({ type: "text_end", contentIndex: 0, content: textOf(output), partial: output });
|
|
41
|
-
output.stopReason = "stop";
|
|
42
|
-
stream.push({ type: "done", reason: "stop", message: output });
|
|
43
|
-
}
|
package/src/session-target.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
export interface RelayTarget {
|
|
2
|
-
mode: "relay";
|
|
3
|
-
baseUrl: string;
|
|
4
|
-
key: string;
|
|
5
|
-
workspaceId: string;
|
|
6
|
-
serviceUrl: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface DirectTarget {
|
|
10
|
-
mode: "direct";
|
|
11
|
-
key: string;
|
|
12
|
-
serviceUrl: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface UnsetTarget {
|
|
16
|
-
mode: "unset";
|
|
17
|
-
baseUrl: string;
|
|
18
|
-
key: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type SessionTarget = RelayTarget | DirectTarget | UnsetTarget;
|
|
22
|
-
|
|
23
|
-
export function relayTarget(baseUrl: string, key: string, workspaceId: string): RelayTarget {
|
|
24
|
-
const base = baseUrl.replace(/\/$/, "");
|
|
25
|
-
return {
|
|
26
|
-
mode: "relay",
|
|
27
|
-
baseUrl: base,
|
|
28
|
-
key,
|
|
29
|
-
workspaceId,
|
|
30
|
-
serviceUrl: `${base}/v1/workspaces/${encodeURIComponent(workspaceId)}/agent`,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function targetFromEnvironment(env: NodeJS.ProcessEnv = process.env): SessionTarget {
|
|
35
|
-
const key = env.POCKETCODER_KEY;
|
|
36
|
-
if (!key) throw new Error("POCKETCODER_KEY is required");
|
|
37
|
-
const directUrl = env.POCKETCODER_AGENTAPI_URL;
|
|
38
|
-
if (directUrl) return { mode: "direct", key, serviceUrl: directUrl.replace(/\/$/, "") };
|
|
39
|
-
|
|
40
|
-
const baseUrl = (env.POCKETCODER_URL ?? "http://127.0.0.1:7080").replace(/\/$/, "");
|
|
41
|
-
const workspaceId = env.POCKETCODER_WORKSPACE_ID;
|
|
42
|
-
if (!workspaceId) return { mode: "unset", baseUrl, key };
|
|
43
|
-
return relayTarget(baseUrl, key, workspaceId);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export class TargetRef {
|
|
47
|
-
private target: SessionTarget;
|
|
48
|
-
private readonly listeners: Array<(target: SessionTarget) => void> = [];
|
|
49
|
-
|
|
50
|
-
constructor(initial: SessionTarget) {
|
|
51
|
-
this.target = initial;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
get current(): SessionTarget {
|
|
55
|
-
return this.target;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
set(next: SessionTarget): void {
|
|
59
|
-
this.target = next;
|
|
60
|
-
for (const listener of this.listeners) listener(next);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
onChange(listener: (target: SessionTarget) => void): void {
|
|
64
|
-
this.listeners.push(listener);
|
|
65
|
-
}
|
|
66
|
-
}
|
package/src/status.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import type { ControlPlaneClient, WorkspaceSummary } from "./control-plane";
|
|
2
|
-
import { TERMINAL_WORKSPACE_STATES } from "./control-plane";
|
|
3
|
-
|
|
4
|
-
export const STATUS_KEY = "pocketcoder";
|
|
5
|
-
|
|
6
|
-
export interface StatusUi {
|
|
7
|
-
setStatus(key: string, text: string | undefined): void;
|
|
8
|
-
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface StatusPollerOptions {
|
|
12
|
-
waitSeconds?: number;
|
|
13
|
-
delay?: (ms: number) => Promise<void>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function statusText(
|
|
17
|
-
workspace: Pick<WorkspaceSummary, "id" | "state" | "agent_state">,
|
|
18
|
-
): string {
|
|
19
|
-
return `ws ${workspace.id.slice(0, 8)} · ${workspace.state}/${workspace.agent_state}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function sleep(ms: number): Promise<void> {
|
|
23
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Keeps the status bar in sync with the workspace via the durable change
|
|
28
|
-
* cursor. Paused during turns so RemoteAgentClient.send() is the only
|
|
29
|
-
* consumer of the change feed while the agent is working.
|
|
30
|
-
*/
|
|
31
|
-
export class StatusPoller {
|
|
32
|
-
private readonly controlPlane: ControlPlaneClient;
|
|
33
|
-
private readonly workspaceId: string;
|
|
34
|
-
private readonly ui: StatusUi;
|
|
35
|
-
private readonly waitSeconds: number;
|
|
36
|
-
private readonly delay: (ms: number) => Promise<void>;
|
|
37
|
-
private cursor: number;
|
|
38
|
-
private paused = false;
|
|
39
|
-
private stopped = false;
|
|
40
|
-
private wake: (() => void) | undefined;
|
|
41
|
-
private controller: AbortController | undefined;
|
|
42
|
-
private loop: Promise<void> | undefined;
|
|
43
|
-
private lastText: string | undefined;
|
|
44
|
-
|
|
45
|
-
constructor(
|
|
46
|
-
controlPlane: ControlPlaneClient,
|
|
47
|
-
workspace: Pick<WorkspaceSummary, "id" | "change_cursor">,
|
|
48
|
-
ui: StatusUi,
|
|
49
|
-
options: StatusPollerOptions = {},
|
|
50
|
-
) {
|
|
51
|
-
this.controlPlane = controlPlane;
|
|
52
|
-
this.workspaceId = workspace.id;
|
|
53
|
-
this.cursor = workspace.change_cursor ?? 0;
|
|
54
|
-
this.ui = ui;
|
|
55
|
-
this.waitSeconds = options.waitSeconds ?? 30;
|
|
56
|
-
this.delay = options.delay ?? sleep;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
start(): void {
|
|
60
|
-
if (!this.loop) this.loop = this.run();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
pause(): void {
|
|
64
|
-
this.paused = true;
|
|
65
|
-
this.controller?.abort();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
resume(): void {
|
|
69
|
-
if (!this.paused) return;
|
|
70
|
-
this.paused = false;
|
|
71
|
-
this.wake?.();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async stop(): Promise<void> {
|
|
75
|
-
this.stopped = true;
|
|
76
|
-
this.controller?.abort();
|
|
77
|
-
this.wake?.();
|
|
78
|
-
await this.loop;
|
|
79
|
-
this.ui.setStatus(STATUS_KEY, undefined);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private async run(): Promise<void> {
|
|
83
|
-
let backoffMs = 1_000;
|
|
84
|
-
while (!this.stopped) {
|
|
85
|
-
if (this.paused) {
|
|
86
|
-
await new Promise<void>((resolve) => {
|
|
87
|
-
this.wake = resolve;
|
|
88
|
-
});
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
this.controller = new AbortController();
|
|
92
|
-
try {
|
|
93
|
-
const change = await this.controlPlane.workspaces.change(
|
|
94
|
-
this.workspaceId,
|
|
95
|
-
this.cursor,
|
|
96
|
-
this.waitSeconds,
|
|
97
|
-
{ signal: this.controller.signal },
|
|
98
|
-
);
|
|
99
|
-
backoffMs = 1_000;
|
|
100
|
-
this.cursor = change.cursor;
|
|
101
|
-
this.lastText = statusText(change.workspace);
|
|
102
|
-
this.ui.setStatus(STATUS_KEY, this.lastText);
|
|
103
|
-
if (TERMINAL_WORKSPACE_STATES.has(change.workspace.state)) {
|
|
104
|
-
this.ui.notify(
|
|
105
|
-
`workspace ${change.workspace.id.slice(0, 8)} is ${change.workspace.state}`,
|
|
106
|
-
change.workspace.state === "succeeded" ? "info" : "warning",
|
|
107
|
-
);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
} catch {
|
|
111
|
-
if (this.stopped || this.paused) continue;
|
|
112
|
-
this.ui.setStatus(
|
|
113
|
-
STATUS_KEY,
|
|
114
|
-
`${this.lastText ?? `ws ${this.workspaceId.slice(0, 8)}`} · reconnecting`,
|
|
115
|
-
);
|
|
116
|
-
await this.delay(backoffMs);
|
|
117
|
-
backoffMs = Math.min(backoffMs * 2, 30_000);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|