@joshuafolkken/kit 1.1387.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-IYVOLN6A.js → chunk-2G5F2YCD.js} +3 -3
- package/dist/hooks/{chunk-2YSW3CXH.js → chunk-3LE6VLSR.js} +17 -6
- package/dist/hooks/{chunk-J3KSS7II.js → chunk-4BHUPFKF.js} +1 -1
- package/dist/hooks/{chunk-EAQEGME7.js → chunk-5SJSSCJG.js} +1 -1
- package/dist/hooks/{chunk-FZ6FUSBM.js → chunk-723HKZO5.js} +1 -1
- package/dist/hooks/{chunk-LMWBRJIP.js → chunk-7KMN5QB5.js} +3 -3
- package/dist/hooks/{chunk-NEC4AT4Z.js → chunk-DGT54IH5.js} +4 -0
- package/dist/hooks/{chunk-STDSEPWU.js → chunk-EMB55BDF.js} +4 -4
- package/dist/hooks/{chunk-JBBMAPC4.js → chunk-IX6TTTI6.js} +4 -0
- package/dist/hooks/{chunk-GJ7JPIHN.js → chunk-JXQBK323.js} +2674 -187
- package/dist/hooks/{chunk-DCY2GWHC.js → chunk-MSYSUSY6.js} +5 -5
- package/dist/hooks/{chunk-EBSHN25W.js → chunk-MU4LJFIV.js} +4 -4
- package/dist/hooks/{chunk-WNFJLBXI.js → chunk-QL6QTPWN.js} +3 -3
- package/dist/hooks/{chunk-SL4GQ2XP.js → chunk-XER6SSIB.js} +91 -18
- package/dist/hooks/{chunk-U32VSRNG.js → chunk-YW452ZAX.js} +89 -34
- package/dist/hooks/{chunk-GNDJ7NJ7.js → chunk-YZXYOAZ3.js} +202 -55
- 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 +288 -229
- package/dist/hooks/investigation-guard.js +8 -8
- package/dist/hooks/pretool-guard.js +14 -14
- package/dist/hooks/session-lang.js +69 -16
- package/dist/hooks/stop-guard.js +13 -38
- package/dist/josh.js +62 -9
- 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-epic.ts +24 -8
- package/scripts/backlog/backlog-drive-launch.ts +3 -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-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 +24 -0
- 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 +15 -0
- package/scripts/init/start-steps.ts +46 -0
- package/scripts/init/start.ts +1 -0
- 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 +24 -0
- 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 +129 -53
- 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 +49 -3
- 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 +61 -13
- package/scripts/run/board/run-board-state.ts +10 -0
- package/scripts/run/board/run-board-status.ts +66 -7
- 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 +50 -21
- package/scripts/run/board/run-board-usual.ts +44 -0
- package/scripts/run/carry/run-carry-args.ts +9 -1
- package/scripts/run/carry/run-carry-cli.ts +2 -45
- package/scripts/run/carry/run-carry-end.ts +73 -0
- package/scripts/run/carry/run-carry.ts +8 -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 +2 -0
- package/scripts/run/event/run-event-scope.ts +1 -0
- package/scripts/run/event/run-event-setup.ts +55 -11
- package/scripts/run/event/run-event-stream.ts +27 -11
- 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/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 +60 -71
- package/scripts/run/wake/run-wake-describe.ts +34 -2
- package/scripts/run/wake/run-wake-driver.ts +11 -4
- 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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { AgentProfile } from '#scripts/agent/agent-role-profile'
|
|
2
|
+
import { backlogrun_launch } from '#scripts/backlog/backlogrun-launch'
|
|
3
|
+
import { poll } from '#scripts/lib/poll'
|
|
4
|
+
import { run_carry, type CarryRead, type RunCarry } from '#scripts/run/carry/run-carry'
|
|
5
|
+
import { run_wake, type RunWake } from './run-wake'
|
|
6
|
+
import type { WakeContext } from './run-wake-describe'
|
|
7
|
+
|
|
8
|
+
// How a supervisor claims the run it watches — including the one `josh backlogrun` starts beside the
|
|
9
|
+
// parent it launched, before that parent has claimed the carry record (joshuafolkken/kit#3804).
|
|
10
|
+
//
|
|
11
|
+
// **That supervisor is started by the command, not by the parent's own shell.** Claude Code removes its
|
|
12
|
+
// token from every process its Bash tool starts, so a supervisor the parent started there could not
|
|
13
|
+
// authenticate the sessions it wakes wherever the token is the only credential. Started by the command,
|
|
14
|
+
// it inherits the token — but before the parent has run `run:carry --begin`, so it waits for that record
|
|
15
|
+
// while the launch that will write it is alive, and gives up once that launch is gone.
|
|
16
|
+
//
|
|
17
|
+
// **While a launch is pending, only a live owner's record is its parent's**: a dead owner's record is one
|
|
18
|
+
// an earlier run left, and supervising it would wake that run beside the parent just launched.
|
|
19
|
+
|
|
20
|
+
interface AwaitPorts {
|
|
21
|
+
read_carry: () => CarryRead
|
|
22
|
+
// The invocation of the launched parent that has not claimed the carry record yet, `undefined` once
|
|
23
|
+
// no such launch is alive.
|
|
24
|
+
pending_invocation: () => string | undefined
|
|
25
|
+
is_owner_live: (carry: RunCarry) => boolean
|
|
26
|
+
sleep: (milliseconds: number) => Promise<void>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type ClaimResult =
|
|
30
|
+
| { kind: 'claimed'; wake: RunWake; carry: RunCarry }
|
|
31
|
+
| { kind: 'running' }
|
|
32
|
+
| { kind: 'refused'; carry_kind: CarryRead['kind'] }
|
|
33
|
+
|
|
34
|
+
function is_live_carry(read: CarryRead, ports: AwaitPorts): boolean {
|
|
35
|
+
return read.kind === 'carried' && ports.is_owner_live(read.carry)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// What the supervisor claims its record for: the live carried run, else the launch about to carry one,
|
|
39
|
+
// else the carried run a supervisor started with no launch pending wakes.
|
|
40
|
+
function supervised_invocation(ports: AwaitPorts): string | undefined {
|
|
41
|
+
const read = ports.read_carry()
|
|
42
|
+
|
|
43
|
+
if (read.kind !== 'carried') return ports.pending_invocation()
|
|
44
|
+
if (ports.is_owner_live(read.carry)) return read.carry.invocation
|
|
45
|
+
|
|
46
|
+
return ports.pending_invocation() ?? read.carry.invocation
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// **The carry record is read once more after the launch is gone**, because a parent that wrote the
|
|
50
|
+
// record and ended between the two reads still left a run to supervise.
|
|
51
|
+
async function await_carry(ports: AwaitPorts, interval_ms: number): Promise<CarryRead> {
|
|
52
|
+
const read = ports.read_carry()
|
|
53
|
+
|
|
54
|
+
if (is_live_carry(read, ports)) return read
|
|
55
|
+
if (ports.pending_invocation() === undefined) return ports.read_carry()
|
|
56
|
+
|
|
57
|
+
await ports.sleep(interval_ms)
|
|
58
|
+
|
|
59
|
+
return await await_carry(ports, interval_ms)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function ports_of(context: WakeContext): AwaitPorts {
|
|
63
|
+
return {
|
|
64
|
+
read_carry: () => run_carry.read_carry(context.carry_target),
|
|
65
|
+
pending_invocation: () => backlogrun_launch.pending_launch(context.git_directory)?.invocation,
|
|
66
|
+
is_owner_live: (carry) => run_carry.is_owner_live(carry),
|
|
67
|
+
sleep: poll.sleep,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The run the claim is held for: the one claimed for, or **a live run carried under another
|
|
72
|
+
// invocation** — a person's own `run:carry --begin` landing before the launched parent's. That run's
|
|
73
|
+
// `run:wake --start` found this supervisor live and answered `running`, so letting the claim go here
|
|
74
|
+
// would leave it with nobody watching it.
|
|
75
|
+
function supervised_carry(
|
|
76
|
+
read: CarryRead,
|
|
77
|
+
invocation: string,
|
|
78
|
+
ports: AwaitPorts,
|
|
79
|
+
): RunCarry | undefined {
|
|
80
|
+
if (read.kind !== 'carried') return undefined
|
|
81
|
+
if (read.carry.invocation === invocation || ports.is_owner_live(read.carry)) return read.carry
|
|
82
|
+
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// **The record is re-pointed at the run it now supervises**, because the loop wakes sessions for the
|
|
87
|
+
// record's invocation. **It carries the state a claim for that run would**, not the state the claim
|
|
88
|
+
// inherited for the pending launch's invocation — that run's wakes are not this one's. A refused write
|
|
89
|
+
// means the record is no longer this process's, which the loop's first pass reads as the stop it is.
|
|
90
|
+
function held_for(
|
|
91
|
+
target: string,
|
|
92
|
+
wake: RunWake,
|
|
93
|
+
carry: RunCarry,
|
|
94
|
+
profile: AgentProfile,
|
|
95
|
+
): ClaimResult {
|
|
96
|
+
if (carry.invocation === wake.invocation) return { kind: 'claimed', wake, carry }
|
|
97
|
+
|
|
98
|
+
const supervised = run_wake.fresh_wake(carry.invocation, new Date(), profile)
|
|
99
|
+
|
|
100
|
+
run_wake.update_wake(target, supervised)
|
|
101
|
+
|
|
102
|
+
return { kind: 'claimed', wake: supervised, carry }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// **The record is claimed before the carry record is awaited**, so the parent's own `run:wake --start`
|
|
106
|
+
// finds this supervisor live and answers `running` rather than starting a second one beside it.
|
|
107
|
+
// A parent that ends without carrying a run leaves nothing to supervise, and the claim is let go.
|
|
108
|
+
async function claim_run(
|
|
109
|
+
context: WakeContext,
|
|
110
|
+
profile: AgentProfile,
|
|
111
|
+
interval_ms: number,
|
|
112
|
+
ports: AwaitPorts = ports_of(context),
|
|
113
|
+
): Promise<ClaimResult> {
|
|
114
|
+
const invocation = supervised_invocation(ports)
|
|
115
|
+
if (invocation === undefined) return { kind: 'refused', carry_kind: ports.read_carry().kind }
|
|
116
|
+
const wake = run_wake.claim(context.wake_target, invocation, new Date(), profile)
|
|
117
|
+
if (wake === undefined) return { kind: 'running' }
|
|
118
|
+
const read = await await_carry(ports, interval_ms)
|
|
119
|
+
const carry = supervised_carry(read, invocation, ports)
|
|
120
|
+
if (carry !== undefined) return held_for(context.wake_target, wake, carry, profile)
|
|
121
|
+
|
|
122
|
+
run_wake.tidy_own_wake(context.wake_target)
|
|
123
|
+
|
|
124
|
+
return { kind: 'refused', carry_kind: read.kind }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const run_wake_await = { await_carry, claim_run, supervised_invocation }
|
|
128
|
+
|
|
129
|
+
export type { AwaitPorts, ClaimResult }
|
|
130
|
+
export { run_wake_await }
|
|
@@ -1,25 +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'
|
|
11
|
-
import {
|
|
9
|
+
import { run_event_setup } from '#scripts/run/event/run-event-setup'
|
|
12
10
|
import { run_event_stream } from '#scripts/run/event/run-event-stream'
|
|
13
11
|
import { run_event_stream_emit } from '#scripts/run/event/run-event-stream-emit'
|
|
14
12
|
import { run_headless } from '#scripts/run/run-headless'
|
|
15
13
|
import { run_stranded_detect, type WatchedRun } from '#scripts/run/run-stranded-detect'
|
|
16
|
-
import { run_wake, type
|
|
14
|
+
import { run_wake, type WakeStopReason, type WakeTidyResult } from './run-wake'
|
|
15
|
+
import { run_wake_await } from './run-wake-await'
|
|
17
16
|
import { run_wake_describe, type WakeContext } from './run-wake-describe'
|
|
18
17
|
import { run_wake_driver } from './run-wake-driver'
|
|
19
18
|
import { run_wake_handoff } from './run-wake-handoff'
|
|
20
19
|
import { run_wake_loop, type LoopPorts, type LoopStop } from './run-wake-loop'
|
|
21
|
-
import { run_wake_restart, type RestartPorts } from './run-wake-restart'
|
|
22
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'
|
|
23
23
|
|
|
24
24
|
// `josh run:wake --start | --list | --stop | --loop` — the supervisor that continues a cut
|
|
25
25
|
// `backlogrun` or `queue` without a person retyping the keyword. **Which invocations it may continue
|
|
@@ -86,6 +86,7 @@ const STOP_BODIES: Record<WakeStopReason, string | undefined> = {
|
|
|
86
86
|
unreadable:
|
|
87
87
|
'The carry record could not be read, so the supervisor stopped rather than guess at it.',
|
|
88
88
|
}
|
|
89
|
+
const NO_PROFILE_NOTE = 'the scheduler profile could not be resolved'
|
|
89
90
|
const SUPERSEDED_RESULT: WakeTidyResult = 'superseded'
|
|
90
91
|
const SUPERSEDED_NOTE =
|
|
91
92
|
'another supervisor now holds the record, so it was left alone; this loop is the superseded one'
|
|
@@ -134,26 +135,14 @@ function group_count(values: ParsedValues): number {
|
|
|
134
135
|
return verbs.filter((flag) => flag === true).length
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
function context_of(directory: string): WakeContext {
|
|
139
|
+
return run_wake_describe.context_of(directory, note_to_stderr)
|
|
140
|
+
}
|
|
141
|
+
|
|
137
142
|
async function resolve_context(): Promise<WakeContext | undefined> {
|
|
138
143
|
const directory = await run_carry.repository_directory()
|
|
139
144
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const log_target = run_wake.wake_log_path(directory)
|
|
143
|
-
|
|
144
|
-
// Every verb reaches this one function, so creating the log here is what makes the path each of
|
|
145
|
-
// them prints a path that exists — including `--list` and the already-running branch of `--start`,
|
|
146
|
-
// neither of which launches anything.
|
|
147
|
-
run_wake_session.ensure_log(log_target, note_to_stderr)
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
carry_target: run_carry.carry_path(directory),
|
|
151
|
-
ended_target: run_carry_ended.ended_path(directory),
|
|
152
|
-
wake_target: run_wake.wake_path(directory),
|
|
153
|
-
log_target,
|
|
154
|
-
event_target: run_event_stream.target_of(directory),
|
|
155
|
-
worktree: path.dirname(directory),
|
|
156
|
-
}
|
|
145
|
+
return directory === undefined ? undefined : context_of(directory)
|
|
157
146
|
}
|
|
158
147
|
|
|
159
148
|
// There is nothing to supervise unless a run is actually carrying a budget. Refusing here rather than
|
|
@@ -165,6 +154,12 @@ function refuse_without_carry(kind: CarryRead['kind']): number {
|
|
|
165
154
|
return report(NONE_VERDICT, FAILURE_EXIT_CODE)
|
|
166
155
|
}
|
|
167
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
|
+
|
|
168
163
|
function wake_session(
|
|
169
164
|
context: WakeContext,
|
|
170
165
|
invocation: string,
|
|
@@ -180,8 +175,11 @@ function wake_session(
|
|
|
180
175
|
// under `claude -p` a turn-end is the process's end (`run-headless.ts`).
|
|
181
176
|
const environment = run_headless.environment()
|
|
182
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)
|
|
183
179
|
|
|
184
|
-
|
|
180
|
+
if (result.kind === 'launched') say_session_running(context)
|
|
181
|
+
|
|
182
|
+
return result
|
|
185
183
|
}
|
|
186
184
|
|
|
187
185
|
// **A record this supervisor owns itself is not a session at work.** A failed driver leaves the record
|
|
@@ -287,14 +285,18 @@ async function finish(stop: LoopStop, context: WakeContext, started_at: string):
|
|
|
287
285
|
return report(stop.reason, exit_code_of(stop.reason))
|
|
288
286
|
}
|
|
289
287
|
|
|
290
|
-
function
|
|
291
|
-
|
|
292
|
-
|
|
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 }
|
|
293
294
|
const argv = run_wake_session.supervisor_argv(SCRIPT_PATH, interval)
|
|
295
|
+
|
|
294
296
|
// **The supervisor's own output goes to the same file as the sessions it starts.** It is detached
|
|
295
297
|
// with the same call, so otherwise its `console.error` — the launch note that says the agent CLI is
|
|
296
298
|
// not on `PATH`, and the stop note behind every warning — would go nowhere at all.
|
|
297
|
-
|
|
299
|
+
return run_wake_session.launch(
|
|
298
300
|
{
|
|
299
301
|
argv,
|
|
300
302
|
cwd: context.worktree,
|
|
@@ -303,6 +305,20 @@ function spawn_supervisor(context: WakeContext, interval: string | undefined): n
|
|
|
303
305
|
},
|
|
304
306
|
note_to_stderr,
|
|
305
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())
|
|
306
322
|
|
|
307
323
|
if (result.kind === 'failed') {
|
|
308
324
|
console.error(`Could not start the supervisor: ${result.note}`)
|
|
@@ -345,40 +361,32 @@ function tidy_up(target: string): void {
|
|
|
345
361
|
if (run_wake.tidy_own_wake(target) === SUPERSEDED_RESULT) note_to_stderr(SUPERSEDED_NOTE)
|
|
346
362
|
}
|
|
347
363
|
|
|
348
|
-
function restart_ports(context: WakeContext, ports: LoopPorts, interval_ms: number): RestartPorts {
|
|
349
|
-
return {
|
|
350
|
-
pass: async () => await run_wake_loop.run_loop(context.wake_target, ports, interval_ms),
|
|
351
|
-
is_resumable: () => run_carry.read_carry(context.carry_target).kind === 'carried',
|
|
352
|
-
pause: async () => {
|
|
353
|
-
await poll.sleep(interval_ms)
|
|
354
|
-
},
|
|
355
|
-
note: note_to_stderr,
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
364
|
// A failed pass is restarted while the carry record is still resumable (`run-wake-restart.ts`), so
|
|
360
|
-
// 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`).
|
|
361
367
|
// **The record is claimed once and held across every restart**, so a `--stop` landing in the pause
|
|
362
368
|
// between two passes still finds it, signals this process, and the next pass reads the removal as the
|
|
363
369
|
// person's stop rather than claiming a fresh record and resuming.
|
|
364
370
|
async function loop(context: WakeContext, interval_ms: number): Promise<number> {
|
|
365
|
-
const read = run_carry.read_carry(context.carry_target)
|
|
366
|
-
|
|
367
|
-
if (read.kind !== 'carried') return refuse_without_carry(read.kind)
|
|
368
|
-
|
|
369
371
|
const profile = run_wake_session.scheduler_profile()
|
|
370
372
|
if (profile === undefined) return report(FAILED_VERDICT, FAILURE_EXIT_CODE)
|
|
371
373
|
|
|
372
|
-
const claimed =
|
|
374
|
+
const claimed = await run_wake_await.claim_run(context, profile, interval_ms)
|
|
373
375
|
|
|
374
|
-
if (claimed ===
|
|
376
|
+
if (claimed.kind === 'running') return report(RUNNING_VERDICT)
|
|
377
|
+
if (claimed.kind === 'refused') return refuse_without_carry(claimed.carry_kind)
|
|
375
378
|
|
|
376
|
-
const ports = ports_for(context, claimed.profile ?? profile)
|
|
377
|
-
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
|
+
})
|
|
378
386
|
|
|
379
387
|
tidy_up(context.wake_target)
|
|
380
388
|
|
|
381
|
-
return await finish(stop, context,
|
|
389
|
+
return await finish(stop, context, claimed.carry.started_at)
|
|
382
390
|
}
|
|
383
391
|
|
|
384
392
|
// **The verdict tracks whether the supervisor is actually running, not merely whether a record is
|
|
@@ -394,30 +402,10 @@ function list(context: WakeContext): number {
|
|
|
394
402
|
return report(run_wake.is_supervisor_live(wake) ? SUPERVISING_VERDICT : STALE_VERDICT)
|
|
395
403
|
}
|
|
396
404
|
|
|
397
|
-
// **Removing the record is the stop; the signal only shortens the wait.** A pid already gone, or one
|
|
398
|
-
// this process may not signal, is not an error — the loop ends at its next pass either way, because it
|
|
399
|
-
// re-reads the record it no longer finds and `run_wake.update_wake` refuses to write one back.
|
|
400
|
-
function signal_supervisor(wake: RunWake): void {
|
|
401
|
-
if (!run_wake.is_supervisor_live(wake)) return
|
|
402
|
-
|
|
403
|
-
try {
|
|
404
|
-
process.kill(wake.pid, 'SIGTERM')
|
|
405
|
-
} catch {
|
|
406
|
-
console.error(
|
|
407
|
-
`Process ${String(wake.pid)} could not be signalled; the record is removed regardless.`,
|
|
408
|
-
)
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
405
|
function stop_supervisor(context: WakeContext): number {
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
if (wake === undefined) return report(NONE_VERDICT)
|
|
416
|
-
|
|
417
|
-
run_wake.remove_wake(context.wake_target)
|
|
418
|
-
signal_supervisor(wake)
|
|
406
|
+
const stopped = run_wake_stop.stop(context.wake_target)
|
|
419
407
|
|
|
420
|
-
return report(STOPPED_VERDICT)
|
|
408
|
+
return report(stopped === undefined ? NONE_VERDICT : STOPPED_VERDICT)
|
|
421
409
|
}
|
|
422
410
|
|
|
423
411
|
async function dispatch(
|
|
@@ -465,6 +453,7 @@ const run_wake_cli = {
|
|
|
465
453
|
ports_for,
|
|
466
454
|
record_failure,
|
|
467
455
|
run,
|
|
456
|
+
start_for_launch,
|
|
468
457
|
stop_body,
|
|
469
458
|
warning_body,
|
|
470
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
|
|
@@ -30,6 +35,8 @@ interface WakeContext {
|
|
|
30
35
|
// side uses. `--list` relays the newest event from here: the report surface belongs to the run, and
|
|
31
36
|
// `--list` is the degenerate last-event read of the same stream the attached session follows.
|
|
32
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
|
|
33
40
|
worktree: string
|
|
34
41
|
}
|
|
35
42
|
|
|
@@ -156,7 +163,32 @@ function describe_wake_cited(wake: RunWake, context: WakeContext): string {
|
|
|
156
163
|
return cite_description(describe_wake(wake, context), repo)
|
|
157
164
|
}
|
|
158
165
|
|
|
159
|
-
|
|
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
|
+
}
|
|
160
192
|
|
|
161
193
|
export type { WakeContext }
|
|
162
194
|
export { run_wake_describe }
|
|
@@ -184,6 +184,15 @@ async function run_driver(
|
|
|
184
184
|
return await run_driver(target, args, wait, remaining - 1)
|
|
185
185
|
}
|
|
186
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
|
+
|
|
187
196
|
async function drive(target: string, wait: Pause = pause): Promise<DriveResult> {
|
|
188
197
|
const read = run_carry.read_carry(target)
|
|
189
198
|
if (read.kind !== 'carried') return { kind: 'failed', note: 'The carry record changed.' }
|
|
@@ -197,10 +206,8 @@ async function drive(target: string, wait: Pause = pause): Promise<DriveResult>
|
|
|
197
206
|
|
|
198
207
|
await run_event_setup.emit(run_event_setup.STAGE.HANDING_OFF)
|
|
199
208
|
|
|
200
|
-
return
|
|
201
|
-
await run_driver(target, args, wait, RERUN_LIMIT),
|
|
202
|
-
target,
|
|
203
|
-
read.carry.invocation,
|
|
209
|
+
return await announced(
|
|
210
|
+
read_result(await run_driver(target, args, wait, RERUN_LIMIT), target, read.carry.invocation),
|
|
204
211
|
)
|
|
205
212
|
}
|
|
206
213
|
|
|
@@ -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 }
|