dsh-tower 1.3.0 → 2.0.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/CHANGELOG.md +77 -0
- package/CONTRIBUTING.md +1 -1
- package/README.en.md +22 -8
- package/README.md +20 -8
- package/cordis.patch.yml +10 -0
- package/docs/README.md +8 -4
- package/docs/architecture/overview.md +13 -9
- package/docs/faq.md +33 -2
- package/docs/guide/hot-reload.md +189 -0
- package/docs/guide/install-upgrade-migration.md +11 -0
- package/docs/guide/quickstart.md +33 -1
- package/docs/operations/runbook.md +23 -0
- package/docs/operations/security.md +23 -0
- package/docs/reference/blockers.md +10 -2
- package/docs/reference/config.md +65 -2
- package/docs/reference/doctor.md +31 -0
- package/docs/reference/errors.md +50 -0
- package/docs/reference/events.md +45 -0
- package/docs/reference/operations.md +52 -0
- package/docs/reference/testing.md +61 -0
- package/docs/reference/tools.md +65 -23
- package/docs/zh/README.md +58 -0
- package/docs/zh/faq.md +72 -0
- package/docs/zh/guide/hot-reload.md +178 -0
- package/docs/zh/guide/install-upgrade-migration.md +57 -0
- package/docs/zh/guide/quickstart.md +96 -0
- package/docs/zh/operations/security.md +42 -0
- package/docs/zh/reference/blockers.md +62 -0
- package/docs/zh/reference/config.md +113 -0
- package/docs/zh/reference/doctor.md +30 -0
- package/docs/zh/reference/errors.md +52 -0
- package/docs/zh/reference/events.md +46 -0
- package/docs/zh/reference/operations.md +48 -0
- package/docs/zh/reference/testing.md +60 -0
- package/docs/zh/reference/tools.md +75 -0
- package/index.js +261 -13
- package/lib/batch.js +578 -0
- package/lib/config.js +270 -8
- package/lib/doctor.js +165 -0
- package/lib/errors.js +111 -0
- package/lib/hmr.js +196 -0
- package/lib/leases.js +317 -0
- package/lib/legacy.js +413 -0
- package/lib/modeStore.js +28 -1
- package/lib/store.js +24 -1
- package/lib/tests.js +628 -0
- package/lib/tools.js +1554 -182
- package/lib/tower.js +957 -102
- package/lib/workspaceLock.js +117 -0
- package/lib/worktreeOwnership.js +166 -0
- package/package.json +4 -1
- package/reviewer.js +1 -0
- package/scripts/check-docs.mjs +135 -0
- package/scripts/enable-dev-hmr.mjs +347 -0
- package/skills/tower-review.md +4 -2
- package/skills/tower-worker-protocol.md +3 -1
- package/worker.js +4 -2
|
@@ -23,6 +23,29 @@
|
|
|
23
23
|
leaves the branch untouched — then call `tower_merge` to record it. A conflict marks the mission
|
|
24
24
|
`blocked`; resolve in the worktree, set the mission back to `active` (or `completed`), retry.
|
|
25
25
|
|
|
26
|
+
## Interrupt, stale roster and takeover
|
|
27
|
+
|
|
28
|
+
A runaway agent: `tower_interrupt({to:"worker-m1",reason:"..."})` interrupts the host subagent turn and
|
|
29
|
+
records `state:"interrupted"`. `tower_roster` shows `stale:true` when an active entry has no catalog
|
|
30
|
+
activity beyond `spawn.staleMs`; `tower_adopt` retires such entries (`retireMissing:false` marks them
|
|
31
|
+
`missing` instead). After the old owner is interrupted/failed/missing, spawn a replacement with
|
|
32
|
+
`takeover:true`; a still-running owner returns `takeover-refused`.
|
|
33
|
+
|
|
34
|
+
## Workspace lock
|
|
35
|
+
|
|
36
|
+
With `mode.workspaceLock:true`, `<git-common-dir>/dsh-tower/tower.lock` names the owning session. A second
|
|
37
|
+
Tower session gets `[tower:workspace-busy]`. Let the owner run `/tower off`; if the owner is gone, wait
|
|
38
|
+
`mode.workspaceStaleMs` (default 5 min) for the stale takeover, or delete the lock deliberately only after
|
|
39
|
+
confirming no Tower session is active. Heartbeats refresh the lock every `mode.heartbeatMs`.
|
|
40
|
+
|
|
41
|
+
## Hot reload
|
|
42
|
+
|
|
43
|
+
`/tower reload` or `tower_reload` asks DSH HMR to reconcile the profile and reload the Host plugin; the
|
|
44
|
+
reload is scheduled after the tool result (`dev.reloadDelayMs`). It fails with `reload-unavailable` on a
|
|
45
|
+
profile without HMR. Run `npm run dev:hmr` once to add this checkout to the profile's HMR watch roots
|
|
46
|
+
(one DSH restart after the profile patch change); see [hot reload](../guide/hot-reload.md). If a reload
|
|
47
|
+
fails, the previous plugin code no longer runs: restart DSH and check the HMR log.
|
|
48
|
+
|
|
26
49
|
## State lock
|
|
27
50
|
|
|
28
51
|
`tower state is locked by another writer` means an operation holds `state.lock`. Wait and retry. Only if
|
|
@@ -7,6 +7,11 @@ Tower's guarantees are **coordination guarantees**, not a sandbox. Understand wh
|
|
|
7
7
|
- **Review binding.** `tower_review` records the branch tip it inspected; the gate rejects a moved tip
|
|
8
8
|
(`tip-moved`) unless a clean conflict-free rebase waived it. A reviewer that modifies the branch after
|
|
9
9
|
spawning is rejected (`reviewer must not modify the mission branch`).
|
|
10
|
+
- **Role enforcement.** The caller is resolved against the roster. Worker mission updates are limited to
|
|
11
|
+
task/blocker/note; `completed`, `abandoned`, scope widening and blocker clearing are tower-only, and
|
|
12
|
+
completion (with its unread-inbox/commit gates) must go through `tower_complete`. Tower-only tools reject
|
|
13
|
+
roster workers with `[tower:not-authorized]`. One live worker per mission; taking over a stopped worker
|
|
14
|
+
is explicit.
|
|
10
15
|
- **Read-only reviewer preset.** The `tower-reviewer` preset removes `write`, `edit`, `pwsh`,
|
|
11
16
|
`tower_mission` and `tower_complete` from the tool surface; reviewers can only read, diff, message,
|
|
12
17
|
file findings, and submit a review.
|
|
@@ -18,6 +23,20 @@ Tower's guarantees are **coordination guarantees**, not a sandbox. Understand wh
|
|
|
18
23
|
but must state a reason; the shipped patch enables that by default — set it to `false` to make the
|
|
19
24
|
mode human-only.
|
|
20
25
|
- **No automatic goal resume.** Entering pauses the active goal; leaving does not resume it.
|
|
26
|
+
- **Advisory workspace lock.** `mode.workspaceLock` prevents two Tower sessions from accidentally driving
|
|
27
|
+
one repository; it is a same-user coordination lock, not authentication between users.
|
|
28
|
+
- **Test discovery is repository code.** `tower_test`/`tower_complete` execute `package.json`
|
|
29
|
+
`tower.testDiscovery` commands in the mission worktree with the DSH process's OS permissions. Set
|
|
30
|
+
`tests.allowPackageDiscovery:false` for untrusted repositories; output/time budgets fail closed but are
|
|
31
|
+
not a sandbox.
|
|
32
|
+
- **Review exemption is opt-in.** `reviewPolicy:"none"` requires `gates.allowReviewExemption:true` and a
|
|
33
|
+
non-empty reason. It is off by default because a model-driven tower could otherwise self-exempt; the
|
|
34
|
+
exemption and its reason are recorded in state/events and never granted to workers.
|
|
35
|
+
- **Leases are coordination, not quotas.** `tower_resource` prevents accidental co-scheduling via a local
|
|
36
|
+
JSON ledger; it does not cap CPU, memory, or process count. DSH exposes no public Job-object quota API,
|
|
37
|
+
so OS-level isolation remains a deployment concern.
|
|
38
|
+
- **Legacy import is read-only.** Imported snapshots live under the canonical state directory and are never
|
|
39
|
+
activated, adopted, or resumed; an operator must explicitly re-plan/migrate work.
|
|
21
40
|
|
|
22
41
|
## Not enforced by the plugin (deployment concerns)
|
|
23
42
|
|
|
@@ -29,6 +48,10 @@ Tower's guarantees are **coordination guarantees**, not a sandbox. Understand wh
|
|
|
29
48
|
high-value branches.
|
|
30
49
|
- **Repository hooks.** `git merge` runs the target repository's hooks (`pre-merge-commit` etc.). Tower
|
|
31
50
|
cannot make those safe; review hooks before running Tower on an untrusted repository.
|
|
51
|
+
- **Package test commands.** Declared discovery commands have the same trust as repository hooks; a
|
|
52
|
+
malicious `package.json` can run code. Disable `tests.allowPackageDiscovery` and review the file.
|
|
53
|
+
- **HMR reload.** With `dev.hotReload`, `/tower reload`/`tower_reload` asks DSH to re-import the plugin
|
|
54
|
+
from disk. Only enable HMR watch roots for checkouts you trust; a reload executes the current files.
|
|
32
55
|
- **Prompt injection.** The reviewer reads untrusted branch content (diff/comments). The read-only preset
|
|
33
56
|
limits impact, but no model-side protection is absolute.
|
|
34
57
|
- **Base branch protection.** Tower writes to the local base branch; server-side branch protection/push
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
`tower_preflight` returns `{ok:false, blockers:[{code, detail}]}`. `tower_merge` and `tower_rebase`
|
|
4
4
|
return the same shape for operation failures. Codes are stable; the detail is human-facing.
|
|
5
5
|
|
|
6
|
-
## Gate blockers (
|
|
6
|
+
## Gate blockers (26)
|
|
7
7
|
|
|
8
8
|
| Code | Meaning / next action |
|
|
9
9
|
|---|---|
|
|
@@ -11,6 +11,7 @@ return the same shape for operation failures. Codes are stable; the detail is hu
|
|
|
11
11
|
| `mission-not-completed` | Mission is not `completed`; only a completed mission (awaiting review) merges. |
|
|
12
12
|
| `tasks-open` | Open tasks remain; the worker must finish or `task_drop` with a reason. |
|
|
13
13
|
| `blockers-present` | The mission has blockers; clear them after resolving. |
|
|
14
|
+
| `no-check-evidence` | `gates.requireChecks` is enabled and the completion report has no `checks`. Return the completed mission to `active`, re-run `tower_complete` with checks, then review again. |
|
|
14
15
|
| `branch-missing` | No worktree/branch yet: spawn a worker first. |
|
|
15
16
|
| `stale-base` | The branch does not contain the base tip: run `tower_rebase`. |
|
|
16
17
|
| `read-only-survey` | Survey mission changed files; survey missions must be read-only. |
|
|
@@ -24,16 +25,23 @@ return the same shape for operation failures. Codes are stable; the detail is hu
|
|
|
24
25
|
| `review-not-approved` | Latest clean review says `fix-then-merge` or `hold`; only `merge` approves. |
|
|
25
26
|
| `tip-moved` | The branch changed after review; review again (or use a clean rebase waiver). |
|
|
26
27
|
| `worker-busy` | A roster agent for this mission is running; wait or retire it. |
|
|
28
|
+
| `worker-status-unknown` | The parent catalog is unavailable, so a live roster agent cannot be proven stopped; retry after the catalog recovers or adopt it. |
|
|
27
29
|
| `base-wrong-branch` | The main checkout is not on the recorded base branch. |
|
|
28
30
|
| `branch-owned-by-closed-missions` | The mission is already merged/abandoned; re-plan under a new title. |
|
|
31
|
+
| `worker-stale` | A roster worker for this mission has no catalog activity beyond `spawn.staleMs`; adopt it (or interrupt it only if it is actually running) before merge. |
|
|
32
|
+
| `tests-missing` | `gates.requireTests` is enabled and there is no passing discovery receipt for the reviewed tip; run `tower_test` or `tower_complete`. |
|
|
33
|
+
| `tests-failed` | The latest discovery receipt failed or did not meet minCount/required checks. Fix the tests and re-run. |
|
|
34
|
+
| `review-exemption-invalid` | The mission requests `reviewPolicy:"none"` but `gates.allowReviewExemption` is off (or the reason is missing). |
|
|
35
|
+
| `merge-intent-pending` | A merge intent was recorded before a crash/failure; run `tower_merge` to reconcile before another gate. |
|
|
29
36
|
|
|
30
|
-
## Merge failures (
|
|
37
|
+
## Merge failures (4)
|
|
31
38
|
|
|
32
39
|
| Code | Meaning / next action |
|
|
33
40
|
|---|---|
|
|
34
41
|
| `merge-conflict` | Real index conflict; the mission is `blocked`. Resolve, restore the mission to `active`/`completed`, retry. |
|
|
35
42
|
| `merge-hook-failed` | A hook/process failed before a commit existed; repository restored, mission stays `completed`. Retry; `timeout: true` means the git process timed out. Raise `timeoutMs` for slow hooks. |
|
|
36
43
|
| `merge-shape` | Git produced unexpected merge parents; inspect the repository, do not force. |
|
|
44
|
+
| `batch-invalid` | A batch id/member/receipt/check reference is invalid or close evidence is incomplete. |
|
|
37
45
|
|
|
38
46
|
A merge that already landed is **not** a blocker: `tower_merge` records it (`reconciled`,
|
|
39
47
|
`alreadyLanded`) or recovers it (`recoveredFromFailure`, `warnings`).
|
package/docs/reference/config.md
CHANGED
|
@@ -2,17 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
Every key is optional; defaults reproduce the shipped behavior. Unknown top-level keys are rejected.
|
|
4
4
|
`section` and `allowAgentControl` are legacy aliases for `mode.promptSection` and
|
|
5
|
-
`mode.allowAgentControl`.
|
|
5
|
+
`mode.allowAgentControl`. A legacy alias only fills a gap: when both forms are set, the canonical
|
|
6
|
+
`mode.*` key wins.
|
|
7
|
+
|
|
8
|
+
## Layers and precedence
|
|
9
|
+
|
|
10
|
+
Lowest precedence first:
|
|
11
|
+
|
|
12
|
+
1. code defaults (the table below),
|
|
13
|
+
2. profile config (the Cordis patch that mounts `dsh-tower` passes it to `apply()`),
|
|
14
|
+
3. repository config at `<git-common-dir>/dsh-tower/config.json` (usually `.git/dsh-tower/config.json`),
|
|
15
|
+
4. per-call tool arguments such as `worktreeRoot` and `timeoutMs`.
|
|
16
|
+
|
|
17
|
+
The repository layer is read every time a Tower tool opens a repository, so a repository can tune
|
|
18
|
+
review, merge, worktree and spawn policy without editing the profile. It uses the same JSON shape as
|
|
19
|
+
the profile config (the same groups and the legacy aliases) and is merged group by group: repo keys
|
|
20
|
+
win, profile keys survive when the repo does not set them. `tower_status` reports
|
|
21
|
+
`config.path` and `config.repoConfigPresent`.
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"review": { "maxRoundsPerReviewer": 3 },
|
|
26
|
+
"merge": { "timeoutMs": 1800000 },
|
|
27
|
+
"worktrees": { "root": "D:/tower-worktrees" },
|
|
28
|
+
"spawn": { "reviewerPreset": "tower-reviewer", "maxDepth": 2 }
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The `mode.*` group is session-scoped, not repository-scoped: it is accepted in the repository file
|
|
33
|
+
for schema compatibility but only the profile config controls the Tower-mode prompt and the
|
|
34
|
+
model-controlled toggle. A malformed file, a non-object value, unknown top-level keys, or a value that
|
|
35
|
+
violates the declared schema (for example `merge.timeoutMs` below 1000) fail with the stable
|
|
36
|
+
`config-invalid` / `config-unknown-key` tool error codes.
|
|
6
37
|
|
|
7
38
|
| Key | Default | Meaning |
|
|
8
39
|
|---|---|---|
|
|
9
40
|
| `section` | `''` | Legacy alias: replace the whole Tower-mode prompt text. Empty keeps the built-in reminder. |
|
|
10
41
|
| `allowAgentControl` | `false` (bundled patch: `true`) | Legacy alias: register `tower_control`. |
|
|
42
|
+
| `init.autoGitInit` | `false` (bundled patch: `true`) | `tower_init` may run `git init` + an empty initial commit when `repoRoot` is an absolute, existing directory that is not a repository. |
|
|
43
|
+
| `init.snapshotDirty` | `false` | `tower_init` may snapshot uncommitted **tracked** changes into a dangling commit used as the mission baseline (untracked files are not included; the commit is kept alive under `refs/tower/snapshot/*`). |
|
|
11
44
|
| `mode.promptSection` | `''` | Replacement prompt text (takes precedence over `section` when both set). |
|
|
12
45
|
| `mode.sectionAppend` | `''` | Extra text appended after the reminder (team conventions). |
|
|
13
46
|
| `mode.allowAgentControl` | `false` | Register `tower_control`; the model may enter/leave with a reason. |
|
|
14
47
|
| `mode.autoPauseGoal` | `true` | Pause an active goal on entry; the goal is never auto-resumed on exit. |
|
|
15
|
-
| `mode.
|
|
48
|
+
| `mode.requireResumeAuthorization` | `false` | Resuming a mission paused with `pauseCause:user` requires an `authorization` reference. |
|
|
49
|
+
| `mode.workspaceLock` | `false` (bundled patch: `true`) | Allow only one Tower session per repository (`tower.lock`); others get `workspace-busy`. |
|
|
50
|
+
| `mode.workspaceLockScope` | `all` | `all` locks every repo tool; `mutations` skips the workspace lock for read-only tools. |
|
|
51
|
+
| `mode.planModePolicy` | `clear` | Tower entry clears an active plan mode (or `refuse` until the plan is exited); a pre-step also clears a plan activated later. |
|
|
52
|
+
| `mode.workspaceStaleMs` | `300000` | A workspace lock older than this can be taken over. |
|
|
53
|
+
| `mode.heartbeatMs` | `10000` | Workspace-lock heartbeat interval. |
|
|
54
|
+
| `mode.denyTools` | goal/todo/team/workflow/subagent tools | Tools refused while the mode is active (12 defaults). |
|
|
16
55
|
| `mode.storePath` | `$DSH_HOME/tower-mode` | Directory for `modes.json`. |
|
|
17
56
|
| `mode.maxEntries` | `200` | Maximum persisted mode entries (1–2000). |
|
|
18
57
|
| `review.maxRoundsPerReviewer` | `5` | Review-round cap per reviewer per branch. |
|
|
@@ -25,6 +64,27 @@ Every key is optional; defaults reproduce the shipped behavior. Unknown top-leve
|
|
|
25
64
|
| `spawn.provider` | `''` | Subagent provider id; empty prefers `spawn`, then the first provider. |
|
|
26
65
|
| `spawn.workerModel` | `{}` | Optional `{provider, model, reasoningEffort, maxTokens}` for workers. |
|
|
27
66
|
| `spawn.reviewerModel` | `{}` | Optional model route for reviewers; independent review is stronger with a different model. |
|
|
67
|
+
| `spawn.maxLiveWorkers` | `4` | Maximum live worker entries per repository (idle `inactive`, failed, interrupted, missing, stale and retired entries do not count); exceeding it returns `worker-limit`. |
|
|
68
|
+
| `spawn.staleMs` | `15000` | Roster entries without catalog activity after this age are marked `stale` and block merge (`worker-stale`); 0 disables. |
|
|
69
|
+
| `spawn.maxWaitMs` | `90000` | Upper bound for `tower_spawn.waitMs` (Kimi-compatible 90s default). |
|
|
70
|
+
| `spawn.requireLease` | `''` | Optional lease name that must be held before a worker can spawn. |
|
|
71
|
+
| `send.maxPerMinute` | `120` | Per-repository `tower_send` rate limit (0 disables); exceeding it returns `rate-limited`. |
|
|
72
|
+
| `send.interruptDefault` | `false` (bundled patch: `true`) | Default for `tower_send.interrupt` (interrupt the running turn before delivering). |
|
|
73
|
+
| `tests.enabled` | `true` | Run declared test discovery when the worktree `package.json` configures it. |
|
|
74
|
+
| `tests.admission` | `complete` | `spawn` runs discovery before starting a worker, `complete` at `tower_complete`, `off` only on explicit `tower_test`. |
|
|
75
|
+
| `tests.timeoutMs` | `60000` | Per discovery command timeout. |
|
|
76
|
+
| `tests.maxBytes` | `16777216` (16 MiB) | Captured stdout cap per command. |
|
|
77
|
+
| `tests.autoChecks` | `true` | Attach an automatic `tests` check to the completion report. |
|
|
78
|
+
| `tests.allowPackageDiscovery` | `true` | Allow repo-controlled `tower.testDiscovery` commands (executes worktree code; see security). |
|
|
79
|
+
| `gates.requireChecks` | `false` | Require at least one `tower_complete.checks` entry before merge (`no-check-evidence`). |
|
|
80
|
+
| `gates.requireTests` | `false` | Require a passing discovery receipt for the reviewed tip (`tests-missing`). |
|
|
81
|
+
| `gates.allowReviewExemption` | `false` | Allow `reviewPolicy:"none"` + reason to skip review blockers; default off deliberately. |
|
|
82
|
+
| `docs.board` | `true` | Write `MISSIONS.md` after mutations. |
|
|
83
|
+
| `docs.boardLocation` | `common` | `common` = `<git-common-dir>/dsh-tower/MISSIONS.md`; `repo` = `<repoRoot>/MISSIONS.md`. |
|
|
84
|
+
| `leases.ttlMs` | `900000` | Default advisory lease lifetime; expiry never auto-releases. |
|
|
85
|
+
| `dev.hotReload` | `true` | Register `tower_reload`; `/tower reload` is disabled when off. It returns `reload-unavailable` when the profile has no HMR service. |
|
|
86
|
+
| `dev.reloadDelayMs` | `50` | Delay before a scheduled reload so the tool result is delivered first. |
|
|
87
|
+
| `dev.reloadRequiredIds` | four Tower loader ids | Entry ids passed to `reconcileProfile` as required. |
|
|
28
88
|
|
|
29
89
|
## Example
|
|
30
90
|
|
|
@@ -35,6 +95,7 @@ Every key is optional; defaults reproduce the shipped behavior. Unknown top-leve
|
|
|
35
95
|
mode:
|
|
36
96
|
allowAgentControl: true
|
|
37
97
|
sectionAppend: 'Team conventions: tests are mandatory.'
|
|
98
|
+
workspaceLock: true
|
|
38
99
|
review:
|
|
39
100
|
maxRoundsPerReviewer: 3
|
|
40
101
|
merge:
|
|
@@ -43,6 +104,8 @@ Every key is optional; defaults reproduce the shipped behavior. Unknown top-leve
|
|
|
43
104
|
reviewerModel:
|
|
44
105
|
provider: deepseek-account
|
|
45
106
|
model: deepseek-flash
|
|
107
|
+
dev:
|
|
108
|
+
hotReload: true
|
|
46
109
|
```
|
|
47
110
|
|
|
48
111
|
## Looking up the effective schema
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Doctor
|
|
2
|
+
|
|
3
|
+
`tower_doctor({repoRoot})` is read-only. It never takes the state lock, writes state, or changes Git.
|
|
4
|
+
It returns:
|
|
5
|
+
|
|
6
|
+
```json
|
|
7
|
+
{
|
|
8
|
+
"ok": true,
|
|
9
|
+
"checks": [
|
|
10
|
+
{ "id": "git", "status": "pass", "detail": "git version 2.55.0" },
|
|
11
|
+
{ "id": "state", "status": "pass", "detail": ".../state.json revision 7" }
|
|
12
|
+
],
|
|
13
|
+
"environmentContract": {
|
|
14
|
+
"platform": "win32",
|
|
15
|
+
"gitVersion": "git version 2.55.0",
|
|
16
|
+
"repoRoot": "D:/repo",
|
|
17
|
+
"commonDir": "D:/repo/.git",
|
|
18
|
+
"stateDir": "D:/repo/.git/dsh-tower"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Checks cover: git availability, repository identity, state schema/load, repo config presence,
|
|
24
|
+
worker/reviewer preset names, subagents and HMR services, workspace lock holder/staleness, live roster,
|
|
25
|
+
worktree existence/ownership/dirtiness, test-discovery policy, legacy snapshots, batches and leases.
|
|
26
|
+
`warn` is advisory; `fail` means the repository needs attention before spawning/merging. A state-load
|
|
27
|
+
failure is reported as a failed check rather than thrown so doctor remains useful on a broken
|
|
28
|
+
repository.
|
|
29
|
+
|
|
30
|
+
The environment contract is informational; it is not an identity or authorization proof. `tower_init`
|
|
31
|
+
still requires the tower role, and `tower_spawn` still checks the live subagent catalog.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Tool error codes
|
|
2
|
+
|
|
3
|
+
Tool failures are thrown errors, not `{ok:false}` results (gate outcomes are the exception: see
|
|
4
|
+
[blockers.md](blockers.md)). Every thrown error carries a stable code as a message prefix:
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
[tower:mission-not-found] unknown mission "M9"
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Callers can match the prefix instead of the prose. Codes are reserved for the conditions below; a
|
|
11
|
+
message without a more specific match is reported as `tool-internal`.
|
|
12
|
+
|
|
13
|
+
| Code | Meaning | Typical trigger |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `invalid-argument` | A tool argument is missing, malformed, or not allowed. | `missionId`/`id` omitted; unknown status; empty report; bad scope glob; `timeoutMs < 1000`; `status: "all"` on an update. |
|
|
16
|
+
| `repo-invalid` | `repoRoot` is missing, relative, or not inside a Git repository. | passing a path in a directory that is not a repo |
|
|
17
|
+
| `not-initialized` | Tower is not initialized for this repository. | a read tool before `tower_init` |
|
|
18
|
+
| `mission-not-found` | No mission with that id exists. | a stale or mistyped `M9` |
|
|
19
|
+
| `finding-not-found` | No finding with that id exists. | `tower_findings({id:"F99"})` |
|
|
20
|
+
| `invalid-transition` | The requested state transition is not allowed. | a blocked→merged attempt; a finding transition not in the graph; a reviewer mutating findings; completing a mission with open tasks, blockers, no commits, or unread inbox messages. |
|
|
21
|
+
| `state-locked` | Another Tower writer holds the state lock. | concurrent mutation after the lock timeout |
|
|
22
|
+
| `state-corrupt` | The Tower state file is unreadable or invalid. | hand-edited `state.json` |
|
|
23
|
+
| `config-invalid` | Tower configuration is invalid. | malformed repo `config.json`, a value that violates the schema |
|
|
24
|
+
| `config-unknown-key` | Tower configuration has an unknown top-level key. | a typo such as `allowAgentToggle` |
|
|
25
|
+
| `git-failed` | A git command failed for a reason other than the delegated cases. | a rejected merge command, invalid ref |
|
|
26
|
+
| `git-timeout` | A git command timed out or exceeded its output buffer. | slow pre-merge hook, huge diff |
|
|
27
|
+
| `spawn-unavailable` | The subagents service is unavailable. | a Host without `subagents` |
|
|
28
|
+
| `provider-missing` | No subagent provider is registered. | an empty provider list |
|
|
29
|
+
| `preset-missing` | The configured agent preset is not registered. | missing `tower-worker`/`tower-reviewer` preset rows, DSH not restarted |
|
|
30
|
+
| `reviewer-prerequisite` | The reviewer spawn prerequisite is not met. | reviewer spawned before the mission is `completed`, or before a worktree exists |
|
|
31
|
+
| `not-authorized` | The calling agent role may not perform this action. | a worker calling a tower-only tool or changing mission status/scope; a reviewer mutating findings |
|
|
32
|
+
| `workspace-busy` | Another Tower session owns this repository's workspace lock. | `mode.workspaceLock:true` and a second session calls a repo-scoped tool |
|
|
33
|
+
| `worker-limit` | The repository reached `spawn.maxLiveWorkers` live workers. | spawning a fifth worker with the default limit |
|
|
34
|
+
| `rate-limited` | The `tower_send` per-minute rate limit was reached. | `send.maxPerMinute` exceeded; retry later |
|
|
35
|
+
| `takeover-refused` | The mission still has a running worker. | `tower_spawn({takeover:true})` before `tower_interrupt` |
|
|
36
|
+
| `reload-unavailable` | The DSH HMR service is not available in this profile. | `tower_reload` / `/tower reload` on a profile without HMR |
|
|
37
|
+
| `reload-failed` | The HMR reload failed. | `reconcileProfile` threw; check the DSH log and restart |
|
|
38
|
+
| `tests-failed` | Declared test discovery failed or did not meet admission requirements. | nonzero command, malformed output, minCount/required shortfall |
|
|
39
|
+
| `tests-timeout` | Test discovery exceeded its time or output budget. | `tests.timeoutMs`, or stdout over `tests.maxBytes` |
|
|
40
|
+
| `lease-busy` | Another holder owns the requested advisory lease. | `tower_resource acquire` while `heavy-ci` is held |
|
|
41
|
+
| `lease-token` | The lease release token does not match the holder. | releasing with another session's token |
|
|
42
|
+
| `batch-invalid` | A batch request or its close evidence is invalid. | unknown member, duplicate id, unsafe receipt path, missing receipt hash |
|
|
43
|
+
| `legacy-invalid` | The legacy snapshot or mapping is invalid. | unknown schema, source inside the Tower state dir, mapping rewrite |
|
|
44
|
+
| `tool-internal` | An unexpected failure. | a bug; read the message and the activity log |
|
|
45
|
+
|
|
46
|
+
## Why thrown?
|
|
47
|
+
|
|
48
|
+
Returned `{ok:false}` shapes are reserved for expected policy outcomes that the tower decides on
|
|
49
|
+
(`tower_merge`/`tower_rebase` gate blockers and `tower_preflight`). Anything that prevents the tool
|
|
50
|
+
from producing a decision is thrown so the model does not mistake it for a normal negative result.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Tower events
|
|
2
|
+
|
|
3
|
+
Tower emits process-local Cordis events after successful tool calls (mutations and read-only tools), so
|
|
4
|
+
other plugins can observe a repository without polling. Events are **not** sent over the network and carry
|
|
5
|
+
no telemetry.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
ctx.on('tower/merge', (payload) => {
|
|
9
|
+
// payload.tool, payload.repoRoot, payload.value (the tool's JSON result)
|
|
10
|
+
})
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Event | Emitted after |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `tower/init` | `tower_init` created or re-anchored state |
|
|
16
|
+
| `tower/plan` | `tower_plan` created missions |
|
|
17
|
+
| `tower/mission` | `tower_mission` changed one mission |
|
|
18
|
+
| `tower/spawn` | `tower_spawn` recorded a worker/reviewer |
|
|
19
|
+
| `tower/complete` | `tower_complete` recorded a completion report |
|
|
20
|
+
| `tower/review` | `tower_review` recorded a verdict |
|
|
21
|
+
| `tower/merge` | `tower_merge` returned (including gate failures; inspect `value.ok`) |
|
|
22
|
+
| `tower/rebase` | `tower_rebase` returned |
|
|
23
|
+
| `tower/teardown` | `tower_teardown` removed worktrees |
|
|
24
|
+
| `tower/finding` | `tower_finding` created one finding |
|
|
25
|
+
| `tower/findings` | `tower_findings` read/listed/updated findings |
|
|
26
|
+
| `tower/send` | `tower_send` persisted a message |
|
|
27
|
+
| `tower/inbox` | `tower_inbox` acknowledged messages |
|
|
28
|
+
| `tower/interrupt` | `tower_interrupt` stopped a live agent |
|
|
29
|
+
| `tower/diff` | `tower_diff` read a mission diff |
|
|
30
|
+
| `tower/adopt` | `tower_adopt` reconciled the roster |
|
|
31
|
+
| `tower/roster` | `tower_roster` read the roster |
|
|
32
|
+
| `tower/status` | `tower_status` read the dashboard |
|
|
33
|
+
| `tower/preflight` | `tower_preflight` evaluated the gate |
|
|
34
|
+
| `tower/doctor` | `tower_doctor` completed its read-only checks |
|
|
35
|
+
| `tower/test` | `tower_test` ran discovery |
|
|
36
|
+
| `tower/batch` | `tower_batch` created, inspected or closed a batch |
|
|
37
|
+
| `tower/resource` | `tower_resource` acquired/released/inspected leases |
|
|
38
|
+
| `tower/import_legacy` | `tower_import_legacy` stored a legacy snapshot |
|
|
39
|
+
| `tower/legacy_map` | `tower_legacy_map` mapped or listed legacy ids |
|
|
40
|
+
|
|
41
|
+
`tower_mode`, `tower_control` and `tower_reload` do not emit Tower events. Payloads are JSON:
|
|
42
|
+
`{tool, repoRoot, value}`. Listener exceptions never affect the tool result.
|
|
43
|
+
|
|
44
|
+
For a durable audit trail use `<git-common-dir>/dsh-tower/activity.jsonl` and `MISSIONS.md` instead;
|
|
45
|
+
events are process-local and are not replayed.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Batches, leases and legacy import
|
|
2
|
+
|
|
3
|
+
## Evidence batches
|
|
4
|
+
|
|
5
|
+
`tower_batch` groups finite mission completion evidence.
|
|
6
|
+
|
|
7
|
+
- `create` freezes `missionIds`, `exclusions`, `requiredChecks` (default `["check"]`) and exact
|
|
8
|
+
relative `receiptPaths`. Members must be known missions and only paused legacy missions may be
|
|
9
|
+
excluded; membership is immutable after create.
|
|
10
|
+
- `status` summarizes members and pending/failed checks.
|
|
11
|
+
- `close` verifies every required check for completed members (from `completionReport.checks`,
|
|
12
|
+
`testRuns`, or close-time `checks`), checks `treeTip` when provided, and requires every frozen
|
|
13
|
+
receipt path to be supplied in `receiptHashes`/`receipts` (or bound to a passing check with
|
|
14
|
+
`receiptHash`). Missing evidence returns `{ok:false, blockers}`; nothing is deleted.
|
|
15
|
+
|
|
16
|
+
A failed close is not a partial close: the batch stays `open` and can be retried after evidence is
|
|
17
|
+
fixed.
|
|
18
|
+
|
|
19
|
+
## Advisory leases
|
|
20
|
+
|
|
21
|
+
`tower_resource` manages per-repository leases in `<git-common-dir>/dsh-tower/leases.json`:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
tower_resource({action:"acquire", name:"heavy-ci", ttlMs:900000})
|
|
25
|
+
tower_resource({action:"status"})
|
|
26
|
+
tower_resource({action:"release", name:"heavy-ci", token:"<token from acquire>"})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- acquire is tower-only; the caller gets a token, pid, holder, created/expiry timestamps;
|
|
30
|
+
- the same token renews; another holder gets `[tower:lease-busy]`;
|
|
31
|
+
- release requires the token; an **expired** lease still requires the token and explicit `force:true`
|
|
32
|
+
(no silent takeover);
|
|
33
|
+
- `spawn.requireLease` makes a worker spawn fail unless that lease is held.
|
|
34
|
+
|
|
35
|
+
Leases coordinate local jobs and CI windows. They are **not** an OS CPU/memory hard cap: DSH has no
|
|
36
|
+
public Job Object quota API, so Tower cannot enforce per-process resource limits; use the live-worker
|
|
37
|
+
cap and your OS/CI scheduler for those.
|
|
38
|
+
|
|
39
|
+
## Legacy import and mapping
|
|
40
|
+
|
|
41
|
+
`tower_import_legacy({repoRoot,sourcePath})` accepts a Tower Codex `schemaVersion:1` state file or a
|
|
42
|
+
DSH legacy state. It:
|
|
43
|
+
|
|
44
|
+
- requires an absolute source path outside `<git-common-dir>/dsh-tower`;
|
|
45
|
+
- validates the schema (16 MiB cap), stores the raw payload byte-for-byte under
|
|
46
|
+
`<git-common-dir>/dsh-tower/legacy/<sha256>.json`, and records only a summary in
|
|
47
|
+
`state.legacySnapshots`;
|
|
48
|
+
- never activates, adopts, or resumes paused missions.
|
|
49
|
+
|
|
50
|
+
`tower_legacy_map` records an audit-only `sourceHash + oldId → newMissionId?` mapping. Mappings are
|
|
51
|
+
append-only; rewriting an existing mapping to another mission is rejected. Mapping does not change
|
|
52
|
+
mission state.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Testing, discovery and evidence
|
|
2
|
+
|
|
3
|
+
Tower 2.1 can execute a repository's declared test discovery and turn its result into durable
|
|
4
|
+
completion evidence. Discovery is **opt-in by configuration**: a worktree with no
|
|
5
|
+
`package.json.tower.testDiscovery` block behaves exactly as before.
|
|
6
|
+
|
|
7
|
+
## Configuration
|
|
8
|
+
|
|
9
|
+
`package.json` (Tower Codex-compatible):
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"tower": {
|
|
14
|
+
"testDiscovery": {
|
|
15
|
+
"version": 1,
|
|
16
|
+
"commands": [
|
|
17
|
+
{
|
|
18
|
+
"id": "unit",
|
|
19
|
+
"command": "node --test dist/tests/*.test.js",
|
|
20
|
+
"format": "node-tap",
|
|
21
|
+
"minCount": 1,
|
|
22
|
+
"required": ["smoke"]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
| Field | Meaning |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `id` | Unique, `[A-Za-z0-9][A-Za-z0-9._-]{0,63}`; also used in diagnostic file names. |
|
|
33
|
+
| `command` | Shell command executed with the worktree as cwd (Windows `pwsh`, POSIX `bash`). |
|
|
34
|
+
| `format` | `node-tap` (TAP `ok N - name` / `not ok`) or `json-array` (JSON array of `{name, ok?}`). |
|
|
35
|
+
| `minCount` | Minimum discovered tests for this command (default 1). |
|
|
36
|
+
| `required` | Test names that must appear and pass. |
|
|
37
|
+
|
|
38
|
+
`tests.*` in the Tower config controls admission, timeout, output cap and whether package discovery
|
|
39
|
+
is allowed. `tests.admission` is `spawn` (before a worker starts; fail-closed, no child), `complete`
|
|
40
|
+
(at `tower_complete`; default), or `off` (explicit `tower_test` only).
|
|
41
|
+
|
|
42
|
+
## Receipts and gates
|
|
43
|
+
|
|
44
|
+
Each run stores a receipt under `<git-common-dir>/dsh-tower/test-runs/<mission>-<stamp>/` with
|
|
45
|
+
stdout/stderr files, SHA-256 hashes, exit codes, counts, required names, the worktree tip, and an
|
|
46
|
+
overall `ok`. `state.missions[].testRuns` keeps the most recent 50 receipts. The tool returns
|
|
47
|
+
`ok:false` with diagnostics on failure; `tower_complete` throws `[tower:tests-failed]` when automatic
|
|
48
|
+
discovery fails.
|
|
49
|
+
|
|
50
|
+
- `tower_test({repoRoot,missionId})` runs discovery on demand and records a receipt.
|
|
51
|
+
- `tower_complete` adds a `tests` check when `tests.autoChecks` is on.
|
|
52
|
+
- `gates.requireChecks` requires any completion check; `gates.requireTests` requires a passing receipt
|
|
53
|
+
for the reviewed tip (`tests-missing`).
|
|
54
|
+
|
|
55
|
+
## Trust boundary
|
|
56
|
+
|
|
57
|
+
Discovery commands are repository-controlled code. `tests.allowPackageDiscovery:false` disables the
|
|
58
|
+
package block; a CI or policy profile should set it when cloning untrusted repositories. The command
|
|
59
|
+
runs with the same OS permissions as DSH, in the mission worktree, with no network access added by
|
|
60
|
+
Tower. Output is capped at `tests.maxBytes` (16 MiB default) and each command at `tests.timeoutMs`
|
|
61
|
+
(60 s default); over-budget runs fail closed.
|
package/docs/reference/tools.md
CHANGED
|
@@ -1,33 +1,75 @@
|
|
|
1
1
|
# Tools reference
|
|
2
2
|
|
|
3
3
|
All tools take `repoRoot` (an absolute path inside the target Git repository) and return JSON.
|
|
4
|
-
The
|
|
5
|
-
`mode.allowAgentControl` is enabled (the bundled patch enables it)
|
|
4
|
+
The build exposes up to 28 tools: 19 core repository tools below (including `tower_interrupt`),
|
|
5
|
+
`tower_mode`, `tower_control` when `mode.allowAgentControl` is enabled (the bundled patch enables it),
|
|
6
|
+
`tower_reload` while `dev.hotReload` is on, and the 2.1 verification/ops tools `tower_doctor`,
|
|
7
|
+
`tower_test`, `tower_batch`, `tower_resource`, `tower_import_legacy`, `tower_legacy_map`.
|
|
8
|
+
|
|
9
|
+
`tower_mission` uses `id` (alias `missionId`); every other mission-scoped tool uses `missionId`
|
|
10
|
+
(alias `id`). Other input aliases: `task_done`/`taskDone`, `task_drop`/`taskDrop`,
|
|
11
|
+
`task_drop_reason`/`taskDropReason`, `clear_blockers`/`clearBlockers`,
|
|
12
|
+
`include_read`/`includeRead`, `dry_run`/`dryRun`, `timeoutMs`/`timeout_ms`,
|
|
13
|
+
`waitMs`/`wait_ms`, `mission_id`/`missionId` on findings, `agent_id`/`agentId` on interrupt,
|
|
14
|
+
`include_gate`/`includeGate` on status, `retire_missing`/`retireMissing` on adopt. Outputs use
|
|
15
|
+
canonical camelCase names.
|
|
16
|
+
|
|
17
|
+
## Roles
|
|
18
|
+
|
|
19
|
+
Roster roles are resolved from `exec.agent` + the roster. Mission-scoped tools (`tower_interrupt`,
|
|
20
|
+
`tower_init`, `tower_plan`, `tower_spawn`, `tower_merge`, `tower_rebase`, `tower_teardown`,
|
|
21
|
+
`tower_adopt`, `tower_reload`) are **tower only**. `tower_batch` create/close, `tower_resource`
|
|
22
|
+
acquire/release, `tower_import_legacy`, `tower_legacy_map` map and review-exemption planning are
|
|
23
|
+
tower-only; their read-only status actions are open to roster roles. A worker may call `tower_mission`
|
|
24
|
+
only for `task_done`/`task_drop`/`blocker`/`note`; status changes, scope widening, `clear_blockers` and
|
|
25
|
+
`completed` are tower-only (completion must go through `tower_complete`). A roster reviewer may read
|
|
26
|
+
findings but not mutate them. Violations throw `[tower:not-authorized]` or
|
|
27
|
+
`[tower:invalid-transition]`.
|
|
28
|
+
|
|
29
|
+
## Tools
|
|
6
30
|
|
|
7
31
|
| Tool | Parameters | Purpose / notes |
|
|
8
32
|
|---|---|---|
|
|
9
|
-
| `tower_init` | `base?` | Create
|
|
10
|
-
| `tower_plan` | `missions[]` (`title`, `scope[]`, `tasks[]?`, `deps[]?`, `kind?`, `context?`) | Create missions `M1..`; overlapping build scopes auto-add a dependency; rejects unknown deps and cycles. |
|
|
11
|
-
| `tower_mission` | `id
|
|
12
|
-
| `tower_status` |
|
|
13
|
-
| `tower_spawn` | `missionId
|
|
14
|
-
| `tower_roster` | — | Roster with live activity (`running`/`inactive`/`missing`/`unknown`) from the subagent catalog. Read-only. |
|
|
15
|
-
| `tower_adopt` |
|
|
16
|
-
| `tower_diff` | `missionId
|
|
17
|
-
| `tower_send` | `to`, `subject`, `body`, `scope?` | Durable message to a roster name, `tower`, or `all`; delivers live to a running agent. |
|
|
18
|
-
| `tower_inbox` | `
|
|
19
|
-
| `tower_complete` | `missionId
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
33
|
+
| `tower_init` | `base?`, `autoGitInit?`, `snapshotDirty?`, `reanchor?` | Create `<git-common-dir>/dsh-tower/state.json`. `autoGitInit` runs `git init` + empty initial commit; `snapshotDirty` records a dangling snapshot commit of uncommitted tracked changes used as the mission baseline; `reanchor` changes base only when no non-terminal mission exists. Returns `dirty`, `snapshotCommit`, `reanchored`. |
|
|
34
|
+
| `tower_plan` | `missions[]` (`title`, `scope[]`, `tasks[]?`, `deps[]?`, `kind?`, `context?`, `references[]?` ≤20, `reviewPolicy?`, `reviewExemptionReason?`) | Create missions `M1..`; overlapping build scopes auto-add a dependency; rejects unknown deps and cycles. `reviewPolicy:"none"` requires a reason and `gates.allowReviewExemption`. |
|
|
35
|
+
| `tower_mission` | `id` (alias `missionId`), `status?`, `task_done?`, `task_drop?`+`task_drop_reason?`, `blocker?`, `clear_blockers?`, `note?`, `scope?`, `pause_cause?`, `authorization?` | Update one mission. Worker-safe fields are task/blocker/note; `merged` cannot be set here. Paused missions record `pause_cause` (user/failure/resource/dependency); resuming a user-paused mission honors `mode.requireResumeAuthorization`. |
|
|
36
|
+
| `tower_status` | `missionId?`, `offset?`, `limit?`, `includeGate?`, `format?` | Dashboard by default. `missionId` returns full detail (tasks/notes/reviews/findings/completionReport). `offset`/`limit` page missions (default 20, max 100), `includeGate` adds gate digests, `format:"markdown"` returns the board. |
|
|
37
|
+
| `tower_spawn` | `missionId` (alias `id`), `role` (`worker`/`reviewer`), `name?`, `instructions?`, `worktreeRoot?`, `wait?`, `waitMs?`, `takeover?`, `includeDirtyBase?` | Provision/reuse the worktree and start a subagent. Worker uses `tower-worker`; reviewer requires a `completed` mission and uses the read-only `tower-reviewer`. `takeover:true` replaces a live worker entry that is no longer running (increments ownerGeneration). Only one live worker per mission; `spawn.maxLiveWorkers` caps the repository. |
|
|
38
|
+
| `tower_roster` | — | Roster with live activity (`running`/`inactive`/`missing`/`unknown`) and `stale` flags from the subagent catalog. Read-only. |
|
|
39
|
+
| `tower_adopt` | `retireMissing?` | Reconcile roster entries with the durable subagent catalog. Provably gone agents are retired (default) or marked `missing` (`retireMissing:false`) so they can be inspected/taken over. |
|
|
40
|
+
| `tower_diff` | `missionId` (alias `id`), `maxBytes?` | Read-only merge-base diff: files, commits, capped patch. Safe for reviewers. |
|
|
41
|
+
| `tower_send` | `to`, `subject`, `body`, `scope?`, `interrupt?`, `consentRef?` | Durable message to a roster name, `tower`, or `all`; delivers live to a running agent. `interrupt:true` interrupts the running turn first (Kimi-style), then delivers; `consentRef` is recorded for audit. Rate-limited by `send.maxPerMinute`. |
|
|
42
|
+
| `tower_inbox` | `limit?`, `include_read?` | Read/ack messages addressed to the caller; unread messages block `tower_complete`. |
|
|
43
|
+
| `tower_complete` | `missionId` (alias `id`), `report`, `checks?` | Worker reports completion (mission becomes `completed`, awaiting review). Requires current owner, no open tasks/blockers, ≥1 commit, no unread inbox. `checks` records `{name, command?, exitCode?, evidence?}` evidence; `gates.requireChecks` can demand it. When configured, test discovery runs automatically and adds a `tests` check; failure throws `[tower:tests-failed]`. |
|
|
44
|
+
| `tower_interrupt` | `to?` (roster name), `agentId?`, `reason?` | Interrupt one running roster agent via `subagents.interrupt`, record `state:"interrupted"`. Idempotent (not running → `interrupted:false`). |
|
|
45
|
+
| `tower_finding` | `type`, `title`, `summary`, `details`, `suggested_fix`, `severity?`, `missionId?`, `location?` | Record an out-of-scope issue; reviewers use this instead of editing. Finds start as `open`. |
|
|
46
|
+
| `tower_findings` | `id?`, `status?`, `note?`, filters (`type?`, `severity?`, `missionId?`, `open?`, `limit?`) | Lifecycle tool: list/read/transition. Statuses `open → triaged → fixed`/`wontfix`, reopen allowed. Only the tower or a worker may mutate; a reviewer can list/read. |
|
|
47
|
+
| `tower_review` | `missionId` (alias `id`), `status` (`clean`/`p1-Nitems`/`p2-Nitems`), `merge` (`merge`/`fix-then-merge`/`hold`), `findings`, `decision`, `checks?`, `breakGlassReason?` | Reviewer verdict bound to the current tip. Reviewer must be the assigned reviewer (or tower break-glass). |
|
|
48
|
+
| `tower_preflight` | `missionId` (alias `id`) | Read-only gate: returns `{ok, blockers[], branchTip, baseTip, changedFiles, review}`. |
|
|
49
|
+
| `tower_merge` | `missionId` (alias `id`), `timeoutMs?` (alias `timeout_ms`) | Merge after a green gate. Records already-landed merges (`reconciled`), recovers a failed call whose commit exists (`recoveredFromFailure`), and separates `merge-hook-failed` from `merge-conflict`. |
|
|
50
|
+
| `tower_rebase` | `missionId` (alias `id`) | Rebase the branch onto the base in its worktree. Refuses while the worker runs or the worktree is dirty. Already-contained branches return `alreadyMerged` untouched. |
|
|
51
|
+
| `tower_teardown` | `force?`, `exclude?`, `dry_run?` (alias `dryRun`) | Remove eligible merged/abandoned worktrees and branches; running agents are always kept. |
|
|
52
|
+
| `tower_mode` | — | Report mode, last change, paused goal, denied tools, store path, plugin version, HMR availability, workspace-lock holder. |
|
|
27
53
|
| `tower_control` | `action` (`enter`/`exit`), `reason` | Model-initiated mode change; only when `mode.allowAgentControl` is true; reason is mandatory. |
|
|
54
|
+
| `tower_reload` | `repoRoot?` | Dev-only: ask DSH HMR to reconcile the profile and reload the Host plugin from disk. Registered while `dev.hotReload` is on; scheduled after the result (default 50 ms); fails with `reload-unavailable` without HMR. When `repoRoot` is supplied the caller must be the tower. |
|
|
55
|
+
| `tower_doctor` | — (takes `repoRoot`) | Read-only health contract: git, state schema, config, presets/subagents/HMR, workspace lock, roster, worktree realpath/ownership, test config, leases. Returns `{ok, checks[{id,status,detail}], environmentContract}`. Never writes. |
|
|
56
|
+
| `tower_test` | `missionId` (alias `id`) | Run the worktree's `package.json` `tower.testDiscovery` and record a receipt; `ok:false` on failure. Use `tower_complete` for automatic admission checks. |
|
|
57
|
+
| `tower_batch` | `action` (`create`/`status`/`close`), `missionIds?`, `exclusions?`, `requiredChecks?`, `receiptPaths?`, `batchId?`, `checks?`, `treeTip?`, `receiptHashes?` | Finite evidence batch. Create freezes members/checks/receipts; close verifies every required check and receipt hash. Membership is immutable; close never deletes missions. |
|
|
58
|
+
| `tower_resource` | `action` (`acquire`/`release`/`status`), `name?`, `holder?`, `token?`, `ttlMs?`, `force?` | Advisory per-repository lease in `<common>/dsh-tower/leases.json`. No OS CPU/memory hard cap; expired leases require explicit `force` and the correct token. |
|
|
59
|
+
| `tower_import_legacy` | `sourcePath` | Read-only import of a Tower Codex `schemaVersion:1` or DSH legacy state. Full payload goes under `<common>/dsh-tower/legacy/<sha256>.json`; state stores a summary only. Never activates/adopts/resumes paused missions. |
|
|
60
|
+
| `tower_legacy_map` | `action` (`map`/`status`), `sourceHash?`, `oldId?`, `newMissionId?` | Audit-only legacy-id mapping; mappings are append-only and never adopt a mission. |
|
|
61
|
+
|
|
62
|
+
## Workspace lock
|
|
63
|
+
|
|
64
|
+
With `mode.workspaceLock:true` (the bundled patch enables it), the first repo-scoped Tower tool call
|
|
65
|
+
from a Tower-mode session acquires `<git-common-dir>/dsh-tower/tower.lock`; another session gets
|
|
66
|
+
`[tower:workspace-busy]` until the holder runs `/tower off` or the lock exceeds
|
|
67
|
+
`mode.workspaceStaleMs`. `mode.workspaceLockScope:"mutations"` skips the lock for read-only tools.
|
|
68
|
+
Code default is off; the shipped patch is on for Kimi-compatible single-active-tower behavior.
|
|
28
69
|
|
|
29
70
|
## Failure results
|
|
30
71
|
|
|
31
|
-
Gate tools return `{ok:false, blockers:[{code, detail}]}
|
|
32
|
-
[blockers.md](blockers.md).
|
|
33
|
-
|
|
72
|
+
Gate tools return `{ok:false, blockers:[{code, detail}]}`; the code list is in
|
|
73
|
+
[blockers.md](blockers.md). Thrown tool errors carry a stable `[tower:<code>]` prefix
|
|
74
|
+
([errors.md](errors.md)). Successful mutations emit process-local events ([events.md](events.md)),
|
|
75
|
+
and, with `docs.board:true`, refresh `<git-common-dir>/dsh-tower/MISSIONS.md`.
|