pi-open-deskos 0.3.0 → 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/src/transport.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  import { connect, type Socket } from "node:net";
2
- import { DESK_LINK_PROTOCOL, type DeskLinkConfig, type DeskLinkRecord } from "./types.ts";
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 Desk Link transport. The reporter never touches a socket directly, so the
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: DeskLinkRecord): void;
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 Desk Link: one outbound TCP connection with newline-delimited JSON. */
35
- export function createTcpTransport(config: DeskLinkConfig): DeskTransport {
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("data", (chunk: Buffer) => {
56
- const parsed = parseRecords(chunk.toString("utf8"), remainder);
57
- remainder = parsed.pending.length > 4096 ? "" : parsed.pending;
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) => announceClose(error.message));
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: DeskLinkRecord) {
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 DESK_LINK_PROTOCOL_DECLARATION = DESK_LINK_PROTOCOL;
109
+ export const ODK_LINK_PROTOCOL_DECLARATION = ODK_LINK_PROTOCOL;
package/src/types.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
- * The Desk Link wire contract, version 1.
2
+ * The Open DeskOS Link wire contract, version 1.
3
3
  *
4
- * A Desk Link carries one Reporting Machine's Reported Sessions to one
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 DESK_LINK_PROTOCOL = 1;
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,6 +27,9 @@ 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;
32
35
  /** Tool and result only: the call identity Pi wrote, which pairs the two. */
@@ -53,7 +56,7 @@ export interface ReportedSession {
53
56
  activity?: string;
54
57
  }
55
58
 
56
- export interface DeskLinkConfig {
59
+ export interface OdkLinkConfig {
57
60
  /** Reporting machine identity, as shown on the desk. */
58
61
  machine: string;
59
62
  host: string;
@@ -98,14 +101,14 @@ export interface HistoryPage {
98
101
  }
99
102
 
100
103
  export interface HelloRecord {
101
- v: typeof DESK_LINK_PROTOCOL;
104
+ v: typeof ODK_LINK_PROTOCOL;
102
105
  type: "hello";
103
106
  machine: string;
104
107
  token: string;
105
108
  }
106
109
 
107
110
  export interface SessionsRecord {
108
- v: typeof DESK_LINK_PROTOCOL;
111
+ v: typeof ODK_LINK_PROTOCOL;
109
112
  type: "sessions";
110
113
  machine: string;
111
114
  sessions: ReportedSession[];
@@ -113,7 +116,7 @@ export interface SessionsRecord {
113
116
 
114
117
  /** Append-only events; a new connection replays the bounded retained tail. */
115
118
  export interface EventsRecord {
116
- v: typeof DESK_LINK_PROTOCOL;
119
+ v: typeof ODK_LINK_PROTOCOL;
117
120
  type: "events";
118
121
  machine: string;
119
122
  sessionId: string;
@@ -121,12 +124,12 @@ export interface EventsRecord {
121
124
  }
122
125
 
123
126
  export interface ByeRecord {
124
- v: typeof DESK_LINK_PROTOCOL;
127
+ v: typeof ODK_LINK_PROTOCOL;
125
128
  type: "bye";
126
129
  machine: string;
127
130
  }
128
131
 
129
- export type DeskLinkRecord = HelloRecord | SessionsRecord | EventsRecord | ByeRecord;
132
+ export type OdkLinkRecord = HelloRecord | SessionsRecord | EventsRecord | ByeRecord;
130
133
 
131
134
  /**
132
135
  * The message parts this package reads. Pi streams these shapes for user,