@linzumi/cli 0.0.12-beta → 0.0.13-beta

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.
@@ -8,12 +8,22 @@
8
8
  Spec: plans/2026-04-24-local-codex-runner-deep-quality-spec.md
9
9
  Relationship: Carries selected queued message seqs through interrupt fusion
10
10
  so the UI can show which pending replies were accepted by the interrupt.
11
+
12
+ - Date: 2026-05-02
13
+ Spec: plans/2026-05-02-agent-first-zero-to-codex-launch-plan.md
14
+ Relationship: Carries Kandan attachment metadata through npm CLI runner queue
15
+ fusion so attachment-backed replies stay intact before Codex sees them.
11
16
  */
17
+ import { type KandanChatAttachment } from "./channelSessionSupport";
18
+
19
+ export type QueuedKandanAttachment = KandanChatAttachment;
20
+
12
21
  export type QueuedKandanMessage = {
13
22
  readonly seq: number;
14
23
  readonly actorSlug: string | undefined;
15
24
  readonly actorUserId: number | undefined;
16
25
  readonly body: string;
26
+ readonly attachments: readonly QueuedKandanAttachment[];
17
27
  };
18
28
 
19
29
  export type QueueInterruptResult =
@@ -98,5 +108,6 @@ function fuseQueuedMessages(
98
108
  actorSlug: "kandan",
99
109
  actorUserId: undefined,
100
110
  body: selected.map(codexInputForQueuedKandanMessage).join("\n\n---\n\n"),
111
+ attachments: selected.flatMap(message => message.attachments),
101
112
  };
102
113
  }
@@ -211,14 +211,15 @@ function openLocalWebSocket(
211
211
  scheme: "ws" | "wss",
212
212
  ): void {
213
213
  let opened = false;
214
- const websocket = new WebSocket(
215
- localForwardUrl(
216
- scheme === "ws" ? "http" : "https",
217
- control.port,
218
- control.path,
219
- control.queryString,
220
- ).replace(/^http/, scheme),
221
- );
214
+ const url = localForwardUrl(
215
+ scheme === "ws" ? "http" : "https",
216
+ control.port,
217
+ control.path,
218
+ control.queryString,
219
+ ).replace(/^http/, scheme);
220
+ const protocols = webSocketProtocols(control.headers);
221
+ const websocket =
222
+ protocols === undefined ? new WebSocket(url) : new WebSocket(url, protocols);
222
223
  sockets.set(control.socketId, websocket);
223
224
  websocket.addEventListener("open", () => {
224
225
  opened = true;
@@ -260,6 +261,28 @@ function openLocalWebSocket(
260
261
  });
261
262
  }
262
263
 
264
+ function webSocketProtocols(headers: JsonValue | undefined): string[] | undefined {
265
+ if (!Array.isArray(headers)) {
266
+ return undefined;
267
+ }
268
+
269
+ const protocols = headers.flatMap(header => {
270
+ if (
271
+ !isWireHeader(header) ||
272
+ header.name.toLowerCase() !== "sec-websocket-protocol"
273
+ ) {
274
+ return [];
275
+ }
276
+
277
+ return header.value
278
+ .split(",")
279
+ .map(protocol => protocol.trim())
280
+ .filter(protocol => protocol !== "");
281
+ });
282
+
283
+ return protocols.length === 0 ? undefined : protocols;
284
+ }
285
+
263
286
  function localForwardUrl(
264
287
  scheme: "http" | "https",
265
288
  port: number,
package/src/protocol.ts CHANGED
@@ -37,6 +37,11 @@
37
37
  Spec: plans/2026-04-26-local-runner-subdomain-forwarding-epr.md
38
38
  Relationship: Defines the typed WebSocket forwarding controls used by
39
39
  isolated preview subdomains.
40
+
41
+ - Date: 2026-05-02
42
+ Spec: plans/2026-05-02-agent-first-zero-to-codex-launch-plan.md
43
+ Relationship: Keeps the npm CLI runner protocol in parity with the legacy
44
+ runner for runtime session setting updates.
40
45
  */
41
46
  export type JsonValue =
42
47
  | null
@@ -182,6 +187,16 @@ export type KandanControl =
182
187
  readonly requestId: string;
183
188
  readonly decision: "approve" | "deny";
184
189
  }
190
+ | {
191
+ readonly type: "update_session_settings";
192
+ readonly instanceId?: string;
193
+ readonly threadId: string;
194
+ readonly model?: string | null;
195
+ readonly reasoningEffort?: string | null;
196
+ readonly approvalPolicy?: string | null;
197
+ readonly sandbox?: string | null;
198
+ readonly fast?: boolean;
199
+ }
185
200
  | {
186
201
  readonly type: "resolve_port_forward_request";
187
202
  readonly instanceId: string;
package/src/runner.ts CHANGED
@@ -211,6 +211,21 @@ async function openLocalCodexRunner(
211
211
  channel: options.channelSession?.channelSlug ?? null,
212
212
  capabilities: capabilitiesPayload(),
213
213
  });
214
+
215
+ const pendingControls: KandanControl[] = [];
216
+ const controlDispatcher: {
217
+ value: ((control: KandanControl) => void) | undefined;
218
+ } = { value: undefined };
219
+ kandan.onControl((control) => {
220
+ const dispatcher = controlDispatcher.value;
221
+ if (dispatcher === undefined) {
222
+ pendingControls.push(control);
223
+ return;
224
+ }
225
+
226
+ dispatcher(control);
227
+ });
228
+
214
229
  await kandan.join(topic, joinPayload(), { rejoinPayload: joinPayload });
215
230
 
216
231
  const started =
@@ -431,7 +446,7 @@ async function openLocalCodexRunner(
431
446
  channelSession?.handleCodexNotification(notification.method, params);
432
447
  });
433
448
 
434
- kandan.onControl((control) => {
449
+ const handleControl = (control: KandanControl) => {
435
450
  log("kandan.control", { control });
436
451
  if (!controlTargetsInstance(control, instanceId)) {
437
452
  log("kandan.control_ignored", {
@@ -584,7 +599,10 @@ async function openLocalCodexRunner(
584
599
  message: error instanceof Error ? error.message : String(error),
585
600
  });
586
601
  });
587
- });
602
+ };
603
+
604
+ controlDispatcher.value = handleControl;
605
+ pendingControls.splice(0).forEach(handleControl);
588
606
 
589
607
  return { instanceId, codexUrl, close };
590
608
  }
@@ -670,7 +688,7 @@ function normalizedWorkDescription(value: string | undefined): string | undefine
670
688
 
671
689
  function makeRunnerLogger(options: RunnerOptions): RunnerLogger {
672
690
  return createRunnerLogger(
673
- options.logFile ?? join(options.cwd, ".kandan-local-codex-runner.log"),
691
+ options.logFile ?? join(options.cwd, ".linzumi-runner.log"),
674
692
  options.launchTui ? undefined : reportRunnerConsoleEvent,
675
693
  );
676
694
  }