Skip to content

Commit 6b993ba

Browse files
authored
Merge pull request #177 from maystudios/worktree-agent-aa990540
Fix hooks: PowerShell quoting, stopTeammate helper, output docs
2 parents e1c167e + 9174b41 commit 6b993ba

3 files changed

Lines changed: 37 additions & 1 deletion

File tree

packages/cli/src/hooks/maxsim-task-completed.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010
* - Always handles errors gracefully — never crashes.
1111
*/
1212

13+
/**
14+
* Output formats:
15+
* - Exit 0: Allow task completion (all gates passed)
16+
* - Exit 2 + stderr: Block completion — gates failed, agent must fix before completing
17+
* - JSON stdout { continue: false, stopReason: "..." } + exit 0: Stop the teammate entirely
18+
*
19+
* Currently uses exit 2 (block + report) when test/build/lint gates fail.
20+
* Use stopTeammate() from shared.ts for permanent stop scenarios.
21+
*/
22+
1323
import * as fs from 'node:fs';
1424
import * as path from 'node:path';
1525
import { spawnSync } from 'node:child_process';

packages/cli/src/hooks/maxsim-teammate-idle.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@
99
* - Always handles errors gracefully — never crashes.
1010
*/
1111

12+
/**
13+
* Output formats:
14+
* - Exit 0: Allow the teammate to remain idle (no pending tasks)
15+
* - Exit 2 + stderr: Block idle and redirect — "Pick up the next available task."
16+
* - JSON stdout { continue: false, stopReason: "..." } + exit 0: Stop the teammate entirely
17+
*
18+
* Currently uses exit 2 (block + redirect) when pending tasks exist.
19+
* Use stopTeammate() from shared.ts for permanent stop scenarios.
20+
*/
21+
1222
import * as fs from 'node:fs';
1323
import * as os from 'node:os';
1424
import * as path from 'node:path';

packages/cli/src/hooks/shared.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,15 @@ export function playSound(soundFile: string): void {
103103
// For named system sounds (no extension) fall back to rundll32.
104104
const isWav = soundFile.toLowerCase().endsWith('.wav');
105105
if (isWav) {
106+
// Use double-quoted string which handles spaces and most special chars
107+
const escaped = soundFile.replace(/"/g, '\\"');
106108
spawnSync(
107109
'powershell',
108110
[
109111
'-NoProfile',
110112
'-NonInteractive',
111113
'-Command',
112-
`$p='${soundFile.replace(/'/g, "''")}'; (New-Object System.Media.SoundPlayer $p).PlaySync()`,
114+
`$p="${escaped}"; (New-Object System.Media.SoundPlayer $p).PlaySync()`,
113115
],
114116
{ stdio: 'ignore' },
115117
);
@@ -135,3 +137,17 @@ export function playSound(soundFile: string): void {
135137
// Never crash on sound failure
136138
}
137139
}
140+
141+
/**
142+
* Send a JSON stop signal to terminate a teammate.
143+
* Outputs the stop payload to stdout and exits cleanly.
144+
* Use this when a teammate should be permanently stopped (not just blocked).
145+
*
146+
* For blocking (retry behavior), use: process.stderr.write(msg); process.exit(2);
147+
* For stopping (permanent), use: stopTeammate(reason);
148+
*/
149+
export function stopTeammate(reason: string): never {
150+
const payload = JSON.stringify({ continue: false, stopReason: reason });
151+
process.stdout.write(`${payload}\n`);
152+
process.exit(0);
153+
}

0 commit comments

Comments
 (0)