@linzumi/cli 0.0.29-beta → 0.0.31-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 +10 -10
- package/dist/index.js +101 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,8 +46,8 @@ Terms:
|
|
|
46
46
|
- **Bootstrapper Codex**: the outer Codex started by the pasted command. It
|
|
47
47
|
sets up Linzumi and local processes, but does not edit the demo app.
|
|
48
48
|
- **Linzumi Commander**: the long-running local bridge started with
|
|
49
|
-
`linzumi commander`; it
|
|
50
|
-
editor, and inner Codex launch.
|
|
49
|
+
`linzumi commander`; it runs as the claimed human's Commander and owns the
|
|
50
|
+
secure tunnel, trusted folder, browser editor, and inner Codex launch.
|
|
51
51
|
- **Linzumi Codex session**: the inner agent running inside the Linzumi thread;
|
|
52
52
|
it edits `/tmp/hello_linzumi` and posts progress.
|
|
53
53
|
- **Human**: the workspace owner who opens the one-time login link and watches
|
|
@@ -152,12 +152,12 @@ npx -y @linzumi/cli@latest commander daemon \
|
|
|
152
152
|
npx -y @linzumi/cli@latest commander wait --runner-id "$commander_id" --timeout-ms 30000
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
The
|
|
156
|
-
workspace/channel scope from the approval
|
|
157
|
-
`~/.linzumi/config.json`, marks approved
|
|
158
|
-
Codex's normal project config so Codex does not
|
|
159
|
-
prompt, advertises the explicit preview port, and
|
|
160
|
-
approving human unless `--listen-user` is explicitly passed. Use a unique
|
|
155
|
+
The human-owned Commander reads `~/.linzumi/agent-token.json`, uses the
|
|
156
|
+
claimed human Commander token and workspace/channel scope from the approval
|
|
157
|
+
flow, reads trusted folders from `~/.linzumi/config.json`, marks approved
|
|
158
|
+
project directories trusted in Codex's normal project config so Codex does not
|
|
159
|
+
stop for an interactive trust prompt, advertises the explicit preview port, and
|
|
160
|
+
listens only to the approving human unless `--listen-user` is explicitly passed. Use a unique
|
|
161
161
|
Commander id per launch. `linzumi commander daemon` writes a status record and
|
|
162
162
|
log under `~/.linzumi/commanders`, and `linzumi commander wait` returns only
|
|
163
163
|
after the Commander is connected.
|
|
@@ -293,7 +293,7 @@ intentionally. Every action is auditable from the thread.
|
|
|
293
293
|
## Pinning a version
|
|
294
294
|
|
|
295
295
|
```bash
|
|
296
|
-
npm install -g @linzumi/cli@0.0.
|
|
296
|
+
npm install -g @linzumi/cli@0.0.31-beta
|
|
297
297
|
linzumi --version
|
|
298
298
|
```
|
|
299
299
|
|
|
@@ -316,7 +316,7 @@ linzumi connect \
|
|
|
316
316
|
### All the flags
|
|
317
317
|
|
|
318
318
|
```text
|
|
319
|
-
--agent-token-file <path>
|
|
319
|
+
--agent-token-file <path> Bootstrap token cache for `linzumi commander`
|
|
320
320
|
--oauth-callback-host <ip> Sign-in callback host your browser can reach
|
|
321
321
|
--codex-bin <path> Codex executable, default `codex`
|
|
322
322
|
--model <name> Model requested for Codex sessions and labelled in Linzumi
|
package/dist/index.js
CHANGED
|
@@ -6622,6 +6622,60 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6622
6622
|
cleanup.actions.push(() => channelSession.close());
|
|
6623
6623
|
kandan.onReconnect(() => channelSession.handleKandanReconnect());
|
|
6624
6624
|
}
|
|
6625
|
+
const dynamicChannelSessions = new Map;
|
|
6626
|
+
cleanup.actions.push(async () => {
|
|
6627
|
+
await Promise.all(Array.from(dynamicChannelSessions.values(), (session) => session.close()));
|
|
6628
|
+
dynamicChannelSessions.clear();
|
|
6629
|
+
});
|
|
6630
|
+
const attachStartedThreadSession = async (control, cwd) => {
|
|
6631
|
+
const workspaceSlug = normalizedWorkDescription(control.workspace);
|
|
6632
|
+
const channelSlug = normalizedWorkDescription(control.channel);
|
|
6633
|
+
const kandanThreadId = normalizedWorkDescription(control.threadId);
|
|
6634
|
+
if (workspaceSlug === undefined || channelSlug === undefined || kandanThreadId === undefined || dynamicChannelSessions.has(kandanThreadId)) {
|
|
6635
|
+
return;
|
|
6636
|
+
}
|
|
6637
|
+
const listenUser = options.channelSession?.listenUser ?? identityFromAccessToken(options.token).actorUsername;
|
|
6638
|
+
if (listenUser === undefined) {
|
|
6639
|
+
throw new Error("missing listen user for Commander-started Codex session");
|
|
6640
|
+
}
|
|
6641
|
+
const session = await attachChannelSession({
|
|
6642
|
+
kandan,
|
|
6643
|
+
codex,
|
|
6644
|
+
topic,
|
|
6645
|
+
instanceId,
|
|
6646
|
+
options: {
|
|
6647
|
+
token: options.token,
|
|
6648
|
+
runnerId: options.runnerId,
|
|
6649
|
+
cwd,
|
|
6650
|
+
codexBin: options.codexBin,
|
|
6651
|
+
fast: control.fast ?? options.fast,
|
|
6652
|
+
launchTui: false,
|
|
6653
|
+
enablePortForwardWatch: true,
|
|
6654
|
+
initialForwardPorts: allowedForwardPorts,
|
|
6655
|
+
suppressedForwardPorts: () => Array.from(managedForwardPorts),
|
|
6656
|
+
onForwardPortApproved: (port) => {
|
|
6657
|
+
liveForwardPorts.add(port);
|
|
6658
|
+
return capabilitiesPayload();
|
|
6659
|
+
},
|
|
6660
|
+
onForwardPortRevoked: (port) => {
|
|
6661
|
+
liveForwardPorts.delete(port);
|
|
6662
|
+
return capabilitiesPayload();
|
|
6663
|
+
},
|
|
6664
|
+
channelSession: {
|
|
6665
|
+
workspaceSlug,
|
|
6666
|
+
channelSlug,
|
|
6667
|
+
kandanThreadId,
|
|
6668
|
+
listenUser,
|
|
6669
|
+
model: control.model,
|
|
6670
|
+
reasoningEffort: control.reasoningEffort,
|
|
6671
|
+
sandbox: control.sandbox,
|
|
6672
|
+
approvalPolicy: control.approvalPolicy
|
|
6673
|
+
}
|
|
6674
|
+
},
|
|
6675
|
+
log
|
|
6676
|
+
});
|
|
6677
|
+
dynamicChannelSessions.set(kandanThreadId, session);
|
|
6678
|
+
};
|
|
6625
6679
|
const heartbeatPayload = () => ({
|
|
6626
6680
|
instanceId,
|
|
6627
6681
|
codexUrl,
|
|
@@ -6684,6 +6738,9 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6684
6738
|
metadata
|
|
6685
6739
|
});
|
|
6686
6740
|
channelSession?.handleCodexNotification(notification.method, params);
|
|
6741
|
+
for (const session of dynamicChannelSessions.values()) {
|
|
6742
|
+
session.handleCodexNotification(notification.method, params);
|
|
6743
|
+
}
|
|
6687
6744
|
});
|
|
6688
6745
|
const handleControl = (control) => {
|
|
6689
6746
|
log("kandan.control", { control });
|
|
@@ -6777,11 +6834,11 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6777
6834
|
pushHeartbeat();
|
|
6778
6835
|
return;
|
|
6779
6836
|
}
|
|
6780
|
-
(channelSession
|
|
6837
|
+
resolveSessionControl(channelSession, dynamicChannelSessions, control).then((handled) => {
|
|
6781
6838
|
if (handled !== undefined) {
|
|
6782
6839
|
return handled;
|
|
6783
6840
|
}
|
|
6784
|
-
return applyControl(codex, kandan, topic, instanceId, options, allowedCwds.value, control);
|
|
6841
|
+
return applyControl(codex, kandan, topic, instanceId, options, allowedCwds.value, control, attachStartedThreadSession);
|
|
6785
6842
|
}).then((response) => {
|
|
6786
6843
|
return kandan.push(topic, "codex_response", response);
|
|
6787
6844
|
}).catch((error) => {
|
|
@@ -6802,6 +6859,19 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6802
6859
|
function controlTargetsInstance(control, instanceId) {
|
|
6803
6860
|
return control.instanceId === undefined || control.instanceId === instanceId;
|
|
6804
6861
|
}
|
|
6862
|
+
async function resolveSessionControl(channelSession, dynamicChannelSessions, control) {
|
|
6863
|
+
const primaryHandled = await (channelSession?.handleControl(control) ?? Promise.resolve(undefined));
|
|
6864
|
+
if (primaryHandled !== undefined) {
|
|
6865
|
+
return primaryHandled;
|
|
6866
|
+
}
|
|
6867
|
+
for (const session of dynamicChannelSessions.values()) {
|
|
6868
|
+
const handled = await session.handleControl(control);
|
|
6869
|
+
if (handled !== undefined) {
|
|
6870
|
+
return handled;
|
|
6871
|
+
}
|
|
6872
|
+
}
|
|
6873
|
+
return;
|
|
6874
|
+
}
|
|
6805
6875
|
async function closeCleanupStack(cleanup) {
|
|
6806
6876
|
if (cleanup.closePromise !== undefined) {
|
|
6807
6877
|
return cleanup.closePromise;
|
|
@@ -6916,7 +6986,7 @@ async function prepareCodexThreadForTuiResume(codex, codexThreadId) {
|
|
|
6916
6986
|
throw new Error(`failed to verify Codex TUI resume: ${verified.error.message}`);
|
|
6917
6987
|
}
|
|
6918
6988
|
}
|
|
6919
|
-
async function applyControl(codex, kandan, topic, instanceId, options, allowedCwds, control) {
|
|
6989
|
+
async function applyControl(codex, kandan, topic, instanceId, options, allowedCwds, control, onStartedThread) {
|
|
6920
6990
|
switch (control.type) {
|
|
6921
6991
|
case "start_instance": {
|
|
6922
6992
|
const cwd = resolveAllowedCwd(control.cwd, allowedCwds);
|
|
@@ -6949,7 +7019,10 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
|
|
|
6949
7019
|
const codexThreadId = extractStartedThreadId(response);
|
|
6950
7020
|
const workDescription = normalizedWorkDescription(control.workDescription);
|
|
6951
7021
|
if (codexThreadId !== undefined && developerPrompt !== undefined) {
|
|
6952
|
-
await postVisibleDeveloperPrompt(kandan, topic, control, developerPrompt);
|
|
7022
|
+
await postVisibleDeveloperPrompt(kandan, topic, control, developerPrompt, codexThreadId);
|
|
7023
|
+
}
|
|
7024
|
+
if (codexThreadId !== undefined && onStartedThread !== undefined) {
|
|
7025
|
+
await onStartedThread(control, cwd.cwd);
|
|
6953
7026
|
}
|
|
6954
7027
|
if (codexThreadId !== undefined && workDescription !== undefined) {
|
|
6955
7028
|
await codex.request("turn/start", {
|
|
@@ -7078,7 +7151,7 @@ work in the approved project folder, keep preview servers reachable through the
|
|
|
7078
7151
|
secure tunnel, and keep the Linzumi thread truthful.
|
|
7079
7152
|
</task_reminder>`;
|
|
7080
7153
|
}
|
|
7081
|
-
async function postVisibleDeveloperPrompt(kandan, topic, control, developerPrompt) {
|
|
7154
|
+
async function postVisibleDeveloperPrompt(kandan, topic, control, developerPrompt, codexThreadId) {
|
|
7082
7155
|
const workspace = normalizedWorkDescription(control.workspace);
|
|
7083
7156
|
const channel = normalizedWorkDescription(control.channel);
|
|
7084
7157
|
const threadId = normalizedWorkDescription(control.threadId);
|
|
@@ -7093,8 +7166,11 @@ async function postVisibleDeveloperPrompt(kandan, topic, control, developerPromp
|
|
|
7093
7166
|
|
|
7094
7167
|
${developerPrompt}`,
|
|
7095
7168
|
payload: {
|
|
7096
|
-
|
|
7097
|
-
|
|
7169
|
+
metadata: {
|
|
7170
|
+
local_codex_runner: {
|
|
7171
|
+
event_type: "codex_start_instructions",
|
|
7172
|
+
codex_thread_id: codexThreadId
|
|
7173
|
+
}
|
|
7098
7174
|
}
|
|
7099
7175
|
},
|
|
7100
7176
|
client_message_id: `codex-start-instructions-${threadId}`
|
|
@@ -7683,6 +7759,8 @@ async function runClaim(command, deps) {
|
|
|
7683
7759
|
const tokenFile = {
|
|
7684
7760
|
apiUrl: command.apiUrl,
|
|
7685
7761
|
agentToken: requiredString(response, "agent_token"),
|
|
7762
|
+
commanderToken: requiredString(response, "commander_token"),
|
|
7763
|
+
commanderTokenExpiresInSeconds: numberValue2(response.commander_token_expires_in_seconds),
|
|
7686
7764
|
agentId: requiredString(response, "agent_id"),
|
|
7687
7765
|
workspaceId: requiredString(response, "workspace_id"),
|
|
7688
7766
|
workspaceName: stringValue(response.workspace_name),
|
|
@@ -7696,7 +7774,7 @@ async function runClaim(command, deps) {
|
|
|
7696
7774
|
cursors: {}
|
|
7697
7775
|
};
|
|
7698
7776
|
writeTokenFile(command.tokenFile, tokenFile, deps.writeTextFile);
|
|
7699
|
-
deps.stdout.write(`Logged in as
|
|
7777
|
+
deps.stdout.write(`Logged in as ${tokenFile.ownerUsername ?? "claimed user"}
|
|
7700
7778
|
`);
|
|
7701
7779
|
if (tokenFile.workspaceName !== undefined) {
|
|
7702
7780
|
deps.stdout.write(`workspace_name: ${tokenFile.workspaceName}
|
|
@@ -7938,6 +8016,8 @@ function readStoredAgentTokenFile(path, readTextFile = readOptionalTextFile) {
|
|
|
7938
8016
|
return {
|
|
7939
8017
|
apiUrl: requiredString(parsed, "apiUrl"),
|
|
7940
8018
|
agentToken: requiredString(parsed, "agentToken"),
|
|
8019
|
+
commanderToken: requiredString(parsed, "commanderToken"),
|
|
8020
|
+
commanderTokenExpiresInSeconds: numberValue2(parsed.commanderTokenExpiresInSeconds),
|
|
7941
8021
|
agentId: requiredString(parsed, "agentId"),
|
|
7942
8022
|
workspaceId: requiredString(parsed, "workspaceId"),
|
|
7943
8023
|
workspaceName: stringValue(parsed.workspaceName),
|
|
@@ -8836,7 +8916,7 @@ async function main(args) {
|
|
|
8836
8916
|
process.stdout.write(connectGuideText());
|
|
8837
8917
|
return;
|
|
8838
8918
|
case "version":
|
|
8839
|
-
process.stdout.write(`linzumi 0.0.
|
|
8919
|
+
process.stdout.write(`linzumi 0.0.31-beta
|
|
8840
8920
|
`);
|
|
8841
8921
|
return;
|
|
8842
8922
|
case "auth":
|
|
@@ -9232,7 +9312,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
9232
9312
|
assertStartDependencies(initialDependencyStatus);
|
|
9233
9313
|
const editorRuntime = await deps.resolveEditorRuntime({
|
|
9234
9314
|
kandanUrl,
|
|
9235
|
-
token: tokenFile.
|
|
9315
|
+
token: tokenFile.commanderToken,
|
|
9236
9316
|
customCodeServerBin,
|
|
9237
9317
|
fetchImpl: trustedFetch(kandanTlsTrustFromEnv())
|
|
9238
9318
|
});
|
|
@@ -9245,7 +9325,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
9245
9325
|
assertStartDependencies(dependencyStatus);
|
|
9246
9326
|
return {
|
|
9247
9327
|
kandanUrl,
|
|
9248
|
-
token: tokenFile.
|
|
9328
|
+
token: tokenFile.commanderToken,
|
|
9249
9329
|
runnerId: stringValue3(values, "runner-id") ?? `agent-runner-${randomUUID3()}`,
|
|
9250
9330
|
cwd,
|
|
9251
9331
|
codexBin,
|
|
@@ -9276,9 +9356,15 @@ function readAgentTokenTextFile(path) {
|
|
|
9276
9356
|
return existsSync10(path) ? readFileSync9(path, "utf8") : undefined;
|
|
9277
9357
|
}
|
|
9278
9358
|
function rejectAgentRunnerTargetingFlags(values) {
|
|
9279
|
-
const unsupportedFlags = [
|
|
9359
|
+
const unsupportedFlags = [
|
|
9360
|
+
"workspace",
|
|
9361
|
+
"channel",
|
|
9362
|
+
"token",
|
|
9363
|
+
"auth-file",
|
|
9364
|
+
"oauth-callback-host"
|
|
9365
|
+
].filter((flag) => values.has(flag));
|
|
9280
9366
|
if (unsupportedFlags.length > 0) {
|
|
9281
|
-
throw new Error(`linzumi commander uses the claimed
|
|
9367
|
+
throw new Error(`linzumi commander uses the claimed human Commander token scope; remove ${unsupportedFlags.map((flag) => `--${flag}`).join(", ")}.`);
|
|
9282
9368
|
}
|
|
9283
9369
|
}
|
|
9284
9370
|
function requiredStoredAgentChannel(channelId) {
|
|
@@ -9343,7 +9429,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
9343
9429
|
process.exit(0);
|
|
9344
9430
|
}
|
|
9345
9431
|
if (values.get("version") === true) {
|
|
9346
|
-
process.stdout.write(`linzumi 0.0.
|
|
9432
|
+
process.stdout.write(`linzumi 0.0.31-beta
|
|
9347
9433
|
`);
|
|
9348
9434
|
process.exit(0);
|
|
9349
9435
|
}
|
|
@@ -9770,7 +9856,7 @@ Usage:
|
|
|
9770
9856
|
linzumi agent runner <folder> [options]
|
|
9771
9857
|
|
|
9772
9858
|
What it does:
|
|
9773
|
-
Starts this computer as the claimed
|
|
9859
|
+
Starts this computer as the claimed human's scoped Linzumi Commander. The command
|
|
9774
9860
|
reads ~/.linzumi/agent-token.json, uses its workspace/channel scope, reads
|
|
9775
9861
|
trusted folders from ~/.linzumi/config.json when no folder is passed, and
|
|
9776
9862
|
listens only to the owning human recorded during claim unless --listen-user is
|
package/package.json
CHANGED