theslopmachine 1.0.26-beta.2 → 1.0.26-beta.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.
|
@@ -274,7 +274,7 @@ export async function captureTmuxPaneArtifact(sessionName, filePath) {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
export async function tmuxSendEnter(sessionName) {
|
|
277
|
-
return runCommand('tmux', ['send-keys', '-t', sessionName, '
|
|
277
|
+
return runCommand('tmux', ['send-keys', '-t', sessionName, 'C-m'])
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
export async function tmuxSendKeys(sessionName, ...keys) {
|
|
@@ -282,11 +282,37 @@ export async function tmuxSendKeys(sessionName, ...keys) {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
export async function tmuxSendCommand(sessionName, command) {
|
|
285
|
-
return tmuxSendKeys(sessionName, command, '
|
|
285
|
+
return tmuxSendKeys(sessionName, command, 'C-m')
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function executableExists(filePath) {
|
|
289
|
+
try {
|
|
290
|
+
await fs.access(filePath, fsConstants.X_OK)
|
|
291
|
+
return true
|
|
292
|
+
} catch {
|
|
293
|
+
return false
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function resolveTmuxShell() {
|
|
298
|
+
const candidates = [
|
|
299
|
+
process.env.SHELL,
|
|
300
|
+
'/bin/zsh',
|
|
301
|
+
'/usr/bin/zsh',
|
|
302
|
+
'/bin/bash',
|
|
303
|
+
'/usr/bin/bash',
|
|
304
|
+
'/bin/sh',
|
|
305
|
+
'/usr/bin/sh',
|
|
306
|
+
].filter(Boolean)
|
|
307
|
+
|
|
308
|
+
for (const candidate of candidates) {
|
|
309
|
+
if (await executableExists(candidate)) return candidate
|
|
310
|
+
}
|
|
311
|
+
return process.env.SHELL || '/bin/sh'
|
|
286
312
|
}
|
|
287
313
|
|
|
288
314
|
export async function tmuxStartShellSession(sessionName, cwd) {
|
|
289
|
-
const shell =
|
|
315
|
+
const shell = await resolveTmuxShell()
|
|
290
316
|
const args = ['new-session', '-d', '-s', sessionName, '-c', cwd, shell]
|
|
291
317
|
if (path.basename(shell) === 'zsh') args.push('-f')
|
|
292
318
|
if (path.basename(shell) === 'bash') args.push('--noprofile', '--norc')
|
|
@@ -294,7 +320,7 @@ export async function tmuxStartShellSession(sessionName, cwd) {
|
|
|
294
320
|
}
|
|
295
321
|
|
|
296
322
|
export async function tmuxRespawnShellPane(sessionName, cwd) {
|
|
297
|
-
const shell =
|
|
323
|
+
const shell = await resolveTmuxShell()
|
|
298
324
|
const args = ['respawn-pane', '-k', '-t', sessionName, '-c', cwd, shell]
|
|
299
325
|
if (path.basename(shell) === 'zsh') args.push('-f')
|
|
300
326
|
if (path.basename(shell) === 'bash') args.push('--noprofile', '--norc')
|
|
@@ -491,6 +517,10 @@ function detectClaudeStartupPrompt(pane) {
|
|
|
491
517
|
return 'workspace-trust'
|
|
492
518
|
}
|
|
493
519
|
|
|
520
|
+
if (pane.includes('Choose the text style that looks best with your terminal')) {
|
|
521
|
+
return 'theme-selection'
|
|
522
|
+
}
|
|
523
|
+
|
|
494
524
|
return null
|
|
495
525
|
}
|
|
496
526
|
|
|
@@ -537,6 +567,14 @@ export function detectClaudePopupOrBlockedPane(pane) {
|
|
|
537
567
|
}
|
|
538
568
|
|
|
539
569
|
const normalized = pane.toLowerCase()
|
|
570
|
+
if (normalized.includes('select login method') || normalized.includes('not logged in') || normalized.includes('run /login')) {
|
|
571
|
+
return 'login-required'
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (normalized.includes('cannot be used with root/sudo privileges')) {
|
|
575
|
+
return 'root-not-allowed'
|
|
576
|
+
}
|
|
577
|
+
|
|
540
578
|
if (/press\s+(enter|return)|hit\s+(enter|return)|enter\s+to\s+continue|return\s+to\s+continue|enter\s+to\s+confirm|return\s+to\s+confirm/.test(normalized)) {
|
|
541
579
|
return 'enter-confirmation'
|
|
542
580
|
}
|
|
@@ -63,12 +63,25 @@ function shouldRetryFreshTmuxLaunch(error) {
|
|
|
63
63
|
return [
|
|
64
64
|
'claude_launch_no_sessionstart',
|
|
65
65
|
'claude_launch_not_ready',
|
|
66
|
-
'claude_launch_prompt_blocked',
|
|
67
66
|
'claude_launch_missing_session_id',
|
|
68
67
|
'claude_orientation_submit_hook_missing',
|
|
69
68
|
].includes(code)
|
|
70
69
|
}
|
|
71
70
|
|
|
71
|
+
function hasProvenClaudeSession(state) {
|
|
72
|
+
if (!state?.sid) return false
|
|
73
|
+
if (!['idle', 'running', 'stopped'].includes(state.status)) return false
|
|
74
|
+
return Boolean(
|
|
75
|
+
state.last_hook_event === 'SessionStart'
|
|
76
|
+
|| state.transcript_path
|
|
77
|
+
|| state.orientation_verified_at
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function resumableSidFromState(state) {
|
|
82
|
+
return hasProvenClaudeSession(state) ? state.sid : null
|
|
83
|
+
}
|
|
84
|
+
|
|
72
85
|
async function waitForLaunchReadyWithRecovery({ paths, runtimeDir, launchTimeoutMs }) {
|
|
73
86
|
const windows = buildLaunchAttemptWindows(launchTimeoutMs)
|
|
74
87
|
let lastInspection = null
|
|
@@ -380,18 +393,19 @@ try {
|
|
|
380
393
|
}
|
|
381
394
|
|
|
382
395
|
const paths = buildRuntimePaths(runtimeDir)
|
|
396
|
+
let provenSidBeforeLaunch = null
|
|
383
397
|
|
|
384
398
|
try {
|
|
385
399
|
await ensureRuntimeDirs(paths)
|
|
386
400
|
|
|
387
401
|
const existingState = await readJsonIfExists(paths.stateFile)
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
&&
|
|
392
|
-
&& !
|
|
402
|
+
provenSidBeforeLaunch = resumableSidFromState(existingState)
|
|
403
|
+
const existingTmuxAlive = existingState?.tmux_session ? await tmuxHasSession(existingState.tmux_session) : false
|
|
404
|
+
const incompleteLaunchState = existingState
|
|
405
|
+
&& ['starting', 'failed'].includes(existingState.status)
|
|
406
|
+
&& !hasProvenClaudeSession(existingState)
|
|
393
407
|
let resumeSessionId = explicitResumeSessionId
|
|
394
|
-
if (!
|
|
408
|
+
if (!incompleteLaunchState && existingState?.tmux_session && existingTmuxAlive) {
|
|
395
409
|
if (!replace) {
|
|
396
410
|
const existingInspection = await inspectLaunchState(paths, existingState.tmux_session)
|
|
397
411
|
const canReuseExistingClaudeProcess = existingState.status !== 'stopped' && !resumeRequested
|
|
@@ -420,7 +434,15 @@ try {
|
|
|
420
434
|
process.exit(0)
|
|
421
435
|
}
|
|
422
436
|
if (existingState.status === 'stopped' || resumeRequested) {
|
|
423
|
-
resumeSessionId = resumeSessionId || existingState
|
|
437
|
+
resumeSessionId = resumeSessionId || resumableSidFromState(existingState)
|
|
438
|
+
if (resumeRequested && !resumeSessionId) {
|
|
439
|
+
emitFailure('claude_live_resume_unproven_sid', 'Cannot resume: runtime state does not contain a proven Claude session id.', {
|
|
440
|
+
sid: existingState.sid || null,
|
|
441
|
+
state_file: paths.stateFile,
|
|
442
|
+
result_file: paths.resultFile,
|
|
443
|
+
})
|
|
444
|
+
process.exit(1)
|
|
445
|
+
}
|
|
424
446
|
await writeState(runtimeDir, {
|
|
425
447
|
status: 'starting',
|
|
426
448
|
sid: resumeSessionId,
|
|
@@ -438,7 +460,7 @@ try {
|
|
|
438
460
|
})
|
|
439
461
|
process.exit(1)
|
|
440
462
|
}
|
|
441
|
-
resumeSessionId = resumeSessionId || existingState
|
|
463
|
+
resumeSessionId = resumeSessionId || resumableSidFromState(existingState)
|
|
442
464
|
await writeState(runtimeDir, {
|
|
443
465
|
status: 'starting',
|
|
444
466
|
sid: resumeSessionId,
|
|
@@ -458,28 +480,30 @@ try {
|
|
|
458
480
|
})
|
|
459
481
|
process.exit(1)
|
|
460
482
|
}
|
|
461
|
-
if (resumeRequested && !resumeSessionId) resumeSessionId = existingState
|
|
483
|
+
if (resumeRequested && !resumeSessionId) resumeSessionId = resumableSidFromState(existingState)
|
|
462
484
|
}
|
|
463
|
-
} else if (existingState && !
|
|
464
|
-
|
|
465
|
-
|
|
485
|
+
} else if (existingState && !incompleteLaunchState && !replace && existingState.status && existingState.status !== 'stopped') {
|
|
486
|
+
const existingSid = resumableSidFromState(existingState)
|
|
487
|
+
if (!existingSid) {
|
|
488
|
+
emitFailure('claude_live_state_stale', 'Existing runtime state is stale and has no proven Claude session id to resume.', {
|
|
466
489
|
sid: existingState.sid || null,
|
|
467
490
|
state_file: paths.stateFile,
|
|
468
491
|
result_file: paths.resultFile,
|
|
469
492
|
})
|
|
470
493
|
process.exit(1)
|
|
471
494
|
}
|
|
472
|
-
resumeSessionId = resumeSessionId ||
|
|
495
|
+
resumeSessionId = resumeSessionId || existingSid
|
|
473
496
|
} else if (resumeRequested && !resumeSessionId) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
497
|
+
const existingSid = resumableSidFromState(existingState)
|
|
498
|
+
if (!existingSid) {
|
|
499
|
+
emitFailure('claude_live_resume_missing_sid', 'Cannot resume: no --resume-sid was provided and runtime state has no proven Claude session id.', {
|
|
500
|
+
sid: existingState?.sid || null,
|
|
477
501
|
state_file: paths.stateFile,
|
|
478
502
|
result_file: paths.resultFile,
|
|
479
503
|
})
|
|
480
504
|
process.exit(1)
|
|
481
505
|
}
|
|
482
|
-
resumeSessionId =
|
|
506
|
+
resumeSessionId = existingSid
|
|
483
507
|
}
|
|
484
508
|
|
|
485
509
|
const utilsDir = resolveUtilsDir()
|
|
@@ -491,20 +515,22 @@ try {
|
|
|
491
515
|
: buildStableTmuxSessionName({ cwd })
|
|
492
516
|
|
|
493
517
|
const tmuxAlreadyAlive = await tmuxHasSession(tmuxSession)
|
|
494
|
-
|
|
518
|
+
const currentState = await readJsonIfExists(paths.stateFile)
|
|
519
|
+
if (tmuxAlreadyAlive && !replace && !existingState?.tmux_session && currentState?.tmux_session !== tmuxSession) {
|
|
495
520
|
throw Object.assign(new Error(`Project tmux host already exists: ${tmuxSession}. Reuse the active runtime state or relaunch with --replace 1.`), {
|
|
496
521
|
code: 'claude_live_tmux_name_in_use',
|
|
497
522
|
})
|
|
498
523
|
}
|
|
499
524
|
|
|
500
|
-
const
|
|
525
|
+
const provenExistingSid = resumableSidFromState(existingState)
|
|
526
|
+
const resumingExistingLane = Boolean(resumeSessionId || provenExistingSid)
|
|
501
527
|
await clearRuntimeArtifacts(paths)
|
|
502
528
|
await writeState(runtimeDir, {
|
|
503
529
|
lane,
|
|
504
530
|
backend: 'claude-live',
|
|
505
531
|
status: 'starting',
|
|
506
532
|
cwd,
|
|
507
|
-
sid: resumeSessionId ||
|
|
533
|
+
sid: resumeSessionId || provenExistingSid,
|
|
508
534
|
resume_from_sid: resumeSessionId,
|
|
509
535
|
agent_name: agentName,
|
|
510
536
|
model: laneModel,
|
|
@@ -667,6 +693,8 @@ try {
|
|
|
667
693
|
}
|
|
668
694
|
await writeState(runtimeDir, {
|
|
669
695
|
status: 'failed',
|
|
696
|
+
sid: provenSidBeforeLaunch,
|
|
697
|
+
resume_from_sid: null,
|
|
670
698
|
last_error: error instanceof Error ? error.message : String(error),
|
|
671
699
|
cleanup_warning: cleanupWarning,
|
|
672
700
|
})
|
|
@@ -674,7 +702,7 @@ try {
|
|
|
674
702
|
? error.code
|
|
675
703
|
: 'claude_live_launch_failed'
|
|
676
704
|
emitFailure(failureCode, error instanceof Error ? error.message : String(error), {
|
|
677
|
-
sid:
|
|
705
|
+
sid: provenSidBeforeLaunch,
|
|
678
706
|
state_file: paths.stateFile,
|
|
679
707
|
result_file: paths.resultFile,
|
|
680
708
|
})
|