runcastle 1.5.6 → 1.5.8
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/bin/runcastle.js +10175 -7680
- package/hook-client.ts +0 -62
- package/index.js +10173 -7678
- package/package.json +4 -1
- package/web/assets/index-D10ktKMC.css +1 -0
- package/web/assets/index-D9aL9SRf.js +188 -0
- package/web/index.html +2 -2
- package/web/assets/index-B1MyomR6.css +0 -1
- package/web/assets/index-DJf8BQof.js +0 -186
package/hook-client.ts
CHANGED
|
@@ -8,9 +8,6 @@
|
|
|
8
8
|
* consumes as context). Zero dependencies (plain `fetch`), 3s timeout (5s and
|
|
9
9
|
* retried for `session-start`, see {@link postHook}). On ANY
|
|
10
10
|
* error it prints `{}` and exits 0 — a hook must never break the user's session.
|
|
11
|
-
*
|
|
12
|
-
* One argument is not a route event: `wait-messages`, the background message
|
|
13
|
-
* waiter of the orchestrator and of a feature's lead (see {@link waitMessages}).
|
|
14
11
|
*/
|
|
15
12
|
|
|
16
13
|
const FETCH_TIMEOUT_MS = 3000
|
|
@@ -44,70 +41,11 @@ async function readStdin(): Promise<string> {
|
|
|
44
41
|
})
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
/** The hook-client argument that runs the orchestrator's message waiter. */
|
|
48
|
-
export const WAIT_MESSAGES_MODE = 'wait-messages'
|
|
49
|
-
|
|
50
|
-
/** Longer than the server's hold, so a held poll is answered rather than cut off. */
|
|
51
|
-
const WAIT_FETCH_TIMEOUT_MS = 40_000
|
|
52
|
-
/** The pause after a failed poll (server restarting, network blip) before the next. */
|
|
53
|
-
const WAIT_RETRY_MS = 3000
|
|
54
|
-
|
|
55
|
-
export interface WaitMessagesDeps {
|
|
56
|
-
fetch?: typeof fetch
|
|
57
|
-
sleep?: (ms: number) => Promise<void>
|
|
58
|
-
waiterId?: string
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* `wait-messages` mode (`whole-cuts-plan-themselves` decision 20). Runs as an
|
|
63
|
-
* orchestrate session's `asyncRewake` hook: long-polls
|
|
64
|
-
* `GET /api/hooks/orchestrator-messages` until the server hands it lines, then
|
|
65
|
-
* resolves exit code 2 with them joined by newlines — Claude Code wakes the
|
|
66
|
-
* session with that output as one system reminder. `superseded` (a newer waiter
|
|
67
|
-
* took over) and `ended` (the session is over) resolve 0 with nothing to say. A
|
|
68
|
-
* hold that ran out polls again; a failed poll waits and retries. Never 2
|
|
69
|
-
* without lines.
|
|
70
|
-
*/
|
|
71
|
-
export async function waitMessages(
|
|
72
|
-
serverUrl: string,
|
|
73
|
-
sessionId: string | undefined,
|
|
74
|
-
deps: WaitMessagesDeps = {},
|
|
75
|
-
): Promise<{ code: 0 | 2; output: string }> {
|
|
76
|
-
if (!sessionId) return { code: 0, output: '' }
|
|
77
|
-
const doFetch = deps.fetch ?? fetch
|
|
78
|
-
const sleep = deps.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms)))
|
|
79
|
-
const waiter = deps.waiterId ?? crypto.randomUUID()
|
|
80
|
-
const url =
|
|
81
|
-
`${serverUrl}/api/hooks/orchestrator-messages` +
|
|
82
|
-
`?sessionId=${encodeURIComponent(sessionId)}&waiter=${encodeURIComponent(waiter)}`
|
|
83
|
-
for (;;) {
|
|
84
|
-
let answer: { status?: string; lines?: unknown }
|
|
85
|
-
try {
|
|
86
|
-
const res = await doFetch(url, { signal: AbortSignal.timeout(WAIT_FETCH_TIMEOUT_MS) })
|
|
87
|
-
answer = (await res.json()) as typeof answer
|
|
88
|
-
} catch {
|
|
89
|
-
await sleep(WAIT_RETRY_MS)
|
|
90
|
-
continue
|
|
91
|
-
}
|
|
92
|
-
if (answer.status === 'superseded' || answer.status === 'ended') return { code: 0, output: '' }
|
|
93
|
-
const lines = Array.isArray(answer.lines) ? answer.lines.filter((l) => typeof l === 'string') : []
|
|
94
|
-
if (answer.status === 'lines' && lines.length > 0) return { code: 2, output: lines.join('\n') }
|
|
95
|
-
// Anything but a hold that ran out answered at once: never spin on it.
|
|
96
|
-
if (answer.status !== 'empty') await sleep(WAIT_RETRY_MS)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
44
|
export async function main(argv: string[] = process.argv.slice(2)): Promise<void> {
|
|
101
45
|
const event = argv[0] ?? ''
|
|
102
46
|
const serverUrl = process.env.RUNCASTLE_SERVER_URL ?? 'http://localhost:4512'
|
|
103
47
|
const sessionId = process.env.RUNCASTLE_SESSION_ID
|
|
104
48
|
|
|
105
|
-
if (event === WAIT_MESSAGES_MODE) {
|
|
106
|
-
const { code, output } = await waitMessages(serverUrl, sessionId)
|
|
107
|
-
if (output) process.stderr.write(output)
|
|
108
|
-
process.exit(code)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
49
|
const raw = await readStdin()
|
|
112
50
|
let payload: unknown
|
|
113
51
|
try {
|