@telora/daemon 0.24.2 → 0.24.7
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/README.md +11 -3
- package/build-info.json +8 -3
- package/dist/backends/claude/one-shot-model.d.ts.map +1 -1
- package/dist/backends/claude/one-shot-model.js +2 -0
- package/dist/backends/claude/one-shot-model.js.map +1 -1
- package/dist/ci/ci-status.d.ts.map +1 -1
- package/dist/ci/ci-status.js +1 -0
- package/dist/ci/ci-status.js.map +1 -1
- package/dist/ci/product-ci-repo.d.ts.map +1 -1
- package/dist/ci/product-ci-repo.js +3 -1
- package/dist/ci/product-ci-repo.js.map +1 -1
- package/dist/cli/env-command.d.ts +21 -0
- package/dist/cli/env-command.d.ts.map +1 -0
- package/dist/cli/env-command.js +42 -0
- package/dist/cli/env-command.js.map +1 -0
- package/dist/completion/state-cascade/review-approved-head-gate.d.ts.map +1 -1
- package/dist/completion/state-cascade/review-approved-head-gate.js +2 -0
- package/dist/completion/state-cascade/review-approved-head-gate.js.map +1 -1
- package/dist/completion/state-cascade/review-capture-head-gate.d.ts +28 -0
- package/dist/completion/state-cascade/review-capture-head-gate.d.ts.map +1 -0
- package/dist/completion/state-cascade/review-capture-head-gate.js +48 -0
- package/dist/completion/state-cascade/review-capture-head-gate.js.map +1 -0
- package/dist/completion/state-cascade/state-cascade.d.ts +12 -0
- package/dist/completion/state-cascade/state-cascade.d.ts.map +1 -1
- package/dist/completion/state-cascade/state-cascade.js +19 -10
- package/dist/completion/state-cascade/state-cascade.js.map +1 -1
- package/dist/composition/cli-env-file-context.d.ts +4 -0
- package/dist/composition/cli-env-file-context.d.ts.map +1 -0
- package/dist/composition/cli-env-file-context.js +27 -0
- package/dist/composition/cli-env-file-context.js.map +1 -0
- package/dist/composition/daemon-contexts.d.ts.map +1 -1
- package/dist/composition/daemon-contexts.js +3 -1
- package/dist/composition/daemon-contexts.js.map +1 -1
- package/dist/composition/execution-git-context.d.ts.map +1 -1
- package/dist/composition/execution-git-context.js +1 -0
- package/dist/composition/execution-git-context.js.map +1 -1
- package/dist/composition/execution-worktree-context.js +1 -1
- package/dist/composition/release-note-pass-context.d.ts +7 -0
- package/dist/composition/release-note-pass-context.d.ts.map +1 -0
- package/dist/composition/release-note-pass-context.js +64 -0
- package/dist/composition/release-note-pass-context.js.map +1 -0
- package/dist/composition/runtime-contexts.d.ts.map +1 -1
- package/dist/composition/runtime-contexts.js +2 -0
- package/dist/composition/runtime-contexts.js.map +1 -1
- package/dist/composition/spawn-pass-deps.d.ts +2 -2
- package/dist/composition/spawn-pass-deps.d.ts.map +1 -1
- package/dist/composition/spawn-pass-deps.js +3 -1
- package/dist/composition/spawn-pass-deps.js.map +1 -1
- package/dist/composition/state-cascade-context.d.ts.map +1 -1
- package/dist/composition/state-cascade-context.js +2 -0
- package/dist/composition/state-cascade-context.js.map +1 -1
- package/dist/config/env-file.d.ts +37 -0
- package/dist/config/env-file.d.ts.map +1 -0
- package/dist/config/env-file.js +85 -0
- package/dist/config/env-file.js.map +1 -0
- package/dist/config/live-daemon-config.js +1 -1
- package/dist/daemon-state/daemon-process.d.ts +10 -0
- package/dist/daemon-state/daemon-process.d.ts.map +1 -1
- package/dist/daemon-state/daemon-process.js +24 -15
- package/dist/daemon-state/daemon-process.js.map +1 -1
- package/dist/daemon-state/provider-window.d.ts.map +1 -1
- package/dist/daemon-state/provider-window.js +1 -0
- package/dist/daemon-state/provider-window.js.map +1 -1
- package/dist/directive/directive-queue.js +1 -1
- package/dist/focus/daemon-contexts.d.ts +3 -0
- package/dist/focus/daemon-contexts.d.ts.map +1 -1
- package/dist/focus/daemon-contexts.js.map +1 -1
- package/dist/focus/focus-loop-registry.d.ts.map +1 -1
- package/dist/focus/focus-loop-registry.js +21 -0
- package/dist/focus/focus-loop-registry.js.map +1 -1
- package/dist/focus/pending-review-approved-head.d.ts +5 -1
- package/dist/focus/pending-review-approved-head.d.ts.map +1 -1
- package/dist/focus/pending-review-approved-head.js +35 -1
- package/dist/focus/pending-review-approved-head.js.map +1 -1
- package/dist/focus/review-spawn-coverage.d.ts +11 -0
- package/dist/focus/review-spawn-coverage.d.ts.map +1 -1
- package/dist/focus/review-spawn-coverage.js +14 -0
- package/dist/focus/review-spawn-coverage.js.map +1 -1
- package/dist/git/delivery-trailer.d.ts.map +1 -1
- package/dist/git/delivery-trailer.js +2 -0
- package/dist/git/delivery-trailer.js.map +1 -1
- package/dist/git/utils.d.ts.map +1 -1
- package/dist/git/utils.js +2 -1
- package/dist/git/utils.js.map +1 -1
- package/dist/index.js +14 -7
- package/dist/index.js.map +1 -1
- package/dist/merge/candidate-rework.d.ts.map +1 -1
- package/dist/merge/candidate-rework.js +24 -2
- package/dist/merge/candidate-rework.js.map +1 -1
- package/dist/merge/completion-merge-activity.d.ts.map +1 -1
- package/dist/merge/completion-merge-activity.js +7 -1
- package/dist/merge/completion-merge-activity.js.map +1 -1
- package/dist/merge/completion-merge-driver.d.ts +15 -5
- package/dist/merge/completion-merge-driver.d.ts.map +1 -1
- package/dist/merge/completion-merge-driver.js +7 -2
- package/dist/merge/completion-merge-driver.js.map +1 -1
- package/dist/merge/completion-merge-gate.d.ts +5 -1
- package/dist/merge/completion-merge-gate.d.ts.map +1 -1
- package/dist/merge/completion-merge-gate.js +23 -7
- package/dist/merge/completion-merge-gate.js.map +1 -1
- package/dist/merge/merge-back-loop.d.ts.map +1 -1
- package/dist/merge/merge-back-loop.js +2 -1
- package/dist/merge/merge-back-loop.js.map +1 -1
- package/dist/merge/merge-gate-coverage.d.ts +0 -2
- package/dist/merge/merge-gate-coverage.d.ts.map +1 -1
- package/dist/merge/merge-gate-coverage.js +11 -9
- package/dist/merge/merge-gate-coverage.js.map +1 -1
- package/dist/prompt-sections/release-note-faithfulness-section.d.ts +28 -0
- package/dist/prompt-sections/release-note-faithfulness-section.d.ts.map +1 -0
- package/dist/prompt-sections/release-note-faithfulness-section.js +99 -0
- package/dist/prompt-sections/release-note-faithfulness-section.js.map +1 -0
- package/dist/prompt-sections/release-note-section.d.ts +58 -0
- package/dist/prompt-sections/release-note-section.d.ts.map +1 -0
- package/dist/prompt-sections/release-note-section.js +285 -0
- package/dist/prompt-sections/release-note-section.js.map +1 -0
- package/dist/queries/api-client/shared.d.ts +1 -0
- package/dist/queries/api-client/shared.d.ts.map +1 -1
- package/dist/queries/api-client/shared.js +16 -1
- package/dist/queries/api-client/shared.js.map +1 -1
- package/dist/queries/release-notes/release-notes.d.ts +242 -0
- package/dist/queries/release-notes/release-notes.d.ts.map +1 -0
- package/dist/queries/release-notes/release-notes.js +148 -0
- package/dist/queries/release-notes/release-notes.js.map +1 -0
- package/dist/release-notes/release-note-author.d.ts +56 -0
- package/dist/release-notes/release-note-author.d.ts.map +1 -0
- package/dist/release-notes/release-note-author.js +107 -0
- package/dist/release-notes/release-note-author.js.map +1 -0
- package/dist/release-notes/release-note-faithfulness.d.ts +40 -0
- package/dist/release-notes/release-note-faithfulness.d.ts.map +1 -0
- package/dist/release-notes/release-note-faithfulness.js +107 -0
- package/dist/release-notes/release-note-faithfulness.js.map +1 -0
- package/dist/release-notes/release-note-pass.d.ts +77 -0
- package/dist/release-notes/release-note-pass.d.ts.map +1 -0
- package/dist/release-notes/release-note-pass.js +201 -0
- package/dist/release-notes/release-note-pass.js.map +1 -0
- package/dist/shell/unified-shell.d.ts.map +1 -1
- package/dist/shell/unified-shell.js +11 -5
- package/dist/shell/unified-shell.js.map +1 -1
- package/dist/testing/fixtures/api-client.js +1 -1
- package/dist/testing/fixtures/approved-head-inert-deps.d.ts +4 -3
- package/dist/testing/fixtures/approved-head-inert-deps.d.ts.map +1 -1
- package/dist/testing/fixtures/approved-head-inert-deps.js +3 -0
- package/dist/testing/fixtures/approved-head-inert-deps.js.map +1 -1
- package/dist/testing/fixtures/completion-judge-stubs.d.ts.map +1 -1
- package/dist/testing/fixtures/completion-judge-stubs.js +2 -0
- package/dist/testing/fixtures/completion-judge-stubs.js.map +1 -1
- package/dist/testing/fixtures/index.js +1 -1
- package/dist/testing/fixtures/pending-spawn-world.d.ts.map +1 -1
- package/dist/testing/fixtures/pending-spawn-world.js +1 -0
- package/dist/testing/fixtures/pending-spawn-world.js.map +1 -1
- package/dist/tower/tower-mode.d.ts +17 -5
- package/dist/tower/tower-mode.d.ts.map +1 -1
- package/dist/tower/tower-mode.js +27 -3
- package/dist/tower/tower-mode.js.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/worktree/capture-head-adoption.d.ts +44 -0
- package/dist/worktree/capture-head-adoption.d.ts.map +1 -0
- package/dist/worktree/capture-head-adoption.js +96 -0
- package/dist/worktree/capture-head-adoption.js.map +1 -0
- package/dist/worktree/safety.d.ts +12 -3
- package/dist/worktree/safety.d.ts.map +1 -1
- package/dist/worktree/safety.js +15 -3
- package/dist/worktree/safety.js.map +1 -1
- package/docs/runbook-tower.md +237 -0
- package/package.json +8 -5
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** The focus branch whose head a review would judge, and the repo its ref lives in (worktrees share it). */
|
|
2
|
+
export interface CaptureHeadTarget {
|
|
3
|
+
repoPath: string;
|
|
4
|
+
branchName: string | null;
|
|
5
|
+
}
|
|
6
|
+
/** What the adoption found at the focus head, and did. */
|
|
7
|
+
export type CaptureHeadAdoption =
|
|
8
|
+
/** The head is not a daemon capture: nothing to adopt, review may judge it. */
|
|
9
|
+
{
|
|
10
|
+
kind: 'not_capture';
|
|
11
|
+
headSha: string;
|
|
12
|
+
}
|
|
13
|
+
/** The head was a capture; `adoptedSha` (the new head) commits it as the team's own. */
|
|
14
|
+
| {
|
|
15
|
+
kind: 'adopted';
|
|
16
|
+
captureSha: string;
|
|
17
|
+
adoptedSha: string;
|
|
18
|
+
trailerValue: string;
|
|
19
|
+
}
|
|
20
|
+
/** The head is a capture and could not be adopted (or the branch moved meanwhile); retried next read. */
|
|
21
|
+
| {
|
|
22
|
+
kind: 'failed';
|
|
23
|
+
captureSha: string;
|
|
24
|
+
reason: string;
|
|
25
|
+
}
|
|
26
|
+
/** The head could not be read. */
|
|
27
|
+
| {
|
|
28
|
+
kind: 'unreadable';
|
|
29
|
+
reason: string;
|
|
30
|
+
};
|
|
31
|
+
/** Subject prefix of an adoption commit -- deliberately not a capture subject. */
|
|
32
|
+
export declare const CAPTURE_ADOPTION_SUBJECT_PREFIX = "chore: adopt captured agent work as the team's own commit (";
|
|
33
|
+
/** The adoption commit's message, as commit-tree `-m` paragraphs (the last one is the trailer block). */
|
|
34
|
+
export declare function captureAdoptionMessage(capture: {
|
|
35
|
+
sha: string;
|
|
36
|
+
subject: string;
|
|
37
|
+
}, trailerValue: string): string[];
|
|
38
|
+
/**
|
|
39
|
+
* Adopt the focus head when it is a daemon capture (module doc). Never throws:
|
|
40
|
+
* an unreadable head, a failed commit or a branch that moved meanwhile is
|
|
41
|
+
* reported, and the next read retries against whatever the head is then.
|
|
42
|
+
*/
|
|
43
|
+
export declare function adoptCaptureHead(target: CaptureHeadTarget): CaptureHeadAdoption;
|
|
44
|
+
//# sourceMappingURL=capture-head-adoption.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-head-adoption.d.ts","sourceRoot":"","sources":["../../src/worktree/capture-head-adoption.ts"],"names":[],"mappings":"AAwCA,4GAA4G;AAC5G,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,0DAA0D;AAC1D,MAAM,MAAM,mBAAmB;AAC7B,+EAA+E;AAC7E;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAC1C,wFAAwF;GACtF;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE;AACnF,yGAAyG;GACvG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACxD,kCAAkC;GAChC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3C,kFAAkF;AAClF,eAAO,MAAM,+BAA+B,gEAAiE,CAAC;AAmB9G,yGAAyG;AACzG,wBAAgB,sBAAsB,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CAShH;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,mBAAmB,CAsB/E"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
|
+
// Pinned by: packages/daemon/src/completion/state-cascade/review-capture-head-gate.test.ts,
|
|
3
|
+
// packages/daemon/src/focus/pending-review-capture-head.test.ts,
|
|
4
|
+
// packages/daemon/src/integration/capture-head-review-regression.test.ts,
|
|
5
|
+
// packages/daemon/src/integration/commits-after-approval-review-regression.test.ts,
|
|
6
|
+
// packages/daemon/src/integration/fully-merged-focus-sibling-red-scenario.test.ts,
|
|
7
|
+
// packages/daemon/src/worktree/capture-head-adoption.test.ts
|
|
8
|
+
// PINNED-BY-END
|
|
9
|
+
/**
|
|
10
|
+
* A focus review never judges a daemon WIP capture: before a review window opens
|
|
11
|
+
* or a review spawns, a focus head that is a daemon capture of uncommitted agent
|
|
12
|
+
* work is ADOPTED as the team's own commit, and the review judges that commit.
|
|
13
|
+
*
|
|
14
|
+
* Why: the completion merge gate never lets a capture anchor its own approval
|
|
15
|
+
* (merge/merge-gate-coverage.ts anchorGates), and review runs once per focus head
|
|
16
|
+
* (focus/pending-review-approved-head.ts), so a review that approved a capture
|
|
17
|
+
* head left a finished focus held off integration with no step that advances it.
|
|
18
|
+
* The fix is at the source: the head a review judges is never a capture.
|
|
19
|
+
* History: docs/daemon/history-platform.md (worktree/capture-head-adoption.ts).
|
|
20
|
+
*
|
|
21
|
+
* The adoption is the daemon committing on the team's behalf, append-only: a new
|
|
22
|
+
* commit on top of the capture with the capture's tree (it changes no file), a
|
|
23
|
+
* subject that is not a capture subject, and the capture's Telora-Delivery
|
|
24
|
+
* trailer -- its delivery's id, or `none` when the capture names no delivery (the
|
|
25
|
+
* adoption itself carries no work, and an `unattributed` stamp would be named as
|
|
26
|
+
* misattributed work). History is never rewritten: verdicts, attribution and
|
|
27
|
+
* holds are keyed on commit shas. The branch moves by a compare-and-swap ref
|
|
28
|
+
* update, so a commit that landed meanwhile is never overwritten. Plumbing
|
|
29
|
+
* (commit-tree + update-ref) runs no hooks and touches no worktree: a worktree on
|
|
30
|
+
* the branch follows it to a commit with the same tree, so its index and files
|
|
31
|
+
* stay exactly as they were.
|
|
32
|
+
*
|
|
33
|
+
* @module worktree/capture-head-adoption
|
|
34
|
+
*/
|
|
35
|
+
import { runGitSync } from '../git/types.js';
|
|
36
|
+
import { classifyDeliveryTrailer, DELIVERY_TRAILER_KEY, NON_DELIVERY_SENTINEL } from '../git/delivery-trailer.js';
|
|
37
|
+
import { isDaemonWipCapture } from './safety.js';
|
|
38
|
+
/** Subject prefix of an adoption commit -- deliberately not a capture subject. */
|
|
39
|
+
export const CAPTURE_ADOPTION_SUBJECT_PREFIX = 'chore: adopt captured agent work as the team\'s own commit (';
|
|
40
|
+
const SHA_RE = /^[0-9a-f]{40,64}$/i;
|
|
41
|
+
const FIELD_SEP = '\x1f';
|
|
42
|
+
/** The head commit of `ref`, or why it could not be read. */
|
|
43
|
+
function readHead(repoPath, ref) {
|
|
44
|
+
const result = runGitSync(['log', '-1', `--format=%H${FIELD_SEP}%s`, ref, '--'], repoPath);
|
|
45
|
+
if (!result.success)
|
|
46
|
+
return { reason: `git log ${ref} failed: ${result.error || result.output}` };
|
|
47
|
+
const [sha = '', ...subject] = result.output.trim().split(FIELD_SEP);
|
|
48
|
+
return SHA_RE.test(sha) ? { sha, subject: subject.join(FIELD_SEP) } : { reason: `${ref} has no readable head` };
|
|
49
|
+
}
|
|
50
|
+
/** The trailer an adoption carries: the capture's delivery, else the non-delivery sentinel (module doc). */
|
|
51
|
+
function adoptionTrailerValue(repoPath, captureSha) {
|
|
52
|
+
const attribution = classifyDeliveryTrailer(repoPath, captureSha);
|
|
53
|
+
return attribution.kind === 'delivery' ? attribution.deliveryId : NON_DELIVERY_SENTINEL;
|
|
54
|
+
}
|
|
55
|
+
/** The adoption commit's message, as commit-tree `-m` paragraphs (the last one is the trailer block). */
|
|
56
|
+
export function captureAdoptionMessage(capture, trailerValue) {
|
|
57
|
+
return [
|
|
58
|
+
`${CAPTURE_ADOPTION_SUBJECT_PREFIX}${capture.sha.slice(0, 12)})`,
|
|
59
|
+
`The daemon captured this focus's uncommitted agent work as ${capture.sha.slice(0, 12)} ` +
|
|
60
|
+
`("${capture.subject}"). A focus review never judges a daemon capture: the completion merge gate ` +
|
|
61
|
+
`never lets a capture anchor its own approval. This commit adopts that work as the team's own, so ` +
|
|
62
|
+
`the review judges a commit the merge gate accepts. It changes no files.`,
|
|
63
|
+
`${DELIVERY_TRAILER_KEY}: ${trailerValue}`,
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Adopt the focus head when it is a daemon capture (module doc). Never throws:
|
|
68
|
+
* an unreadable head, a failed commit or a branch that moved meanwhile is
|
|
69
|
+
* reported, and the next read retries against whatever the head is then.
|
|
70
|
+
*/
|
|
71
|
+
export function adoptCaptureHead(target) {
|
|
72
|
+
if (!target.branchName)
|
|
73
|
+
return { kind: 'unreadable', reason: 'the focus has no branch' };
|
|
74
|
+
const ref = `refs/heads/${target.branchName}`;
|
|
75
|
+
const head = readHead(target.repoPath, ref);
|
|
76
|
+
if ('reason' in head)
|
|
77
|
+
return { kind: 'unreadable', reason: head.reason };
|
|
78
|
+
if (!isDaemonWipCapture(head))
|
|
79
|
+
return { kind: 'not_capture', headSha: head.sha };
|
|
80
|
+
const trailerValue = adoptionTrailerValue(target.repoPath, head.sha);
|
|
81
|
+
const paragraphs = captureAdoptionMessage(head, trailerValue).flatMap((p) => ['-m', p]);
|
|
82
|
+
const commit = runGitSync(['commit-tree', `${head.sha}^{tree}`, '-p', head.sha, ...paragraphs], target.repoPath);
|
|
83
|
+
const adoptedSha = commit.output.trim();
|
|
84
|
+
if (!commit.success || !SHA_RE.test(adoptedSha)) {
|
|
85
|
+
return { kind: 'failed', captureSha: head.sha, reason: `git commit-tree failed: ${commit.error || commit.output}` };
|
|
86
|
+
}
|
|
87
|
+
// Compare-and-swap: moves the branch only if its head is still the capture. The
|
|
88
|
+
// reflog step reads as a commit, so git/focus-own-delta.ts counts it as the focus's own.
|
|
89
|
+
const reflog = `commit: adopt daemon capture ${head.sha.slice(0, 12)} as the team's own (telora-daemon)`;
|
|
90
|
+
const moved = runGitSync(['update-ref', '-m', reflog, ref, adoptedSha, head.sha], target.repoPath);
|
|
91
|
+
if (!moved.success) {
|
|
92
|
+
return { kind: 'failed', captureSha: head.sha, reason: `${ref} moved or could not be updated: ${moved.error || moved.output}` };
|
|
93
|
+
}
|
|
94
|
+
return { kind: 'adopted', captureSha: head.sha, adoptedSha, trailerValue };
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=capture-head-adoption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-head-adoption.js","sourceRoot":"","sources":["../../src/worktree/capture-head-adoption.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,12 +29,21 @@ export declare function branchHasUnmergedCommits(branchName: string, targetBranc
|
|
|
29
29
|
/**
|
|
30
30
|
* Subject prefixes of the two commits the daemon writes to capture an agent's
|
|
31
31
|
* uncommitted work: commitWipChanges (team exit) and safetyCommitWorktree
|
|
32
|
-
* (worktree removal, terminated-team reconcile).
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* (worktree removal, terminated-team reconcile). A daemon capture is recognized
|
|
33
|
+
* by these (isDaemonWipCapture), so the writers build their subjects from the
|
|
34
|
+
* same constants.
|
|
35
35
|
*/
|
|
36
36
|
export declare const WIP_CAPTURE_SUBJECT_PREFIX = "WIP: Uncommitted work captured by daemon on agent exit (";
|
|
37
37
|
export declare const SAFETY_COMMIT_SUBJECT_PREFIX = "wip: uncommitted agent work for \"";
|
|
38
|
+
/**
|
|
39
|
+
* True for a commit the daemon wrote to capture an agent's uncommitted work (a
|
|
40
|
+
* commitWipChanges or safetyCommitWorktree subject). The ONE recognizer: the merge
|
|
41
|
+
* gate's capture rule (merge/merge-gate-coverage.ts) and the review's capture-head
|
|
42
|
+
* adoption (worktree/capture-head-adoption.ts) both read it. Pure.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isDaemonWipCapture(commit: {
|
|
45
|
+
readonly subject: string;
|
|
46
|
+
}): boolean;
|
|
38
47
|
/** The subject commitWipChanges writes for `contextName`. */
|
|
39
48
|
export declare function wipCaptureSubject(contextName: string): string;
|
|
40
49
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../src/worktree/safety.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../src/worktree/safety.ts"],"names":[],"mappings":"AA6BA,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAChC,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,kCAAkC,CAAC;AAEtF;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,gBAAgB,EAAE,sBAAsB,CAAC;IAClD,QAAQ,CAAC,kBAAkB,EAAE,6BAA6B,CAAC;IAC3D,QAAQ,CAAC,eAAe,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;CAC9E;AAMD;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAa3E;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,OAAO,CAMT;AAED;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,6DAA6D,CAAC;AACrG,eAAO,MAAM,4BAA4B,uCAAsC,CAAC;AAEhF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAEhF;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,sBAAsB,EACjC,WAAW,CAAC,EAAE,uBAAuB,GACpC,MAAM,GAAG,IAAI,CAaf;AAED;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,EAAE,SAAS,MAAM,EAMzD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,qCAAqC,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAOnF;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,sBAAsB,GAChC,MAAM,GAAG,IAAI,CAUf;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE;IACP,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iEAAiE;IACjE,SAAS,EAAE,sBAAsB,CAAC;IAClC,4EAA4E;IAC5E,WAAW,CAAC,EAAE,uBAAuB,CAAC;CACvC,GACA,OAAO,CAoFT;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,sBAAsB,EACjC,WAAW,CAAC,EAAE,uBAAuB,GACpC,MAAM,GAAG,IAAI,CASf"}
|
package/dist/worktree/safety.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
// PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
|
|
2
2
|
// Pinned by: packages/daemon/src/completion/team-exit/teardown-merge-gate.test.ts,
|
|
3
3
|
// packages/daemon/src/completion/team-teardown/mid-flight-merge-gate.test.ts,
|
|
4
|
+
// packages/daemon/src/focus/pending-review-capture-head.test.ts,
|
|
4
5
|
// packages/daemon/src/integration/approved-head-hold-release-completion-regression.test.ts,
|
|
6
|
+
// packages/daemon/src/integration/capture-head-review-regression.test.ts,
|
|
5
7
|
// packages/daemon/src/merge/attributed-capture-merge-gate.test.ts,
|
|
6
8
|
// packages/daemon/src/merge/completion-merge-gate.test.ts,
|
|
7
9
|
// packages/daemon/src/merge/merge-gate-coverage.test.ts,
|
|
8
10
|
// packages/daemon/src/merge/safe-remove-merge-gate.test.ts,
|
|
9
11
|
// packages/daemon/src/verify/attribution/exit-capture-attribution-regression.test.ts,
|
|
10
12
|
// packages/daemon/src/worktree/capture-attribution-commit.test.ts,
|
|
13
|
+
// packages/daemon/src/worktree/capture-head-adoption.test.ts,
|
|
11
14
|
// packages/daemon/src/worktree/safety-commit-guard.test.ts,
|
|
12
15
|
// packages/daemon/src/worktree/safety-commit-under-guard.test.ts,
|
|
13
16
|
// packages/daemon/src/worktree/unmerged-capture-refusal.test.ts,
|
|
@@ -60,12 +63,21 @@ export function branchHasUnmergedCommits(branchName, targetBranch, repoPath) {
|
|
|
60
63
|
/**
|
|
61
64
|
* Subject prefixes of the two commits the daemon writes to capture an agent's
|
|
62
65
|
* uncommitted work: commitWipChanges (team exit) and safetyCommitWorktree
|
|
63
|
-
* (worktree removal, terminated-team reconcile).
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
+
* (worktree removal, terminated-team reconcile). A daemon capture is recognized
|
|
67
|
+
* by these (isDaemonWipCapture), so the writers build their subjects from the
|
|
68
|
+
* same constants.
|
|
66
69
|
*/
|
|
67
70
|
export const WIP_CAPTURE_SUBJECT_PREFIX = 'WIP: Uncommitted work captured by daemon on agent exit (';
|
|
68
71
|
export const SAFETY_COMMIT_SUBJECT_PREFIX = 'wip: uncommitted agent work for "';
|
|
72
|
+
/**
|
|
73
|
+
* True for a commit the daemon wrote to capture an agent's uncommitted work (a
|
|
74
|
+
* commitWipChanges or safetyCommitWorktree subject). The ONE recognizer: the merge
|
|
75
|
+
* gate's capture rule (merge/merge-gate-coverage.ts) and the review's capture-head
|
|
76
|
+
* adoption (worktree/capture-head-adoption.ts) both read it. Pure.
|
|
77
|
+
*/
|
|
78
|
+
export function isDaemonWipCapture(commit) {
|
|
79
|
+
return commit.subject.startsWith(WIP_CAPTURE_SUBJECT_PREFIX) || commit.subject.startsWith(SAFETY_COMMIT_SUBJECT_PREFIX);
|
|
80
|
+
}
|
|
69
81
|
/** The subject commitWipChanges writes for `contextName`. */
|
|
70
82
|
export function wipCaptureSubject(contextName) {
|
|
71
83
|
return `${WIP_CAPTURE_SUBJECT_PREFIX}${contextName})`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/worktree/safety.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/worktree/safety.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Runbook: the product tower
|
|
2
|
+
|
|
3
|
+
The tower is traffic control for a product's armed foci. It changes what
|
|
4
|
+
ARMED means -- from "run now" to "run when cleared" -- and it never arms or
|
|
5
|
+
un-arms anything: arming stays the human go-signal. Decision record:
|
|
6
|
+
`docs/DECISIONS.md` ("The tower: traffic control for armed foci lives at the
|
|
7
|
+
product").
|
|
8
|
+
|
|
9
|
+
With the tower in `enforce`, an operator can arm several foci of a product,
|
|
10
|
+
set the product's limit to 1, and walk away: they run one at a time, in focus
|
|
11
|
+
rank order.
|
|
12
|
+
|
|
13
|
+
## The pieces
|
|
14
|
+
|
|
15
|
+
| Piece | Where | What it is |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| The limit | `products.tower_concurrency_limit` | How many of the product's foci may hold a live coding or review team at once. `NULL` = no product limit: the host session cap alone bounds it. Set with `telora_product` `update` `towerConcurrencyLimit` (`null` clears it). |
|
|
18
|
+
| The verdict | `product_focuses.tower_verdict` | The tower's verdict on one armed, active focus: `cleared`, or `holding` with exactly one reason and since-when. |
|
|
19
|
+
| The pass | `packages/daemon/src/tower/tower-pass.ts` | One pass per configured product; the SOLE writer of verdicts. |
|
|
20
|
+
| Admission | `packages/daemon/src/tower/tower-admission.ts` | What the spawn passes ask before they start a coding or review team. |
|
|
21
|
+
| The graph | `focus_tower_edges` | Declared `depends_on` / `excludes` edges between two foci of the product, with provenance and evidence. Written over MCP (`telora_product_tower`); shown on the product page's Focus tab. |
|
|
22
|
+
| Landed | `packages/daemon/src/tower/focus-landed.ts` | The ONE definition of "this focus's work has landed", shared with the PRD frontier. |
|
|
23
|
+
| The vocabulary | `@telora/daemon-core` `tower.ts` | Modes, verdict states, reasons (in precedence order), edge kinds and provenance, and the fail-open freshness rule -- one definition for the daemon and the dashboard. |
|
|
24
|
+
|
|
25
|
+
## The verdict
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
{ state: 'cleared' | 'holding',
|
|
29
|
+
reason: null | 'product_limit_reached' | 'waits_on' | 'excluded_by',
|
|
30
|
+
since: ISO, -- when this state + reason began (kept across re-confirmations)
|
|
31
|
+
computedAt: ISO, -- when the pass last confirmed it
|
|
32
|
+
armedAt: ISO|null, -- the arm (agent_assigned_at) it was computed under
|
|
33
|
+
mode: 'observe' | 'enforce',
|
|
34
|
+
detail: { limit?, occupied?, focusId?, focusName?, evidence? } }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The reasons, and what the dashboard says for each:
|
|
38
|
+
|
|
39
|
+
| Reason | Dashboard | Means |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `product_limit_reached` | Held by tower: product limit reached (N of L running) | The product's free slots went to foci ahead of this one. |
|
|
42
|
+
| `waits_on` | Held by tower: waits on <focus> | A `depends_on` predecessor has not landed (tower graph). |
|
|
43
|
+
| `excluded_by` | Held by tower: excluded by <focus> on <evidence> | An `excludes` edge joins it to a focus that is running or cleared ahead of it (tower graph). |
|
|
44
|
+
|
|
45
|
+
## The graph
|
|
46
|
+
|
|
47
|
+
Edges join two foci of ONE product (an edge across products, or from a focus to
|
|
48
|
+
itself, is refused):
|
|
49
|
+
|
|
50
|
+
- **`depends_on`** (directed): `toFocusId` waits until `fromFocusId` has
|
|
51
|
+
LANDED. A `depends_on` edge that would close a cycle is refused at the write,
|
|
52
|
+
and the refusal names the cycle (`UI -> Schema -> API -> UI`). The refusal is
|
|
53
|
+
atomic: a database trigger serializes each product's `depends_on` writes, so
|
|
54
|
+
two concurrent writes can never close a cycle between them.
|
|
55
|
+
- **`excludes`** (mutual): the two foci are never cleared together -- e.g. both
|
|
56
|
+
rewrite the same files, fixtures or migration journal. Stored once per pair.
|
|
57
|
+
|
|
58
|
+
Every edge carries **provenance** (`declared` -- written by a human or an agent
|
|
59
|
+
over MCP; `inferred` and `observed` are reserved for the follow-on focus that
|
|
60
|
+
draws edges itself), **evidence** (why the edge holds -- required), and who
|
|
61
|
+
wrote it and when. Deleting an edge leaves a tombstone (`deleted_at`).
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
telora_product_tower { action: "edge_create", productId, fromFocusId, toFocusId,
|
|
65
|
+
kind: "depends_on" | "excludes", evidence }
|
|
66
|
+
telora_product_tower { action: "edge_list", productId, focusId? }
|
|
67
|
+
telora_product_tower { action: "edge_delete", towerEdgeId }
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## The landed rule
|
|
71
|
+
|
|
72
|
+
A focus has LANDED -- judged by the daemon, no human close -- when:
|
|
73
|
+
|
|
74
|
+
1. it has at least one delivery, and
|
|
75
|
+
2. every non-cancelled delivery is `done` (at least one: a focus whose every
|
|
76
|
+
delivery was cancelled landed nothing), and
|
|
77
|
+
3. its branch tip is contained in the integration branch
|
|
78
|
+
(`git rev-list --count <integration>..<branch>` reads 0; no checkout) -- or
|
|
79
|
+
the branch is gone AND the focus's merge record (`focus_merges`, written by
|
|
80
|
+
the daemon when it merges) names a merge commit integration contains. The
|
|
81
|
+
daemon removes a focus branch only after merging it; the record is the proof.
|
|
82
|
+
A branch that is gone with no such record (a hand-deleted branch, a legacy
|
|
83
|
+
focus that never recorded one) is unproven: unknown, never landed.
|
|
84
|
+
|
|
85
|
+
The PRD frontier's completion signal is this same rule (`prd/controller-shared.ts`
|
|
86
|
+
`computeCompletedFocusIds`): a frontier overcome clears only when its focus has
|
|
87
|
+
landed. A landed check git cannot answer (integration unreadable, a ref or
|
|
88
|
+
containment read that errored, an unreadable or missing merge record) is UNKNOWN: the tower leaves the dependent focus's verdict unavailable (fail
|
|
89
|
+
open) rather than holding it, and the PRD frontier waits a tick.
|
|
90
|
+
|
|
91
|
+
## Precedence
|
|
92
|
+
|
|
93
|
+
A verdict records exactly one reason. When several apply, the first in this
|
|
94
|
+
order wins (daemon-core `TOWER_HOLD_REASONS`):
|
|
95
|
+
|
|
96
|
+
1. `waits_on` -- a fact about this focus's prerequisites;
|
|
97
|
+
2. `excluded_by` -- a fact about this focus's collisions;
|
|
98
|
+
3. `product_limit_reached` -- a fact about the queue.
|
|
99
|
+
|
|
100
|
+
A focus held for a graph reason takes no slot. `excluded_by` holds the focus
|
|
101
|
+
reached second, in rank order, while the other runs or is cleared, so two
|
|
102
|
+
excluded foci are never cleared together.
|
|
103
|
+
|
|
104
|
+
## The slot rule
|
|
105
|
+
|
|
106
|
+
A focus occupies one of the product's slots **only while it has a live coding
|
|
107
|
+
or review team**. An armed focus with no live team -- waiting on CI, a verify
|
|
108
|
+
hold, an escalation -- occupies nothing, so one stalled focus never holds the
|
|
109
|
+
product's queue.
|
|
110
|
+
|
|
111
|
+
Each pass, per product:
|
|
112
|
+
|
|
113
|
+
1. Every focus with a live team is cleared (the tower never stops a running team).
|
|
114
|
+
2. Free slots = limit - live foci (unbounded with no limit).
|
|
115
|
+
3. The other armed foci are walked in **focus rank order** (lowest
|
|
116
|
+
`priority_rank` first) -- foci the spawn passes last found wanting a team
|
|
117
|
+
ahead of foci they found idle. A focus that waits on an unlanded
|
|
118
|
+
predecessor, or is excluded by a focus already running or cleared, holds
|
|
119
|
+
for that reason; the rest each take a free slot (cleared) until none is
|
|
120
|
+
left, and the remainder hold `product_limit_reached`.
|
|
121
|
+
|
|
122
|
+
"Wanting" is CURRENT demand: a focus wants a slot when it could run now if the
|
|
123
|
+
tower and the host cap let it. Before the tower allocates, every ready-focus
|
|
124
|
+
pass refreshes stale agent roles (as the spawn pass does) and probes each
|
|
125
|
+
surfaced focus -- ready, or holding a queued directive -- through the gates its
|
|
126
|
+
spawn pass applies, in the pass's order, bar the tower and the host cap
|
|
127
|
+
(`packages/daemon/src/focus/spawn-demand-probe.ts`): its role, a live team, the
|
|
128
|
+
review budget, an open parking escalation (the guard `spawnFocusTeam` applies),
|
|
129
|
+
a demoted just-torn-down respawn, a queued review whose window cannot be read
|
|
130
|
+
(the ready pass withholds it), the operator-hold park, a review at an approved
|
|
131
|
+
head, a verify hold, a cooldown, no team work, and -- for a queued directive --
|
|
132
|
+
an active row the pass could not read (it cannot spawn without one). So a focus held by any of
|
|
133
|
+
those reserves no slot, even the first time it surfaces; a focus whose gate
|
|
134
|
+
clears is ranked with the waiters on that same pass; a focus that spawned has
|
|
135
|
+
had its demand met; and a focus no longer surfaced wants nothing -- when a team
|
|
136
|
+
exits, its focus reserves nothing and the slot goes to the next wanting focus on
|
|
137
|
+
the same pass. The gates that dispose of, park or release a queued directive (a
|
|
138
|
+
stale stage, a closed review window, a superseded coding directive, the
|
|
139
|
+
operator-hold park and its release, a review at an approved head) run for real
|
|
140
|
+
before allocation: they are idempotent and the pending pass would make the same
|
|
141
|
+
disposal on that poll. A probe that cannot answer keeps a fresh record and
|
|
142
|
+
otherwise fails open (the focus wants a slot), so a read error never demotes a
|
|
143
|
+
higher-ranked focus behind the waiters.
|
|
144
|
+
|
|
145
|
+
The pass runs at the head of every ready-focus pass -- on daemon start, on
|
|
146
|
+
every 30s poll, on the engine's 60s timer and on a completion trigger; there is
|
|
147
|
+
no production path to the ready pass that skips it, and the passes never overlap
|
|
148
|
+
(`runReadyFocusPassThroughTower`, pinned by `tower/tower-entrypoints.test.ts`) -- so a change to the armed set, the limit, focus rank or a
|
|
149
|
+
focus's team presence is reflected within one poll. It writes only a verdict
|
|
150
|
+
that changed, or one older than half the freshness bound (5 minutes), so a
|
|
151
|
+
running tower's verdicts never go stale. It also clears the verdict of a focus
|
|
152
|
+
that is no longer armed. A pass that throws for one product is logged and never
|
|
153
|
+
stops another product's pass; admission fails open on that product's foci at
|
|
154
|
+
once (see the fail-open rule).
|
|
155
|
+
|
|
156
|
+
## The three modes: `TELORA_TOWER_MODE`
|
|
157
|
+
|
|
158
|
+
Declared in `@telora/daemon-core` `env-config.ts`; default **`observe`**.
|
|
159
|
+
|
|
160
|
+
| Mode | Tower pass | Spawn passes |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| `off` | Does not run. | Exactly as before the tower: API order, host cap only. |
|
|
163
|
+
| `observe` | Writes verdicts; the dashboard shows them, marked "(observe only)". | Ignore verdicts. Every spawn decision `enforce` would have made differently is logged: `[tower] observe: enforce would hold "<focus>" (<reason> since <t>); decision taken: spawned`. |
|
|
164
|
+
| `enforce` | Writes verdicts. | Start no coding or review team for a focus whose verdict is holding AND fresh; offer ready foci, and foci with a queued (e.g. review) directive, to the host in focus rank order. |
|
|
165
|
+
|
|
166
|
+
## The fail-open rule
|
|
167
|
+
|
|
168
|
+
In every mode, only a fresh, explicit holding verdict ever stops a spawn, and
|
|
169
|
+
only while this daemon's tower pass for the focus's product is STANDING: its
|
|
170
|
+
most recent pass for the product succeeded, within the freshness bound. When the
|
|
171
|
+
product's pass fails -- or has not yet succeeded since the daemon started --
|
|
172
|
+
every verdict of that product is unobeyed AT ONCE, however fresh, and the daemon
|
|
173
|
+
logs `[tower] "<focus>": its product's tower pass failed -- failing open`.
|
|
174
|
+
Another product's holds are untouched. A verdict that is
|
|
175
|
+
|
|
176
|
+
- missing,
|
|
177
|
+
- unreadable,
|
|
178
|
+
- older than the bounded age (`TOWER_VERDICT_MAX_AGE_MS`, 5 minutes), or
|
|
179
|
+
- computed under an earlier arm (its `armedAt` is not the focus's current `agent_assigned_at`)
|
|
180
|
+
|
|
181
|
+
is NO verdict: the focus spawns exactly as it would with the tower off, the
|
|
182
|
+
daemon logs `[tower] "<focus>": tower verdict unavailable (<why>) -- failing
|
|
183
|
+
open`, and the dashboard names it ("Tower verdict unavailable (stale) --
|
|
184
|
+
spawns as with the tower off"). A tower that stops writing therefore releases
|
|
185
|
+
every hold within five minutes rather than freezing the product.
|
|
186
|
+
|
|
187
|
+
The dashboard judges freshness on a clock while the page is open, so a hold
|
|
188
|
+
turns into "unavailable" on screen when the spawn pass stops obeying it. Whether
|
|
189
|
+
the product's tower is running at all comes from the daemon heartbeat
|
|
190
|
+
(`metadata.tower` = `{ [productId]: { mode, pass } }`): with the tower running,
|
|
191
|
+
an armed focus with no verdict reads "unavailable" even when no focus has one,
|
|
192
|
+
and a failed pass marks every armed focus of the product "unavailable (the
|
|
193
|
+
product's tower pass failed)"; with `TELORA_TOWER_MODE=off` no tower line shows.
|
|
194
|
+
|
|
195
|
+
Why admission fails OPEN while the merge gates fail CLOSED: the failures are not
|
|
196
|
+
symmetric. A wrongly blocked merge is visible -- a held delivery, a red gate, a
|
|
197
|
+
named reason. A wrongly held spawn is silent -- a focus that simply never runs,
|
|
198
|
+
which is the daemon's recurring bug class. The tower is in front of every
|
|
199
|
+
spawn, so it must never be able to cause that silently.
|
|
200
|
+
|
|
201
|
+
## Moving a host from observe to enforce
|
|
202
|
+
|
|
203
|
+
1. Leave the host in `observe` (the default) and set the product's limit:
|
|
204
|
+
`telora_product update { productId, towerConcurrencyLimit: 1 }`.
|
|
205
|
+
2. Arm the foci. On the dashboard each armed focus now carries the tower's
|
|
206
|
+
line; on the host, read the divergences:
|
|
207
|
+
`telora-daemon logs | grep '\[tower\]'`.
|
|
208
|
+
3. Judge them on real foci: every `observe: enforce would hold` line should be
|
|
209
|
+
a spawn you would have wanted held, and no focus should show "tower verdict
|
|
210
|
+
unavailable" while the daemon is healthy.
|
|
211
|
+
4. Set `TELORA_TOWER_MODE=enforce` in the daemon's environment and restart it
|
|
212
|
+
(drain first if teams are running). Holds now take effect on the next poll.
|
|
213
|
+
5. To back out: `TELORA_TOWER_MODE=observe` (verdicts kept, not obeyed) or
|
|
214
|
+
`off` (no pass at all) and restart. Clearing the product limit
|
|
215
|
+
(`towerConcurrencyLimit: null`) releases every `product_limit_reached`
|
|
216
|
+
hold on the next poll without a restart.
|
|
217
|
+
|
|
218
|
+
## Reading a stuck focus
|
|
219
|
+
|
|
220
|
+
- **"Held by tower: product limit reached"** -- look at which foci are running
|
|
221
|
+
(they hold the slots) and their rank; raise the limit or re-rank if the
|
|
222
|
+
order is wrong.
|
|
223
|
+
- **"Held by tower: waits on <focus>"** -- the predecessor has not landed: it
|
|
224
|
+
still has open deliveries, or its branch is not yet merged into integration.
|
|
225
|
+
If the dependency is wrong, delete the edge (`edge_delete`).
|
|
226
|
+
- **"Held by tower: excluded by <focus> on <evidence>"** -- the other focus is
|
|
227
|
+
running (or was cleared ahead of this one); this one clears when it stops.
|
|
228
|
+
- **"Tower verdict unavailable"** -- the focus will spawn anyway. Check the
|
|
229
|
+
daemon log for `[tower] pass failed for product ...`.
|
|
230
|
+
- **"Waiting for a session slot"** -- the tower has cleared it; the HOST cap is
|
|
231
|
+
full. That is the session cap, not the tower.
|
|
232
|
+
|
|
233
|
+
## Stated limit
|
|
234
|
+
|
|
235
|
+
A product served by two daemon hosts would get two towers writing the same
|
|
236
|
+
verdicts, each counting only its own live teams. No product is configured that
|
|
237
|
+
way.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telora/daemon",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.7",
|
|
4
4
|
"description": "Agent orchestration daemon for Telora - spawns and manages Claude Code instances",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"scripts/telora-daemon-wrapper.sh",
|
|
21
21
|
"build-info.json",
|
|
22
22
|
"README.md",
|
|
23
|
-
".env.example"
|
|
23
|
+
".env.example",
|
|
24
|
+
"docs/runbook-tower.md"
|
|
24
25
|
],
|
|
25
26
|
"scripts": {
|
|
26
27
|
"dev": "tsx watch src/index.ts",
|
|
@@ -33,12 +34,14 @@
|
|
|
33
34
|
"test": "node scripts/run-tests.mjs",
|
|
34
35
|
"stamp-build-info": "node scripts/stamp-build-info.mjs",
|
|
35
36
|
"stamp-build-info:local": "node scripts/stamp-build-info.mjs --empty",
|
|
36
|
-
"
|
|
37
|
+
"copy-shipped-docs": "node scripts/copy-shipped-docs.mjs",
|
|
38
|
+
"prepublishOnly": "npm run build && npm run stamp-build-info",
|
|
39
|
+
"prepack": "npm run copy-shipped-docs"
|
|
37
40
|
},
|
|
38
41
|
"//testRunner": "Uses Node.js built-in test runner (node:test) via tsx for TypeScript support. The root package uses Vitest; this is a deliberate choice for the daemon package to avoid framework dependencies. Test files live beside the modules they exercise (owner-directory layout); the runner discovers them recursively.",
|
|
39
42
|
"dependencies": {
|
|
40
|
-
"@telora/daemon-core": "^0.3.
|
|
41
|
-
"@telora/mcp-products": "^0.23.
|
|
43
|
+
"@telora/daemon-core": "^0.3.1",
|
|
44
|
+
"@telora/mcp-products": "^0.23.4",
|
|
42
45
|
"commander": "^14.0.3",
|
|
43
46
|
"yaml": "^2.4.0",
|
|
44
47
|
"zod": "^4.3.6"
|