botmux 3.4.2 → 3.4.3
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/dist/adapters/cli/pi-initial-prompt-extension.d.ts +27 -0
- package/dist/adapters/cli/pi-initial-prompt-extension.d.ts.map +1 -0
- package/dist/adapters/cli/pi-initial-prompt-extension.js +38 -0
- package/dist/adapters/cli/pi-initial-prompt-extension.js.map +1 -0
- package/dist/adapters/cli/pi-initial-prompt.d.ts +21 -0
- package/dist/adapters/cli/pi-initial-prompt.d.ts.map +1 -0
- package/dist/adapters/cli/pi-initial-prompt.js +55 -0
- package/dist/adapters/cli/pi-initial-prompt.js.map +1 -0
- package/dist/adapters/cli/pi.d.ts.map +1 -1
- package/dist/adapters/cli/pi.js +15 -4
- package/dist/adapters/cli/pi.js.map +1 -1
- package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
- package/dist/adapters/cli/read-isolation.js +5 -0
- package/dist/adapters/cli/read-isolation.js.map +1 -1
- package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
- package/dist/adapters/cli/shared-hints.js +8 -0
- package/dist/adapters/cli/shared-hints.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +20 -0
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/core/inflight-input-tracker.d.ts +1 -0
- package/dist/core/inflight-input-tracker.d.ts.map +1 -1
- package/dist/core/inflight-input-tracker.js.map +1 -1
- package/dist/platform/secure-host-file.d.ts.map +1 -1
- package/dist/platform/secure-host-file.js +173 -54
- package/dist/platform/secure-host-file.js.map +1 -1
- package/dist/utils/child-env.d.ts +1 -1
- package/dist/utils/child-env.d.ts.map +1 -1
- package/dist/utils/child-env.js +2 -0
- package/dist/utils/child-env.js.map +1 -1
- package/dist/utils/pending-input-queue.d.ts +19 -0
- package/dist/utils/pending-input-queue.d.ts.map +1 -1
- package/dist/utils/pending-input-queue.js +21 -1
- package/dist/utils/pending-input-queue.js.map +1 -1
- package/dist/worker.js +102 -24
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* 7. On 'restart', kills CLI and re-spawns with --resume
|
|
14
14
|
*/
|
|
15
15
|
import { randomBytes } from 'node:crypto';
|
|
16
|
-
import { mkdirSync, writeFileSync, unlinkSync, existsSync, statSync, lstatSync, readdirSync, readlinkSync, readFileSync, realpathSync, copyFileSync, watch as fsWatch, createWriteStream, openSync, closeSync, fstatSync, constants as fsConstants } from 'node:fs';
|
|
16
|
+
import { mkdirSync, writeFileSync, unlinkSync, rmdirSync, existsSync, statSync, lstatSync, readdirSync, readlinkSync, readFileSync, realpathSync, copyFileSync, watch as fsWatch, createWriteStream, openSync, closeSync, fstatSync, constants as fsConstants } from 'node:fs';
|
|
17
17
|
import { atomicWriteFileSync } from './utils/atomic-write.js';
|
|
18
18
|
import { join, basename, dirname } from 'node:path';
|
|
19
19
|
import { homedir, tmpdir } from 'node:os';
|
|
@@ -29,7 +29,7 @@ import { canStartInjectionFlush, shouldDeferUserFlush, shouldFlushInjectionsFirs
|
|
|
29
29
|
import { stripAnsiForLog, tailChars } from './utils/crash-log.js';
|
|
30
30
|
import { CodexUpdateDialogGuard } from './utils/codex-update-dialog.js';
|
|
31
31
|
import { installStdioEpipeGuard, isIgnorableStreamError } from './utils/stdio-epipe-guard.js';
|
|
32
|
-
import { mergeQueuedCliInput, pendingInputMayFlush, pendingInputAllowsTypeAhead, shouldDeferArgsBakedDurablePrompt, shouldDeferInitialPromptForArgLimit, shouldStopPendingBatch, terminalReleasesDurableTurn, } from './utils/pending-input-queue.js';
|
|
32
|
+
import { mergeQueuedCliInput, pendingInputMayFlush, pendingInputAllowsTypeAhead, resolveInitialPromptDelivery, shouldDeferArgsBakedDurablePrompt, shouldDeferInitialPromptForArgLimit, shouldStopPendingBatch, terminalReleasesDurableTurn, } from './utils/pending-input-queue.js';
|
|
33
33
|
import { ReadyGate, shouldArmReadyGate } from './utils/ready-gate.js';
|
|
34
34
|
import { shouldRunStartupCommandsOnSpawn, shouldDeferInitialPromptForStartup } from './core/startup-commands.js';
|
|
35
35
|
import { sanitizePerBotEnv } from './core/per-bot-env.js';
|
|
@@ -144,6 +144,36 @@ let remoteWsUrl;
|
|
|
144
144
|
let remoteThreadId;
|
|
145
145
|
let rpcDialogDismissTimer = null;
|
|
146
146
|
let rpcEnginePidMarker = null;
|
|
147
|
+
const piInitialPromptCleanupPaths = [];
|
|
148
|
+
const piInitialPromptCleanupDirs = [];
|
|
149
|
+
let piInitialPromptReadonlyRoots = [];
|
|
150
|
+
let piInitialPromptAdditionalArgs = [];
|
|
151
|
+
let piInitialPromptEnv = {};
|
|
152
|
+
function cleanupPiInitialPromptFiles() {
|
|
153
|
+
while (piInitialPromptCleanupPaths.length > 0) {
|
|
154
|
+
const p = piInitialPromptCleanupPaths.pop();
|
|
155
|
+
if (!p)
|
|
156
|
+
continue;
|
|
157
|
+
try {
|
|
158
|
+
unlinkSync(p);
|
|
159
|
+
}
|
|
160
|
+
catch { /* best effort */ }
|
|
161
|
+
}
|
|
162
|
+
while (piInitialPromptCleanupDirs.length > 0) {
|
|
163
|
+
const dir = piInitialPromptCleanupDirs.pop();
|
|
164
|
+
if (!dir)
|
|
165
|
+
continue;
|
|
166
|
+
// Non-recursive on purpose: remove only an empty session-owned directory,
|
|
167
|
+
// never a shared root or a path that unexpectedly gained other content.
|
|
168
|
+
try {
|
|
169
|
+
rmdirSync(dir);
|
|
170
|
+
}
|
|
171
|
+
catch { /* best effort */ }
|
|
172
|
+
}
|
|
173
|
+
piInitialPromptReadonlyRoots = [];
|
|
174
|
+
piInitialPromptAdditionalArgs = [];
|
|
175
|
+
piInitialPromptEnv = {};
|
|
176
|
+
}
|
|
147
177
|
function stopCodexRpcEngine() {
|
|
148
178
|
const engine = codexRpcEngine;
|
|
149
179
|
codexRpcEngine = undefined;
|
|
@@ -672,6 +702,9 @@ let reattachIdleProbeTimer = null;
|
|
|
672
702
|
* adapter's hermesBridgeAttach reads the correct mode. */
|
|
673
703
|
let lastSpawnEffectiveResume = false;
|
|
674
704
|
let lastSpawnEffectiveCliSessionId;
|
|
705
|
+
let lastSpawnDeferInitialPrompt = false;
|
|
706
|
+
let lastSpawnQueuedInitialPrompt;
|
|
707
|
+
let lastSpawnQueuedInitialPromptLogicalContent;
|
|
675
708
|
let idleDetector = null;
|
|
676
709
|
let isTmuxMode = false;
|
|
677
710
|
/** True once a crash diagnostic tmux shell (bmx-diag-<sid>) is live. */
|
|
@@ -5068,6 +5101,7 @@ async function flushPending() {
|
|
|
5068
5101
|
if (!codexRpcEngine)
|
|
5069
5102
|
inflightInputs.onWrite(item);
|
|
5070
5103
|
const msg = item.content;
|
|
5104
|
+
const logicalMsg = item.logicalContent ?? msg;
|
|
5071
5105
|
currentBotmuxTurnId = item.turnId;
|
|
5072
5106
|
currentBotmuxDispatchAttempt = item.dispatchAttempt;
|
|
5073
5107
|
currentVcMeetingImTurnOrigin = item.vcMeetingImTurnOrigin;
|
|
@@ -5087,14 +5121,14 @@ async function flushPending() {
|
|
|
5087
5121
|
bridgeIngest();
|
|
5088
5122
|
}
|
|
5089
5123
|
catch { /* best-effort */ }
|
|
5090
|
-
bridgeTurnId = bridgeMarkPendingTurn(
|
|
5124
|
+
bridgeTurnId = bridgeMarkPendingTurn(logicalMsg, item.turnId, item.dispatchAttempt);
|
|
5091
5125
|
}
|
|
5092
5126
|
else if (codexBridgeActive) {
|
|
5093
5127
|
// Codex mark works even before the rollout path is known: the
|
|
5094
5128
|
// queue is path-agnostic, and the late-attach below will start
|
|
5095
5129
|
// ingest from offset 0 so the user_message that lands shortly
|
|
5096
5130
|
// after still fingerprint-matches this turn.
|
|
5097
|
-
codexBridgeMarkPendingTurn(
|
|
5131
|
+
codexBridgeMarkPendingTurn(logicalMsg, item.turnId, item.dispatchAttempt);
|
|
5098
5132
|
}
|
|
5099
5133
|
if (durableWrite
|
|
5100
5134
|
&& cliAdapter.reliableTurnTerminal === true
|
|
@@ -5148,7 +5182,7 @@ async function flushPending() {
|
|
|
5148
5182
|
// do. Otherwise surface it as a submit failure so the message isn't
|
|
5149
5183
|
// silently lost.
|
|
5150
5184
|
if (backend)
|
|
5151
|
-
scheduleSubmitFailureNotify(
|
|
5185
|
+
scheduleSubmitFailureNotify(logicalMsg, undefined, '会话 JSONL', bridgeTurnId, undefined, turnSeq, item);
|
|
5152
5186
|
break;
|
|
5153
5187
|
}
|
|
5154
5188
|
// Persist any sessionId the adapter observed via authoritative sources
|
|
@@ -5167,7 +5201,7 @@ async function flushPending() {
|
|
|
5167
5201
|
// nulled backend) the user already got a "CLI exited" notice; don't also
|
|
5168
5202
|
// nag that the submit wasn't confirmed.
|
|
5169
5203
|
if (result && result.submitted === false && backend) {
|
|
5170
|
-
scheduleSubmitFailureNotify(
|
|
5204
|
+
scheduleSubmitFailureNotify(logicalMsg, result.recheck, '会话 JSONL', bridgeTurnId, result.failureReason, turnSeq, item);
|
|
5171
5205
|
}
|
|
5172
5206
|
// All structured bridges now drain every pending message in one flush:
|
|
5173
5207
|
// Claude's BridgeTurnQueue handles `attachment(queued_command)` events
|
|
@@ -6340,6 +6374,30 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6340
6374
|
// baking it into args would drop the message that triggered the resume.
|
|
6341
6375
|
// Finally, defer adapter-declared over-limit prompts to avoid backend command
|
|
6342
6376
|
// string limits (tmux "command too long") while preserving short argv prompts.
|
|
6377
|
+
let preparedInitialPrompt;
|
|
6378
|
+
let promptArgPreparationChanged = false;
|
|
6379
|
+
let preparedDeferredInput;
|
|
6380
|
+
if (cfg.prompt) {
|
|
6381
|
+
const prepared = cliAdapter.prepareInitialPromptArg?.({
|
|
6382
|
+
initialPrompt: cfg.prompt,
|
|
6383
|
+
sessionId: effectiveAdapterSessionId,
|
|
6384
|
+
sessionDataDir: process.env.SESSION_DATA_DIR,
|
|
6385
|
+
});
|
|
6386
|
+
if (prepared?.readonlyRoots?.length) {
|
|
6387
|
+
piInitialPromptReadonlyRoots = [
|
|
6388
|
+
...new Set([...piInitialPromptReadonlyRoots, ...prepared.readonlyRoots]),
|
|
6389
|
+
];
|
|
6390
|
+
}
|
|
6391
|
+
if (prepared?.cleanupPaths?.length) {
|
|
6392
|
+
piInitialPromptCleanupPaths.push(...prepared.cleanupPaths);
|
|
6393
|
+
}
|
|
6394
|
+
if (prepared?.cleanupDirs?.length) {
|
|
6395
|
+
piInitialPromptCleanupDirs.push(...prepared.cleanupDirs);
|
|
6396
|
+
}
|
|
6397
|
+
preparedDeferredInput = prepared?.deferredInput;
|
|
6398
|
+
preparedInitialPrompt = prepared?.initialPrompt ?? cfg.prompt;
|
|
6399
|
+
promptArgPreparationChanged = preparedInitialPrompt !== cfg.prompt;
|
|
6400
|
+
}
|
|
6343
6401
|
const deferInitialPrompt = shouldDeferInitialPromptForStartup({
|
|
6344
6402
|
hasStartupCommands: !!cfg.startupCommands?.length,
|
|
6345
6403
|
adoptMode: cfg.adoptMode === true,
|
|
@@ -6349,11 +6407,25 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6349
6407
|
adoptMode: cfg.adoptMode === true,
|
|
6350
6408
|
dispatchAttempt: cfg.dispatchAttempt,
|
|
6351
6409
|
}) || (effectiveResume && cliAdapter.initialPromptArgsIgnoredOnResume === true)
|
|
6352
|
-
|| shouldDeferInitialPromptForArgLimit({
|
|
6410
|
+
|| (!promptArgPreparationChanged && shouldDeferInitialPromptForArgLimit({
|
|
6353
6411
|
passesInitialPromptViaArgs: cliAdapter.passesInitialPromptViaArgs === true,
|
|
6354
6412
|
prompt: cfg.prompt,
|
|
6355
6413
|
maxInitialPromptArgBytes: cliAdapter.maxInitialPromptArgBytes,
|
|
6356
|
-
});
|
|
6414
|
+
}));
|
|
6415
|
+
const initialPromptDelivery = resolveInitialPromptDelivery({
|
|
6416
|
+
originalPrompt: cfg.prompt,
|
|
6417
|
+
preparedArg: preparedInitialPrompt,
|
|
6418
|
+
preparedDeferredContent: preparedDeferredInput?.content,
|
|
6419
|
+
defer: deferInitialPrompt,
|
|
6420
|
+
});
|
|
6421
|
+
preparedInitialPrompt = initialPromptDelivery.argvPrompt;
|
|
6422
|
+
lastSpawnQueuedInitialPrompt = initialPromptDelivery.queuedContent;
|
|
6423
|
+
lastSpawnQueuedInitialPromptLogicalContent = initialPromptDelivery.logicalContent;
|
|
6424
|
+
if (deferInitialPrompt && preparedDeferredInput) {
|
|
6425
|
+
piInitialPromptAdditionalArgs = [...(preparedDeferredInput.additionalArgs ?? [])];
|
|
6426
|
+
piInitialPromptEnv = { ...(preparedDeferredInput.env ?? {}) };
|
|
6427
|
+
}
|
|
6428
|
+
lastSpawnDeferInitialPrompt = deferInitialPrompt;
|
|
6357
6429
|
kiroSessionIdCaptureArmed = cfg.cliId === 'kiro-cli' && !effectiveCliSessionId && !willReattachPersistent;
|
|
6358
6430
|
kiroSessionIdCaptureBuffer = '';
|
|
6359
6431
|
// Per-bot local read isolation: assemble the Seatbelt profile context (the gate
|
|
@@ -6432,7 +6504,7 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6432
6504
|
resume: effectiveResume,
|
|
6433
6505
|
workingDir: cfg.workingDir,
|
|
6434
6506
|
resumeSessionId: effectiveCliSessionId,
|
|
6435
|
-
initialPrompt:
|
|
6507
|
+
initialPrompt: preparedInitialPrompt,
|
|
6436
6508
|
botName: cfg.botName,
|
|
6437
6509
|
botOpenId: cfg.botOpenId,
|
|
6438
6510
|
larkAppId: cfg.larkAppId,
|
|
@@ -6447,6 +6519,12 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6447
6519
|
remoteWsUrl,
|
|
6448
6520
|
remoteThreadId,
|
|
6449
6521
|
});
|
|
6522
|
+
// Pi's deferred long-first-prompt command is implemented by a session-scoped
|
|
6523
|
+
// extension. Keep its launch args across owned process restarts while the
|
|
6524
|
+
// queued/in-flight command may still need replay.
|
|
6525
|
+
if (piInitialPromptAdditionalArgs.length > 0) {
|
|
6526
|
+
args.unshift(...piInitialPromptAdditionalArgs);
|
|
6527
|
+
}
|
|
6450
6528
|
// Extra args from env (CLI_DISABLE_DEFAULT_ARGS is removed — adapters own their defaults)
|
|
6451
6529
|
const extra = (process.env.CLI_EXTRA_ARGS ?? '').trim();
|
|
6452
6530
|
if (extra)
|
|
@@ -6576,6 +6654,9 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6576
6654
|
// them past the server's global env.
|
|
6577
6655
|
if (cliAdapter.spawnEnv)
|
|
6578
6656
|
Object.assign(childEnv, cliAdapter.spawnEnv);
|
|
6657
|
+
if (Object.keys(piInitialPromptEnv).length > 0) {
|
|
6658
|
+
Object.assign(childEnv, piInitialPromptEnv);
|
|
6659
|
+
}
|
|
6579
6660
|
// v2 read isolation: point the CLI at its PER-BOT config dir (set AFTER spawnEnv
|
|
6580
6661
|
// so it overrides any adapter default). claude → CLAUDE_CONFIG_DIR, codex →
|
|
6581
6662
|
// CODEX_HOME. Both are in BOTMUX_INJECTED_ENV_KEYS so the tmux backend forwards
|
|
@@ -6662,6 +6743,9 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6662
6743
|
cliId: cliAdapter.id,
|
|
6663
6744
|
resolvedBin: canonical(cliAdapter.resolvedBin),
|
|
6664
6745
|
}),
|
|
6746
|
+
// buildV2DenyPaths masks the shared pi-initial-prompts root. Re-open
|
|
6747
|
+
// only this session's private child directory for Pi's @file/extension.
|
|
6748
|
+
...piInitialPromptReadonlyRoots.map(canonical),
|
|
6665
6749
|
];
|
|
6666
6750
|
finalDenyPaths = carve.finalDenyPaths.map(canonical);
|
|
6667
6751
|
traverseDirs = carve.traverseDirs.map(canonical);
|
|
@@ -6908,6 +6992,7 @@ async function spawnCli(cfg, opts = {}) {
|
|
|
6908
6992
|
extraExecPaths: cliAdapter.sandboxExtraExecPaths?.(),
|
|
6909
6993
|
readonlyRoots: [
|
|
6910
6994
|
...(cfg.skillReadonlyRoots ?? []),
|
|
6995
|
+
...piInitialPromptReadonlyRoots,
|
|
6911
6996
|
...(readIsoLinuxMasks?.ownReadOnlyPaths ?? []),
|
|
6912
6997
|
],
|
|
6913
6998
|
mcpGatewaySocketPath: sessionMcpGatewayHost?.socketPath,
|
|
@@ -7661,6 +7746,8 @@ function killCli(opts = {}) {
|
|
|
7661
7746
|
currentCliCredentialIsolated = false;
|
|
7662
7747
|
stopSessionMcpGatewayHost();
|
|
7663
7748
|
stopCodexRpcEngine();
|
|
7749
|
+
if (!opts.preservePending)
|
|
7750
|
+
cleanupPiInitialPromptFiles();
|
|
7664
7751
|
destroyCrashDiagnosticTerminal('killCli');
|
|
7665
7752
|
idleDetector?.dispose();
|
|
7666
7753
|
idleDetector = null;
|
|
@@ -9133,20 +9220,7 @@ process.on('message', async (raw) => {
|
|
|
9133
9220
|
// Tier-1/Tier-2 fresh demotion, which clears the flag). Adopt spawns
|
|
9134
9221
|
// return from spawnCli before that write — exclude them explicitly so
|
|
9135
9222
|
// the stale module-level value can't leak in.
|
|
9136
|
-
const deferInitialPrompt =
|
|
9137
|
-
hasStartupCommands: !!msg.startupCommands?.length,
|
|
9138
|
-
adoptMode: msg.adoptMode === true,
|
|
9139
|
-
passesInitialPromptViaArgs: cliAdapter?.passesInitialPromptViaArgs === true,
|
|
9140
|
-
}) || shouldDeferArgsBakedDurablePrompt({
|
|
9141
|
-
passesInitialPromptViaArgs: cliAdapter?.passesInitialPromptViaArgs === true,
|
|
9142
|
-
adoptMode: msg.adoptMode === true,
|
|
9143
|
-
dispatchAttempt: msg.dispatchAttempt,
|
|
9144
|
-
}) || (msg.adoptMode !== true && lastSpawnEffectiveResume && cliAdapter?.initialPromptArgsIgnoredOnResume === true)
|
|
9145
|
-
|| shouldDeferInitialPromptForArgLimit({
|
|
9146
|
-
passesInitialPromptViaArgs: cliAdapter?.passesInitialPromptViaArgs === true,
|
|
9147
|
-
prompt: msg.prompt,
|
|
9148
|
-
maxInitialPromptArgBytes: cliAdapter?.maxInitialPromptArgBytes,
|
|
9149
|
-
});
|
|
9223
|
+
const deferInitialPrompt = lastSpawnDeferInitialPrompt;
|
|
9150
9224
|
if (msg.prompt && cliAdapter?.passesInitialPromptViaArgs && !deferInitialPrompt && codexBridgeFallbackActive()) {
|
|
9151
9225
|
// Args-baked first prompts (notably Pi) never pass through the normal
|
|
9152
9226
|
// 'message' IPC path, so the structured bridge would otherwise see the
|
|
@@ -9171,7 +9245,10 @@ process.on('message', async (raw) => {
|
|
|
9171
9245
|
deferInitialPrompt,
|
|
9172
9246
|
})) {
|
|
9173
9247
|
pendingMessages.push({
|
|
9174
|
-
content: msg.prompt,
|
|
9248
|
+
content: lastSpawnQueuedInitialPrompt ?? msg.prompt,
|
|
9249
|
+
...(lastSpawnQueuedInitialPromptLogicalContent
|
|
9250
|
+
? { logicalContent: lastSpawnQueuedInitialPromptLogicalContent }
|
|
9251
|
+
: {}),
|
|
9175
9252
|
turnId: msg.turnId,
|
|
9176
9253
|
dispatchAttempt: msg.dispatchAttempt,
|
|
9177
9254
|
vcMeetingImTurnOrigin: msg.vcMeetingImTurnOrigin,
|
|
@@ -9633,6 +9710,7 @@ process.on('message', async (raw) => {
|
|
|
9633
9710
|
});
|
|
9634
9711
|
// ─── Cleanup ─────────────────────────────────────────────────────────────────
|
|
9635
9712
|
function cleanup() {
|
|
9713
|
+
cleanupPiInitialPromptFiles();
|
|
9636
9714
|
stopSessionMcpGatewayHost();
|
|
9637
9715
|
if (tmuxRestartTimer) {
|
|
9638
9716
|
clearTimeout(tmuxRestartTimer);
|