theslopmachine 1.0.24 → 1.0.25

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.
@@ -546,13 +546,13 @@ export async function waitForHookEvent(paths, startIndex, labels, timeoutMs, mat
546
546
  return waitFor(async () => {
547
547
  const events = await readJsonl(paths.hookEventsFile)
548
548
  const pending = events.slice(startIndex)
549
- const match = pending.find((event) => labels.has(event.label) && (!matcher || matcher(event)))
550
- if (!match) {
549
+ const matchIndex = pending.findIndex((event) => labels.has(event.label) && (!matcher || matcher(event)))
550
+ if (matchIndex === -1) {
551
551
  return null
552
552
  }
553
553
  return {
554
- event: match,
555
- eventsLength: events.length,
554
+ event: pending[matchIndex],
555
+ eventsLength: startIndex + matchIndex + 1,
556
556
  }
557
557
  }, {
558
558
  timeoutMs,
@@ -23,6 +23,7 @@ import {
23
23
  readState,
24
24
  tmuxHasSession,
25
25
  tmuxPasteFileAndEnter,
26
+ tmuxSendEnter,
26
27
  waitForRateLimitReset,
27
28
  waitForHookEvent,
28
29
  writeState,
@@ -50,6 +51,8 @@ const runtimeDir = argv['runtime-dir']
50
51
  const promptFile = typeof argv['prompt-file'] === 'string' ? argv['prompt-file'].trim() : ''
51
52
  const turnTimeoutMs = Number.parseInt(argv['timeout-ms'] || String(DEFAULT_TURN_TIMEOUT_MS), 10)
52
53
  const retryOnLimit = argv['retry-on-limit'] !== '0'
54
+ const SUBMIT_CONFIRM_TIMEOUT_MS = 5000
55
+ const SUBMIT_ENTER_RETRIES = 3
53
56
 
54
57
  if (!runtimeDir || !promptFile) {
55
58
  emitFailure('claude_live_turn_invalid_args', 'Missing required turn arguments', {
@@ -70,6 +73,38 @@ function emitTurnFailure(resultPayload) {
70
73
  })
71
74
  }
72
75
 
76
+ async function confirmPromptSubmitted({ paths, hookStartIndex, turnId, tmuxSession, runtimeDir, turnDir }) {
77
+ let currentHookStartIndex = hookStartIndex
78
+ for (let attempt = 0; attempt <= SUBMIT_ENTER_RETRIES; attempt += 1) {
79
+ try {
80
+ const { eventsLength } = await waitForHookEvent(
81
+ paths,
82
+ currentHookStartIndex,
83
+ new Set(['UserPromptSubmit']),
84
+ SUBMIT_CONFIRM_TIMEOUT_MS,
85
+ (hookEvent) => hookEvent?.turn_id === turnId,
86
+ )
87
+ await writeState(runtimeDir, {
88
+ current_turn_prompt_submitted_at: new Date().toISOString(),
89
+ current_turn_submit_enter_retries: attempt,
90
+ })
91
+ return { ok: true, eventsLength }
92
+ } catch {
93
+ if (attempt === SUBMIT_ENTER_RETRIES) {
94
+ break
95
+ }
96
+ await tmuxSendEnter(tmuxSession)
97
+ await writeState(runtimeDir, {
98
+ current_turn_submit_enter_retries: attempt + 1,
99
+ })
100
+ }
101
+ }
102
+
103
+ const blockerFile = path.join(turnDir, `prompt-submit-unconfirmed-pane-${Date.now()}.txt`)
104
+ await captureTmuxPaneArtifact(tmuxSession, blockerFile)
105
+ return { ok: false, blockerFile }
106
+ }
107
+
73
108
  try {
74
109
  const promptSource = path.resolve(promptFile)
75
110
 
@@ -237,6 +272,34 @@ try {
237
272
  current_turn_prompt_injected_at: new Date().toISOString(),
238
273
  })
239
274
 
275
+ const submitConfirmation = await confirmPromptSubmitted({
276
+ paths,
277
+ hookStartIndex,
278
+ turnId,
279
+ tmuxSession: liveState.tmux_session || state.tmux_session,
280
+ runtimeDir,
281
+ turnDir,
282
+ })
283
+ if (!submitConfirmation.ok) {
284
+ const resultPayload = buildFailureResult('claude_prompt_submit_unconfirmed', 'Claude prompt was pasted, but UserPromptSubmit did not fire after repeated Enter sends', liveState.sid || state.sid)
285
+ await writeTurnArtifacts(paths, turnId, prompt, resultPayload, argv['result-file'] || null)
286
+ await writeState(runtimeDir, {
287
+ status: 'failed',
288
+ current_turn_id: null,
289
+ current_turn_prompt_file: null,
290
+ current_turn_prompt_source: null,
291
+ current_turn_started_at: null,
292
+ current_turn_transport: 'tmux-paste-buffer',
293
+ last_completed_turn_id: turnId,
294
+ last_turn_number: turnNumber + 1,
295
+ last_error: resultPayload.msg,
296
+ prompt_submit_unconfirmed_pane_file: submitConfirmation.blockerFile,
297
+ })
298
+ emitTurnFailure(resultPayload)
299
+ process.exit(1)
300
+ }
301
+ hookStartIndex = submitConfirmation.eventsLength
302
+
240
303
  const { event, eventsLength } = await waitForHookEvent(
241
304
  paths,
242
305
  hookStartIndex,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theslopmachine",
3
- "version": "1.0.24",
3
+ "version": "1.0.25",
4
4
  "description": "SlopMachine installer and project bootstrap CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",