@linzumi/cli 0.0.11-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.
- package/README.md +208 -229
- package/package.json +2 -2
- package/src/agentBootstrap.ts +806 -0
- package/src/channelSession.ts +630 -31
- package/src/channelSessionSupport.ts +54 -1
- package/src/forwardTunnel.ts +32 -7
- package/src/index.ts +373 -55
- package/src/kandanQueue.ts +11 -0
- package/src/localCapabilities.ts +14 -1
- package/src/localConfig.ts +99 -0
- package/src/localEditor.ts +1 -1
- package/src/localForwarding.ts +31 -8
- package/src/protocol.ts +16 -0
- package/src/runner.ts +49 -15
|
@@ -8,12 +8,26 @@
|
|
|
8
8
|
Spec: plans/2026-04-24-local-codex-runner-deep-quality-spec.md
|
|
9
9
|
Relationship: Keeps local Codex version discovery bounded so channel
|
|
10
10
|
availability is not blocked indefinitely by a bad executable.
|
|
11
|
+
|
|
12
|
+
- Date: 2026-05-02
|
|
13
|
+
Spec: plans/2026-05-02-agent-first-zero-to-codex-launch-plan.md
|
|
14
|
+
Relationship: Keeps the npm CLI runner in parity with the legacy local
|
|
15
|
+
runner by carrying Kandan attachment metadata into queued Codex input.
|
|
11
16
|
*/
|
|
12
17
|
import { spawnSync } from "node:child_process";
|
|
13
18
|
import { hostname } from "node:os";
|
|
14
|
-
import { integerValue, objectValue, stringValue } from "./json";
|
|
19
|
+
import { arrayValue, integerValue, objectValue, stringValue } from "./json";
|
|
15
20
|
import { type JsonObject, type JsonValue, isJsonObject } from "./protocol";
|
|
16
21
|
|
|
22
|
+
export type KandanChatAttachment = {
|
|
23
|
+
readonly id: string | undefined;
|
|
24
|
+
readonly kind: string | undefined;
|
|
25
|
+
readonly fileName: string | undefined;
|
|
26
|
+
readonly contentType: string | undefined;
|
|
27
|
+
readonly sizeBytes: number | undefined;
|
|
28
|
+
readonly url: string | undefined;
|
|
29
|
+
};
|
|
30
|
+
|
|
17
31
|
export type KandanChatEvent = {
|
|
18
32
|
readonly seq: number;
|
|
19
33
|
readonly type: string;
|
|
@@ -24,6 +38,7 @@ export type KandanChatEvent = {
|
|
|
24
38
|
readonly replyToSeq: number | undefined;
|
|
25
39
|
readonly localRunnerEventType: string | undefined;
|
|
26
40
|
readonly body: string;
|
|
41
|
+
readonly attachments: readonly KandanChatAttachment[];
|
|
27
42
|
};
|
|
28
43
|
|
|
29
44
|
export type RunnerIdentity = {
|
|
@@ -114,9 +129,45 @@ export function parseKandanChatEvent(
|
|
|
114
129
|
stringValue(payload.body) ??
|
|
115
130
|
stringValue(payload.text) ??
|
|
116
131
|
"",
|
|
132
|
+
attachments: parseKandanChatAttachments(
|
|
133
|
+
arrayValue(payload.attachments) ?? arrayValue(value.attachments) ?? [],
|
|
134
|
+
),
|
|
117
135
|
};
|
|
118
136
|
}
|
|
119
137
|
|
|
138
|
+
function parseKandanChatAttachments(
|
|
139
|
+
attachments: readonly JsonValue[],
|
|
140
|
+
): readonly KandanChatAttachment[] {
|
|
141
|
+
return attachments.flatMap(attachment => {
|
|
142
|
+
const value = objectValue(attachment);
|
|
143
|
+
|
|
144
|
+
if (value === undefined) {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return [
|
|
149
|
+
{
|
|
150
|
+
id: stringValue(value.id),
|
|
151
|
+
kind: stringValue(value.kind),
|
|
152
|
+
fileName:
|
|
153
|
+
stringValue(value.file_name) ??
|
|
154
|
+
stringValue(value.fileName) ??
|
|
155
|
+
stringValue(value.name),
|
|
156
|
+
contentType:
|
|
157
|
+
stringValue(value.content_type) ??
|
|
158
|
+
stringValue(value.contentType) ??
|
|
159
|
+
stringValue(value.mime_type) ??
|
|
160
|
+
stringValue(value.mimeType),
|
|
161
|
+
sizeBytes:
|
|
162
|
+
integerValue(value.size_bytes) ??
|
|
163
|
+
integerValue(value.sizeBytes) ??
|
|
164
|
+
integerValue(value.size),
|
|
165
|
+
url: stringValue(value.url),
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
120
171
|
export function isCodexAuthoredEvent(
|
|
121
172
|
event: Pick<
|
|
122
173
|
KandanChatEvent,
|
|
@@ -206,10 +257,12 @@ export function localRunnerPayload(
|
|
|
206
257
|
codexThreadId: string,
|
|
207
258
|
context: RunnerPayloadContext,
|
|
208
259
|
sourceMessageSeq?: number | undefined,
|
|
260
|
+
extraLocalRunnerMetadata?: JsonObject | undefined,
|
|
209
261
|
): JsonObject {
|
|
210
262
|
return {
|
|
211
263
|
metadata: {
|
|
212
264
|
local_codex_runner: {
|
|
265
|
+
...(extraLocalRunnerMetadata ?? {}),
|
|
213
266
|
runner_id: options.runnerId,
|
|
214
267
|
instance_id: instanceId,
|
|
215
268
|
event_type: eventType,
|
package/src/forwardTunnel.ts
CHANGED
|
@@ -394,14 +394,15 @@ function openWebSocketStream(
|
|
|
394
394
|
|
|
395
395
|
let opened = false;
|
|
396
396
|
const pendingMessages: WebSocketPendingMessage[] = [];
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
request.queryString,
|
|
403
|
-
),
|
|
397
|
+
const url = localForwardWebSocketUrl(
|
|
398
|
+
scheme,
|
|
399
|
+
request.port,
|
|
400
|
+
request.path,
|
|
401
|
+
request.queryString,
|
|
404
402
|
);
|
|
403
|
+
const protocols = webSocketProtocols(request.headers);
|
|
404
|
+
const socket =
|
|
405
|
+
protocols === undefined ? new WebSocket(url) : new WebSocket(url, protocols);
|
|
405
406
|
streams.set(streamId, { kind: "websocket", socket, pendingMessages });
|
|
406
407
|
socket.addEventListener("open", () => {
|
|
407
408
|
opened = true;
|
|
@@ -425,6 +426,11 @@ function openWebSocketStream(
|
|
|
425
426
|
});
|
|
426
427
|
});
|
|
427
428
|
socket.addEventListener("close", (event) => {
|
|
429
|
+
const stream = streams.get(streamId);
|
|
430
|
+
if (stream?.kind !== "websocket" || stream.socket !== socket) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
428
434
|
streams.delete(streamId);
|
|
429
435
|
send({
|
|
430
436
|
type: "websocket_close",
|
|
@@ -433,6 +439,11 @@ function openWebSocketStream(
|
|
|
433
439
|
});
|
|
434
440
|
});
|
|
435
441
|
socket.addEventListener("error", () => {
|
|
442
|
+
const stream = streams.get(streamId);
|
|
443
|
+
if (stream?.kind !== "websocket" || stream.socket !== socket) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
436
447
|
streams.delete(streamId);
|
|
437
448
|
if (!opened && scheme === "ws") {
|
|
438
449
|
openWebSocketStream(
|
|
@@ -648,6 +659,20 @@ function requestHeaders(headers: JsonValue | undefined): Headers {
|
|
|
648
659
|
return result;
|
|
649
660
|
}
|
|
650
661
|
|
|
662
|
+
function webSocketProtocols(headers: Headers): string[] | undefined {
|
|
663
|
+
const protocolHeader = headers.get("sec-websocket-protocol");
|
|
664
|
+
if (protocolHeader === null) {
|
|
665
|
+
return undefined;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const protocols = protocolHeader
|
|
669
|
+
.split(",")
|
|
670
|
+
.map((protocol) => protocol.trim())
|
|
671
|
+
.filter((protocol) => protocol !== "");
|
|
672
|
+
|
|
673
|
+
return protocols.length === 0 ? undefined : protocols;
|
|
674
|
+
}
|
|
675
|
+
|
|
651
676
|
function responseHeaders(headers: Headers): JsonObject[] {
|
|
652
677
|
return Array.from(headers.entries())
|
|
653
678
|
.filter(([name]) => !blockedFetchResponseHeaderNames.has(name.toLowerCase()))
|