pi-open-deskos 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -19
- package/README.zh-CN.md +76 -19
- package/fixtures/control-v2.json +2 -2
- package/package.json +7 -6
- package/session-control +38 -0
- package/src/config.ts +135 -15
- package/src/console-client.ts +47 -21
- package/src/console-extension.ts +84 -43
- package/src/control-transport.ts +3 -3
- package/src/desk-set.ts +69 -0
- package/src/events.ts +77 -19
- package/src/index.ts +172 -29
- package/src/processes.ts +117 -13
- package/src/reporter.ts +117 -39
- package/src/session-host-client.mjs +274 -0
- package/src/session-host.ts +632 -0
- package/src/session-replay.ts +38 -0
- package/src/transport.ts +34 -11
- package/src/types.ts +18 -9
package/src/transport.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { connect, type Socket } from "node:net";
|
|
2
|
-
import {
|
|
2
|
+
import { ODK_LINK_PROTOCOL, type OdkLinkConfig, type OdkLinkRecord } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
const MAX_INBOUND_FRAME_BYTES = 1024 * 1024;
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
|
-
* A
|
|
7
|
+
* A Open DeskOS Link transport. The reporter never touches a socket directly, so the
|
|
6
8
|
* wire contract is testable without a network.
|
|
7
9
|
*/
|
|
8
10
|
export interface DeskTransport {
|
|
9
|
-
send(record:
|
|
11
|
+
send(record: OdkLinkRecord): void;
|
|
10
12
|
close(): void;
|
|
11
13
|
onOpen(handler: () => void): void;
|
|
12
14
|
onClose(handler: (reason: string) => void): void;
|
|
@@ -31,8 +33,8 @@ export function parseRecords(chunk: string, remainder: string): { records: unkno
|
|
|
31
33
|
return { records, pending };
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
/** The real
|
|
35
|
-
export function createTcpTransport(config:
|
|
36
|
+
/** The real Open DeskOS Link: one outbound TCP connection with newline-delimited JSON. */
|
|
37
|
+
export function createTcpTransport(config: OdkLinkConfig, options: { connectTimeoutMs?: number } = {}): DeskTransport {
|
|
36
38
|
const openHandlers: Array<() => void> = [];
|
|
37
39
|
const closeHandlers: Array<(reason: string) => void> = [];
|
|
38
40
|
const recordHandlers: Array<(record: unknown) => void> = [];
|
|
@@ -42,6 +44,9 @@ export function createTcpTransport(config: DeskLinkConfig): DeskTransport {
|
|
|
42
44
|
const socket: Socket = connect({ host: config.host, port: config.port });
|
|
43
45
|
socket.setKeepAlive(true, 15000);
|
|
44
46
|
socket.setNoDelay(true);
|
|
47
|
+
// Node's stream decoder retains incomplete UTF-8 code points across packets.
|
|
48
|
+
socket.setEncoding("utf8");
|
|
49
|
+
socket.setTimeout(options.connectTimeoutMs ?? 10000);
|
|
45
50
|
|
|
46
51
|
const announceClose = (reason: string): void => {
|
|
47
52
|
if (closed) return;
|
|
@@ -50,18 +55,36 @@ export function createTcpTransport(config: DeskLinkConfig): DeskTransport {
|
|
|
50
55
|
};
|
|
51
56
|
|
|
52
57
|
socket.on("connect", () => {
|
|
58
|
+
if (closed) return;
|
|
59
|
+
socket.setTimeout(0);
|
|
53
60
|
for (const handler of openHandlers) handler();
|
|
54
61
|
});
|
|
55
|
-
socket.on("
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
socket.on("timeout", () => {
|
|
63
|
+
announceClose("connection timeout");
|
|
64
|
+
socket.destroy();
|
|
65
|
+
});
|
|
66
|
+
socket.on("data", (chunk: string) => {
|
|
67
|
+
if (closed) return;
|
|
68
|
+
const text = remainder + chunk;
|
|
69
|
+
const lines = text.split("\n");
|
|
70
|
+
// Never discard a prefix and parse the suffix as an independent record.
|
|
71
|
+
if (lines.some((line) => Buffer.byteLength(line) + 1 > MAX_INBOUND_FRAME_BYTES)) {
|
|
72
|
+
announceClose("inbound record exceeds 1 MiB frame limit");
|
|
73
|
+
socket.destroy();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const parsed = parseRecords(chunk, remainder);
|
|
77
|
+
remainder = parsed.pending;
|
|
58
78
|
for (const record of parsed.records) for (const handler of recordHandlers) handler(record);
|
|
59
79
|
});
|
|
60
|
-
socket.on("error", (error: Error) =>
|
|
80
|
+
socket.on("error", (error: Error) => {
|
|
81
|
+
announceClose(error.message);
|
|
82
|
+
socket.destroy();
|
|
83
|
+
});
|
|
61
84
|
socket.on("close", () => announceClose("link closed"));
|
|
62
85
|
|
|
63
86
|
return {
|
|
64
|
-
send(record:
|
|
87
|
+
send(record: OdkLinkRecord) {
|
|
65
88
|
if (closed || socket.destroyed) return;
|
|
66
89
|
socket.write(`${JSON.stringify(record)}\n`);
|
|
67
90
|
},
|
|
@@ -83,4 +106,4 @@ export function createTcpTransport(config: DeskLinkConfig): DeskTransport {
|
|
|
83
106
|
};
|
|
84
107
|
}
|
|
85
108
|
|
|
86
|
-
export const
|
|
109
|
+
export const ODK_LINK_PROTOCOL_DECLARATION = ODK_LINK_PROTOCOL;
|
package/src/types.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The Open DeskOS Link wire contract, version 1.
|
|
3
3
|
*
|
|
4
|
-
* A
|
|
4
|
+
* A Open DeskOS Link carries one Reporting Machine's Reported Sessions to one
|
|
5
5
|
* Open DeskOS runtime. Records are newline-delimited JSON over one connection
|
|
6
6
|
* the package opens; Open DeskOS never reaches back to this machine.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
export const
|
|
9
|
+
export const ODK_LINK_PROTOCOL = 1;
|
|
10
10
|
|
|
11
11
|
/** Event bounds, identical to the runtime's local session collector. */
|
|
12
12
|
export const MAX_EVENTS_PER_SESSION = 300;
|
|
@@ -27,8 +27,15 @@ export type SessionEventKind = "user" | "thinking" | "tool" | "result" | "assist
|
|
|
27
27
|
export interface SessionEvent {
|
|
28
28
|
kind: SessionEventKind;
|
|
29
29
|
text: string;
|
|
30
|
+
/** The actual persisted assistant SessionMessageEntry ID. */
|
|
31
|
+
eventId?: string;
|
|
32
|
+
stopReason?: "stop" | "toolUse" | "length" | "error" | "aborted";
|
|
30
33
|
/** Result-only label, kept outside the Markdown body. */
|
|
31
34
|
toolName?: string;
|
|
35
|
+
/** Tool and result only: the call identity Pi wrote, which pairs the two. */
|
|
36
|
+
toolCallId?: string;
|
|
37
|
+
/** Result only, and only when Pi recorded that result as an error. */
|
|
38
|
+
isError?: true;
|
|
32
39
|
/** Present only when a result or assistant body was shortened. */
|
|
33
40
|
/** Present only when the event body was shortened by its kind's limit. */
|
|
34
41
|
truncated?: true;
|
|
@@ -49,7 +56,7 @@ export interface ReportedSession {
|
|
|
49
56
|
activity?: string;
|
|
50
57
|
}
|
|
51
58
|
|
|
52
|
-
export interface
|
|
59
|
+
export interface OdkLinkConfig {
|
|
53
60
|
/** Reporting machine identity, as shown on the desk. */
|
|
54
61
|
machine: string;
|
|
55
62
|
host: string;
|
|
@@ -94,14 +101,14 @@ export interface HistoryPage {
|
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
export interface HelloRecord {
|
|
97
|
-
v: typeof
|
|
104
|
+
v: typeof ODK_LINK_PROTOCOL;
|
|
98
105
|
type: "hello";
|
|
99
106
|
machine: string;
|
|
100
107
|
token: string;
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
export interface SessionsRecord {
|
|
104
|
-
v: typeof
|
|
111
|
+
v: typeof ODK_LINK_PROTOCOL;
|
|
105
112
|
type: "sessions";
|
|
106
113
|
machine: string;
|
|
107
114
|
sessions: ReportedSession[];
|
|
@@ -109,7 +116,7 @@ export interface SessionsRecord {
|
|
|
109
116
|
|
|
110
117
|
/** Append-only events; a new connection replays the bounded retained tail. */
|
|
111
118
|
export interface EventsRecord {
|
|
112
|
-
v: typeof
|
|
119
|
+
v: typeof ODK_LINK_PROTOCOL;
|
|
113
120
|
type: "events";
|
|
114
121
|
machine: string;
|
|
115
122
|
sessionId: string;
|
|
@@ -117,12 +124,12 @@ export interface EventsRecord {
|
|
|
117
124
|
}
|
|
118
125
|
|
|
119
126
|
export interface ByeRecord {
|
|
120
|
-
v: typeof
|
|
127
|
+
v: typeof ODK_LINK_PROTOCOL;
|
|
121
128
|
type: "bye";
|
|
122
129
|
machine: string;
|
|
123
130
|
}
|
|
124
131
|
|
|
125
|
-
export type
|
|
132
|
+
export type OdkLinkRecord = HelloRecord | SessionsRecord | EventsRecord | ByeRecord;
|
|
126
133
|
|
|
127
134
|
/**
|
|
128
135
|
* The message parts this package reads. Pi streams these shapes for user,
|
|
@@ -141,6 +148,8 @@ export interface ThinkingPart {
|
|
|
141
148
|
|
|
142
149
|
export interface ToolCallPart {
|
|
143
150
|
type: "toolCall";
|
|
151
|
+
/** Pi's identity for this call; its result echoes the same value. */
|
|
152
|
+
id?: string;
|
|
144
153
|
name: string;
|
|
145
154
|
arguments: unknown;
|
|
146
155
|
}
|