@linzumi/cli 0.0.43-beta → 0.0.45-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 +1 -1
- package/dist/index.js +761 -113
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
3
|
-
import { existsSync as existsSync11, readFileSync as
|
|
3
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11, realpathSync as realpathSync6 } from "node:fs";
|
|
4
4
|
import { homedir as homedir9 } from "node:os";
|
|
5
5
|
import { resolve as resolve9 } from "node:path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -2671,6 +2671,7 @@ function initialChannelSessionState(cursor, rootSeq, kandanThreadId, codexThread
|
|
|
2671
2671
|
pendingApprovalRequests: new Map,
|
|
2672
2672
|
approvalPromptChain: Promise.resolve(),
|
|
2673
2673
|
pendingPortForwardRequests: new Map,
|
|
2674
|
+
portForwardPreviousProcessingStates: new Map,
|
|
2674
2675
|
queuedPortForwardCandidates: new Map,
|
|
2675
2676
|
approvedForwardPorts: new Set,
|
|
2676
2677
|
approvedForwardTargets: new Map,
|
|
@@ -2995,6 +2996,9 @@ async function resolvePendingPortForwardRequest(args, state, payloadContext, con
|
|
|
2995
2996
|
state.pendingPortForwardRequests.delete(control.requestId);
|
|
2996
2997
|
if (control.decision === "deny") {
|
|
2997
2998
|
state.dismissedForwardTargets.set(request.port, approvedTargetFromRequest(request));
|
|
2999
|
+
await publishForwardPortResolvedEvent(args, request, {
|
|
3000
|
+
decision: "deny"
|
|
3001
|
+
});
|
|
2998
3002
|
await publishMessageStateForPortForwardResult(args, state, request, "failed");
|
|
2999
3003
|
args.log("port_forward.request_denied", {
|
|
3000
3004
|
request_id: control.requestId,
|
|
@@ -3014,7 +3018,10 @@ async function resolvePendingPortForwardRequest(args, state, payloadContext, con
|
|
|
3014
3018
|
processName: processIdentity?.appName ?? null,
|
|
3015
3019
|
processIconKey: processIdentity?.iconKey ?? null
|
|
3016
3020
|
});
|
|
3017
|
-
await publishForwardPortResolvedEvent(args, request,
|
|
3021
|
+
await publishForwardPortResolvedEvent(args, request, {
|
|
3022
|
+
decision: "approve",
|
|
3023
|
+
capabilities
|
|
3024
|
+
});
|
|
3018
3025
|
await publishMessageStateForPortForwardResult(args, state, request, "processed");
|
|
3019
3026
|
await publishPortForwardReadyMessage(args, state, payloadContext, request);
|
|
3020
3027
|
args.log("port_forward.request_approved", {
|
|
@@ -3099,12 +3106,14 @@ async function publishPortForwardPrompt(args, state, payloadContext, candidate)
|
|
|
3099
3106
|
candidate
|
|
3100
3107
|
});
|
|
3101
3108
|
state.pendingPortForwardRequests.set(requestId, request);
|
|
3109
|
+
if (state.activeProcessingState !== undefined) {
|
|
3110
|
+
state.portForwardPreviousProcessingStates.set(requestId, state.activeProcessingState);
|
|
3111
|
+
}
|
|
3102
3112
|
const processIdentity = guessCanonicalProcessFromCommand(candidate.command);
|
|
3103
3113
|
const processIconPath = processIdentity?.iconKey === undefined ? undefined : `/web/process-icons/${processIdentity.iconKey}.png`;
|
|
3104
3114
|
const processName = processIdentity?.appName ?? label;
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
status: "processing",
|
|
3115
|
+
state.activeProcessingState = {
|
|
3116
|
+
seq: sourceSeq,
|
|
3108
3117
|
reason: "awaiting approval",
|
|
3109
3118
|
approval: {
|
|
3110
3119
|
requestId,
|
|
@@ -3133,7 +3142,13 @@ async function publishPortForwardPrompt(args, state, payloadContext, candidate)
|
|
|
3133
3142
|
allowedActorSlug: args.options.channelSession.listenUser,
|
|
3134
3143
|
allowedActorUserId: args.options.channelSession.listenUser.toLowerCase() === (payloadContext.runnerIdentity.actorUsername ?? "").toLowerCase() ? payloadContext.runnerIdentity.actorUserId : undefined
|
|
3135
3144
|
}
|
|
3145
|
+
};
|
|
3146
|
+
await publishMessageState(args, state.kandanThreadId, sourceSeq, {
|
|
3147
|
+
status: "processing",
|
|
3148
|
+
reason: "awaiting approval",
|
|
3149
|
+
approval: state.activeProcessingState.approval
|
|
3136
3150
|
});
|
|
3151
|
+
await publishForwardPortRequestedEvent(args, state, request, processIdentity);
|
|
3137
3152
|
args.log("port_forward.request_pending", {
|
|
3138
3153
|
request_id: requestId,
|
|
3139
3154
|
port: candidate.port,
|
|
@@ -3174,6 +3189,10 @@ async function expireLostPendingPortForwardRequest(args, state, payloadContext,
|
|
|
3174
3189
|
return;
|
|
3175
3190
|
}
|
|
3176
3191
|
state.pendingPortForwardRequests.delete(request.requestId);
|
|
3192
|
+
await publishForwardPortResolvedEvent(args, request, {
|
|
3193
|
+
decision: "expired",
|
|
3194
|
+
reason: "listener_exited"
|
|
3195
|
+
});
|
|
3177
3196
|
await publishMessageStateForPortForwardResult(args, state, request, "failed", "port_forward_listener_exited");
|
|
3178
3197
|
args.log("port_forward.pending_request_expired", {
|
|
3179
3198
|
request_id: request.requestId,
|
|
@@ -3220,17 +3239,23 @@ async function publishPortForwardReadyMessage(args, state, payloadContext, reque
|
|
|
3220
3239
|
}
|
|
3221
3240
|
}, args.log);
|
|
3222
3241
|
}
|
|
3223
|
-
async function publishForwardPortRequestedEvent(args, request) {
|
|
3242
|
+
async function publishForwardPortRequestedEvent(args, state, request, processIdentity) {
|
|
3224
3243
|
await pushOptional(args.kandan, args.topic, "forward_port_requested", {
|
|
3225
3244
|
instanceId: args.instanceId,
|
|
3226
3245
|
requestId: request.requestId,
|
|
3246
|
+
sourceSeq: request.sourceSeq,
|
|
3227
3247
|
port: request.port,
|
|
3228
3248
|
pid: request.pid,
|
|
3229
3249
|
command: request.command,
|
|
3250
|
+
codexThreadId: state.codexThreadId ?? null,
|
|
3251
|
+
kandanThreadId: state.kandanThreadId ?? null,
|
|
3252
|
+
channelSlug: args.options.channelSession.channelSlug ?? null,
|
|
3253
|
+
...processIdentity?.appName === undefined ? {} : { processName: processIdentity.appName },
|
|
3254
|
+
...processIdentity?.iconKey === undefined ? {} : { processIconKey: processIdentity.iconKey },
|
|
3230
3255
|
...request.cwd === undefined ? {} : { cwd: request.cwd }
|
|
3231
3256
|
}, args.log);
|
|
3232
3257
|
}
|
|
3233
|
-
async function publishForwardPortResolvedEvent(args, request,
|
|
3258
|
+
async function publishForwardPortResolvedEvent(args, request, result) {
|
|
3234
3259
|
await pushOptional(args.kandan, args.topic, "forward_port_resolved", {
|
|
3235
3260
|
instanceId: args.instanceId,
|
|
3236
3261
|
requestId: request.requestId,
|
|
@@ -3238,18 +3263,52 @@ async function publishForwardPortResolvedEvent(args, request, capabilities) {
|
|
|
3238
3263
|
pid: request.pid,
|
|
3239
3264
|
command: request.command,
|
|
3240
3265
|
...request.cwd === undefined ? {} : { cwd: request.cwd },
|
|
3241
|
-
|
|
3242
|
-
|
|
3266
|
+
sourceSeq: request.sourceSeq,
|
|
3267
|
+
decision: result.decision,
|
|
3268
|
+
...result.reason === undefined ? {} : { reason: result.reason },
|
|
3269
|
+
...result.capabilities === undefined ? {} : { capabilities: result.capabilities }
|
|
3243
3270
|
}, args.log);
|
|
3244
3271
|
}
|
|
3245
3272
|
async function publishMessageStateForPortForwardResult(args, state, request, status, failedReason = "port_forward_denied") {
|
|
3246
3273
|
if (state.kandanThreadId === undefined) {
|
|
3247
3274
|
return;
|
|
3248
3275
|
}
|
|
3276
|
+
const previousProcessingState = state.portForwardPreviousProcessingStates.get(request.requestId);
|
|
3277
|
+
state.portForwardPreviousProcessingStates.delete(request.requestId);
|
|
3249
3278
|
const activeProcessingState = state.activeProcessingState;
|
|
3250
3279
|
if (activeProcessingState !== undefined && activeProcessingState.seq === request.sourceSeq) {
|
|
3251
|
-
|
|
3252
|
-
|
|
3280
|
+
const resolvingActiveApproval = activeProcessingState.reason === "awaiting approval" && activeProcessingState.approval.requestId === request.requestId;
|
|
3281
|
+
if (resolvingActiveApproval) {
|
|
3282
|
+
state.activeProcessingState = undefined;
|
|
3283
|
+
if (previousProcessingState !== undefined && previousProcessingState.seq === request.sourceSeq) {
|
|
3284
|
+
state.activeProcessingState = previousProcessingState;
|
|
3285
|
+
await publishMessageState(args, state.kandanThreadId, request.sourceSeq, processingMessageStateFromActive(previousProcessingState), undefined, undefined, state.codexThreadId);
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
switch (state.turn.status) {
|
|
3289
|
+
case "active":
|
|
3290
|
+
case "completing":
|
|
3291
|
+
if (state.turn.queuedSeq === request.sourceSeq) {
|
|
3292
|
+
state.activeProcessingState = {
|
|
3293
|
+
seq: request.sourceSeq,
|
|
3294
|
+
reason: "running terminal command"
|
|
3295
|
+
};
|
|
3296
|
+
await publishMessageState(args, state.kandanThreadId, request.sourceSeq, {
|
|
3297
|
+
status: "processing",
|
|
3298
|
+
reason: "running terminal command"
|
|
3299
|
+
});
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
break;
|
|
3303
|
+
case "idle":
|
|
3304
|
+
case "starting":
|
|
3305
|
+
break;
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
if (!resolvingActiveApproval) {
|
|
3309
|
+
await publishMessageState(args, state.kandanThreadId, request.sourceSeq, processingMessageStateFromActive(activeProcessingState), undefined, undefined, state.codexThreadId);
|
|
3310
|
+
return;
|
|
3311
|
+
}
|
|
3253
3312
|
}
|
|
3254
3313
|
switch (status) {
|
|
3255
3314
|
case "processed":
|
|
@@ -3419,7 +3478,7 @@ function kandanMessageClaimKey(args, threadId, seq) {
|
|
|
3419
3478
|
function claimKandanMessage(args, state, event) {
|
|
3420
3479
|
const threadId = event.threadId;
|
|
3421
3480
|
if (threadId === undefined) {
|
|
3422
|
-
return
|
|
3481
|
+
return true;
|
|
3423
3482
|
}
|
|
3424
3483
|
const key = kandanMessageClaimKey(args, threadId, event.seq);
|
|
3425
3484
|
if (claimedKandanMessageKeys.has(key)) {
|
|
@@ -3428,8 +3487,9 @@ function claimKandanMessage(args, state, event) {
|
|
|
3428
3487
|
claimedKandanMessageKeys.add(key);
|
|
3429
3488
|
state.claimedKandanMessageKeys.add(key);
|
|
3430
3489
|
if (state.claimedKandanMessageKeys.size > maxClaimedKandanMessageKeys) {
|
|
3431
|
-
const
|
|
3432
|
-
|
|
3490
|
+
const overflow = state.claimedKandanMessageKeys.size - maxClaimedKandanMessageKeys;
|
|
3491
|
+
const expiredKeys = Array.from(state.claimedKandanMessageKeys).slice(0, overflow);
|
|
3492
|
+
for (const expiredKey of expiredKeys) {
|
|
3433
3493
|
state.claimedKandanMessageKeys.delete(expiredKey);
|
|
3434
3494
|
claimedKandanMessageKeys.delete(expiredKey);
|
|
3435
3495
|
}
|
|
@@ -3604,7 +3664,16 @@ async function drainKandanMessageQueue(args, state, payloadContext) {
|
|
|
3604
3664
|
new_codex_thread_id: newCodexThreadId
|
|
3605
3665
|
});
|
|
3606
3666
|
await postCodexThreadReboundMessage(args, state, payloadContext, oldCodexThreadId, newCodexThreadId);
|
|
3607
|
-
|
|
3667
|
+
try {
|
|
3668
|
+
state.pendingReconnectContextInjection = await fetchReconnectContextInjection(args, state);
|
|
3669
|
+
} catch (contextError) {
|
|
3670
|
+
state.pendingReconnectContextInjection = undefined;
|
|
3671
|
+
args.log("codex.thread_reconnect_context_failed", {
|
|
3672
|
+
kandan_thread_id: state.kandanThreadId,
|
|
3673
|
+
codex_thread_id: newCodexThreadId,
|
|
3674
|
+
message: contextError instanceof Error ? contextError.message : String(contextError)
|
|
3675
|
+
});
|
|
3676
|
+
}
|
|
3608
3677
|
requeuePendingKandanMessageFront(state.queue, next);
|
|
3609
3678
|
state.turn = { status: "idle" };
|
|
3610
3679
|
await drainKandanMessageQueue(args, state, payloadContext);
|
|
@@ -4963,6 +5032,9 @@ async function refreshActiveProcessingState(args, state, turnId, reason) {
|
|
|
4963
5032
|
if (state.activeProcessingState?.seq === seq && state.activeProcessingState.reason === reason) {
|
|
4964
5033
|
return;
|
|
4965
5034
|
}
|
|
5035
|
+
if (state.activeProcessingState?.seq === seq && state.activeProcessingState.reason === "awaiting approval") {
|
|
5036
|
+
return;
|
|
5037
|
+
}
|
|
4966
5038
|
state.activeProcessingState = { seq, reason };
|
|
4967
5039
|
await publishMessageState(args, state.kandanThreadId, seq, {
|
|
4968
5040
|
status: "processing",
|
|
@@ -4974,6 +5046,14 @@ async function refreshActiveProcessingHeartbeat(args, state) {
|
|
|
4974
5046
|
if (activeProcessingState === undefined || state.kandanThreadId === undefined) {
|
|
4975
5047
|
return;
|
|
4976
5048
|
}
|
|
5049
|
+
if (activeProcessingState.reason === "awaiting approval") {
|
|
5050
|
+
switch (activeProcessingState.approval.kind) {
|
|
5051
|
+
case "local_runner_port_forward":
|
|
5052
|
+
return;
|
|
5053
|
+
default:
|
|
5054
|
+
break;
|
|
5055
|
+
}
|
|
5056
|
+
}
|
|
4977
5057
|
await publishMessageState(args, state.kandanThreadId, activeProcessingState.seq, processingMessageStateFromActive(activeProcessingState), undefined, undefined, state.codexThreadId);
|
|
4978
5058
|
}
|
|
4979
5059
|
function clearActiveProcessingState(state, seq) {
|
|
@@ -5936,6 +6016,7 @@ function pathLooksAbsolute(pathValue) {
|
|
|
5936
6016
|
|
|
5937
6017
|
// src/localForwarding.ts
|
|
5938
6018
|
import { gzipSync } from "node:zlib";
|
|
6019
|
+
import NodeWebSocket from "ws";
|
|
5939
6020
|
var maxForwardBodyBytes = 64 * 1024 * 1024;
|
|
5940
6021
|
var gzipForwardThresholdBytes = 32 * 1024;
|
|
5941
6022
|
async function handleForwardHttpRequest(control, allowedPorts) {
|
|
@@ -5979,15 +6060,15 @@ function isForwardHttpRequestControl(control) {
|
|
|
5979
6060
|
function isForwardWebSocketControl(control) {
|
|
5980
6061
|
return control.type === "forward_websocket_open" || control.type === "forward_websocket_send" || control.type === "forward_websocket_close";
|
|
5981
6062
|
}
|
|
5982
|
-
function createForwardWebSocketManager(kandan, topic, allowedPorts) {
|
|
6063
|
+
function createForwardWebSocketManager(kandan, topic, allowedPorts, socketFactory = defaultForwardWebSocketFactory) {
|
|
5983
6064
|
const sockets = new Map;
|
|
5984
6065
|
const pushEvent = (payload) => kandan.push(topic, "forward:websocket_event", payload).catch(() => {
|
|
5985
6066
|
return;
|
|
5986
6067
|
});
|
|
5987
6068
|
const closeSocket = (socketId) => {
|
|
5988
|
-
const
|
|
6069
|
+
const stream = sockets.get(socketId);
|
|
5989
6070
|
sockets.delete(socketId);
|
|
5990
|
-
socket
|
|
6071
|
+
stream?.socket.close();
|
|
5991
6072
|
};
|
|
5992
6073
|
return {
|
|
5993
6074
|
handle: (control) => {
|
|
@@ -6001,12 +6082,12 @@ function createForwardWebSocketManager(kandan, topic, allowedPorts) {
|
|
|
6001
6082
|
});
|
|
6002
6083
|
return;
|
|
6003
6084
|
}
|
|
6004
|
-
openLocalWebSocket(control, sockets, pushEvent, "ws");
|
|
6085
|
+
openLocalWebSocket(control, sockets, pushEvent, socketFactory, "ws");
|
|
6005
6086
|
return;
|
|
6006
6087
|
}
|
|
6007
6088
|
case "forward_websocket_send": {
|
|
6008
|
-
const
|
|
6009
|
-
if (
|
|
6089
|
+
const stream = sockets.get(control.socketId);
|
|
6090
|
+
if (stream === undefined || stream.socket.readyState !== WebSocket.OPEN) {
|
|
6010
6091
|
pushEvent({
|
|
6011
6092
|
socketId: control.socketId,
|
|
6012
6093
|
type: "error",
|
|
@@ -6017,12 +6098,12 @@ function createForwardWebSocketManager(kandan, topic, allowedPorts) {
|
|
|
6017
6098
|
const body = Buffer.from(control.bodyBase64, "base64");
|
|
6018
6099
|
switch (control.opcode) {
|
|
6019
6100
|
case "text":
|
|
6020
|
-
socket.send(body.toString());
|
|
6101
|
+
stream.socket.send(body.toString());
|
|
6021
6102
|
return;
|
|
6022
6103
|
case "binary":
|
|
6023
6104
|
case "ping":
|
|
6024
6105
|
case "pong":
|
|
6025
|
-
socket.send(body);
|
|
6106
|
+
stream.socket.send(body);
|
|
6026
6107
|
return;
|
|
6027
6108
|
}
|
|
6028
6109
|
}
|
|
@@ -6038,18 +6119,27 @@ function createForwardWebSocketManager(kandan, topic, allowedPorts) {
|
|
|
6038
6119
|
}
|
|
6039
6120
|
};
|
|
6040
6121
|
}
|
|
6041
|
-
function openLocalWebSocket(control, sockets, pushEvent, scheme) {
|
|
6122
|
+
function openLocalWebSocket(control, sockets, pushEvent, socketFactory, scheme) {
|
|
6042
6123
|
let opened = false;
|
|
6043
|
-
const url =
|
|
6124
|
+
const url = localForwardWebSocketUrl(scheme, control.port, control.path, control.queryString);
|
|
6044
6125
|
const protocols = webSocketProtocols(control.headers);
|
|
6045
|
-
const
|
|
6126
|
+
const headers = webSocketHeaders(control.headers);
|
|
6127
|
+
const websocket = socketFactory(url, protocols, headers);
|
|
6046
6128
|
websocket.binaryType = "arraybuffer";
|
|
6047
|
-
sockets.
|
|
6129
|
+
const previousStream = sockets.get(control.socketId);
|
|
6130
|
+
previousStream?.socket.close();
|
|
6131
|
+
sockets.set(control.socketId, { socket: websocket });
|
|
6048
6132
|
websocket.addEventListener("open", () => {
|
|
6133
|
+
if (!currentWebSocket(sockets, control.socketId, websocket)) {
|
|
6134
|
+
return;
|
|
6135
|
+
}
|
|
6049
6136
|
opened = true;
|
|
6050
6137
|
pushEvent({ socketId: control.socketId, type: "open" });
|
|
6051
6138
|
});
|
|
6052
6139
|
websocket.addEventListener("message", (event) => {
|
|
6140
|
+
if (!currentWebSocket(sockets, control.socketId, websocket)) {
|
|
6141
|
+
return;
|
|
6142
|
+
}
|
|
6053
6143
|
const body = typeof event.data === "string" ? Buffer.from(event.data) : Buffer.from(event.data);
|
|
6054
6144
|
pushEvent({
|
|
6055
6145
|
socketId: control.socketId,
|
|
@@ -6059,6 +6149,9 @@ function openLocalWebSocket(control, sockets, pushEvent, scheme) {
|
|
|
6059
6149
|
});
|
|
6060
6150
|
});
|
|
6061
6151
|
websocket.addEventListener("close", (event) => {
|
|
6152
|
+
if (!currentWebSocket(sockets, control.socketId, websocket)) {
|
|
6153
|
+
return;
|
|
6154
|
+
}
|
|
6062
6155
|
sockets.delete(control.socketId);
|
|
6063
6156
|
pushEvent({
|
|
6064
6157
|
socketId: control.socketId,
|
|
@@ -6068,18 +6161,32 @@ function openLocalWebSocket(control, sockets, pushEvent, scheme) {
|
|
|
6068
6161
|
});
|
|
6069
6162
|
});
|
|
6070
6163
|
websocket.addEventListener("error", () => {
|
|
6164
|
+
if (!currentWebSocket(sockets, control.socketId, websocket)) {
|
|
6165
|
+
return;
|
|
6166
|
+
}
|
|
6071
6167
|
sockets.delete(control.socketId);
|
|
6072
6168
|
if (!opened && scheme === "ws") {
|
|
6073
|
-
openLocalWebSocket(control, sockets, pushEvent, "wss");
|
|
6169
|
+
openLocalWebSocket(control, sockets, pushEvent, socketFactory, "wss");
|
|
6074
6170
|
return;
|
|
6075
6171
|
}
|
|
6076
6172
|
pushEvent({
|
|
6077
6173
|
socketId: control.socketId,
|
|
6078
6174
|
type: "error",
|
|
6079
|
-
error: "websocket_error"
|
|
6175
|
+
error: "websocket_error",
|
|
6176
|
+
attemptedScheme: scheme
|
|
6080
6177
|
});
|
|
6081
6178
|
});
|
|
6082
6179
|
}
|
|
6180
|
+
function defaultForwardWebSocketFactory(url, protocols, headers) {
|
|
6181
|
+
if (headers === undefined) {
|
|
6182
|
+
return protocols === undefined ? new WebSocket(url) : new WebSocket(url, protocols);
|
|
6183
|
+
}
|
|
6184
|
+
const options = { headers };
|
|
6185
|
+
return protocols === undefined ? new NodeWebSocket(url, options) : new NodeWebSocket(url, protocols, options);
|
|
6186
|
+
}
|
|
6187
|
+
function currentWebSocket(sockets, socketId, socket) {
|
|
6188
|
+
return sockets.get(socketId)?.socket === socket;
|
|
6189
|
+
}
|
|
6083
6190
|
function webSocketProtocols(headers) {
|
|
6084
6191
|
if (!Array.isArray(headers)) {
|
|
6085
6192
|
return;
|
|
@@ -6092,6 +6199,21 @@ function webSocketProtocols(headers) {
|
|
|
6092
6199
|
});
|
|
6093
6200
|
return protocols.length === 0 ? undefined : protocols;
|
|
6094
6201
|
}
|
|
6202
|
+
function webSocketHeaders(headers) {
|
|
6203
|
+
if (!Array.isArray(headers)) {
|
|
6204
|
+
return;
|
|
6205
|
+
}
|
|
6206
|
+
const forwarded = headers.reduce((acc, header) => {
|
|
6207
|
+
if (isOctWebSocketHeader(header)) {
|
|
6208
|
+
acc[header.name] = header.value;
|
|
6209
|
+
}
|
|
6210
|
+
return acc;
|
|
6211
|
+
}, {});
|
|
6212
|
+
return Object.keys(forwarded).length === 0 ? undefined : forwarded;
|
|
6213
|
+
}
|
|
6214
|
+
function isOctWebSocketHeader(value) {
|
|
6215
|
+
return isHeader(value) && value.name.toLowerCase().startsWith("x-oct-");
|
|
6216
|
+
}
|
|
6095
6217
|
function localForwardUrl(scheme, port, path, queryString) {
|
|
6096
6218
|
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
6097
6219
|
const url = new URL(`${scheme}://127.0.0.1:${port}${normalizedPath}`);
|
|
@@ -6100,6 +6222,12 @@ function localForwardUrl(scheme, port, path, queryString) {
|
|
|
6100
6222
|
}
|
|
6101
6223
|
return url.toString();
|
|
6102
6224
|
}
|
|
6225
|
+
function localForwardWebSocketUrl(scheme, port, path, queryString) {
|
|
6226
|
+
const httpScheme = scheme === "ws" ? "http" : "https";
|
|
6227
|
+
const url = new URL(localForwardUrl(httpScheme, port, path, queryString));
|
|
6228
|
+
url.protocol = `${scheme}:`;
|
|
6229
|
+
return url.toString();
|
|
6230
|
+
}
|
|
6103
6231
|
async function fetchWithHttpsFallback(port, path, queryString, request) {
|
|
6104
6232
|
try {
|
|
6105
6233
|
return await fetch(localForwardUrl("http", port, path, queryString), request);
|
|
@@ -6212,8 +6340,10 @@ var blockedForwardHeaderNames = new Set([
|
|
|
6212
6340
|
"connection",
|
|
6213
6341
|
"content-encoding",
|
|
6214
6342
|
"content-length",
|
|
6343
|
+
"cookie",
|
|
6215
6344
|
"host",
|
|
6216
6345
|
"keep-alive",
|
|
6346
|
+
"authorization",
|
|
6217
6347
|
"proxy-authenticate",
|
|
6218
6348
|
"proxy-authorization",
|
|
6219
6349
|
"te",
|
|
@@ -6225,10 +6355,12 @@ var blockedForwardHeaderNames = new Set([
|
|
|
6225
6355
|
// src/localEditor.ts
|
|
6226
6356
|
import { spawn as spawn2 } from "node:child_process";
|
|
6227
6357
|
import {
|
|
6358
|
+
copyFileSync,
|
|
6228
6359
|
cpSync,
|
|
6229
6360
|
existsSync as existsSync2,
|
|
6230
6361
|
mkdirSync as mkdirSync3,
|
|
6231
6362
|
mkdtempSync,
|
|
6363
|
+
readFileSync as readFileSync2,
|
|
6232
6364
|
realpathSync as realpathSync3,
|
|
6233
6365
|
writeFileSync as writeFileSync2
|
|
6234
6366
|
} from "node:fs";
|
|
@@ -6430,6 +6562,7 @@ function prepareCodeServerProfile(collaboration, editorRuntime) {
|
|
|
6430
6562
|
mkdirSync3(collaborationServerDir, { recursive: true });
|
|
6431
6563
|
mkdirSync3(tempDir, { recursive: true });
|
|
6432
6564
|
if (editorRuntime !== undefined) {
|
|
6565
|
+
ensureCodeServerBrowserExtensionAssets(editorRuntime);
|
|
6433
6566
|
installDirectory(editorRuntime.assets.documentStateExtensionDir, join4(extensionsDir, "kandan.document-state-telemetry"));
|
|
6434
6567
|
}
|
|
6435
6568
|
writeFileSync2(join4(userSettingsDir, "settings.json"), JSON.stringify(codeServerSettings(collaboration), null, 2));
|
|
@@ -6438,6 +6571,68 @@ function prepareCodeServerProfile(collaboration, editorRuntime) {
|
|
|
6438
6571
|
return { ok: false, reason: "code_server_spawn_failed" };
|
|
6439
6572
|
}
|
|
6440
6573
|
}
|
|
6574
|
+
function ensureCodeServerBrowserExtensionAssets(runtime) {
|
|
6575
|
+
const vscodeRoot = codeServerVscodeRoot(runtime);
|
|
6576
|
+
const repairs = [
|
|
6577
|
+
{
|
|
6578
|
+
source: join4(vscodeRoot, "extensions", "git-base", "dist", "extension.js"),
|
|
6579
|
+
target: join4(vscodeRoot, "extensions", "git-base", "dist", "browser", "extension.js"),
|
|
6580
|
+
required: true
|
|
6581
|
+
},
|
|
6582
|
+
{
|
|
6583
|
+
source: join4(vscodeRoot, "extensions", "git-base", "dist", "extension.js.map"),
|
|
6584
|
+
target: join4(vscodeRoot, "extensions", "git-base", "dist", "browser", "extension.js.map"),
|
|
6585
|
+
required: false
|
|
6586
|
+
},
|
|
6587
|
+
{
|
|
6588
|
+
source: join4(vscodeRoot, "extensions", "merge-conflict", "dist", "mergeConflictMain.js"),
|
|
6589
|
+
target: join4(vscodeRoot, "extensions", "merge-conflict", "dist", "browser", "mergeConflictMain.js"),
|
|
6590
|
+
required: true
|
|
6591
|
+
},
|
|
6592
|
+
{
|
|
6593
|
+
source: join4(vscodeRoot, "extensions", "merge-conflict", "dist", "mergeConflictMain.js.map"),
|
|
6594
|
+
target: join4(vscodeRoot, "extensions", "merge-conflict", "dist", "browser", "mergeConflictMain.js.map"),
|
|
6595
|
+
required: false
|
|
6596
|
+
}
|
|
6597
|
+
];
|
|
6598
|
+
repairs.forEach(({ source, target, required }) => {
|
|
6599
|
+
switch (true) {
|
|
6600
|
+
case existsSync2(target):
|
|
6601
|
+
return;
|
|
6602
|
+
case (!required && !existsSync2(source)):
|
|
6603
|
+
return;
|
|
6604
|
+
default:
|
|
6605
|
+
mkdirSync3(dirname2(target), { recursive: true });
|
|
6606
|
+
copyFileSync(source, target);
|
|
6607
|
+
return;
|
|
6608
|
+
}
|
|
6609
|
+
});
|
|
6610
|
+
}
|
|
6611
|
+
function codeServerVscodeRoot(runtime) {
|
|
6612
|
+
const roots = uniquePaths([
|
|
6613
|
+
join4(runtime.root, "lib", "vscode"),
|
|
6614
|
+
join4(dirname2(dirname2(runtime.codeServerBin)), "lib", "vscode"),
|
|
6615
|
+
...wrappedCodeServerBin(runtime.codeServerBin).map((codeServerBin) => join4(dirname2(dirname2(codeServerBin)), "lib", "vscode"))
|
|
6616
|
+
]);
|
|
6617
|
+
const rootWithRequiredAssets = roots.find((root) => codeServerBrowserAssetSources(root).every((source) => existsSync2(source)));
|
|
6618
|
+
return rootWithRequiredAssets ?? roots[0];
|
|
6619
|
+
}
|
|
6620
|
+
function wrappedCodeServerBin(codeServerBin) {
|
|
6621
|
+
try {
|
|
6622
|
+
const script = readFileSync2(codeServerBin, "utf8");
|
|
6623
|
+
const match = script.match(/exec\s+(?:"([^"]+)"|'([^']+)'|([^\s]+))\s+"\$@"/u);
|
|
6624
|
+
const target = match?.[1] ?? match?.[2] ?? match?.[3];
|
|
6625
|
+
return target === undefined || target.trim() === "" ? [] : [target];
|
|
6626
|
+
} catch (_error) {
|
|
6627
|
+
return [];
|
|
6628
|
+
}
|
|
6629
|
+
}
|
|
6630
|
+
function codeServerBrowserAssetSources(vscodeRoot) {
|
|
6631
|
+
return [
|
|
6632
|
+
join4(vscodeRoot, "extensions", "git-base", "dist", "extension.js"),
|
|
6633
|
+
join4(vscodeRoot, "extensions", "merge-conflict", "dist", "mergeConflictMain.js")
|
|
6634
|
+
];
|
|
6635
|
+
}
|
|
6441
6636
|
function prepareCodeServerLaunch(options) {
|
|
6442
6637
|
const platform = options.platform ?? process.platform;
|
|
6443
6638
|
if (platform === "linux") {
|
|
@@ -6570,12 +6765,16 @@ function prepareLocalEditorCollaboration(collaboration, runnerId, serverPort, br
|
|
|
6570
6765
|
}
|
|
6571
6766
|
const targetPath = `/local-codex-runners/${encodeURIComponent(runnerId)}/forwards/${serverPort}/preview-target`;
|
|
6572
6767
|
const serverUrl = new URL(targetPath, `${browserBaseUrl}/`).toString();
|
|
6573
|
-
const
|
|
6768
|
+
const collaborationStatePath = `/api/v2/editor/sessions/${collaboration.editorSessionId}/collaboration-state`;
|
|
6769
|
+
const collaborationBootstrapPath = collaboration.bootstrapToken === undefined || collaboration.bootstrapToken === "" ? undefined : `${targetPath}/_kandan-collaboration/${encodeURIComponent(collaboration.bootstrapToken)}`;
|
|
6770
|
+
const collaborationStateUrl = collaborationBootstrapPath === undefined ? collaborationStatePath : `${collaborationBootstrapPath}${collaborationStatePath}`;
|
|
6771
|
+
const bootstrapServerUrl = collaborationBootstrapPath === undefined ? serverUrl : new URL(collaborationBootstrapPath, `${browserBaseUrl}/`).toString();
|
|
6574
6772
|
return {
|
|
6575
6773
|
...collaboration,
|
|
6576
6774
|
serverPort,
|
|
6577
6775
|
serverUrl,
|
|
6578
|
-
bootstrapServerUrl
|
|
6776
|
+
bootstrapServerUrl,
|
|
6777
|
+
collaborationStateUrl
|
|
6579
6778
|
};
|
|
6580
6779
|
}
|
|
6581
6780
|
function codeServerSettings(collaboration) {
|
|
@@ -6588,6 +6787,7 @@ function codeServerSettings(collaboration) {
|
|
|
6588
6787
|
"oct.joinAcceptMode": "auto",
|
|
6589
6788
|
...collaboration === undefined ? {} : {
|
|
6590
6789
|
"oct.kandanEditorSessionId": collaboration.editorSessionId,
|
|
6790
|
+
"oct.kandanCollaborationStateUrl": collaboration.collaborationStateUrl,
|
|
6591
6791
|
"oct.serverUrl": collaboration.bootstrapServerUrl
|
|
6592
6792
|
},
|
|
6593
6793
|
"security.workspace.trust.enabled": false,
|
|
@@ -6666,6 +6866,7 @@ async function startCollaborationSidecar(collaboration, profile, editorRuntime,
|
|
|
6666
6866
|
serverPort: collaboration.serverPort,
|
|
6667
6867
|
serverUrl: collaboration.serverUrl,
|
|
6668
6868
|
bootstrapServerUrl: collaboration.bootstrapServerUrl,
|
|
6869
|
+
collaborationStateUrl: collaboration.collaborationStateUrl,
|
|
6669
6870
|
process: child,
|
|
6670
6871
|
exited
|
|
6671
6872
|
}
|
|
@@ -6906,7 +7107,7 @@ import {
|
|
|
6906
7107
|
existsSync as existsSync3,
|
|
6907
7108
|
mkdirSync as mkdirSync4,
|
|
6908
7109
|
mkdtempSync as mkdtempSync2,
|
|
6909
|
-
readFileSync as
|
|
7110
|
+
readFileSync as readFileSync3,
|
|
6910
7111
|
renameSync,
|
|
6911
7112
|
rmSync,
|
|
6912
7113
|
writeFileSync as writeFileSync3
|
|
@@ -7394,7 +7595,7 @@ function installedRuntime(cacheRoot, manifest) {
|
|
|
7394
7595
|
return { ok: false };
|
|
7395
7596
|
}
|
|
7396
7597
|
try {
|
|
7397
|
-
const installed = JSON.parse(
|
|
7598
|
+
const installed = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
7398
7599
|
if (isJsonObject(installed) && installed.version === manifest.version && installed.platform === manifest.platform && (installed.archiveSha256 === undefined || installed.archiveSha256 === manifest.archiveSha256)) {
|
|
7399
7600
|
return {
|
|
7400
7601
|
ok: true,
|
|
@@ -7625,7 +7826,7 @@ function manifestAssetChecksums(assets) {
|
|
|
7625
7826
|
return checksums;
|
|
7626
7827
|
}
|
|
7627
7828
|
function fileSha256Sync(path) {
|
|
7628
|
-
return createHash2("sha256").update(
|
|
7829
|
+
return createHash2("sha256").update(readFileSync3(path)).digest("hex");
|
|
7629
7830
|
}
|
|
7630
7831
|
function defaultEditorRuntimeCacheRoot() {
|
|
7631
7832
|
return join5(homedir4(), ".linzumi", "editor-runtimes");
|
|
@@ -7804,6 +8005,7 @@ function firstNonBlank(...values) {
|
|
|
7804
8005
|
}
|
|
7805
8006
|
|
|
7806
8007
|
// src/phoenix.ts
|
|
8008
|
+
var defaultPushTimeoutMs = 30000;
|
|
7807
8009
|
function phoenixWebsocketUrl(baseUrl, token) {
|
|
7808
8010
|
const parsed = new URL(baseUrl);
|
|
7809
8011
|
switch (parsed.protocol) {
|
|
@@ -7819,7 +8021,7 @@ function phoenixWebsocketUrl(baseUrl, token) {
|
|
|
7819
8021
|
parsed.searchParams.set("vsn", "2.0.0");
|
|
7820
8022
|
return parsed.toString();
|
|
7821
8023
|
}
|
|
7822
|
-
async function connectPhoenixClient(baseUrl, token, socketFactory = (url) => new WebSocket(url)) {
|
|
8024
|
+
async function connectPhoenixClient(baseUrl, token, socketFactory = (url) => new WebSocket(url), options = {}) {
|
|
7823
8025
|
const pending = new Map;
|
|
7824
8026
|
const joins = new Map;
|
|
7825
8027
|
const controlCallbacks = new Set;
|
|
@@ -7837,9 +8039,13 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url) => new
|
|
|
7837
8039
|
rejectReady: undefined,
|
|
7838
8040
|
reconnectTimer: undefined
|
|
7839
8041
|
};
|
|
8042
|
+
const pushTimeoutMs = options.pushTimeoutMs ?? defaultPushTimeoutMs;
|
|
7840
8043
|
const rejectPending = (message) => {
|
|
7841
8044
|
const error = new Error(message);
|
|
7842
|
-
pending.forEach((pendingPush) =>
|
|
8045
|
+
pending.forEach((pendingPush) => {
|
|
8046
|
+
clearTimeout(pendingPush.timer);
|
|
8047
|
+
pendingPush.reject(error);
|
|
8048
|
+
});
|
|
7843
8049
|
pending.clear();
|
|
7844
8050
|
};
|
|
7845
8051
|
const resetReady = () => {
|
|
@@ -7855,6 +8061,7 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url) => new
|
|
|
7855
8061
|
const pendingPush = pending.get(ref);
|
|
7856
8062
|
if (pendingPush !== undefined) {
|
|
7857
8063
|
pending.delete(ref);
|
|
8064
|
+
clearTimeout(pendingPush.timer);
|
|
7858
8065
|
if (name === "phx_error") {
|
|
7859
8066
|
pendingPush.reject(new Error("phoenix push failed"));
|
|
7860
8067
|
} else if (isNonOkPushReply(payload) && pendingPush.event !== "phx_join") {
|
|
@@ -7880,7 +8087,12 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url) => new
|
|
|
7880
8087
|
state.nextRef += 1;
|
|
7881
8088
|
const frame = [null, ref, topic, event, payload];
|
|
7882
8089
|
return new Promise((resolve5, reject) => {
|
|
7883
|
-
|
|
8090
|
+
const timer = setTimeout(() => {
|
|
8091
|
+
if (pending.delete(ref)) {
|
|
8092
|
+
reject(new Error(`phoenix push timed out after ${pushTimeoutMs}ms: ${event}`));
|
|
8093
|
+
}
|
|
8094
|
+
}, pushTimeoutMs);
|
|
8095
|
+
pending.set(ref, { event, timer, resolve: resolve5, reject });
|
|
7884
8096
|
websocket.send(JSON.stringify(frame));
|
|
7885
8097
|
});
|
|
7886
8098
|
};
|
|
@@ -7955,10 +8167,10 @@ async function connectPhoenixClient(baseUrl, token, socketFactory = (url) => new
|
|
|
7955
8167
|
return pushOnOpenSocket(topic, event, payload);
|
|
7956
8168
|
};
|
|
7957
8169
|
return {
|
|
7958
|
-
join: async (topic, payload,
|
|
8170
|
+
join: async (topic, payload, options2) => {
|
|
7959
8171
|
const reply = await push(topic, "phx_join", payload);
|
|
7960
8172
|
if (isJoinReply(reply)) {
|
|
7961
|
-
joins.set(topic, { payload:
|
|
8173
|
+
joins.set(topic, { payload: options2?.rejoinPayload ?? (() => payload) });
|
|
7962
8174
|
return reply.response;
|
|
7963
8175
|
}
|
|
7964
8176
|
throw new Error(`phoenix join failed: ${joinErrorMessage(reply)}`);
|
|
@@ -8030,7 +8242,7 @@ import {
|
|
|
8030
8242
|
existsSync as existsSync5,
|
|
8031
8243
|
mkdirSync as mkdirSync6,
|
|
8032
8244
|
openSync as openSync2,
|
|
8033
|
-
readFileSync as
|
|
8245
|
+
readFileSync as readFileSync5,
|
|
8034
8246
|
unlinkSync as unlinkSync2,
|
|
8035
8247
|
writeSync
|
|
8036
8248
|
} from "node:fs";
|
|
@@ -8042,27 +8254,38 @@ import {
|
|
|
8042
8254
|
existsSync as existsSync4,
|
|
8043
8255
|
linkSync,
|
|
8044
8256
|
mkdirSync as mkdirSync5,
|
|
8045
|
-
readFileSync as
|
|
8257
|
+
readFileSync as readFileSync4,
|
|
8046
8258
|
realpathSync as realpathSync4,
|
|
8047
8259
|
unlinkSync,
|
|
8048
8260
|
writeFileSync as writeFileSync4
|
|
8049
8261
|
} from "node:fs";
|
|
8050
8262
|
import { homedir as homedir5 } from "node:os";
|
|
8051
8263
|
import { basename as basename4, dirname as dirname4, join as join6, resolve as resolve5 } from "node:path";
|
|
8264
|
+
|
|
8265
|
+
// src/defaultUrls.ts
|
|
8266
|
+
var defaultLinzumiHttpUrl = "https://serve.linzumi.com";
|
|
8267
|
+
var defaultLinzumiWebSocketUrl = "wss://serve.linzumi.com";
|
|
8268
|
+
|
|
8269
|
+
// src/localConfig.ts
|
|
8270
|
+
var prodConfigScope = "prod";
|
|
8052
8271
|
function localConfigPath(env = process.env) {
|
|
8053
8272
|
const override = env.LINZUMI_CONFIG_FILE;
|
|
8054
8273
|
return override !== undefined && override.trim() !== "" ? resolve5(expandUserPath(override)) : resolve5(homedir5(), ".linzumi", "config.json");
|
|
8055
8274
|
}
|
|
8275
|
+
function localConfigScopeKey(linzumiUrl) {
|
|
8276
|
+
const normalizedUrl = kandanHttpBaseUrl(linzumiUrl);
|
|
8277
|
+
const normalizedProdUrl = kandanHttpBaseUrl(defaultLinzumiWebSocketUrl);
|
|
8278
|
+
return normalizedUrl === normalizedProdUrl ? prodConfigScope : normalizedUrl;
|
|
8279
|
+
}
|
|
8280
|
+
function localConfigScopeFileStem(linzumiUrl) {
|
|
8281
|
+
const scopeKey = localConfigScopeKey(linzumiUrl);
|
|
8282
|
+
return scopeKey === prodConfigScope ? prodConfigScope : Buffer.from(scopeKey).toString("base64url");
|
|
8283
|
+
}
|
|
8056
8284
|
function readLocalConfig(path = localConfigPath()) {
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
if (!isConfigPayload(parsed)) {
|
|
8062
|
-
throw new Error(`invalid Linzumi config: ${path}`);
|
|
8063
|
-
}
|
|
8064
|
-
const allowedCwds = uniqueStrings(parsed.allowedCwds);
|
|
8065
|
-
return parsed.machineId === undefined ? { version: 1, allowedCwds } : { version: 1, machineId: parsed.machineId, allowedCwds };
|
|
8285
|
+
return readLocalConfigSection(path);
|
|
8286
|
+
}
|
|
8287
|
+
function readLocalConfigForLinzumiUrl(linzumiUrl, path = localConfigPath()) {
|
|
8288
|
+
return readLocalConfigSection(path, linzumiUrl);
|
|
8066
8289
|
}
|
|
8067
8290
|
function ensureLocalMachineId(path = localConfigPath(), createMachineId = randomUUID2) {
|
|
8068
8291
|
const config = readLocalConfig(path);
|
|
@@ -8078,13 +8301,36 @@ function ensureLocalMachineId(path = localConfigPath(), createMachineId = random
|
|
|
8078
8301
|
writeLocalConfig({ ...latestConfig, machineId }, path);
|
|
8079
8302
|
return machineId;
|
|
8080
8303
|
}
|
|
8081
|
-
function
|
|
8304
|
+
function ensureLocalMachineIdForLinzumiUrl(linzumiUrl, path = localConfigPath(), createMachineId = randomUUID2) {
|
|
8305
|
+
if (localConfigScopeKey(linzumiUrl) === prodConfigScope) {
|
|
8306
|
+
return ensureLocalMachineId(path, createMachineId);
|
|
8307
|
+
}
|
|
8308
|
+
const config = readLocalConfigForLinzumiUrl(linzumiUrl, path);
|
|
8309
|
+
if (config.machineId !== undefined) {
|
|
8310
|
+
return config.machineId;
|
|
8311
|
+
}
|
|
8312
|
+
const machineId = ensureLocalMachineIdSeed(path, createMachineId, linzumiUrl);
|
|
8313
|
+
const latestConfig = readLocalConfigForLinzumiUrl(linzumiUrl, path);
|
|
8314
|
+
const latestMachineId = latestConfig.machineId;
|
|
8315
|
+
if (latestMachineId !== undefined) {
|
|
8316
|
+
return latestMachineId;
|
|
8317
|
+
}
|
|
8318
|
+
writeLocalConfigSection({ ...latestConfig, machineId }, path, linzumiUrl);
|
|
8319
|
+
return machineId;
|
|
8320
|
+
}
|
|
8321
|
+
function localMachineIdSeedPath(configPath = localConfigPath(), linzumiUrl) {
|
|
8322
|
+
if (linzumiUrl !== undefined && localConfigScopeKey(linzumiUrl) !== prodConfigScope) {
|
|
8323
|
+
return join6(dirname4(configPath), `${basename4(configPath)}.${localConfigScopeFileStem(linzumiUrl)}.machine-id`);
|
|
8324
|
+
}
|
|
8082
8325
|
return join6(dirname4(configPath), `${basename4(configPath)}.machine-id`);
|
|
8083
8326
|
}
|
|
8084
|
-
function
|
|
8327
|
+
function readConfiguredAllowedCwdDetailsForLinzumiUrl(linzumiUrl, path = localConfigPath()) {
|
|
8328
|
+
return readConfiguredAllowedCwdDetailsFromConfig(readLocalConfigForLinzumiUrl(linzumiUrl, path));
|
|
8329
|
+
}
|
|
8330
|
+
function readConfiguredAllowedCwdDetailsFromConfig(config) {
|
|
8085
8331
|
const allowedCwds = [];
|
|
8086
8332
|
const missingAllowedCwds = [];
|
|
8087
|
-
for (const cwd of
|
|
8333
|
+
for (const cwd of config.allowedCwds) {
|
|
8088
8334
|
const absolutePath = resolve5(expandUserPath(cwd));
|
|
8089
8335
|
try {
|
|
8090
8336
|
const realPath = realpathSync4(absolutePath);
|
|
@@ -8103,36 +8349,91 @@ function readConfiguredAllowedCwdDetails(path = localConfigPath()) {
|
|
|
8103
8349
|
};
|
|
8104
8350
|
}
|
|
8105
8351
|
function addAllowedCwd(pathValue, path = localConfigPath()) {
|
|
8352
|
+
return addAllowedCwdToConfig(pathValue, path);
|
|
8353
|
+
}
|
|
8354
|
+
function addAllowedCwdForLinzumiUrl(pathValue, linzumiUrl, path = localConfigPath()) {
|
|
8355
|
+
return addAllowedCwdToConfig(pathValue, path, linzumiUrl);
|
|
8356
|
+
}
|
|
8357
|
+
function addAllowedCwdToConfig(pathValue, path, linzumiUrl) {
|
|
8106
8358
|
const normalizedPath = realpathSync4(resolve5(expandUserPath(pathValue)));
|
|
8107
|
-
const config =
|
|
8359
|
+
const config = readLocalConfigSection(path, linzumiUrl);
|
|
8108
8360
|
const allowedCwds = uniqueStrings([...config.allowedCwds, normalizedPath]);
|
|
8109
|
-
|
|
8361
|
+
writeLocalConfigSection({ ...config, version: 1, allowedCwds }, path, linzumiUrl);
|
|
8110
8362
|
return allowedCwds;
|
|
8111
8363
|
}
|
|
8112
8364
|
function removeAllowedCwd(pathValue, path = localConfigPath()) {
|
|
8365
|
+
return removeAllowedCwdFromConfig(pathValue, path);
|
|
8366
|
+
}
|
|
8367
|
+
function removeAllowedCwdForLinzumiUrl(pathValue, linzumiUrl, path = localConfigPath()) {
|
|
8368
|
+
return removeAllowedCwdFromConfig(pathValue, path, linzumiUrl);
|
|
8369
|
+
}
|
|
8370
|
+
function removeAllowedCwdFromConfig(pathValue, path, linzumiUrl) {
|
|
8113
8371
|
const requestedPath = resolve5(expandUserPath(pathValue));
|
|
8114
8372
|
const normalizedRequest = realpathOrResolved(requestedPath);
|
|
8115
|
-
const config =
|
|
8373
|
+
const config = readLocalConfigSection(path, linzumiUrl);
|
|
8116
8374
|
const allowedCwds = config.allowedCwds.filter((cwd) => {
|
|
8117
8375
|
const normalizedExisting = realpathOrResolved(cwd);
|
|
8118
8376
|
return cwd !== pathValue && normalizedExisting !== normalizedRequest;
|
|
8119
8377
|
});
|
|
8120
|
-
|
|
8378
|
+
writeLocalConfigSection({ ...config, version: 1, allowedCwds }, path, linzumiUrl);
|
|
8121
8379
|
return allowedCwds;
|
|
8122
8380
|
}
|
|
8123
8381
|
function writeLocalConfig(config, path = localConfigPath()) {
|
|
8382
|
+
writeLocalConfigSection(config, path);
|
|
8383
|
+
}
|
|
8384
|
+
function readLocalConfigFile(path) {
|
|
8385
|
+
if (!existsSync4(path)) {
|
|
8386
|
+
return { version: 1, allowedCwds: [] };
|
|
8387
|
+
}
|
|
8388
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
8389
|
+
if (!isConfigPayload(parsed)) {
|
|
8390
|
+
throw new Error(`invalid Linzumi config: ${path}`);
|
|
8391
|
+
}
|
|
8392
|
+
return parsed;
|
|
8393
|
+
}
|
|
8394
|
+
function readLocalConfigSection(path, linzumiUrl) {
|
|
8395
|
+
const parsed = readLocalConfigFile(path);
|
|
8396
|
+
const scopeKey = linzumiUrl === undefined ? prodConfigScope : localConfigScopeKey(linzumiUrl);
|
|
8397
|
+
const section = scopeKey === prodConfigScope ? parsed : parsed[scopeKey] ?? emptySection();
|
|
8398
|
+
if (!isConfigSection(section)) {
|
|
8399
|
+
throw new Error(`invalid Linzumi config section ${scopeKey}: ${path}`);
|
|
8400
|
+
}
|
|
8401
|
+
const allowedCwds = uniqueStrings(section.allowedCwds);
|
|
8402
|
+
return section.machineId === undefined ? { version: 1, allowedCwds } : { version: 1, machineId: section.machineId, allowedCwds };
|
|
8403
|
+
}
|
|
8404
|
+
function writeLocalConfigSection(config, path, linzumiUrl) {
|
|
8405
|
+
const scopeKey = linzumiUrl === undefined ? prodConfigScope : localConfigScopeKey(linzumiUrl);
|
|
8406
|
+
const nextSection = normalizedConfigSection(config);
|
|
8407
|
+
const next = scopeKey === prodConfigScope ? { ...readLocalConfigFile(path), ...nextSection, version: 1 } : {
|
|
8408
|
+
...readLocalConfigFile(path),
|
|
8409
|
+
version: 1,
|
|
8410
|
+
[scopeKey]: nextSection
|
|
8411
|
+
};
|
|
8124
8412
|
mkdirSync5(dirname4(path), { recursive: true });
|
|
8125
|
-
writeFileSync4(path, `${JSON.stringify(
|
|
8413
|
+
writeFileSync4(path, `${JSON.stringify(next, null, 2)}
|
|
8126
8414
|
`, "utf8");
|
|
8127
8415
|
}
|
|
8128
8416
|
function isConfigPayload(value) {
|
|
8129
|
-
return typeof value === "object" && value !== null && value.version === 1 &&
|
|
8417
|
+
return typeof value === "object" && value !== null && value.version === 1 && isConfigSection(value);
|
|
8418
|
+
}
|
|
8419
|
+
function isConfigSection(value) {
|
|
8420
|
+
return typeof value === "object" && value !== null && Array.isArray(value.allowedCwds) && machineIdValid(value.machineId) && value.allowedCwds.every((cwd) => typeof cwd === "string" && cwd.trim() !== "");
|
|
8421
|
+
}
|
|
8422
|
+
function normalizedConfigSection(config) {
|
|
8423
|
+
if (!isConfigPayload(config)) {
|
|
8424
|
+
throw new Error("invalid Linzumi config");
|
|
8425
|
+
}
|
|
8426
|
+
const allowedCwds = uniqueStrings(config.allowedCwds);
|
|
8427
|
+
return config.machineId === undefined ? { allowedCwds } : { machineId: config.machineId, allowedCwds };
|
|
8428
|
+
}
|
|
8429
|
+
function emptySection() {
|
|
8430
|
+
return { allowedCwds: [] };
|
|
8130
8431
|
}
|
|
8131
8432
|
function machineIdValid(value) {
|
|
8132
8433
|
return value === undefined || typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
8133
8434
|
}
|
|
8134
|
-
function ensureLocalMachineIdSeed(configPath, createMachineId) {
|
|
8135
|
-
const seedPath = localMachineIdSeedPath(configPath);
|
|
8435
|
+
function ensureLocalMachineIdSeed(configPath, createMachineId, linzumiUrl) {
|
|
8436
|
+
const seedPath = localMachineIdSeedPath(configPath, linzumiUrl);
|
|
8136
8437
|
if (existsSync4(seedPath)) {
|
|
8137
8438
|
return readMachineIdSeed(seedPath);
|
|
8138
8439
|
}
|
|
@@ -8157,7 +8458,7 @@ function ensureLocalMachineIdSeed(configPath, createMachineId) {
|
|
|
8157
8458
|
}
|
|
8158
8459
|
}
|
|
8159
8460
|
function readMachineIdSeed(seedPath) {
|
|
8160
|
-
const machineId =
|
|
8461
|
+
const machineId = readFileSync4(seedPath, "utf8").trim();
|
|
8161
8462
|
if (!machineIdValid(machineId)) {
|
|
8162
8463
|
throw new Error(`invalid Linzumi machine id seed: ${seedPath}`);
|
|
8163
8464
|
}
|
|
@@ -8183,15 +8484,16 @@ function realpathOrResolved(pathValue) {
|
|
|
8183
8484
|
}
|
|
8184
8485
|
|
|
8185
8486
|
// src/version.ts
|
|
8186
|
-
var linzumiCliVersion = "0.0.
|
|
8487
|
+
var linzumiCliVersion = "0.0.45-beta";
|
|
8187
8488
|
var linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
|
|
8188
8489
|
|
|
8189
8490
|
// src/runnerLock.ts
|
|
8190
|
-
function runnerLockPath(machineId, configPath = localConfigPath()) {
|
|
8191
|
-
|
|
8491
|
+
function runnerLockPath(machineId, configPath = localConfigPath(), linzumiUrl) {
|
|
8492
|
+
const lockName = linzumiUrl === undefined ? encodeURIComponent(machineId) : localConfigScopeFileStem(linzumiUrl);
|
|
8493
|
+
return join7(dirname5(configPath), "runners", `${lockName}.lock`);
|
|
8192
8494
|
}
|
|
8193
8495
|
function acquireRunnerLock(options) {
|
|
8194
|
-
const path = runnerLockPath(options.machineId, options.configPath);
|
|
8496
|
+
const path = runnerLockPath(options.machineId, options.configPath, options.linzumiUrl);
|
|
8195
8497
|
const isPidAlive = options.isPidAlive ?? processIsAlive;
|
|
8196
8498
|
const record = {
|
|
8197
8499
|
version: 1,
|
|
@@ -8200,9 +8502,11 @@ function acquireRunnerLock(options) {
|
|
|
8200
8502
|
pid: options.pid ?? process.pid,
|
|
8201
8503
|
cwd: options.cwd,
|
|
8202
8504
|
workspace: options.workspace,
|
|
8505
|
+
...options.linzumiUrl === undefined ? {} : { linzumiUrl: kandanHttpBaseUrl(options.linzumiUrl) },
|
|
8203
8506
|
startedAt: (options.now ?? (() => new Date))().toISOString(),
|
|
8204
8507
|
cliVersion: options.cliVersion ?? linzumiCliVersion
|
|
8205
8508
|
};
|
|
8509
|
+
rejectLiveLegacyProductionRunnerLock(options.machineId, options.configPath, options.linzumiUrl, isPidAlive);
|
|
8206
8510
|
writeLockOrHandleExisting(path, record, isPidAlive, options.beforeReadExistingLock, options.beforeReplaceStaleLock);
|
|
8207
8511
|
return {
|
|
8208
8512
|
path,
|
|
@@ -8210,6 +8514,28 @@ function acquireRunnerLock(options) {
|
|
|
8210
8514
|
release: () => releaseRunnerLock(path, record)
|
|
8211
8515
|
};
|
|
8212
8516
|
}
|
|
8517
|
+
function rejectLiveLegacyProductionRunnerLock(machineId, configPath, linzumiUrl, isPidAlive) {
|
|
8518
|
+
if (linzumiUrl === undefined || localConfigScopeKey(linzumiUrl) !== localConfigScopeKey(defaultLinzumiHttpUrl)) {
|
|
8519
|
+
return;
|
|
8520
|
+
}
|
|
8521
|
+
const legacyPath = runnerLockPath(machineId, configPath);
|
|
8522
|
+
const existing = readRunnerLockIfPresent(legacyPath);
|
|
8523
|
+
if (existing === undefined) {
|
|
8524
|
+
return;
|
|
8525
|
+
}
|
|
8526
|
+
if (isPidAlive(existing.pid)) {
|
|
8527
|
+
throw new Error(activeRunnerLockMessage(legacyPath, existing));
|
|
8528
|
+
}
|
|
8529
|
+
withStaleReplacementLock(legacyPath, isPidAlive, () => {
|
|
8530
|
+
const latest = existsSync5(legacyPath) ? readRunnerLock(legacyPath) : undefined;
|
|
8531
|
+
if (latest !== undefined && isPidAlive(latest.pid)) {
|
|
8532
|
+
throw new Error(activeRunnerLockMessage(legacyPath, latest));
|
|
8533
|
+
}
|
|
8534
|
+
if (latest !== undefined) {
|
|
8535
|
+
unlinkSync2(legacyPath);
|
|
8536
|
+
}
|
|
8537
|
+
});
|
|
8538
|
+
}
|
|
8213
8539
|
function writeLockOrHandleExisting(path, record, isPidAlive, beforeReadExistingLock, beforeReplaceStaleLock) {
|
|
8214
8540
|
if (tryCreateLock(path, record)) {
|
|
8215
8541
|
return;
|
|
@@ -8295,7 +8621,7 @@ function withStaleReplacementLock(path, isPidAlive, callback) {
|
|
|
8295
8621
|
function readReplacementLockPidIfPresent(path) {
|
|
8296
8622
|
let value;
|
|
8297
8623
|
try {
|
|
8298
|
-
value =
|
|
8624
|
+
value = readFileSync5(path, "utf8").trim();
|
|
8299
8625
|
} catch (error) {
|
|
8300
8626
|
if (isNodeErrorCode2(error, "ENOENT")) {
|
|
8301
8627
|
return;
|
|
@@ -8335,14 +8661,17 @@ function readRunnerLockIfPresent(path) {
|
|
|
8335
8661
|
}
|
|
8336
8662
|
}
|
|
8337
8663
|
function readRunnerLock(path) {
|
|
8338
|
-
const parsed = JSON.parse(
|
|
8664
|
+
const parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
8339
8665
|
if (!isRunnerLockRecord(parsed)) {
|
|
8340
8666
|
throw new Error(`invalid Linzumi runner lock: ${path}`);
|
|
8341
8667
|
}
|
|
8342
8668
|
return parsed;
|
|
8343
8669
|
}
|
|
8344
8670
|
function isRunnerLockRecord(value) {
|
|
8345
|
-
return typeof value === "object" && value !== null && value.version === 1 && typeof value.machineId === "string" && value.machineId.trim() !== "" && typeof value.runnerId === "string" && value.runnerId.trim() !== "" && Number.isInteger(value.pid) && value.pid > 0 && typeof value.cwd === "string" && value.cwd.trim() !== "" && workspaceValid(value.workspace) && typeof value.startedAt === "string" && value.startedAt.trim() !== "" && typeof value.cliVersion === "string" && value.cliVersion.trim() !== "";
|
|
8671
|
+
return typeof value === "object" && value !== null && value.version === 1 && typeof value.machineId === "string" && value.machineId.trim() !== "" && typeof value.runnerId === "string" && value.runnerId.trim() !== "" && Number.isInteger(value.pid) && value.pid > 0 && typeof value.cwd === "string" && value.cwd.trim() !== "" && workspaceValid(value.workspace) && linzumiUrlValid(value.linzumiUrl) && typeof value.startedAt === "string" && value.startedAt.trim() !== "" && typeof value.cliVersion === "string" && value.cliVersion.trim() !== "";
|
|
8672
|
+
}
|
|
8673
|
+
function linzumiUrlValid(value) {
|
|
8674
|
+
return value === undefined || typeof value === "string" && value.trim() !== "";
|
|
8346
8675
|
}
|
|
8347
8676
|
function workspaceValid(value) {
|
|
8348
8677
|
return value === null || typeof value === "string" && value.trim() !== "";
|
|
@@ -8357,8 +8686,10 @@ function processIsAlive(pid) {
|
|
|
8357
8686
|
}
|
|
8358
8687
|
function activeRunnerLockMessage(path, record) {
|
|
8359
8688
|
const workspace = record.workspace === null ? "workspace: unknown" : `workspace: ${record.workspace}`;
|
|
8689
|
+
const linzumiUrl = record.linzumiUrl === undefined ? undefined : `linzumi url: ${displayLinzumiUrl(record.linzumiUrl)}`;
|
|
8360
8690
|
return [
|
|
8361
|
-
"another Linzumi runner is already running for this machine",
|
|
8691
|
+
record.linzumiUrl === undefined ? "another Linzumi runner is already running for this machine" : "another Linzumi runner is already running for this Linzumi URL",
|
|
8692
|
+
...linzumiUrl === undefined ? [] : [linzumiUrl],
|
|
8362
8693
|
`runner id: ${record.runnerId}`,
|
|
8363
8694
|
`pid: ${record.pid}`,
|
|
8364
8695
|
`cwd: ${record.cwd}`,
|
|
@@ -8370,6 +8701,12 @@ function activeRunnerLockMessage(path, record) {
|
|
|
8370
8701
|
].join(`
|
|
8371
8702
|
`);
|
|
8372
8703
|
}
|
|
8704
|
+
function displayLinzumiUrl(linzumiUrl) {
|
|
8705
|
+
if (linzumiUrl === localConfigScopeKey(defaultLinzumiHttpUrl)) {
|
|
8706
|
+
return defaultLinzumiHttpUrl;
|
|
8707
|
+
}
|
|
8708
|
+
return localConfigScopeKey(linzumiUrl) === localConfigScopeKey(defaultLinzumiHttpUrl) ? defaultLinzumiHttpUrl : linzumiUrl;
|
|
8709
|
+
}
|
|
8373
8710
|
function isNodeErrorCode2(error, code) {
|
|
8374
8711
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
8375
8712
|
}
|
|
@@ -8531,6 +8868,7 @@ async function runLocalCodexRunner(options) {
|
|
|
8531
8868
|
runnerId: options.runnerId,
|
|
8532
8869
|
cwd: options.cwd,
|
|
8533
8870
|
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8871
|
+
linzumiUrl: options.kandanUrl,
|
|
8534
8872
|
configPath: options.runnerLockConfigPath
|
|
8535
8873
|
});
|
|
8536
8874
|
cleanup.actions.push(() => runnerLock.release());
|
|
@@ -8552,6 +8890,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8552
8890
|
const allowedForwardPorts = options.allowedForwardPorts ?? [];
|
|
8553
8891
|
const liveForwardPorts = new Set(allowedForwardPorts);
|
|
8554
8892
|
const managedForwardPorts = new Set;
|
|
8893
|
+
const kandanControlPort = explicitUrlPort(options.kandanUrl);
|
|
8894
|
+
const suppressedForwardPorts = () => suppressedForwardPortsForRunner(kandanControlPort, managedForwardPorts);
|
|
8555
8895
|
const forwardPortAttributions = new Map;
|
|
8556
8896
|
const setForwardPortAttribution = (port, attribution) => {
|
|
8557
8897
|
forwardPortAttributions.set(port, {
|
|
@@ -8559,7 +8899,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8559
8899
|
codexThreadId: attribution.codexThreadId ?? null,
|
|
8560
8900
|
channelSlug: attribution.channelSlug ?? null,
|
|
8561
8901
|
processName: attribution.processName ?? null,
|
|
8562
|
-
processIconKey: attribution.processIconKey ?? null
|
|
8902
|
+
processIconKey: attribution.processIconKey ?? null,
|
|
8903
|
+
localEditor: attribution.localEditor === true
|
|
8563
8904
|
});
|
|
8564
8905
|
};
|
|
8565
8906
|
const clearForwardPortAttribution = (port) => {
|
|
@@ -8573,7 +8914,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8573
8914
|
codexThreadId: attribution?.codexThreadId ?? null,
|
|
8574
8915
|
channelSlug: attribution?.channelSlug ?? null,
|
|
8575
8916
|
processName: attribution?.processName ?? null,
|
|
8576
|
-
processIconKey: attribution?.processIconKey ?? null
|
|
8917
|
+
processIconKey: attribution?.processIconKey ?? null,
|
|
8918
|
+
localEditor: attribution?.localEditor === true
|
|
8577
8919
|
};
|
|
8578
8920
|
});
|
|
8579
8921
|
const allowedCwds = { value: [...options.allowedCwds] };
|
|
@@ -8584,11 +8926,35 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8584
8926
|
value: { status: "disabled" }
|
|
8585
8927
|
};
|
|
8586
8928
|
const localEditorGeneration = { value: 0 };
|
|
8929
|
+
const localEditorForwardState = {
|
|
8930
|
+
watcher: undefined,
|
|
8931
|
+
approvedPorts: new Set,
|
|
8932
|
+
approvedTargets: new Map,
|
|
8933
|
+
dismissedTargets: new Map,
|
|
8934
|
+
pendingRequests: new Map,
|
|
8935
|
+
queuedCandidates: new Map
|
|
8936
|
+
};
|
|
8587
8937
|
cleanup.actions.push(() => {
|
|
8588
8938
|
if (localEditorState.value.status === "running") {
|
|
8589
8939
|
localEditorState.value.process.kill("SIGINT");
|
|
8590
8940
|
localEditorState.value.collaboration?.process.kill("SIGINT");
|
|
8591
8941
|
}
|
|
8942
|
+
localEditorForwardState.watcher?.close();
|
|
8943
|
+
});
|
|
8944
|
+
const pendingEditorForwardPortRequests = () => Array.from(localEditorForwardState.pendingRequests.values()).sort((left, right) => left.port - right.port).map((request) => {
|
|
8945
|
+
const processIdentity = guessCanonicalProcessFromCommand(request.command);
|
|
8946
|
+
const label = processIdentity?.appName ?? portForwardPromptLabel(approvedTargetFromRequest(request));
|
|
8947
|
+
return {
|
|
8948
|
+
requestId: request.requestId,
|
|
8949
|
+
port: request.port,
|
|
8950
|
+
pid: request.pid,
|
|
8951
|
+
command: request.command,
|
|
8952
|
+
...request.cwd === undefined ? {} : { cwd: request.cwd },
|
|
8953
|
+
summary: `Make ${label} on port ${request.port} accessible on Linzumi?`,
|
|
8954
|
+
reason: portForwardPromptReason(approvedTargetFromRequest(request)),
|
|
8955
|
+
...processIdentity?.appName === undefined ? {} : { processName: processIdentity.appName },
|
|
8956
|
+
...processIdentity?.iconKey === undefined ? {} : { processIconKey: processIdentity.iconKey }
|
|
8957
|
+
};
|
|
8592
8958
|
});
|
|
8593
8959
|
const capabilitiesPayload = () => ({
|
|
8594
8960
|
codexAppServer: true,
|
|
@@ -8600,6 +8966,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8600
8966
|
portForwarding: liveForwardPorts.size > 0,
|
|
8601
8967
|
allowedPorts: Array.from(liveForwardPorts).sort((left, right) => left - right),
|
|
8602
8968
|
forwardedPortAttributions: buildForwardPortAttributionPayload(),
|
|
8969
|
+
pendingEditorForwardPortRequests: pendingEditorForwardPortRequests(),
|
|
8603
8970
|
toolStatus: options.dependencyStatus === undefined ? null : dependencyStatusPayload(options.dependencyStatus),
|
|
8604
8971
|
editorRuntime: options.dependencyStatus?.editorRuntime === undefined ? null : dependencyStatusPayload(options.dependencyStatus).editorRuntime,
|
|
8605
8972
|
...localEditorCapabilities(options.editorRuntime, allowedCwds.value, localEditorState.value)
|
|
@@ -8611,6 +8978,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8611
8978
|
const joinPayload = () => ({
|
|
8612
8979
|
clientName: "kandan-local-codex-runner",
|
|
8613
8980
|
clientId,
|
|
8981
|
+
runnerPid: process.pid,
|
|
8614
8982
|
version: linzumiCliVersion,
|
|
8615
8983
|
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8616
8984
|
channel: options.channelSession?.channelSlug ?? null,
|
|
@@ -8648,12 +9016,204 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8648
9016
|
});
|
|
8649
9017
|
});
|
|
8650
9018
|
};
|
|
9019
|
+
const pushEditorPortForwardEvent = (event, payload) => {
|
|
9020
|
+
kandan.push(topic, event, payload).catch((error) => {
|
|
9021
|
+
log(`kandan.${event}_push_failed`, {
|
|
9022
|
+
message: error instanceof Error ? error.message : String(error)
|
|
9023
|
+
});
|
|
9024
|
+
});
|
|
9025
|
+
};
|
|
9026
|
+
const revokeEditorForwardPort = (port, reason) => {
|
|
9027
|
+
const target = localEditorForwardState.approvedTargets.get(port);
|
|
9028
|
+
localEditorForwardState.approvedPorts.delete(port);
|
|
9029
|
+
localEditorForwardState.approvedTargets.delete(port);
|
|
9030
|
+
liveForwardPorts.delete(port);
|
|
9031
|
+
clearForwardPortAttribution(port);
|
|
9032
|
+
pushEditorPortForwardEvent("forward_port_revoked", {
|
|
9033
|
+
instanceId,
|
|
9034
|
+
port,
|
|
9035
|
+
...target === undefined ? {} : {
|
|
9036
|
+
pid: target.pid,
|
|
9037
|
+
command: target.command,
|
|
9038
|
+
...target.cwd === undefined ? {} : { cwd: target.cwd }
|
|
9039
|
+
},
|
|
9040
|
+
reason,
|
|
9041
|
+
capabilities: revocationCapabilities(capabilitiesPayload(), port)
|
|
9042
|
+
});
|
|
9043
|
+
};
|
|
9044
|
+
const clearEditorPortForwarding = (reason) => {
|
|
9045
|
+
localEditorForwardState.watcher?.close();
|
|
9046
|
+
localEditorForwardState.watcher = undefined;
|
|
9047
|
+
localEditorForwardState.pendingRequests.clear();
|
|
9048
|
+
localEditorForwardState.queuedCandidates.clear();
|
|
9049
|
+
const revokedPorts = Array.from(localEditorForwardState.approvedPorts);
|
|
9050
|
+
for (const port of revokedPorts) {
|
|
9051
|
+
revokeEditorForwardPort(port, reason);
|
|
9052
|
+
}
|
|
9053
|
+
localEditorForwardState.dismissedTargets.clear();
|
|
9054
|
+
return revokedPorts;
|
|
9055
|
+
};
|
|
9056
|
+
const drainQueuedEditorPortForwardPrompt = () => {
|
|
9057
|
+
if (localEditorForwardState.pendingRequests.size > 0 || localEditorForwardState.queuedCandidates.size === 0) {
|
|
9058
|
+
return;
|
|
9059
|
+
}
|
|
9060
|
+
const next = Array.from(localEditorForwardState.queuedCandidates.values()).sort((left, right) => left.port - right.port)[0];
|
|
9061
|
+
if (next === undefined) {
|
|
9062
|
+
return;
|
|
9063
|
+
}
|
|
9064
|
+
localEditorForwardState.queuedCandidates.delete(next.port);
|
|
9065
|
+
publishEditorPortForwardPrompt(next);
|
|
9066
|
+
if (localEditorForwardState.pendingRequests.size === 0) {
|
|
9067
|
+
drainQueuedEditorPortForwardPrompt();
|
|
9068
|
+
}
|
|
9069
|
+
};
|
|
9070
|
+
const publishEditorPortForwardPrompt = (candidate) => {
|
|
9071
|
+
const review = reviewPortForwardCandidate({
|
|
9072
|
+
candidate,
|
|
9073
|
+
threadBound: true,
|
|
9074
|
+
suppressedPorts: new Set(suppressedForwardPorts()),
|
|
9075
|
+
approvedPorts: localEditorForwardState.approvedPorts,
|
|
9076
|
+
approvedTargets: localEditorForwardState.approvedTargets,
|
|
9077
|
+
dismissedTargets: localEditorForwardState.dismissedTargets,
|
|
9078
|
+
pendingRequests: Array.from(localEditorForwardState.pendingRequests.values())
|
|
9079
|
+
});
|
|
9080
|
+
switch (review.type) {
|
|
9081
|
+
case "skip":
|
|
9082
|
+
return;
|
|
9083
|
+
case "remember_approved_target":
|
|
9084
|
+
localEditorForwardState.approvedTargets.set(review.target.port, review.target);
|
|
9085
|
+
return;
|
|
9086
|
+
case "revoke_and_prompt":
|
|
9087
|
+
if (localEditorForwardState.pendingRequests.size > 0) {
|
|
9088
|
+
localEditorForwardState.queuedCandidates.set(candidate.port, candidate);
|
|
9089
|
+
return;
|
|
9090
|
+
}
|
|
9091
|
+
revokeEditorForwardPort(review.revoked.port, review.reason);
|
|
9092
|
+
break;
|
|
9093
|
+
case "prompt":
|
|
9094
|
+
if (localEditorForwardState.pendingRequests.size > 0) {
|
|
9095
|
+
localEditorForwardState.queuedCandidates.set(candidate.port, candidate);
|
|
9096
|
+
return;
|
|
9097
|
+
}
|
|
9098
|
+
break;
|
|
9099
|
+
}
|
|
9100
|
+
const requestId = `editor-port-forward-${randomUUID3()}`;
|
|
9101
|
+
const request = pendingRequestFromCandidate({
|
|
9102
|
+
requestId,
|
|
9103
|
+
sourceSeq: 0,
|
|
9104
|
+
candidate
|
|
9105
|
+
});
|
|
9106
|
+
localEditorForwardState.pendingRequests.set(request.port, request);
|
|
9107
|
+
const processIdentity = guessCanonicalProcessFromCommand(candidate.command);
|
|
9108
|
+
pushEditorPortForwardEvent("forward_port_requested", {
|
|
9109
|
+
instanceId,
|
|
9110
|
+
requestId,
|
|
9111
|
+
source: "local_editor",
|
|
9112
|
+
port: request.port,
|
|
9113
|
+
pid: request.pid,
|
|
9114
|
+
command: request.command,
|
|
9115
|
+
...request.cwd === undefined ? {} : { cwd: request.cwd },
|
|
9116
|
+
...processIdentity?.appName === undefined ? {} : { processName: processIdentity.appName },
|
|
9117
|
+
...processIdentity?.iconKey === undefined ? {} : { processIconKey: processIdentity.iconKey },
|
|
9118
|
+
capabilities: capabilitiesPayload()
|
|
9119
|
+
});
|
|
9120
|
+
};
|
|
9121
|
+
const expireLostEditorPortForwardCandidate = (candidate) => {
|
|
9122
|
+
const queuedCandidate = localEditorForwardState.queuedCandidates.get(candidate.port);
|
|
9123
|
+
if (queuedCandidate !== undefined && sameForwardCandidate(queuedCandidate, candidate)) {
|
|
9124
|
+
localEditorForwardState.queuedCandidates.delete(candidate.port);
|
|
9125
|
+
return;
|
|
9126
|
+
}
|
|
9127
|
+
const pendingRequest = localEditorForwardState.pendingRequests.get(candidate.port);
|
|
9128
|
+
if (pendingRequest !== undefined && sameForwardCandidate(approvedTargetFromRequest(pendingRequest), candidate)) {
|
|
9129
|
+
localEditorForwardState.pendingRequests.delete(candidate.port);
|
|
9130
|
+
pushEditorPortForwardEvent("forward_port_revoked", {
|
|
9131
|
+
instanceId,
|
|
9132
|
+
port: candidate.port,
|
|
9133
|
+
pid: candidate.pid,
|
|
9134
|
+
command: candidate.command,
|
|
9135
|
+
...candidate.cwd === undefined ? {} : { cwd: candidate.cwd },
|
|
9136
|
+
reason: "listener_exited",
|
|
9137
|
+
capabilities: revocationCapabilities(capabilitiesPayload(), candidate.port)
|
|
9138
|
+
});
|
|
9139
|
+
drainQueuedEditorPortForwardPrompt();
|
|
9140
|
+
return;
|
|
9141
|
+
}
|
|
9142
|
+
if (localEditorForwardState.approvedPorts.has(candidate.port)) {
|
|
9143
|
+
revokeEditorForwardPort(candidate.port, "listener_exited");
|
|
9144
|
+
}
|
|
9145
|
+
};
|
|
9146
|
+
const resolveEditorPortForwardRequest = (control) => {
|
|
9147
|
+
const request = Array.from(localEditorForwardState.pendingRequests.values()).find((request2) => request2.requestId === control.requestId);
|
|
9148
|
+
if (request === undefined) {
|
|
9149
|
+
return;
|
|
9150
|
+
}
|
|
9151
|
+
localEditorForwardState.pendingRequests.delete(request.port);
|
|
9152
|
+
if (control.decision === "deny") {
|
|
9153
|
+
localEditorForwardState.dismissedTargets.set(request.port, approvedTargetFromRequest(request));
|
|
9154
|
+
pushEditorPortForwardEvent("forward_port_resolved", {
|
|
9155
|
+
instanceId,
|
|
9156
|
+
requestId: request.requestId,
|
|
9157
|
+
source: "local_editor",
|
|
9158
|
+
port: request.port,
|
|
9159
|
+
pid: request.pid,
|
|
9160
|
+
command: request.command,
|
|
9161
|
+
...request.cwd === undefined ? {} : { cwd: request.cwd },
|
|
9162
|
+
decision: "deny",
|
|
9163
|
+
capabilities: capabilitiesPayload()
|
|
9164
|
+
});
|
|
9165
|
+
drainQueuedEditorPortForwardPrompt();
|
|
9166
|
+
return { instanceId, ok: true };
|
|
9167
|
+
}
|
|
9168
|
+
localEditorForwardState.approvedPorts.add(request.port);
|
|
9169
|
+
localEditorForwardState.approvedTargets.set(request.port, approvedTargetFromRequest(request));
|
|
9170
|
+
liveForwardPorts.add(request.port);
|
|
9171
|
+
const processIdentity = guessCanonicalProcessFromCommand(request.command);
|
|
9172
|
+
setForwardPortAttribution(request.port, {
|
|
9173
|
+
localEditor: true,
|
|
9174
|
+
processName: processIdentity?.appName ?? null,
|
|
9175
|
+
processIconKey: processIdentity?.iconKey ?? null
|
|
9176
|
+
});
|
|
9177
|
+
pushEditorPortForwardEvent("forward_port_resolved", {
|
|
9178
|
+
instanceId,
|
|
9179
|
+
requestId: request.requestId,
|
|
9180
|
+
source: "local_editor",
|
|
9181
|
+
port: request.port,
|
|
9182
|
+
pid: request.pid,
|
|
9183
|
+
command: request.command,
|
|
9184
|
+
...request.cwd === undefined ? {} : { cwd: request.cwd },
|
|
9185
|
+
decision: "approve",
|
|
9186
|
+
capabilities: capabilitiesPayload()
|
|
9187
|
+
});
|
|
9188
|
+
drainQueuedEditorPortForwardPrompt();
|
|
9189
|
+
return { instanceId, ok: true, port: request.port };
|
|
9190
|
+
};
|
|
9191
|
+
const startLocalEditorPortForwardWatcher = (state) => {
|
|
9192
|
+
if (state.process.pid === undefined) {
|
|
9193
|
+
log("port_forward.local_editor_watch_skipped", {
|
|
9194
|
+
reason: "editor_pid_missing"
|
|
9195
|
+
});
|
|
9196
|
+
return;
|
|
9197
|
+
}
|
|
9198
|
+
localEditorForwardState.watcher?.close();
|
|
9199
|
+
localEditorForwardState.watcher = startPortForwardWatcher({
|
|
9200
|
+
rootPid: state.process.pid,
|
|
9201
|
+
onCandidate: publishEditorPortForwardPrompt,
|
|
9202
|
+
onCandidateLost: expireLostEditorPortForwardCandidate,
|
|
9203
|
+
onError: (error) => {
|
|
9204
|
+
log("port_forward.local_editor_watch_failed", {
|
|
9205
|
+
message: error.message
|
|
9206
|
+
});
|
|
9207
|
+
}
|
|
9208
|
+
});
|
|
9209
|
+
};
|
|
8651
9210
|
const watchLocalEditorExit = (state, generation, initialStatusPushed) => {
|
|
8652
9211
|
const handleExit = () => {
|
|
8653
9212
|
if (localEditorGeneration.value !== generation || localEditorState.value.status !== "running" || localEditorState.value.process !== state.process) {
|
|
8654
9213
|
return;
|
|
8655
9214
|
}
|
|
8656
9215
|
localEditorState.value = { status: "disabled" };
|
|
9216
|
+
const revokedEditorPorts = clearEditorPortForwarding("editor_exited");
|
|
8657
9217
|
liveForwardPorts.delete(state.port);
|
|
8658
9218
|
managedForwardPorts.delete(state.port);
|
|
8659
9219
|
if (state.collaboration !== undefined) {
|
|
@@ -8666,7 +9226,11 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8666
9226
|
cwd: state.cwd,
|
|
8667
9227
|
capabilities: {
|
|
8668
9228
|
...capabilitiesPayload(),
|
|
8669
|
-
revokedPorts: state.collaboration === undefined ? [state.port] : [
|
|
9229
|
+
revokedPorts: state.collaboration === undefined ? [state.port, ...revokedEditorPorts] : [
|
|
9230
|
+
state.port,
|
|
9231
|
+
state.collaboration.serverPort,
|
|
9232
|
+
...revokedEditorPorts
|
|
9233
|
+
]
|
|
8670
9234
|
}
|
|
8671
9235
|
});
|
|
8672
9236
|
};
|
|
@@ -8724,7 +9288,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8724
9288
|
launchTui: options.launchTui,
|
|
8725
9289
|
enablePortForwardWatch: true,
|
|
8726
9290
|
initialForwardPorts: allowedForwardPorts,
|
|
8727
|
-
suppressedForwardPorts
|
|
9291
|
+
suppressedForwardPorts,
|
|
8728
9292
|
onForwardPortApproved: (port, attribution) => {
|
|
8729
9293
|
liveForwardPorts.add(port);
|
|
8730
9294
|
setForwardPortAttribution(port, attribution);
|
|
@@ -8779,7 +9343,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8779
9343
|
launchTui: false,
|
|
8780
9344
|
enablePortForwardWatch: true,
|
|
8781
9345
|
initialForwardPorts: allowedForwardPorts,
|
|
8782
|
-
suppressedForwardPorts
|
|
9346
|
+
suppressedForwardPorts,
|
|
8783
9347
|
onForwardPortApproved: (port, attribution) => {
|
|
8784
9348
|
liveForwardPorts.add(port);
|
|
8785
9349
|
setForwardPortAttribution(port, attribution);
|
|
@@ -8950,6 +9514,11 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8950
9514
|
if (result.ok) {
|
|
8951
9515
|
localEditorGeneration.value += 1;
|
|
8952
9516
|
const editorGeneration = localEditorGeneration.value;
|
|
9517
|
+
const restartedEditorRevokedPorts = localEditorState.value.status === "running" ? [
|
|
9518
|
+
localEditorState.value.port,
|
|
9519
|
+
...localEditorState.value.collaboration === undefined ? [] : [localEditorState.value.collaboration.serverPort],
|
|
9520
|
+
...clearEditorPortForwarding("editor_restarted")
|
|
9521
|
+
] : [];
|
|
8953
9522
|
if (localEditorState.value.status === "running") {
|
|
8954
9523
|
liveForwardPorts.delete(localEditorState.value.port);
|
|
8955
9524
|
managedForwardPorts.delete(localEditorState.value.port);
|
|
@@ -8969,10 +9538,14 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8969
9538
|
instanceId,
|
|
8970
9539
|
requestId: control.requestId,
|
|
8971
9540
|
ok: true,
|
|
8972
|
-
capabilities:
|
|
9541
|
+
capabilities: {
|
|
9542
|
+
...capabilitiesPayload(),
|
|
9543
|
+
...restartedEditorRevokedPorts.length === 0 ? {} : { revokedPorts: restartedEditorRevokedPorts }
|
|
9544
|
+
},
|
|
8973
9545
|
...result.event
|
|
8974
9546
|
});
|
|
8975
9547
|
if (result.state.status === "running") {
|
|
9548
|
+
startLocalEditorPortForwardWatcher(result.state);
|
|
8976
9549
|
watchLocalEditorExit(result.state, editorGeneration, initialStatusPushed);
|
|
8977
9550
|
}
|
|
8978
9551
|
return initialStatusPushed;
|
|
@@ -9005,6 +9578,17 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
9005
9578
|
pushHeartbeat();
|
|
9006
9579
|
return;
|
|
9007
9580
|
}
|
|
9581
|
+
if (isResolvePortForwardRequestControl(control)) {
|
|
9582
|
+
const response = resolveEditorPortForwardRequest(control);
|
|
9583
|
+
if (response !== undefined) {
|
|
9584
|
+
kandan.push(topic, "codex_response", response).catch((error) => {
|
|
9585
|
+
log("kandan.control_response_push_failed", {
|
|
9586
|
+
message: error instanceof Error ? error.message : String(error)
|
|
9587
|
+
});
|
|
9588
|
+
});
|
|
9589
|
+
return;
|
|
9590
|
+
}
|
|
9591
|
+
}
|
|
9008
9592
|
if (isSetPortForwardEnabledControl(control)) {
|
|
9009
9593
|
switch (control.enabled) {
|
|
9010
9594
|
case true:
|
|
@@ -9014,6 +9598,8 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
9014
9598
|
liveForwardPorts.delete(control.port);
|
|
9015
9599
|
managedForwardPorts.delete(control.port);
|
|
9016
9600
|
clearForwardPortAttribution(control.port);
|
|
9601
|
+
localEditorForwardState.approvedPorts.delete(control.port);
|
|
9602
|
+
localEditorForwardState.approvedTargets.delete(control.port);
|
|
9017
9603
|
kandan.push(topic, "forward_port_revoked", {
|
|
9018
9604
|
instanceId,
|
|
9019
9605
|
port: control.port,
|
|
@@ -9205,6 +9791,24 @@ function codexTuiArgs(codexUrl, codexThreadId, session, fast) {
|
|
|
9205
9791
|
const overrides = codexTuiConfigArgs(session, fast);
|
|
9206
9792
|
return codexThreadId === undefined ? ["--remote", codexUrl, ...overrides] : ["resume", "--remote", codexUrl, ...overrides, codexThreadId];
|
|
9207
9793
|
}
|
|
9794
|
+
function suppressedForwardPortsForRunner(kandanControlPort, managedForwardPorts) {
|
|
9795
|
+
const suppressedPorts = new Set(managedForwardPorts);
|
|
9796
|
+
if (kandanControlPort !== undefined) {
|
|
9797
|
+
suppressedPorts.add(kandanControlPort);
|
|
9798
|
+
}
|
|
9799
|
+
return Array.from(suppressedPorts).sort((left, right) => left - right);
|
|
9800
|
+
}
|
|
9801
|
+
function explicitUrlPort(url) {
|
|
9802
|
+
const parsed = new URL(url);
|
|
9803
|
+
if (parsed.port === "") {
|
|
9804
|
+
return;
|
|
9805
|
+
}
|
|
9806
|
+
const port = Number.parseInt(parsed.port, 10);
|
|
9807
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
9808
|
+
throw new Error(`invalid Kandan URL port: ${url}`);
|
|
9809
|
+
}
|
|
9810
|
+
return port;
|
|
9811
|
+
}
|
|
9208
9812
|
function codexTuiConfigArgs(session, fast) {
|
|
9209
9813
|
const modelArgs = session?.model === undefined ? [] : ["--model", session.model];
|
|
9210
9814
|
const reasoningArgs = session?.reasoningEffort === undefined ? [] : ["-c", `model_reasoning_effort="${session.reasoningEffort}"`];
|
|
@@ -9621,6 +10225,9 @@ function isUpdateRunnerConfigControl(control) {
|
|
|
9621
10225
|
function isSetPortForwardEnabledControl(control) {
|
|
9622
10226
|
return control.type === "set_port_forward_enabled" && Number.isInteger(control.port) && control.port > 0 && control.port <= 65535;
|
|
9623
10227
|
}
|
|
10228
|
+
function isResolvePortForwardRequestControl(control) {
|
|
10229
|
+
return control.type === "resolve_port_forward_request" && typeof control.requestId === "string" && (control.decision === "approve" || control.decision === "deny");
|
|
10230
|
+
}
|
|
9624
10231
|
function normalizeAllowedCwds(values) {
|
|
9625
10232
|
return Array.from(new Set(values.flatMap((value) => {
|
|
9626
10233
|
const normalized = value.trim();
|
|
@@ -9656,7 +10263,7 @@ function allowedCwdSuggestions(cwd, allowedCwds) {
|
|
|
9656
10263
|
}
|
|
9657
10264
|
|
|
9658
10265
|
// src/authCache.ts
|
|
9659
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as
|
|
10266
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
9660
10267
|
import { homedir as homedir6 } from "node:os";
|
|
9661
10268
|
import { dirname as dirname6, join as join9 } from "node:path";
|
|
9662
10269
|
function defaultAuthFilePath() {
|
|
@@ -9667,7 +10274,7 @@ function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePat
|
|
|
9667
10274
|
if (!existsSync6(authFilePath)) {
|
|
9668
10275
|
return;
|
|
9669
10276
|
}
|
|
9670
|
-
const authFile = parseAuthFile(
|
|
10277
|
+
const authFile = parseAuthFile(readFileSync6(authFilePath, "utf8"));
|
|
9671
10278
|
const kandanBaseUrl = kandanHttpBaseUrl(kandanUrl);
|
|
9672
10279
|
const entry = authFile.local_codex_runner?.[kandanBaseUrl];
|
|
9673
10280
|
if (entry === undefined || entry.access_token.trim() === "") {
|
|
@@ -9685,7 +10292,7 @@ function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePat
|
|
|
9685
10292
|
}
|
|
9686
10293
|
function writeCachedLocalRunnerToken(args) {
|
|
9687
10294
|
const authFilePath = args.authFilePath ?? defaultAuthFilePath();
|
|
9688
|
-
const existing = existsSync6(authFilePath) ? parseAuthFile(
|
|
10295
|
+
const existing = existsSync6(authFilePath) ? parseAuthFile(readFileSync6(authFilePath, "utf8")) : { version: 1 };
|
|
9689
10296
|
const kandanBaseUrl = kandanHttpBaseUrl(args.kandanUrl);
|
|
9690
10297
|
const issuedAt = new Date;
|
|
9691
10298
|
const expiresAt = args.expiresInSeconds === undefined ? undefined : new Date(issuedAt.getTime() + args.expiresInSeconds * 1000).toISOString();
|
|
@@ -9794,12 +10401,8 @@ async function acquireAndCacheToken(args) {
|
|
|
9794
10401
|
return token.accessToken;
|
|
9795
10402
|
}
|
|
9796
10403
|
|
|
9797
|
-
// src/defaultUrls.ts
|
|
9798
|
-
var defaultLinzumiHttpUrl = "https://serve.linzumi.com";
|
|
9799
|
-
var defaultLinzumiWebSocketUrl = "wss://serve.linzumi.com";
|
|
9800
|
-
|
|
9801
10404
|
// src/kandanTls.ts
|
|
9802
|
-
import { existsSync as existsSync7, readFileSync as
|
|
10405
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
|
|
9803
10406
|
import { Agent } from "undici";
|
|
9804
10407
|
import { WebSocket as WsWebSocket } from "ws";
|
|
9805
10408
|
function kandanTlsTrustFromEnv() {
|
|
@@ -9813,7 +10416,7 @@ function kandanTlsTrustFromCaFile(caFile) {
|
|
|
9813
10416
|
if (!existsSync7(trimmed)) {
|
|
9814
10417
|
throw new Error(`KANDAN_TLS_CA_FILE does not exist: ${trimmed}`);
|
|
9815
10418
|
}
|
|
9816
|
-
const ca =
|
|
10419
|
+
const ca = readFileSync7(trimmed, "utf8");
|
|
9817
10420
|
return {
|
|
9818
10421
|
caFile: trimmed,
|
|
9819
10422
|
ca,
|
|
@@ -9842,7 +10445,7 @@ function trustedWebSocketFactory(trust, WebSocketImpl = WsWebSocket) {
|
|
|
9842
10445
|
}
|
|
9843
10446
|
|
|
9844
10447
|
// src/agentBootstrap.ts
|
|
9845
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as
|
|
10448
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
9846
10449
|
import { dirname as dirname7, join as join10 } from "node:path";
|
|
9847
10450
|
import { homedir as homedir7 } from "node:os";
|
|
9848
10451
|
async function runAgentCliCommand(args, deps = {
|
|
@@ -10452,7 +11055,7 @@ function authorizationHeaders(token) {
|
|
|
10452
11055
|
return { authorization: `Bearer ${token}` };
|
|
10453
11056
|
}
|
|
10454
11057
|
function readOptionalTextFile(path) {
|
|
10455
|
-
return existsSync8(path) ?
|
|
11058
|
+
return existsSync8(path) ? readFileSync8(path, "utf8") : undefined;
|
|
10456
11059
|
}
|
|
10457
11060
|
function writeTextFile(path, content) {
|
|
10458
11061
|
mkdirSync8(dirname7(path), { recursive: true });
|
|
@@ -10532,7 +11135,7 @@ Launch target:
|
|
|
10532
11135
|
}
|
|
10533
11136
|
|
|
10534
11137
|
// src/helloLinzumiProject.ts
|
|
10535
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as
|
|
11138
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
10536
11139
|
import { dirname as dirname8, join as join11, resolve as resolve7 } from "node:path";
|
|
10537
11140
|
import { fileURLToPath } from "node:url";
|
|
10538
11141
|
var defaultHelloLinzumiProjectDir = "/tmp/hello_linzumi";
|
|
@@ -10542,7 +11145,7 @@ var defaultHelloLinzumiPort = 8787;
|
|
|
10542
11145
|
var defaultHelloLinzumiHost = "0.0.0.0";
|
|
10543
11146
|
var markerFile = ".linzumi-demo-project";
|
|
10544
11147
|
var moduleDir = dirname8(fileURLToPath(import.meta.url));
|
|
10545
|
-
var linzumiLogoSvg =
|
|
11148
|
+
var linzumiLogoSvg = readFileSync9(join11(moduleDir, "assets", "linzumi-logo.svg"), "utf8");
|
|
10546
11149
|
function createHelloLinzumiProject(input = {}) {
|
|
10547
11150
|
const options = typeof input === "string" ? { rootPath: input } : input;
|
|
10548
11151
|
const root = resolveHelloProjectRoot(options);
|
|
@@ -10597,7 +11200,7 @@ function assertWritableDemoRoot(root, reset) {
|
|
|
10597
11200
|
return;
|
|
10598
11201
|
}
|
|
10599
11202
|
const markerPath = join11(root, markerFile);
|
|
10600
|
-
const isDemoRoot = existsSync9(markerPath) &&
|
|
11203
|
+
const isDemoRoot = existsSync9(markerPath) && readFileSync9(markerPath, "utf8").trim() === "hello-linzumi";
|
|
10601
11204
|
if (isDemoRoot && reset) {
|
|
10602
11205
|
rmSync2(root, { recursive: true, force: true });
|
|
10603
11206
|
return;
|
|
@@ -11100,7 +11703,7 @@ import {
|
|
|
11100
11703
|
closeSync as closeSync2,
|
|
11101
11704
|
mkdirSync as mkdirSync10,
|
|
11102
11705
|
openSync as openSync3,
|
|
11103
|
-
readFileSync as
|
|
11706
|
+
readFileSync as readFileSync10,
|
|
11104
11707
|
watch,
|
|
11105
11708
|
writeFileSync as writeFileSync8
|
|
11106
11709
|
} from "node:fs";
|
|
@@ -11185,12 +11788,12 @@ function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), proce
|
|
|
11185
11788
|
if (!existsSync10(statusFile)) {
|
|
11186
11789
|
return { status: "missing", runnerId, statusFile };
|
|
11187
11790
|
}
|
|
11188
|
-
const record = parseRecord(
|
|
11791
|
+
const record = parseRecord(readFileSync10(statusFile, "utf8"));
|
|
11189
11792
|
return processIsRunning(record.pid) && processMatchesRecord(record, processIdentityReader) ? { status: "running", record } : { status: "stopped", record };
|
|
11190
11793
|
}
|
|
11191
11794
|
async function waitForCommanderDaemon(options) {
|
|
11192
11795
|
const now = options.now ?? (() => Date.now());
|
|
11193
|
-
const readTextFile = options.readTextFile ?? ((path) => existsSync10(path) ?
|
|
11796
|
+
const readTextFile = options.readTextFile ?? ((path) => existsSync10(path) ? readFileSync10(path, "utf8") : undefined);
|
|
11194
11797
|
const statusImpl = options.statusImpl ?? commanderDaemonStatus;
|
|
11195
11798
|
const deadline = now() + options.timeoutMs;
|
|
11196
11799
|
while (now() <= deadline) {
|
|
@@ -11445,7 +12048,7 @@ async function main(args) {
|
|
|
11445
12048
|
}
|
|
11446
12049
|
case "start": {
|
|
11447
12050
|
const options = await parseStartRunnerArgs(parsed.args);
|
|
11448
|
-
|
|
12051
|
+
addAllowedCwdForLinzumiUrl(options.cwd, options.kandanUrl);
|
|
11449
12052
|
await runLocalCodexRunner(withLocalMachineId(options));
|
|
11450
12053
|
return;
|
|
11451
12054
|
}
|
|
@@ -11548,19 +12151,16 @@ function runHelloCommand(args) {
|
|
|
11548
12151
|
`);
|
|
11549
12152
|
}
|
|
11550
12153
|
function runPathsCommand(args) {
|
|
11551
|
-
const
|
|
11552
|
-
if (
|
|
12154
|
+
const { subcommand, pathValue, linzumiUrl, help } = parsePathsCommandArgs(args);
|
|
12155
|
+
if (help || subcommand === undefined || subcommand === "help") {
|
|
11553
12156
|
process.stdout.write(pathsHelpText());
|
|
11554
12157
|
return;
|
|
11555
12158
|
}
|
|
11556
|
-
if (rest.length > 0) {
|
|
11557
|
-
throw new Error("linzumi paths accepts one path argument");
|
|
11558
|
-
}
|
|
11559
12159
|
switch (subcommand) {
|
|
11560
12160
|
case "list": {
|
|
11561
|
-
const config = readLocalConfig();
|
|
12161
|
+
const config = linzumiUrl === undefined ? readLocalConfig() : readLocalConfigForLinzumiUrl(linzumiUrl);
|
|
11562
12162
|
if (config.allowedCwds.length === 0) {
|
|
11563
|
-
process.stdout.write(`No trusted paths configured in ${localConfigPath()}
|
|
12163
|
+
process.stdout.write(`No trusted paths configured in ${localConfigPath()}${pathsScopeSuffix(linzumiUrl)}
|
|
11564
12164
|
`);
|
|
11565
12165
|
return;
|
|
11566
12166
|
}
|
|
@@ -11574,7 +12174,11 @@ function runPathsCommand(args) {
|
|
|
11574
12174
|
throw new Error("missing path for linzumi paths add");
|
|
11575
12175
|
}
|
|
11576
12176
|
const trustedPath = realpathSync6(resolve9(expandUserPath(pathValue)));
|
|
11577
|
-
|
|
12177
|
+
if (linzumiUrl === undefined) {
|
|
12178
|
+
addAllowedCwd(pathValue);
|
|
12179
|
+
} else {
|
|
12180
|
+
addAllowedCwdForLinzumiUrl(pathValue, linzumiUrl);
|
|
12181
|
+
}
|
|
11578
12182
|
process.stdout.write(`Trusted ${trustedPath}
|
|
11579
12183
|
`);
|
|
11580
12184
|
return;
|
|
@@ -11583,7 +12187,11 @@ function runPathsCommand(args) {
|
|
|
11583
12187
|
if (pathValue === undefined || pathValue.trim() === "") {
|
|
11584
12188
|
throw new Error("missing path for linzumi paths remove");
|
|
11585
12189
|
}
|
|
11586
|
-
|
|
12190
|
+
if (linzumiUrl === undefined) {
|
|
12191
|
+
removeAllowedCwd(pathValue);
|
|
12192
|
+
} else {
|
|
12193
|
+
removeAllowedCwdForLinzumiUrl(pathValue, linzumiUrl);
|
|
12194
|
+
}
|
|
11587
12195
|
process.stdout.write(`Removed trusted path ${pathValue}
|
|
11588
12196
|
`);
|
|
11589
12197
|
return;
|
|
@@ -11592,6 +12200,46 @@ function runPathsCommand(args) {
|
|
|
11592
12200
|
throw new Error(`invalid paths command: ${subcommand}`);
|
|
11593
12201
|
}
|
|
11594
12202
|
}
|
|
12203
|
+
function parsePathsCommandArgs(args) {
|
|
12204
|
+
const positional = [];
|
|
12205
|
+
let linzumiUrl;
|
|
12206
|
+
let help = false;
|
|
12207
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
12208
|
+
const arg = args[index];
|
|
12209
|
+
switch (arg) {
|
|
12210
|
+
case "--help":
|
|
12211
|
+
help = true;
|
|
12212
|
+
break;
|
|
12213
|
+
case "--linzumi-url":
|
|
12214
|
+
case "--kandan-url": {
|
|
12215
|
+
const value = args[index + 1];
|
|
12216
|
+
if (value === undefined || value.startsWith("--")) {
|
|
12217
|
+
throw new Error(`missing value for ${arg}`);
|
|
12218
|
+
}
|
|
12219
|
+
linzumiUrl = value;
|
|
12220
|
+
index += 1;
|
|
12221
|
+
break;
|
|
12222
|
+
}
|
|
12223
|
+
default:
|
|
12224
|
+
if (arg !== undefined && arg.startsWith("--")) {
|
|
12225
|
+
throw new Error(`invalid flag: ${arg}`);
|
|
12226
|
+
}
|
|
12227
|
+
positional.push(arg ?? "");
|
|
12228
|
+
}
|
|
12229
|
+
}
|
|
12230
|
+
if (positional.length > 2) {
|
|
12231
|
+
throw new Error("linzumi paths accepts one path argument");
|
|
12232
|
+
}
|
|
12233
|
+
return {
|
|
12234
|
+
subcommand: positional[0],
|
|
12235
|
+
pathValue: positional[1],
|
|
12236
|
+
linzumiUrl,
|
|
12237
|
+
help
|
|
12238
|
+
};
|
|
12239
|
+
}
|
|
12240
|
+
function pathsScopeSuffix(linzumiUrl) {
|
|
12241
|
+
return linzumiUrl === undefined ? "" : ` for ${linzumiUrl}`;
|
|
12242
|
+
}
|
|
11595
12243
|
async function runCommanderDaemonCommand(args) {
|
|
11596
12244
|
const [subcommand, ...rest] = args;
|
|
11597
12245
|
switch (subcommand) {
|
|
@@ -11823,7 +12471,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
11823
12471
|
const kandanUrl = stringValue3(values, "linzumi-url") ?? agentApiUrlToKandanUrl(tokenFile.apiUrl);
|
|
11824
12472
|
const requestedCwdValue = cwdArg ?? stringValue3(values, "cwd");
|
|
11825
12473
|
const requestedCwd = resolveUserPath(requestedCwdValue ?? process.cwd());
|
|
11826
|
-
const configuredAllowedCwds2 = requestedCwdValue === undefined && !values.has("allowed-cwd") ?
|
|
12474
|
+
const configuredAllowedCwds2 = requestedCwdValue === undefined && !values.has("allowed-cwd") ? readConfiguredAllowedCwdDetailsForLinzumiUrl(kandanUrl) : { allowedCwds: [], missingAllowedCwds: [] };
|
|
11827
12475
|
const allowedCwds = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(parseAllowedCwdList(stringValue3(values, "allowed-cwd"))) : requestedCwdValue === undefined ? configuredAllowedCwds2.allowedCwds.length > 0 ? [...configuredAllowedCwds2.allowedCwds] : assertConfiguredAllowedCwds([requestedCwd]) : assertConfiguredAllowedCwds([requestedCwd]);
|
|
11828
12476
|
const cwd = allowedCwds[0] ?? requestedCwd;
|
|
11829
12477
|
const codexBin = stringValue3(values, "codex-bin") ?? "codex";
|
|
@@ -11870,7 +12518,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
11870
12518
|
};
|
|
11871
12519
|
}
|
|
11872
12520
|
function readAgentTokenTextFile(path) {
|
|
11873
|
-
return existsSync11(path) ?
|
|
12521
|
+
return existsSync11(path) ? readFileSync11(path, "utf8") : undefined;
|
|
11874
12522
|
}
|
|
11875
12523
|
function rejectAgentRunnerTargetingFlags(values) {
|
|
11876
12524
|
const unsupportedFlags = [
|
|
@@ -11954,7 +12602,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11954
12602
|
const kandanUrl = stringValue3(values, "linzumi-url") ?? defaultLinzumiWebSocketUrl;
|
|
11955
12603
|
const cwd = stringValue3(values, "cwd") ?? process.cwd();
|
|
11956
12604
|
const cwdAllowedCwds = assertConfiguredAllowedCwds([cwd]);
|
|
11957
|
-
const localConfiguredAllowedCwds = values.has("allowed-cwd") ? { allowedCwds: [], missingAllowedCwds: [] } :
|
|
12605
|
+
const localConfiguredAllowedCwds = values.has("allowed-cwd") ? { allowedCwds: [], missingAllowedCwds: [] } : readConfiguredAllowedCwdDetailsForLinzumiUrl(kandanUrl);
|
|
11958
12606
|
const configuredAllowedCwds2 = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(parseAllowedCwdList(stringValue3(values, "allowed-cwd"))) : [...localConfiguredAllowedCwds.allowedCwds];
|
|
11959
12607
|
const codexBin = stringValue3(values, "codex-bin") ?? "codex";
|
|
11960
12608
|
const customCodeServerBin = stringValue3(values, "code-server-bin");
|
|
@@ -12167,7 +12815,7 @@ function parseChannelPath(channel) {
|
|
|
12167
12815
|
function withLocalMachineId(options) {
|
|
12168
12816
|
return {
|
|
12169
12817
|
...options,
|
|
12170
|
-
machineId: ensureLocalMachineId()
|
|
12818
|
+
machineId: localConfigScopeKey(options.kandanUrl) === localConfigScopeKey(defaultLinzumiWebSocketUrl) ? ensureLocalMachineId() : ensureLocalMachineIdForLinzumiUrl(options.kandanUrl)
|
|
12171
12819
|
};
|
|
12172
12820
|
}
|
|
12173
12821
|
function required(values, key) {
|
|
@@ -12349,13 +12997,13 @@ function pathsHelpText() {
|
|
|
12349
12997
|
return `Linzumi trusted paths
|
|
12350
12998
|
|
|
12351
12999
|
Usage:
|
|
12352
|
-
linzumi paths list
|
|
12353
|
-
linzumi paths add <path>
|
|
12354
|
-
linzumi paths remove <path>
|
|
13000
|
+
linzumi paths [--linzumi-url <ws-url>] list
|
|
13001
|
+
linzumi paths [--linzumi-url <ws-url>] add <path>
|
|
13002
|
+
linzumi paths [--linzumi-url <ws-url>] remove <path>
|
|
12355
13003
|
|
|
12356
|
-
Trusted paths are stored in ~/.linzumi/config.json.
|
|
12357
|
-
|
|
12358
|
-
|
|
13004
|
+
Trusted paths are stored in ~/.linzumi/config.json. Production/default paths
|
|
13005
|
+
use the root config fields; explicit --linzumi-url paths use a URL-scoped config
|
|
13006
|
+
section so local, staging, and production runners do not share trust state.
|
|
12359
13007
|
`;
|
|
12360
13008
|
}
|
|
12361
13009
|
function startHelpText() {
|