@joshuafolkken/kit 1.1383.0 → 1.1435.0
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/.claude/skills/epic-commands/SKILL.md +1 -1
- package/.claude/skills/epic-commands/epic-bundle.md +5 -0
- package/.claude/skills/workflow-commands/backlogrun-lanes.md +5 -3
- package/.claude/skills/workflow-commands/backlogrun-steps.md +4 -3
- package/.claude/skills/workflow-commands/backlogrun.md +1 -0
- package/.claude/skills/workflow-commands/chain-rule.md +4 -2
- package/.claude/skills/workflow-commands/issue-comments.md +6 -0
- package/.claude/skills/workflow-commands/observation-ledger.md +3 -2
- package/.claude/skills/workflow-commands/pre-gate-cut.md +5 -3
- package/README.md +25 -0
- package/dist/hooks/audit-provision.js +2 -2
- package/dist/hooks/batch-guard.js +7 -7
- package/dist/hooks/{chunk-LOJTBU5V.js → chunk-2G5F2YCD.js} +3 -3
- package/dist/hooks/{chunk-4MGHBTOM.js → chunk-3LE6VLSR.js} +25 -8
- package/dist/hooks/{chunk-Y5R47QHU.js → chunk-4BHUPFKF.js} +1 -1
- package/dist/hooks/{chunk-KC6LUZZ2.js → chunk-5SJSSCJG.js} +1 -1
- package/dist/hooks/{chunk-FZ6FUSBM.js → chunk-723HKZO5.js} +1 -1
- package/dist/hooks/{chunk-RYP3TSUJ.js → chunk-7KMN5QB5.js} +3 -3
- package/dist/hooks/{chunk-NEC4AT4Z.js → chunk-DGT54IH5.js} +4 -0
- package/dist/hooks/{chunk-YKUITFT7.js → chunk-EMB55BDF.js} +4 -4
- package/dist/hooks/{chunk-JBBMAPC4.js → chunk-IX6TTTI6.js} +4 -0
- package/dist/hooks/{chunk-6MAW7BCM.js → chunk-JXQBK323.js} +2677 -190
- package/dist/hooks/{chunk-KCF2YE7O.js → chunk-MSYSUSY6.js} +5 -5
- package/dist/hooks/{chunk-LC56R53A.js → chunk-MU4LJFIV.js} +4 -4
- package/dist/hooks/{chunk-7VSZ2N4B.js → chunk-QL6QTPWN.js} +3 -3
- package/dist/hooks/{chunk-TT742SIO.js → chunk-XER6SSIB.js} +92 -19
- package/dist/hooks/{chunk-2X2ART5G.js → chunk-YW452ZAX.js} +500 -29
- package/dist/hooks/{chunk-6AC4C2IO.js → chunk-YZXYOAZ3.js} +187 -443
- package/dist/hooks/codex-hook-adapter.js +15 -15
- package/dist/hooks/duplicate-read-guard.js +8 -8
- package/dist/hooks/format-edited.js +5 -5
- package/dist/hooks/inputs.json +262 -201
- package/dist/hooks/investigation-guard.js +8 -8
- package/dist/hooks/pretool-guard.js +14 -14
- package/dist/hooks/session-lang.js +124 -15
- package/dist/hooks/stop-guard.js +98 -55
- package/dist/josh.js +63 -10
- package/package.json +4 -3
- package/prompts/review.md +22 -0
- package/scripts/agent/claude-agent-argv.ts +13 -1
- package/scripts/auth/auth-setup-cli.ts +101 -0
- package/scripts/auth/claude-token-output.ts +78 -0
- package/scripts/auth/claude-token.ts +68 -0
- package/scripts/backlog/backlog-drive-cli.ts +2 -4
- package/scripts/backlog/backlog-drive-epic.ts +24 -8
- package/scripts/backlog/backlog-drive-launch.ts +5 -0
- package/scripts/backlog/backlog-drive-named-offer.ts +7 -2
- package/scripts/backlog/backlog-drive-named.ts +4 -2
- package/scripts/backlog/backlog-drive-owner.ts +72 -5
- package/scripts/backlog/backlog-drive-unread.ts +56 -0
- package/scripts/backlog/backlog-drive.ts +16 -8
- package/scripts/backlog/backlog-overlap.ts +22 -5
- package/scripts/backlog/backlog-ready.ts +1 -3
- package/scripts/backlog/backlogrun-args.ts +13 -2
- package/scripts/backlog/backlogrun-cli.ts +91 -19
- package/scripts/backlog/backlogrun-launch.ts +80 -5
- package/scripts/backlog/backlogrun-replace.ts +166 -0
- package/scripts/backlog/backlogrun-stop.ts +178 -0
- package/scripts/behavior/behavior-cli.ts +6 -1
- package/scripts/doctor/doctor-io.ts +60 -9
- package/scripts/doctor/doctor.ts +20 -1
- package/scripts/document/document-byte-budget.ts +3 -3
- package/scripts/epic/epic-bundle-cli.ts +8 -8
- package/scripts/epic/epic-bundle-referenced.ts +2 -5
- package/scripts/epic/epic-bundle.ts +39 -4
- package/scripts/exports/unused-members-cli.ts +2 -1
- package/scripts/followup/followup-checks-event.ts +82 -0
- package/scripts/followup/git-followup-finish.ts +16 -2
- package/scripts/followup/git-followup-workflow.ts +5 -2
- package/scripts/followup/git-pr-followup-checks.ts +18 -4
- package/scripts/followup/git-pr-followup-wrapup.ts +5 -0
- package/scripts/followup/git-pr-followup.ts +4 -1
- package/scripts/fresh-machine/fresh-machine-cli.ts +204 -0
- package/scripts/fresh-machine/fresh-machine-docker.ts +65 -0
- package/scripts/fresh-machine/fresh-machine-image.ts +62 -0
- package/scripts/fresh-machine/fresh-machine-log.ts +61 -0
- package/scripts/fresh-machine/fresh-machine-patterns.ts +130 -0
- package/scripts/fresh-machine/fresh-machine-run.ts +215 -0
- package/scripts/fresh-machine/fresh-machine-script.ts +45 -0
- package/scripts/fresh-machine/fresh-machine-spec.ts +57 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +34 -0
- package/scripts/fresh-machine/specs/kit-test-backlogrun.json +31 -0
- package/scripts/gate/gate-report.ts +70 -15
- package/scripts/gate/project-checks.ts +13 -2
- package/scripts/gate/scoped-green.ts +2 -1
- package/scripts/gate/verification-gate.ts +4 -3
- package/scripts/gh/gh-author-association.ts +14 -0
- package/scripts/gh/git-gh-conditional.ts +66 -0
- package/scripts/gh/git-gh-exec.ts +1 -1
- package/scripts/gh/git-gh-issue-read.ts +6 -3
- package/scripts/gh/git-gh-issue-write.ts +5 -1
- package/scripts/gh/git-gh-issue.ts +4 -1
- package/scripts/gh/git-pr-checks-eval.ts +13 -0
- package/scripts/gh/git-pr-checks-parse.ts +8 -1
- package/scripts/gh/git-pr-checks-watch.ts +11 -4
- package/scripts/gh/git-pr-checks.ts +36 -2
- package/scripts/git/git-command.ts +16 -4
- package/scripts/git/git-schemas.ts +1 -0
- package/scripts/git/git-spawn-sync.ts +1 -1
- package/scripts/hooks/pretool-guard.ts +3 -1
- package/scripts/init/hook-command-rewrite.ts +31 -0
- package/scripts/init/init-actions.ts +4 -1
- package/scripts/init/init-ai-copy.ts +33 -10
- package/scripts/init/init-basic.ts +8 -3
- package/scripts/init/init-logic-toolchain.ts +76 -0
- package/scripts/init/init-logic.ts +2 -0
- package/scripts/init/init.ts +5 -23
- package/scripts/init/project-profile.ts +36 -7
- package/scripts/init/start-exec.ts +27 -1
- package/scripts/init/start-plan.ts +30 -4
- package/scripts/init/start-steps.ts +77 -3
- package/scripts/init/start.ts +21 -13
- package/scripts/init/workflow-mark.ts +35 -0
- package/scripts/issue/issue-ack-cli.ts +84 -0
- package/scripts/issue/issue-labels.ts +40 -8
- package/scripts/issue/issue-state-cli.ts +14 -4
- package/scripts/issue/issue-watch-diff.ts +152 -0
- package/scripts/issue/issue-watch-gate.ts +81 -0
- package/scripts/issue/issue-watch-hook.ts +54 -0
- package/scripts/issue/issue-watch.ts +240 -0
- package/scripts/issue/measure-rerun-cli.ts +3 -4
- package/scripts/josh/josh-command-map.ts +4 -0
- package/scripts/josh/josh-commands-backlog.ts +2 -2
- package/scripts/josh/josh-commands-development.ts +1 -1
- package/scripts/josh/josh-commands-issue.ts +7 -0
- package/scripts/josh/josh-commands-lane.ts +11 -2
- package/scripts/josh/josh-commands-project.ts +25 -1
- package/scripts/josh/josh-verdict.ts +33 -4
- package/scripts/lane/claude-lane-broker.ts +142 -0
- package/scripts/lane/lane-await.ts +6 -2
- package/scripts/lane/lane-cache.ts +37 -7
- package/scripts/lane/lane-capacity.ts +8 -6
- package/scripts/lane/lane-child-invocation.ts +1 -0
- package/scripts/lane/lane-cli.ts +15 -1
- package/scripts/lane/lane-close.ts +15 -6
- package/scripts/lane/lane-dispatch.ts +36 -10
- package/scripts/lane/lane-environment.ts +1 -1
- package/scripts/lane/lane-launch-cli.ts +13 -4
- package/scripts/lane/lane-ledger.ts +21 -3
- package/scripts/lane/lane-measure-cli.ts +10 -2
- package/scripts/lane/lane-open.ts +9 -1
- package/scripts/lane/lane-preserve.ts +49 -0
- package/scripts/lane/lane-reap.ts +20 -5
- package/scripts/lane/lane-registry.ts +6 -0
- package/scripts/lane/lane-restart-cli.ts +51 -0
- package/scripts/lane/lane-restart.ts +175 -0
- package/scripts/lane/{openai-review-broker.ts → lane-session-broker.ts} +106 -28
- package/scripts/lane/lane-spent.ts +195 -0
- package/scripts/lane/lane-stage-stats.ts +27 -3
- package/scripts/lane/lane-stop-stats.ts +96 -0
- package/scripts/lane/openai-lane-supervisor.ts +2 -2
- package/scripts/lane/process-tools.ts +31 -0
- package/scripts/lib/buffered-process.ts +36 -2
- package/scripts/lib/pnpm-reach.ts +27 -0
- package/scripts/lib/status-icons.ts +5 -1
- package/scripts/lint/lint-parallel.ts +19 -2
- package/scripts/observations/observation-ledger-home.ts +1 -0
- package/scripts/review/review-attest-cli.ts +21 -6
- package/scripts/review/review-brief-cli.ts +2 -2
- package/scripts/review/review-comment-round.ts +114 -0
- package/scripts/review/review-record-cli.ts +39 -17
- package/scripts/rules/implementation-cut.ts +3 -3
- package/scripts/rules/pre-gate-cut.ts +1 -1
- package/scripts/run/board/run-board-checks.ts +42 -0
- package/scripts/run/board/run-board-cli.ts +2 -0
- package/scripts/run/board/run-board-every.ts +3 -1
- package/scripts/run/board/run-board-fit.ts +16 -5
- package/scripts/run/board/run-board-fresh.ts +64 -0
- package/scripts/run/board/run-board-header.ts +139 -37
- package/scripts/run/board/run-board-labels.ts +47 -4
- package/scripts/run/board/run-board-lane-event.ts +66 -0
- package/scripts/run/board/run-board-layout.ts +57 -9
- package/scripts/run/board/run-board-now.ts +234 -0
- package/scripts/run/board/run-board-operation-read.ts +75 -0
- package/scripts/run/board/run-board-operation.ts +150 -0
- package/scripts/run/board/run-board-phase.ts +26 -1
- package/scripts/run/board/run-board-plan.ts +4 -6
- package/scripts/run/board/run-board-read.ts +58 -5
- package/scripts/run/board/run-board-reason.ts +124 -0
- package/scripts/run/board/run-board-render-checks.ts +55 -0
- package/scripts/run/board/run-board-render-legend.ts +12 -3
- package/scripts/run/board/run-board-render-slot.ts +2 -2
- package/scripts/run/board/run-board-render-tail.ts +128 -0
- package/scripts/run/board/run-board-render-thanks.ts +50 -0
- package/scripts/run/board/run-board-render-title.ts +58 -0
- package/scripts/run/board/run-board-render.ts +96 -80
- package/scripts/run/board/run-board-row-yield.ts +32 -0
- package/scripts/run/board/run-board-setup.ts +121 -0
- package/scripts/run/board/run-board-span.ts +49 -7
- package/scripts/run/board/run-board-state.ts +10 -0
- package/scripts/run/board/run-board-status.ts +76 -13
- package/scripts/run/board/run-board-thanks-csv.ts +71 -0
- package/scripts/run/board/run-board-thanks-read.ts +112 -0
- package/scripts/run/board/run-board-thanks.ts +53 -0
- package/scripts/run/board/run-board-tick.ts +53 -16
- package/scripts/run/board/run-board-usual.ts +44 -0
- package/scripts/run/carry/run-carry-added.ts +1 -1
- package/scripts/run/carry/run-carry-args.ts +9 -1
- package/scripts/run/carry/run-carry-change.ts +1 -1
- package/scripts/run/carry/run-carry-cli.ts +39 -52
- package/scripts/run/carry/run-carry-end.ts +73 -0
- package/scripts/run/carry/run-carry-ended.ts +9 -1
- package/scripts/run/carry/run-carry.ts +24 -1
- package/scripts/run/entry/run-entry-cli.ts +5 -0
- package/scripts/run/event/run-event-checks.ts +54 -0
- package/scripts/run/event/run-event-render.ts +3 -0
- package/scripts/run/event/run-event-scope.ts +1 -0
- package/scripts/run/event/run-event-setup.ts +91 -0
- package/scripts/run/event/run-event-stream.ts +46 -9
- package/scripts/run/merge/run-merge-cli.ts +7 -4
- package/scripts/run/merge/run-merge-ship.ts +61 -0
- package/scripts/run/merge/run-merge-steps.ts +7 -14
- package/scripts/run/merge/run-merge-token.ts +16 -2
- package/scripts/run/run-headless.ts +1 -0
- package/scripts/run/run-step.ts +2 -0
- package/scripts/run/run-stranded-detect.ts +58 -12
- package/scripts/run/ship/run-ship-cli.ts +22 -2
- package/scripts/run/ship/run-ship-return.ts +31 -13
- package/scripts/run/ship/run-ship-review-steps.ts +17 -4
- package/scripts/run/ship/run-ship-stage.ts +1 -0
- package/scripts/run/wake/run-wake-await.ts +130 -0
- package/scripts/run/wake/run-wake-cli.ts +108 -74
- package/scripts/run/wake/run-wake-describe.ts +36 -2
- package/scripts/run/wake/run-wake-driver.ts +21 -5
- package/scripts/run/wake/run-wake-handoff.ts +1 -1
- package/scripts/run/wake/run-wake-session.ts +20 -3
- package/scripts/run/wake/run-wake-stop.ts +37 -0
- package/scripts/run/wake/run-wake-supervise.ts +44 -0
- package/scripts/sync/claude-settings-merge.ts +144 -0
- package/scripts/sync/sync-ai-files.ts +28 -3
- package/scripts/sync/sync.ts +15 -0
- package/scripts/test/project-test-script.ts +73 -0
- package/scripts/test/skip-marker.ts +14 -1
- package/scripts/test/test-related.ts +3 -2
- package/scripts/test/test-unit-guard.ts +78 -14
- package/scripts/time-runtime/time-reported-failure.ts +11 -3
- package/scripts/uninstall/distributed-files.ts +92 -0
- package/scripts/uninstall/uninstall-plan.ts +119 -0
- package/scripts/uninstall/uninstall-report.ts +61 -0
- package/scripts/uninstall/uninstall.ts +206 -0
- package/templates/AGENTS.basic.md +4 -0
- package/templates/GEMINI.basic.md +6 -0
- package/dist/hooks/chunk-6V5OPIUU.js +0 -13
- package/scripts/run/board/run-board-track.ts +0 -22
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { randomUUID } from 'node:crypto'
|
|
3
|
-
import path from 'node:path'
|
|
4
3
|
import { fileURLToPath } from 'node:url'
|
|
5
|
-
import type { AgentProfile } from '#scripts/agent/agent-role-profile'
|
|
4
|
+
import type { AgentProfile, AgentProvider } from '#scripts/agent/agent-role-profile'
|
|
6
5
|
import { backlog_budget } from '#scripts/backlog/backlog-budget'
|
|
7
6
|
import { cli_flags } from '#scripts/lib/cli-flags'
|
|
8
|
-
import { poll } from '#scripts/lib/poll'
|
|
9
7
|
import { telegram_notify } from '#scripts/notify/telegram-notify'
|
|
10
8
|
import { run_carry, type CarryRead } from '#scripts/run/carry/run-carry'
|
|
9
|
+
import { run_event_setup } from '#scripts/run/event/run-event-setup'
|
|
11
10
|
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
11
|
+
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
12
12
|
import { run_headless } from '#scripts/run/run-headless'
|
|
13
|
-
import {
|
|
13
|
+
import { run_stranded_detect, type WatchedRun } from '#scripts/run/run-stranded-detect'
|
|
14
|
+
import { run_wake, type WakeStopReason, type WakeTidyResult } from './run-wake'
|
|
15
|
+
import { run_wake_await } from './run-wake-await'
|
|
14
16
|
import { run_wake_describe, type WakeContext } from './run-wake-describe'
|
|
15
17
|
import { run_wake_driver } from './run-wake-driver'
|
|
16
18
|
import { run_wake_handoff } from './run-wake-handoff'
|
|
17
19
|
import { run_wake_loop, type LoopPorts, type LoopStop } from './run-wake-loop'
|
|
18
|
-
import { run_wake_restart, type RestartPorts } from './run-wake-restart'
|
|
19
20
|
import { run_wake_session, type LaunchResult } from './run-wake-session'
|
|
21
|
+
import { run_wake_stop } from './run-wake-stop'
|
|
22
|
+
import { run_wake_supervise } from './run-wake-supervise'
|
|
20
23
|
|
|
21
24
|
// `josh run:wake --start | --list | --stop | --loop` — the supervisor that continues a cut
|
|
22
25
|
// `backlogrun` or `queue` without a person retyping the keyword. **Which invocations it may continue
|
|
@@ -83,6 +86,7 @@ const STOP_BODIES: Record<WakeStopReason, string | undefined> = {
|
|
|
83
86
|
unreadable:
|
|
84
87
|
'The carry record could not be read, so the supervisor stopped rather than guess at it.',
|
|
85
88
|
}
|
|
89
|
+
const NO_PROFILE_NOTE = 'the scheduler profile could not be resolved'
|
|
86
90
|
const SUPERSEDED_RESULT: WakeTidyResult = 'superseded'
|
|
87
91
|
const SUPERSEDED_NOTE =
|
|
88
92
|
'another supervisor now holds the record, so it was left alone; this loop is the superseded one'
|
|
@@ -131,25 +135,14 @@ function group_count(values: ParsedValues): number {
|
|
|
131
135
|
return verbs.filter((flag) => flag === true).length
|
|
132
136
|
}
|
|
133
137
|
|
|
138
|
+
function context_of(directory: string): WakeContext {
|
|
139
|
+
return run_wake_describe.context_of(directory, note_to_stderr)
|
|
140
|
+
}
|
|
141
|
+
|
|
134
142
|
async function resolve_context(): Promise<WakeContext | undefined> {
|
|
135
143
|
const directory = await run_carry.repository_directory()
|
|
136
144
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const log_target = run_wake.wake_log_path(directory)
|
|
140
|
-
|
|
141
|
-
// Every verb reaches this one function, so creating the log here is what makes the path each of
|
|
142
|
-
// them prints a path that exists — including `--list` and the already-running branch of `--start`,
|
|
143
|
-
// neither of which launches anything.
|
|
144
|
-
run_wake_session.ensure_log(log_target, note_to_stderr)
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
carry_target: run_carry.carry_path(directory),
|
|
148
|
-
wake_target: run_wake.wake_path(directory),
|
|
149
|
-
log_target,
|
|
150
|
-
event_target: run_event_stream.target_of(directory),
|
|
151
|
-
worktree: path.dirname(directory),
|
|
152
|
-
}
|
|
145
|
+
return directory === undefined ? undefined : context_of(directory)
|
|
153
146
|
}
|
|
154
147
|
|
|
155
148
|
// There is nothing to supervise unless a run is actually carrying a budget. Refusing here rather than
|
|
@@ -161,6 +154,12 @@ function refuse_without_carry(kind: CarryRead['kind']): number {
|
|
|
161
154
|
return report(NONE_VERDICT, FAILURE_EXIT_CODE)
|
|
162
155
|
}
|
|
163
156
|
|
|
157
|
+
// **A session that started is said on the stream.** The driver's `waiting on judgment` is the moment it
|
|
158
|
+
// asked; from here a session is at work on the answer, until the supervisor's next hand-off.
|
|
159
|
+
function say_session_running(context: WakeContext): void {
|
|
160
|
+
run_event_setup.emit_to(context.event_target, run_event_setup.STAGE.SESSION, new Date())
|
|
161
|
+
}
|
|
162
|
+
|
|
164
163
|
function wake_session(
|
|
165
164
|
context: WakeContext,
|
|
166
165
|
invocation: string,
|
|
@@ -176,14 +175,20 @@ function wake_session(
|
|
|
176
175
|
// under `claude -p` a turn-end is the process's end (`run-headless.ts`).
|
|
177
176
|
const environment = run_headless.environment()
|
|
178
177
|
const target = { cwd: context.worktree, env: environment, log_path: context.log_target }
|
|
178
|
+
const result = run_wake_session.launch({ argv: built.argv, ...target, profile }, note_to_stderr)
|
|
179
|
+
|
|
180
|
+
if (result.kind === 'launched') say_session_running(context)
|
|
179
181
|
|
|
180
|
-
return
|
|
182
|
+
return result
|
|
181
183
|
}
|
|
182
184
|
|
|
185
|
+
// **A record this supervisor owns itself is not a session at work.** A failed driver leaves the record
|
|
186
|
+
// adopted for this process, so reading that owner as live would have every restarted pass wait on
|
|
187
|
+
// itself and never drive again.
|
|
183
188
|
function ports_for(context: WakeContext, profile: AgentProfile): LoopPorts {
|
|
184
189
|
return {
|
|
185
190
|
read_carry: () => run_carry.read_carry(context.carry_target),
|
|
186
|
-
is_owner_live: (read) => read.kind === 'carried' && run_carry.
|
|
191
|
+
is_owner_live: (read) => read.kind === 'carried' && run_carry.is_held_by_another(read.carry),
|
|
187
192
|
has_work: async () => true,
|
|
188
193
|
new_session_id: () => randomUUID(),
|
|
189
194
|
hand_off: () => {
|
|
@@ -211,14 +216,45 @@ function warning_body(stop: LoopStop, body: string, context: WakeContext): strin
|
|
|
211
216
|
return [body, log_hint(context), stop.note].filter(Boolean).join('\n')
|
|
212
217
|
}
|
|
213
218
|
|
|
214
|
-
|
|
215
|
-
|
|
219
|
+
// Whether the warning reached a person — the answer `record_failure` leaves its marker on.
|
|
220
|
+
async function warn_of_stop(stop: LoopStop, body: string, context: WakeContext): Promise<boolean> {
|
|
221
|
+
return await telegram_notify.warn({
|
|
216
222
|
issue_title: WARNING_TITLE,
|
|
217
223
|
body: warning_body(stop, body, context),
|
|
218
224
|
recovery: WARNING_RECOVERY,
|
|
219
225
|
})
|
|
220
226
|
}
|
|
221
227
|
|
|
228
|
+
// **A failed supervisor that leaves the run stranded says so on the stream, with its cause.** Nothing
|
|
229
|
+
// else writes there when the supervisor gives up, so a reader of the events would see the last merge
|
|
230
|
+
// and a run that looks as if it were still watching. The cause is the note's first line — the
|
|
231
|
+
// driver's own `error …` — because the rest is a stderr tail the log already holds.
|
|
232
|
+
//
|
|
233
|
+
// **Only a strand, and only one a person was told of.** A `failed` stop is not always a strand: a
|
|
234
|
+
// `run:carry --end` under a live driver fails it too, and that run ended cleanly. And the event is the
|
|
235
|
+
// marker `run_stranded_detect` holds its own notification back on, so it is left only when the warning
|
|
236
|
+
// was sent — after a failed send the stop-time detector still finds no marker, and notifies.
|
|
237
|
+
function is_strand_told(stop: LoopStop, watched: WatchedRun, is_warned: boolean): boolean {
|
|
238
|
+
if (!is_warned || stop.reason !== run_wake_loop.FAILED_REASON) return false
|
|
239
|
+
|
|
240
|
+
return run_stranded_detect.is_left_stranded(watched)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function record_failure(
|
|
244
|
+
stop: LoopStop,
|
|
245
|
+
watched: WatchedRun,
|
|
246
|
+
is_warned: boolean,
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
if (!is_strand_told(stop, watched, is_warned)) return
|
|
249
|
+
|
|
250
|
+
const [cause = ''] = (stop.note ?? '').split('\n', 1)
|
|
251
|
+
|
|
252
|
+
await run_event_stream_emit.emit(
|
|
253
|
+
run_event_stream.EVENT_KIND.STRANDED,
|
|
254
|
+
`The supervisor failed and stopped watching the run — ${cause}`,
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
222
258
|
// **Only a failed wake exits non-zero; `unreadable` warns but exits zero.** The
|
|
223
259
|
// verdict-and-exit-code table in `docs/josh-commands-automation.md` is a contract callers branch on,
|
|
224
260
|
// and what that one needs is the notification, not a different exit code.
|
|
@@ -234,25 +270,33 @@ function stop_body(reason: WakeStopReason): string | undefined {
|
|
|
234
270
|
return STOP_BODIES[reason]
|
|
235
271
|
}
|
|
236
272
|
|
|
237
|
-
|
|
273
|
+
// `started_at` is the run this supervisor took on — what `record_failure` tells an ended run by once
|
|
274
|
+
// the carry record that held it no longer reads.
|
|
275
|
+
async function finish(stop: LoopStop, context: WakeContext, started_at: string): Promise<number> {
|
|
238
276
|
const body = stop_body(stop.reason)
|
|
239
277
|
|
|
240
278
|
if (body === undefined) return report(stop.reason)
|
|
241
279
|
|
|
242
280
|
console.error(stop.note ?? body)
|
|
243
|
-
await warn_of_stop(stop, body, context)
|
|
281
|
+
const is_warned = await warn_of_stop(stop, body, context)
|
|
282
|
+
|
|
283
|
+
await record_failure(stop, { ...context, started_at }, is_warned)
|
|
244
284
|
|
|
245
285
|
return report(stop.reason, exit_code_of(stop.reason))
|
|
246
286
|
}
|
|
247
287
|
|
|
248
|
-
function
|
|
249
|
-
|
|
250
|
-
|
|
288
|
+
function launch_supervisor(
|
|
289
|
+
context: WakeContext,
|
|
290
|
+
interval: string | undefined,
|
|
291
|
+
profile: AgentProfile | undefined,
|
|
292
|
+
): LaunchResult {
|
|
293
|
+
if (profile === undefined) return { kind: 'failed', note: NO_PROFILE_NOTE }
|
|
251
294
|
const argv = run_wake_session.supervisor_argv(SCRIPT_PATH, interval)
|
|
295
|
+
|
|
252
296
|
// **The supervisor's own output goes to the same file as the sessions it starts.** It is detached
|
|
253
297
|
// with the same call, so otherwise its `console.error` — the launch note that says the agent CLI is
|
|
254
298
|
// not on `PATH`, and the stop note behind every warning — would go nowhere at all.
|
|
255
|
-
|
|
299
|
+
return run_wake_session.launch(
|
|
256
300
|
{
|
|
257
301
|
argv,
|
|
258
302
|
cwd: context.worktree,
|
|
@@ -261,6 +305,20 @@ function spawn_supervisor(context: WakeContext, interval: string | undefined): n
|
|
|
261
305
|
},
|
|
262
306
|
note_to_stderr,
|
|
263
307
|
)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// The supervisor `josh backlogrun` starts beside its launched parent, on the parent's provider, from its
|
|
311
|
+
// own process so it inherits the credential the parent's Bash tool does not pass on (kit#3804). No carry
|
|
312
|
+
// record stands yet, so `start`'s check is skipped; its `--loop` waits for the parent to claim one.
|
|
313
|
+
function start_for_launch(git_directory: string, provider: AgentProvider): LaunchResult {
|
|
314
|
+
const resolved = run_wake_session.chosen_profile(provider)
|
|
315
|
+
if (resolved.kind === 'rejected') return { kind: 'failed', note: resolved.note }
|
|
316
|
+
|
|
317
|
+
return launch_supervisor(context_of(git_directory), undefined, resolved.profile)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function spawn_supervisor(context: WakeContext, interval: string | undefined): number {
|
|
321
|
+
const result = launch_supervisor(context, interval, run_wake_session.scheduler_profile())
|
|
264
322
|
|
|
265
323
|
if (result.kind === 'failed') {
|
|
266
324
|
console.error(`Could not start the supervisor: ${result.note}`)
|
|
@@ -303,40 +361,32 @@ function tidy_up(target: string): void {
|
|
|
303
361
|
if (run_wake.tidy_own_wake(target) === SUPERSEDED_RESULT) note_to_stderr(SUPERSEDED_NOTE)
|
|
304
362
|
}
|
|
305
363
|
|
|
306
|
-
function restart_ports(context: WakeContext, ports: LoopPorts, interval_ms: number): RestartPorts {
|
|
307
|
-
return {
|
|
308
|
-
pass: async () => await run_wake_loop.run_loop(context.wake_target, ports, interval_ms),
|
|
309
|
-
is_resumable: () => run_carry.read_carry(context.carry_target).kind === 'carried',
|
|
310
|
-
pause: async () => {
|
|
311
|
-
await poll.sleep(interval_ms)
|
|
312
|
-
},
|
|
313
|
-
note: note_to_stderr,
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
364
|
// A failed pass is restarted while the carry record is still resumable (`run-wake-restart.ts`), so
|
|
318
|
-
// only a failure past the restart bound reaches `finish` and its warning
|
|
365
|
+
// only a failure past the restart bound reaches `finish` and its warning; the Claude lanes are brokered
|
|
366
|
+
// for as long as it supervises (`run-wake-supervise.ts`).
|
|
319
367
|
// **The record is claimed once and held across every restart**, so a `--stop` landing in the pause
|
|
320
368
|
// between two passes still finds it, signals this process, and the next pass reads the removal as the
|
|
321
369
|
// person's stop rather than claiming a fresh record and resuming.
|
|
322
370
|
async function loop(context: WakeContext, interval_ms: number): Promise<number> {
|
|
323
|
-
const read = run_carry.read_carry(context.carry_target)
|
|
324
|
-
|
|
325
|
-
if (read.kind !== 'carried') return refuse_without_carry(read.kind)
|
|
326
|
-
|
|
327
371
|
const profile = run_wake_session.scheduler_profile()
|
|
328
372
|
if (profile === undefined) return report(FAILED_VERDICT, FAILURE_EXIT_CODE)
|
|
329
373
|
|
|
330
|
-
const claimed =
|
|
374
|
+
const claimed = await run_wake_await.claim_run(context, profile, interval_ms)
|
|
331
375
|
|
|
332
|
-
if (claimed ===
|
|
376
|
+
if (claimed.kind === 'running') return report(RUNNING_VERDICT)
|
|
377
|
+
if (claimed.kind === 'refused') return refuse_without_carry(claimed.carry_kind)
|
|
333
378
|
|
|
334
|
-
const ports = ports_for(context, claimed.profile ?? profile)
|
|
335
|
-
const stop = await
|
|
379
|
+
const ports = ports_for(context, claimed.wake.profile ?? profile)
|
|
380
|
+
const stop = await run_wake_supervise.supervise({
|
|
381
|
+
context,
|
|
382
|
+
ports,
|
|
383
|
+
interval_ms,
|
|
384
|
+
note: note_to_stderr,
|
|
385
|
+
})
|
|
336
386
|
|
|
337
387
|
tidy_up(context.wake_target)
|
|
338
388
|
|
|
339
|
-
return await finish(stop, context)
|
|
389
|
+
return await finish(stop, context, claimed.carry.started_at)
|
|
340
390
|
}
|
|
341
391
|
|
|
342
392
|
// **The verdict tracks whether the supervisor is actually running, not merely whether a record is
|
|
@@ -352,30 +402,10 @@ function list(context: WakeContext): number {
|
|
|
352
402
|
return report(run_wake.is_supervisor_live(wake) ? SUPERVISING_VERDICT : STALE_VERDICT)
|
|
353
403
|
}
|
|
354
404
|
|
|
355
|
-
// **Removing the record is the stop; the signal only shortens the wait.** A pid already gone, or one
|
|
356
|
-
// this process may not signal, is not an error — the loop ends at its next pass either way, because it
|
|
357
|
-
// re-reads the record it no longer finds and `run_wake.update_wake` refuses to write one back.
|
|
358
|
-
function signal_supervisor(wake: RunWake): void {
|
|
359
|
-
if (!run_wake.is_supervisor_live(wake)) return
|
|
360
|
-
|
|
361
|
-
try {
|
|
362
|
-
process.kill(wake.pid, 'SIGTERM')
|
|
363
|
-
} catch {
|
|
364
|
-
console.error(
|
|
365
|
-
`Process ${String(wake.pid)} could not be signalled; the record is removed regardless.`,
|
|
366
|
-
)
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
405
|
function stop_supervisor(context: WakeContext): number {
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
if (wake === undefined) return report(NONE_VERDICT)
|
|
374
|
-
|
|
375
|
-
run_wake.remove_wake(context.wake_target)
|
|
376
|
-
signal_supervisor(wake)
|
|
406
|
+
const stopped = run_wake_stop.stop(context.wake_target)
|
|
377
407
|
|
|
378
|
-
return report(STOPPED_VERDICT)
|
|
408
|
+
return report(stopped === undefined ? NONE_VERDICT : STOPPED_VERDICT)
|
|
379
409
|
}
|
|
380
410
|
|
|
381
411
|
async function dispatch(
|
|
@@ -419,7 +449,11 @@ const run_wake_cli = {
|
|
|
419
449
|
SUPERVISING_VERDICT,
|
|
420
450
|
UNKNOWN_VERDICT,
|
|
421
451
|
USAGE,
|
|
452
|
+
finish,
|
|
453
|
+
ports_for,
|
|
454
|
+
record_failure,
|
|
422
455
|
run,
|
|
456
|
+
start_for_launch,
|
|
423
457
|
stop_body,
|
|
424
458
|
warning_body,
|
|
425
459
|
}
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
1
2
|
import { agent_role_profile } from '#scripts/agent/agent-role-profile'
|
|
2
3
|
import { gh_spawn } from '#scripts/gh/gh-spawn'
|
|
3
4
|
import { issue_citation } from '#scripts/rules/issue-citation'
|
|
4
5
|
import { run_carry, type CarryRead, type RunCarry } from '#scripts/run/carry/run-carry'
|
|
6
|
+
import { run_carry_ended } from '#scripts/run/carry/run-carry-ended'
|
|
5
7
|
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
6
8
|
import { run_liveness } from '#scripts/run/run-liveness'
|
|
7
9
|
import { run_wake, type RunWake, type WakeDecision, type WakeDecisionInput } from './run-wake'
|
|
10
|
+
import { run_wake_session } from './run-wake-session'
|
|
8
11
|
|
|
9
12
|
// The `--list` presentation of a wake record. What the command *does* stays in `run-wake-cli.ts`; how
|
|
10
13
|
// a supervisor's record reads back to a person lives here, one cohesive block.
|
|
11
14
|
|
|
12
15
|
const UNKNOWN_CUTS = 'an unreadable number of'
|
|
13
16
|
const REPO_LOOKUP_TIMEOUT_MS = 10_000
|
|
14
|
-
|
|
17
|
+
// The whole run, not the supervisor alone: `run:wake --stop` leaves the run's record behind, and a
|
|
18
|
+
// record with no owner is what the next `josh backlogrun` stands on without starting.
|
|
19
|
+
const STOP_COMMAND = 'pnpm josh backlogrun --stop'
|
|
15
20
|
const UNREADABLE_TIME = 'an unreadable time'
|
|
16
21
|
|
|
17
22
|
// The three paths every verb needs: the carry record it reads, its own record, and the work tree a
|
|
@@ -19,6 +24,8 @@ const UNREADABLE_TIME = 'an unreadable time'
|
|
|
19
24
|
// checkout — a lane must never be where the next session resumes, because a lane belongs to one child.
|
|
20
25
|
interface WakeContext {
|
|
21
26
|
carry_target: string
|
|
27
|
+
// Where `run:carry --end` records the run it removes — what tells an ended run from an unread one.
|
|
28
|
+
ended_target: string
|
|
22
29
|
wake_target: string
|
|
23
30
|
// Where everything this supervisor starts writes its output. It is named
|
|
24
31
|
// in `--list` and in every warning, because a path nobody is told is a file nobody reads.
|
|
@@ -28,6 +35,8 @@ interface WakeContext {
|
|
|
28
35
|
// side uses. `--list` relays the newest event from here: the report surface belongs to the run, and
|
|
29
36
|
// `--list` is the degenerate last-event read of the same stream the attached session follows.
|
|
30
37
|
event_target: string
|
|
38
|
+
// The common git directory every record is keyed on — what finds the launch `josh backlogrun` made.
|
|
39
|
+
git_directory: string
|
|
31
40
|
worktree: string
|
|
32
41
|
}
|
|
33
42
|
|
|
@@ -154,7 +163,32 @@ function describe_wake_cited(wake: RunWake, context: WakeContext): string {
|
|
|
154
163
|
return cite_description(describe_wake(wake, context), repo)
|
|
155
164
|
}
|
|
156
165
|
|
|
157
|
-
|
|
166
|
+
function context_of(directory: string, note: (line: string) => void): WakeContext {
|
|
167
|
+
const log_target = run_wake.wake_log_path(directory)
|
|
168
|
+
|
|
169
|
+
// Every verb reaches this one function, so creating the log here is what makes the path each of
|
|
170
|
+
// them prints a path that exists — including `--list` and the already-running branch of `--start`,
|
|
171
|
+
// neither of which launches anything.
|
|
172
|
+
run_wake_session.ensure_log(log_target, note)
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
carry_target: run_carry.carry_path(directory),
|
|
176
|
+
ended_target: run_carry_ended.ended_path(directory),
|
|
177
|
+
wake_target: run_wake.wake_path(directory),
|
|
178
|
+
log_target,
|
|
179
|
+
event_target: run_event_stream.target_of(directory),
|
|
180
|
+
git_directory: directory,
|
|
181
|
+
worktree: path.dirname(directory),
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const run_wake_describe = {
|
|
186
|
+
STOP_COMMAND,
|
|
187
|
+
cite_description,
|
|
188
|
+
context_of,
|
|
189
|
+
describe_wake,
|
|
190
|
+
describe_wake_cited,
|
|
191
|
+
}
|
|
158
192
|
|
|
159
193
|
export type { WakeContext }
|
|
160
194
|
export { run_wake_describe }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
|
|
2
2
|
import { run_carry, type CarryRead } from '#scripts/run/carry/run-carry'
|
|
3
|
+
import { run_event_setup } from '#scripts/run/event/run-event-setup'
|
|
3
4
|
import { run_invocation } from '#scripts/run/run-invocation'
|
|
4
5
|
import type { DriveResult } from './run-wake-loop'
|
|
5
6
|
|
|
@@ -125,10 +126,16 @@ function read_result(
|
|
|
125
126
|
return driver_result(result.out, run_carry.read_carry(target), invocation, result.err)
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
// **A record this process already owns is claimed again.** A driver that failed leaves the record
|
|
130
|
+
// adopted for the supervisor and not handed off, and the restarted pass re-drives it from here; only a
|
|
131
|
+
// record another live process holds is refused.
|
|
128
132
|
function claim_record(target: string, invocation: string): boolean {
|
|
129
133
|
const current = run_carry.read_carry(target)
|
|
130
134
|
if (current.kind !== 'carried' || current.carry.invocation !== invocation) return false
|
|
131
|
-
|
|
135
|
+
|
|
136
|
+
if (current.carry.is_handed_off !== true && run_carry.is_held_by_another(current.carry)) {
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
132
139
|
|
|
133
140
|
return run_carry.adopt_carry(target, current.carry, run_carry.owner_of(process.pid)) !== undefined
|
|
134
141
|
}
|
|
@@ -177,6 +184,15 @@ async function run_driver(
|
|
|
177
184
|
return await run_driver(target, args, wait, remaining - 1)
|
|
178
185
|
}
|
|
179
186
|
|
|
187
|
+
// **A result handed to a session is said on the stream first.** The driver has stopped asking, so
|
|
188
|
+
// without it the board keeps the `handing off` this pass wrote and a run waiting on a session's
|
|
189
|
+
// judgment reads as one still getting ready.
|
|
190
|
+
async function announced(result: DriveResult): Promise<DriveResult> {
|
|
191
|
+
if (result.kind === 'judgment') await run_event_setup.emit(run_event_setup.STAGE.JUDGING)
|
|
192
|
+
|
|
193
|
+
return result
|
|
194
|
+
}
|
|
195
|
+
|
|
180
196
|
async function drive(target: string, wait: Pause = pause): Promise<DriveResult> {
|
|
181
197
|
const read = run_carry.read_carry(target)
|
|
182
198
|
if (read.kind !== 'carried') return { kind: 'failed', note: 'The carry record changed.' }
|
|
@@ -188,10 +204,10 @@ async function drive(target: string, wait: Pause = pause): Promise<DriveResult>
|
|
|
188
204
|
return { kind: 'failed', note: 'The driver could not claim the carry record.' }
|
|
189
205
|
}
|
|
190
206
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
read.carry.invocation,
|
|
207
|
+
await run_event_setup.emit(run_event_setup.STAGE.HANDING_OFF)
|
|
208
|
+
|
|
209
|
+
return await announced(
|
|
210
|
+
read_result(await run_driver(target, args, wait, RERUN_LIMIT), target, read.carry.invocation),
|
|
195
211
|
)
|
|
196
212
|
}
|
|
197
213
|
|
|
@@ -21,7 +21,7 @@ function hand_off(target: string): void {
|
|
|
21
21
|
|
|
22
22
|
if (read.kind !== 'carried' || read.carry.is_handed_off === true) return
|
|
23
23
|
|
|
24
|
-
stamp_file.
|
|
24
|
+
stamp_file.replace_stamp(target, { ...read.carry, is_handed_off: true })
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const run_wake_handoff = { hand_off }
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { agent_argv, type AgentArgvResult } from '#scripts/agent/agent-argv'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
agent_role_profile,
|
|
4
|
+
type AgentProfile,
|
|
5
|
+
type AgentProvider,
|
|
6
|
+
type ProfileResult,
|
|
7
|
+
} from '#scripts/agent/agent-role-profile'
|
|
3
8
|
import { claude_agent_argv } from '#scripts/agent/claude-agent-argv'
|
|
9
|
+
import { backlogrun_launch } from '#scripts/backlog/backlogrun-launch'
|
|
4
10
|
import { detached_launch, type LaunchArgv } from '#scripts/run/detached-launch'
|
|
5
11
|
import { run_invocation } from '#scripts/run/run-invocation'
|
|
6
12
|
|
|
@@ -46,10 +52,20 @@ function scheduler_profile(): AgentProfile | undefined {
|
|
|
46
52
|
return undefined
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
|
|
55
|
+
// The scheduler profile of the provider a run was started on, whatever session the command was typed in
|
|
56
|
+
// (joshuafolkken/kit#3804).
|
|
57
|
+
function chosen_profile(provider: AgentProvider): ProfileResult {
|
|
58
|
+
const environment = backlogrun_launch.chosen_environment(provider)
|
|
59
|
+
|
|
60
|
+
return agent_role_profile.resolve(agent_role_profile.SCHEDULER, environment)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// **A Claude supervisor clears an inherited Codex session key**, so one started from a command typed in
|
|
64
|
+
// a Codex session does not read itself as both.
|
|
65
|
+
function supervisor_environment(profile: AgentProfile): Record<string, string | undefined> {
|
|
50
66
|
return profile.provider === 'openai'
|
|
51
67
|
? { CODEX_THREAD_ID: SUPERVISOR_SESSION }
|
|
52
|
-
: { CLAUDE_CODE_CHILD_SESSION: SUPERVISOR_SESSION }
|
|
68
|
+
: { CLAUDE_CODE_CHILD_SESSION: SUPERVISOR_SESSION, CODEX_THREAD_ID: undefined }
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
// **What comes back is always the rebuilt text, and it is refused unless it matches the record.** The
|
|
@@ -149,6 +165,7 @@ const run_wake_session = {
|
|
|
149
165
|
ensure_log: detached_launch.ensure_log,
|
|
150
166
|
is_safe_argv: detached_launch.is_safe_argv,
|
|
151
167
|
launch: detached_launch.launch,
|
|
168
|
+
chosen_profile,
|
|
152
169
|
scheduler_profile,
|
|
153
170
|
supervisor_argv,
|
|
154
171
|
supervisor_environment,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { run_wake, type RunWake } from './run-wake'
|
|
2
|
+
|
|
3
|
+
// Stopping the supervisor, apart from the command that prints the verdict: `run:wake --stop` and
|
|
4
|
+
// `josh backlogrun --stop` both stop it here, so the one stop is never written twice.
|
|
5
|
+
|
|
6
|
+
// **Removing the record is the stop; the signal only shortens the wait.** A pid already gone, or one
|
|
7
|
+
// this process may not signal, is not an error — the loop ends at its next pass either way, because it
|
|
8
|
+
// re-reads the record it no longer finds and `run_wake.update_wake` refuses to write one back.
|
|
9
|
+
function signal(pid: number): void {
|
|
10
|
+
try {
|
|
11
|
+
process.kill(pid, 'SIGTERM')
|
|
12
|
+
} catch {
|
|
13
|
+
console.error(
|
|
14
|
+
`Process ${String(pid)} could not be signalled; the record is removed regardless.`,
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function signal_supervisor(wake: RunWake): void {
|
|
20
|
+
if (run_wake.is_supervisor_live(wake)) signal(wake.pid)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The record that was stopped, `undefined` where there was none to stop.
|
|
24
|
+
function stop(wake_target: string): RunWake | undefined {
|
|
25
|
+
const wake = run_wake.read_wake(wake_target)
|
|
26
|
+
|
|
27
|
+
if (wake === undefined) return undefined
|
|
28
|
+
|
|
29
|
+
run_wake.remove_wake(wake_target)
|
|
30
|
+
signal_supervisor(wake)
|
|
31
|
+
|
|
32
|
+
return wake
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const run_wake_stop = { signal, stop }
|
|
36
|
+
|
|
37
|
+
export { run_wake_stop }
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { claude_lane_broker } from '#scripts/lane/claude-lane-broker'
|
|
2
|
+
import { poll } from '#scripts/lib/poll'
|
|
3
|
+
import { run_carry } from '#scripts/run/carry/run-carry'
|
|
4
|
+
import type { WakeContext } from './run-wake-describe'
|
|
5
|
+
import { run_wake_loop, type LoopPorts, type LoopStop } from './run-wake-loop'
|
|
6
|
+
import { run_wake_restart, type RestartPorts } from './run-wake-restart'
|
|
7
|
+
|
|
8
|
+
// The supervisor's whole life: the restarting loop (`run-wake-restart.ts`), with the Claude lanes
|
|
9
|
+
// brokered beside it.
|
|
10
|
+
//
|
|
11
|
+
// **The lanes are brokered for the supervisor's life, not one drive's**: a lane's ship asks for its
|
|
12
|
+
// reviewer or its relaunch whenever it gets there, a hand-back to a session included, and only this
|
|
13
|
+
// process holds the token those sessions sign in with (joshuafolkken/kit#3817, `claude-lane-broker.ts`).
|
|
14
|
+
|
|
15
|
+
interface SuperviseTarget {
|
|
16
|
+
context: WakeContext
|
|
17
|
+
ports: LoopPorts
|
|
18
|
+
interval_ms: number
|
|
19
|
+
note: (text: string) => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function restart_ports(target: SuperviseTarget): RestartPorts {
|
|
23
|
+
const { context, ports, interval_ms } = target
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
pass: async () => await run_wake_loop.run_loop(context.wake_target, ports, interval_ms),
|
|
27
|
+
is_resumable: () => run_carry.read_carry(context.carry_target).kind === 'carried',
|
|
28
|
+
pause: async () => {
|
|
29
|
+
await poll.sleep(interval_ms)
|
|
30
|
+
},
|
|
31
|
+
note: target.note,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function supervise(target: SuperviseTarget): Promise<LoopStop> {
|
|
36
|
+
return await claude_lane_broker.serve_during(
|
|
37
|
+
async () => await run_wake_restart.supervise(restart_ports(target)),
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const run_wake_supervise = { supervise }
|
|
42
|
+
|
|
43
|
+
export { run_wake_supervise }
|
|
44
|
+
export type { SuperviseTarget }
|