dsh-tower 2.1.0 → 2.4.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 +137 -0
- package/COMPATIBILITY.md +6 -1
- package/CONTRIBUTING.md +78 -5
- package/PRINCIPLES.md +163 -0
- package/README.en.md +35 -19
- package/README.md +28 -18
- package/SECURITY.md +6 -0
- package/{lib → adapters}/archive.js +1 -1
- package/{lib → adapters}/caseSensitivity.js +1 -1
- package/adapters/default.js +51 -10
- package/{lib → adapters}/git.js +1 -1
- package/{lib → adapters}/leases.js +1 -1
- package/{lib → adapters}/legacy.js +1 -1
- package/adapters/persistence.js +543 -0
- package/adapters/store-fs.js +28 -1
- package/adapters/store.js +204 -0
- package/{lib → adapters}/tests.js +9 -5
- package/adapters/vcs-git.js +15 -1
- package/{lib → adapters}/workspaceLock.js +1 -1
- package/{lib → adapters}/worktreeOwnership.js +1 -1
- package/app/archive.js +4 -6
- package/app/batch.js +9 -10
- package/app/board.js +2 -4
- package/app/checkCompletion.js +9 -10
- package/app/enrichedMissions.js +2 -4
- package/app/findings.js +6 -7
- package/app/gate.js +1 -2
- package/app/init.js +24 -29
- package/app/merge.js +58 -51
- package/app/messages.js +7 -8
- package/app/mission.js +5 -5
- package/app/plan.js +8 -5
- package/app/provision.js +12 -14
- package/app/read.js +6 -3
- package/app/rebase.js +14 -16
- package/app/recording.js +7 -8
- package/app/review.js +15 -8
- package/app/roster.js +14 -15
- package/{lib → app}/spawn-prechecks.js +4 -3
- package/app/spawn.js +12 -13
- package/app/teardown.js +8 -9
- package/app/tower.js +89 -35
- package/cordis.patch.yml +56 -28
- package/docs/README.md +7 -2
- package/docs/adr/0001-ports-adapters-as-data.md +49 -0
- package/docs/adr/0002-single-state-json.md +50 -0
- package/docs/adr/0003-allow-agent-control-default-true.md +48 -0
- package/docs/adr/0004-surface-budget.md +70 -0
- package/docs/adr/0005-component-surface.md +130 -0
- package/docs/adr/README.md +52 -0
- package/docs/agents/workflow.md +51 -0
- package/docs/architecture/overview.md +192 -43
- package/docs/guide/hot-reload.md +5 -5
- package/docs/guide/install-upgrade-migration.md +13 -0
- package/docs/guide/release.md +1 -1
- package/docs/operations/incident.md +82 -0
- package/docs/operations/runbook.md +12 -3
- package/docs/operations/security.md +1 -1
- package/docs/reference/blockers.md +2 -2
- package/docs/reference/component-surface.md +135 -0
- package/docs/reference/config.generated.md +2 -9
- package/docs/reference/config.md +11 -9
- package/docs/reference/doctor.md +53 -2
- package/docs/reference/errors.generated.md +41 -37
- package/docs/reference/errors.md +53 -41
- package/docs/reference/events.md +38 -6
- package/docs/reference/surface-budget.md +157 -0
- package/docs/reference/tools.generated.md +8 -5
- package/docs/reference/tools.md +10 -7
- package/docs/zh/README.md +1 -1
- package/docs/zh/guide/hot-reload.md +4 -4
- package/docs/zh/guide/install-upgrade-migration.md +10 -0
- package/docs/zh/operations/security.md +1 -1
- package/docs/zh/reference/blockers.md +2 -2
- package/docs/zh/reference/config.md +9 -9
- package/docs/zh/reference/doctor.md +29 -1
- package/docs/zh/reference/tools.md +10 -8
- package/domain/batch.js +2 -2
- package/domain/components.js +641 -0
- package/domain/config/schema.js +62 -28
- package/domain/contracts/outputs.js +16 -2
- package/domain/contracts/tool-inputs/ops.js +1 -0
- package/domain/errors.js +308 -43
- package/domain/events.js +207 -0
- package/domain/integration/gate.js +17 -2
- package/domain/integration/merge.js +35 -0
- package/domain/lockBudget.js +30 -0
- package/domain/mission.js +12 -1
- package/domain/missions/completion.js +14 -0
- package/domain/missions/lifecycle.js +9 -3
- package/domain/missions/plan.js +14 -0
- package/domain/missions/review.js +90 -7
- package/domain/read/views.js +3 -0
- package/domain/state/archive.js +1 -1
- package/domain/state.js +1 -1
- package/domain/surface.js +347 -0
- package/host/capability-batch.js +15 -0
- package/host/capability-hmr.js +30 -0
- package/host/capability-import.js +11 -0
- package/host/capability-leases.js +11 -0
- package/host/capability-registry.js +149 -0
- package/host/capability-tests.js +16 -0
- package/{lib → host}/doctor.js +157 -18
- package/{lib → host}/hmr.js +2 -4
- package/{lib → host}/modeStore.js +1 -1
- package/host/presets.js +77 -0
- package/{lib → host}/tool-registry.js +235 -44
- package/host/tower.js +32 -0
- package/index.js +40 -25
- package/locale/en.json +31 -11
- package/locale/zh.json +30 -10
- package/package.json +24 -4
- package/ports/archive.js +18 -0
- package/ports/caseSensitivity.js +15 -0
- package/ports/clock.js +10 -2
- package/ports/config.js +19 -0
- package/ports/contract.js +38 -0
- package/ports/events.js +24 -0
- package/ports/ids.js +10 -2
- package/ports/index.js +54 -2
- package/ports/ownership.js +17 -0
- package/ports/state.js +16 -0
- package/ports/store.js +52 -3
- package/ports/vcs.js +25 -4
- package/scripts/architecture-waivers.json +19 -0
- package/scripts/architecture.mjs +634 -23
- package/scripts/check-components.mjs +235 -0
- package/scripts/check-docs.mjs +8 -35
- package/scripts/check-pack.mjs +68 -39
- package/scripts/check-philosophy.mjs +266 -0
- package/scripts/component-surface.json +395 -0
- package/scripts/domain-metrics.mjs +13 -4
- package/scripts/generate-component-surface.mjs +59 -0
- package/scripts/generate-config-reference.mjs +14 -1
- package/scripts/generate-error-reference.mjs +68 -14
- package/scripts/generate-events-reference.mjs +83 -0
- package/scripts/generate-tools-reference.mjs +18 -25
- package/scripts/generate-types.mjs +127 -0
- package/scripts/issue-audit.mjs +2 -2
- package/scripts/lib/surface.mjs +87 -0
- package/scripts/lib/tool-surface.mjs +85 -0
- package/scripts/mount-smoke/fixtures.mjs +3 -3
- package/scripts/mount-smoke/surface.mjs +96 -27
- package/scripts/mount-smoke.mjs +3 -1
- package/scripts/pack-manifest.txt +75 -22
- package/scripts/regression-audit.mjs +164 -0
- package/scripts/release-check.mjs +1 -1
- package/scripts/surface-baseline.json +52 -0
- package/scripts/surface-baseline.mjs +118 -0
- package/scripts/types-coverage.mjs +184 -0
- package/scripts/types-strict-manifest.txt +10 -0
- package/scripts/types-strict.mjs +141 -0
- package/skills.js +8 -1
- package/tools/adopt.js +2 -3
- package/tools/batch.js +2 -3
- package/tools/complete.js +2 -3
- package/tools/diff.js +3 -3
- package/tools/doctor.js +7 -10
- package/tools/finding.js +2 -3
- package/tools/findings.js +3 -3
- package/tools/import-legacy.js +3 -5
- package/tools/inbox.js +3 -3
- package/tools/index.js +9 -8
- package/tools/init.js +3 -6
- package/tools/interrupt.js +2 -3
- package/tools/legacy-map.js +3 -5
- package/tools/merge.js +3 -3
- package/tools/mission.js +2 -3
- package/tools/plan.js +6 -5
- package/tools/preflight.js +2 -3
- package/tools/rebase.js +3 -3
- package/tools/recover.js +38 -13
- package/tools/reload.js +36 -40
- package/tools/resource.js +3 -5
- package/tools/review.js +3 -4
- package/tools/roster.js +2 -3
- package/tools/send.js +2 -2
- package/tools/spawn.js +8 -5
- package/tools/status.js +11 -4
- package/tools/teardown.js +2 -3
- package/tools/test.js +2 -2
- package/types/index.d.ts +29 -13
- package/ui-bundle/README.md +37 -3
- package/ui-bundle/client.cards.js +356 -0
- package/ui-bundle/client.denied.js +142 -0
- package/ui-bundle/client.dock.js +333 -0
- package/ui-bundle/client.helpers.js +271 -0
- package/ui-bundle/client.js +79 -1747
- package/ui-bundle/client.locales.js +247 -0
- package/ui-bundle/client.mode.js +206 -0
- package/ui-bundle/client.overlay.js +64 -0
- package/ui-bundle/client.styles.js +151 -0
- package/ui-bundle/client.toolview.js +82 -0
- package/ui-bundle/package.json +1 -0
- package/lib/persistence.js +0 -356
- package/lib/presets.js +0 -35
- package/lib/store.js +0 -133
- /package/{lib → adapters}/stateFile.js +0 -0
- /package/{lib → app}/briefing.js +0 -0
- /package/{lib → app}/spawn-support.js +0 -0
- /package/{lib → host}/compatibility.js +0 -0
- /package/{lib → host}/config.js +0 -0
- /package/{lib → host}/modeState.js +0 -0
package/tools/reload.js
CHANGED
|
@@ -1,52 +1,48 @@
|
|
|
1
1
|
// tower_reload: dev-only HMR reload request.
|
|
2
|
-
|
|
2
|
+
//
|
|
3
|
+
// #121: this tool is the `tower-hmr` component. It is registered only by that
|
|
4
|
+
// component's entry point, so the row is the switch; there is no second
|
|
5
|
+
// `dev.hotReload` boolean to disagree with it.
|
|
3
6
|
import { SCHEMAS, output } from '../domain/contracts/outputs.js'
|
|
4
7
|
import { TowerToolError } from '../domain/errors.js'
|
|
5
|
-
import { runtimeConfig } from '../lib/config.js'
|
|
6
|
-
import { createHmrBridge } from '../lib/hmr.js'
|
|
7
8
|
import { ReloadInput } from '../domain/contracts/tool-inputs/index.js'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @param {any} ctx
|
|
11
|
-
* @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireTowerActor: any, sharedHmrBridge: any }} deps
|
|
12
|
+
* @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireTowerActor: any, sharedHmrBridge: any, runtimeConfig: any, createHmrBridge: any }} deps
|
|
12
13
|
*/
|
|
13
14
|
export function registerReloadTool(ctx, deps) {
|
|
14
|
-
const { define, actorFor, requireTowerActor, sharedHmrBridge } = deps
|
|
15
|
-
// Dev-only hot reload. The tool is registered whenever dev.hotReload is on;
|
|
16
|
-
// it reports reload-unavailable when the profile has no HMR service. When the
|
|
17
|
-
// Host passed a shared bridge, its status() reflects the command's reloads too.
|
|
15
|
+
const { define, actorFor, requireTowerActor, sharedHmrBridge, runtimeConfig, createHmrBridge } = deps
|
|
18
16
|
const dev = runtimeConfig().dev ?? {}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
logger: ctx.logger ?? console,
|
|
26
|
-
})
|
|
27
|
-
define(ctx, {
|
|
28
|
-
name: 'tower_reload',
|
|
29
|
-
description:
|
|
30
|
-
'Dev-only: ask DSH HMR to reconcile this profile and reload the Tower Host plugin from disk. The reload is scheduled a few milliseconds after this call returns (so the result reaches the model first). Use after editing the plugin source in a linked profile; plugin_manager install/update already triggers reconciliation by itself. Registered while dev.hotReload is on; fails with reload-unavailable when the HMR service is absent.',
|
|
31
|
-
parameters: ReloadInput,
|
|
32
|
-
output: output(SCHEMAS.reload),
|
|
33
|
-
async execute(args, exec) {
|
|
34
|
-
if (typeof args.repoRoot === 'string') {
|
|
35
|
-
const tower = await Tower.open(args.repoRoot)
|
|
36
|
-
const actor = await actorFor(tower, exec)
|
|
37
|
-
requireTowerActor(actor, 'reload the Tower plugin')
|
|
38
|
-
}
|
|
39
|
-
if (!bridge.available) {
|
|
40
|
-
throw new TowerToolError('reload-unavailable', 'this DSH profile has no HMR service; restart DSH instead')
|
|
41
|
-
}
|
|
42
|
-
const outcome = bridge.schedule()
|
|
43
|
-
return {
|
|
44
|
-
ok: true,
|
|
45
|
-
...outcome,
|
|
46
|
-
...(outcome.lastError ? { error: outcome.lastError } : {}),
|
|
47
|
-
restartHint: 'If the new code does not appear, restart DSH or check the profile hmr entry.',
|
|
48
|
-
}
|
|
49
|
-
},
|
|
17
|
+
const bridge =
|
|
18
|
+
sharedHmrBridge ??
|
|
19
|
+
createHmrBridge(ctx, {
|
|
20
|
+
requiredIds: dev.reloadRequiredIds,
|
|
21
|
+
delayMs: dev.reloadDelayMs,
|
|
22
|
+
logger: ctx.logger ?? console,
|
|
50
23
|
})
|
|
51
|
-
|
|
24
|
+
define(ctx, {
|
|
25
|
+
name: 'tower_reload',
|
|
26
|
+
description:
|
|
27
|
+
'Dev-only: ask DSH HMR to reconcile this profile and reload the Tower Host plugin from disk. Registered only while the tower-hmr component is on (the row is shipped off). The reload is scheduled a few milliseconds after this call returns (so the result reaches the model first). Use after editing the plugin source in a linked profile; plugin_manager install/update already triggers reconciliation by itself. Fails with reload-unavailable when the HMR service is absent.',
|
|
28
|
+
parameters: ReloadInput,
|
|
29
|
+
output: output(SCHEMAS.reload),
|
|
30
|
+
async execute(args, exec) {
|
|
31
|
+
if (typeof args.repoRoot === 'string') {
|
|
32
|
+
const tower = await deps.openTower(args.repoRoot)
|
|
33
|
+
const actor = await actorFor(tower, exec)
|
|
34
|
+
requireTowerActor(actor, 'reload the Tower plugin')
|
|
35
|
+
}
|
|
36
|
+
if (!bridge.available) {
|
|
37
|
+
throw new TowerToolError('reload-unavailable', 'this DSH profile has no HMR service; restart DSH instead')
|
|
38
|
+
}
|
|
39
|
+
const outcome = bridge.schedule()
|
|
40
|
+
return {
|
|
41
|
+
ok: true,
|
|
42
|
+
...outcome,
|
|
43
|
+
...(outcome.lastError ? { error: outcome.lastError } : {}),
|
|
44
|
+
restartHint: 'If the new code does not appear, restart DSH or check the profile hmr entry.',
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
})
|
|
52
48
|
}
|
package/tools/resource.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
// tower_resource: advisory resource leases.
|
|
2
|
-
import { Tower } from '../app/tower.js'
|
|
3
2
|
import { SCHEMAS, output } from '../domain/contracts/outputs.js'
|
|
4
3
|
import { TowerToolError } from '../domain/errors.js'
|
|
5
|
-
import { acquireLease, releaseLease, inspectLeases } from '../lib/leases.js'
|
|
6
4
|
import { ResourceInput } from '../domain/contracts/tool-inputs/index.js'
|
|
7
5
|
import { LEASE_ACTION } from '../domain/vocabulary.js'
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* @param {any} ctx
|
|
11
|
-
* @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireTowerActor: any }} deps
|
|
9
|
+
* @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireTowerActor: any, acquireLease: any, releaseLease: any, inspectLeases: any }} deps
|
|
12
10
|
*/
|
|
13
11
|
export function registerResourceTool(ctx, deps) {
|
|
14
|
-
const { define, actorFor, requireTowerActor } = deps
|
|
12
|
+
const { define, actorFor, requireTowerActor, acquireLease, releaseLease, inspectLeases } = deps
|
|
15
13
|
define(ctx, {
|
|
16
14
|
name: 'tower_resource',
|
|
17
15
|
description:
|
|
@@ -19,7 +17,7 @@ export function registerResourceTool(ctx, deps) {
|
|
|
19
17
|
parameters: ResourceInput,
|
|
20
18
|
output: output(SCHEMAS.resource),
|
|
21
19
|
async execute(args, exec) {
|
|
22
|
-
const tower = await
|
|
20
|
+
const tower = await deps.openTower(args.repoRoot)
|
|
23
21
|
const actor = await actorFor(tower, exec)
|
|
24
22
|
const redact = (leases) => leases.map(({ token, ...lease }) => lease)
|
|
25
23
|
if (args.action === LEASE_ACTION.status) {
|
package/tools/review.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// tower_review: independent review verdict.
|
|
2
|
-
import { Tower } from '../app/tower.js'
|
|
3
2
|
import { SCHEMAS, output } from '../domain/contracts/outputs.js'
|
|
4
3
|
import { TowerToolError } from '../domain/errors.js'
|
|
5
4
|
import { ReviewInput } from '../domain/contracts/tool-inputs/index.js'
|
|
@@ -7,21 +6,21 @@ import { isReviewerRole, isTowerRole } from '../domain/vocabulary.js'
|
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* @param {any} ctx
|
|
10
|
-
* @param {{ define: import('../types/index.js').DefineTool, actorFor: any }} deps
|
|
9
|
+
* @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any }} deps
|
|
11
10
|
*/
|
|
12
11
|
export function registerReviewTool(ctx, deps) {
|
|
13
12
|
const { define, actorFor } = deps
|
|
14
13
|
define(ctx, {
|
|
15
14
|
name: 'tower_review',
|
|
16
15
|
description:
|
|
17
|
-
'Submit an independent review verdict
|
|
16
|
+
'Submit an independent review verdict bound to the current branch tip: a completed mission starts a review round, and while the mission is active a later route may still record a verdict for that same reviewed tip, appending to the same round without changing the status (a moved tip is rejected with an explanation). status is "clean", "p1-Nitems", or "p2-Nitems"; merge is "merge", "fix-then-merge", or "hold" (only clean + merge is later approval). Non-clean returns the mission to active. Reviewers are capped at 5 rounds per branch; a tower-submitted review is break-glass and requires breakGlassReason.',
|
|
18
17
|
parameters: ReviewInput,
|
|
19
18
|
aliases: {"missionId":["id"]},
|
|
20
19
|
requiresMissionId: true,
|
|
21
20
|
board: true,
|
|
22
21
|
output: output(SCHEMAS.review),
|
|
23
22
|
async execute(args, exec) {
|
|
24
|
-
const tower = await
|
|
23
|
+
const tower = await deps.openTower(args.repoRoot)
|
|
25
24
|
const actor = await actorFor(tower, exec)
|
|
26
25
|
if (!isReviewerRole(actor.role) && !isTowerRole(actor.role)) {
|
|
27
26
|
throw new TowerToolError('not-authorized', `only an assigned reviewer or the tower may review; ${actor.name} is ${actor.role}`)
|
package/tools/roster.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// tower_roster: authoritative roster activity.
|
|
2
|
-
import { Tower } from '../app/tower.js'
|
|
3
2
|
import { SCHEMAS, output } from '../domain/contracts/outputs.js'
|
|
4
3
|
import { TowerToolError } from '../domain/errors.js'
|
|
5
4
|
import { RosterInput } from '../domain/contracts/tool-inputs/index.js'
|
|
@@ -7,7 +6,7 @@ import { PARENT_STATUS_OK, ROSTER_ACTIVITY, ROSTER_GONE_STATES } from '../domain
|
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* @param {any} ctx
|
|
10
|
-
* @param {{ define: import('../types/index.js').DefineTool }} deps
|
|
9
|
+
* @param {{ define: import('../types/index.js').DefineTool, openTower: any }} deps
|
|
11
10
|
*/
|
|
12
11
|
export function registerRosterTool(ctx, deps) {
|
|
13
12
|
const { define } = deps
|
|
@@ -18,7 +17,7 @@ export function registerRosterTool(ctx, deps) {
|
|
|
18
17
|
parameters: RosterInput,
|
|
19
18
|
output: output(SCHEMAS.roster),
|
|
20
19
|
async execute(args) {
|
|
21
|
-
const tower = await
|
|
20
|
+
const tower = await deps.openTower(args.repoRoot)
|
|
22
21
|
const view = await tower.rosterView()
|
|
23
22
|
const subagents = ctx.get('subagents')
|
|
24
23
|
if (!subagents) throw new TowerToolError('spawn-unavailable', 'subagents service is unavailable; cannot observe roster')
|
package/tools/send.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// tower_send: durable inbox message delivery.
|
|
2
|
-
import { Tower } from '../app/tower.js'
|
|
3
2
|
import { SCHEMAS, output } from '../domain/contracts/outputs.js'
|
|
4
3
|
import { TowerToolError } from '../domain/errors.js'
|
|
5
4
|
import { SendInput } from '../domain/contracts/tool-inputs/index.js'
|
|
@@ -17,6 +16,7 @@ import {
|
|
|
17
16
|
* @param {any} ctx
|
|
18
17
|
* @param {{
|
|
19
18
|
* define: import('../types/index.js').DefineTool,
|
|
19
|
+
* openTower: any,
|
|
20
20
|
* actorFor: any,
|
|
21
21
|
* checkSendRate: any,
|
|
22
22
|
* }} deps
|
|
@@ -31,7 +31,7 @@ export function registerSendTool(ctx, deps) {
|
|
|
31
31
|
aliases: { consentRef: ['consent_ref'] },
|
|
32
32
|
output: output(SCHEMAS.send),
|
|
33
33
|
async execute(args, exec) {
|
|
34
|
-
const tower = await
|
|
34
|
+
const tower = await deps.openTower(args.repoRoot)
|
|
35
35
|
checkSendRate(tower.config, args.repoRoot)
|
|
36
36
|
const actor = await actorFor(tower, exec)
|
|
37
37
|
const view = await tower.rosterView()
|
package/tools/spawn.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// tower_spawn: provision and start a worker/reviewer.
|
|
2
|
-
import { Tower } from '../app/tower.js'
|
|
3
2
|
import { SCHEMAS, output } from '../domain/contracts/outputs.js'
|
|
4
3
|
import { TowerToolError, classifyError } from '../domain/errors.js'
|
|
5
4
|
import { SpawnInput } from '../domain/contracts/tool-inputs/index.js'
|
|
@@ -9,13 +8,14 @@ import {
|
|
|
9
8
|
precheckSpawnServices,
|
|
10
9
|
precheckWorkerOwnership,
|
|
11
10
|
resolveRosterName,
|
|
12
|
-
} from '../
|
|
13
|
-
import { buildAgentOptions, buildSpawnBriefing, waitForSpawnResult } from '../
|
|
11
|
+
} from '../app/spawn-prechecks.js'
|
|
12
|
+
import { buildAgentOptions, buildSpawnBriefing, waitForSpawnResult } from '../app/spawn-support.js'
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* @param {any} ctx
|
|
17
16
|
* @param {{
|
|
18
17
|
* define: import('../types/index.js').DefineTool,
|
|
18
|
+
* openTower: any,
|
|
19
19
|
* actorFor: any,
|
|
20
20
|
* requireTowerActor: any,
|
|
21
21
|
* enqueueSpawn: any,
|
|
@@ -25,6 +25,7 @@ import { buildAgentOptions, buildSpawnBriefing, waitForSpawnResult } from '../li
|
|
|
25
25
|
* recordSpawnSuccess: any,
|
|
26
26
|
* runMissionDiscovery: any,
|
|
27
27
|
* textOf: any,
|
|
28
|
+
* inspectLeases: any,
|
|
28
29
|
* }} deps
|
|
29
30
|
*/
|
|
30
31
|
export function registerSpawnTool(ctx, deps) {
|
|
@@ -43,7 +44,7 @@ export function registerSpawnTool(ctx, deps) {
|
|
|
43
44
|
board: true,
|
|
44
45
|
output: output(SCHEMAS.spawn),
|
|
45
46
|
async execute(args, exec) {
|
|
46
|
-
const tower = await
|
|
47
|
+
const tower = await deps.openTower(args.repoRoot)
|
|
47
48
|
const actor = await actorFor(tower, exec)
|
|
48
49
|
requireTowerActor(actor, 'spawn tower agents')
|
|
49
50
|
const role = args.role ?? DEFAULT_ROLE
|
|
@@ -68,7 +69,9 @@ export function registerSpawnTool(ctx, deps) {
|
|
|
68
69
|
role, tower, ctx, view, missionId: args.missionId, takeover: args.takeover,
|
|
69
70
|
maxLiveWorkers: config.spawn.maxLiveWorkers, budget: backoff.budget, observeActivity,
|
|
70
71
|
})
|
|
71
|
-
const { subagents, provider } = await precheckSpawnServices({
|
|
72
|
+
const { subagents, provider } = await precheckSpawnServices({
|
|
73
|
+
tower, ctx, config, preset, actor, exec, inspectLeases: deps.inspectLeases,
|
|
74
|
+
})
|
|
72
75
|
|
|
73
76
|
const provisioned = await tower.provision({
|
|
74
77
|
missionId: args.missionId,
|
package/tools/status.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// tower_status: dashboard and mission detail.
|
|
2
|
-
import { Tower } from '../app/tower.js'
|
|
3
2
|
import { SCHEMAS, output } from '../domain/contracts/outputs.js'
|
|
4
3
|
import { classifyError } from '../domain/errors.js'
|
|
5
4
|
import { StatusInput } from '../domain/contracts/tool-inputs/index.js'
|
|
@@ -10,22 +9,28 @@ import { archiveView } from '../app/archive.js'
|
|
|
10
9
|
* @param {any} ctx
|
|
11
10
|
* @param {{
|
|
12
11
|
* define: import('../types/index.js').DefineTool,
|
|
12
|
+
* openTower: any,
|
|
13
13
|
* workspaceHook: any,
|
|
14
14
|
* spawnBackoffView: any,
|
|
15
15
|
* observeActivity: any,
|
|
16
|
+
* componentReport: (ctx: any) => Promise<{ enabled: string[], disabled: Array<{ id: string, reason: string }> }>,
|
|
16
17
|
* }} deps
|
|
17
18
|
*/
|
|
18
19
|
export function registerStatusTool(ctx, deps) {
|
|
19
|
-
const { define, workspaceHook, spawnBackoffView, observeActivity } = deps
|
|
20
|
+
const { define, workspaceHook, spawnBackoffView, observeActivity, componentReport } = deps
|
|
20
21
|
define(ctx, {
|
|
21
22
|
name: 'tower_status',
|
|
22
23
|
description:
|
|
23
|
-
'Read the Tower dashboard for a repository: state path, base branch, revision, mission status counts, paged mission summaries (scope/deps/tasks/worktree/owner), roster size, findings, archive count, config path and the activity tail. Pass missionId for one full mission (tasks, notes, reviews, findings, completion report; archived missions are loaded from their year file); offset/limit page the mission list (default 20, max 100); includeGate adds preflight gate digests for the page; includeArchived appends compact archived summaries after the active page; format:"markdown" returns the human-readable mission board (active missions plus an archived count). Read-only.',
|
|
24
|
+
'Read the Tower dashboard for a repository: state path, base branch, revision, mission status counts, paged mission summaries (scope/deps/tasks/worktree/owner), roster size, findings, archive count, config path and the activity tail. Also reports the repo-scoped git lock holder (`gitLock: {held,pid,phase,heldMs}`) so a merge holding the mutex is visible. Pass missionId for one full mission (tasks, notes, reviews, findings, completion report; archived missions are loaded from their year file); offset/limit page the mission list (default 20, max 100); includeGate adds preflight gate digests for the page; includeArchived appends compact archived summaries after the active page; format:"markdown" returns the human-readable mission board (active missions plus an archived count). Read-only.',
|
|
24
25
|
parameters: StatusInput,
|
|
25
26
|
aliases: { mission_id: ['missionId'], include_gate: ['includeGate'], include_archived: ['includeArchived'] },
|
|
26
27
|
output: output(SCHEMAS.status),
|
|
27
28
|
async execute(args, exec) {
|
|
28
|
-
const tower = await
|
|
29
|
+
const tower = await deps.openTower(args.repoRoot)
|
|
30
|
+
// #121: report the mounted optional components, including the exact
|
|
31
|
+
// "disabled by component <id>" reason. A capability that is off must be a
|
|
32
|
+
// visible fact of the dashboard, never a silent absence from the tools.
|
|
33
|
+
const components = await componentReport(ctx)
|
|
29
34
|
const boardPath = tower.boardFile()
|
|
30
35
|
const mode = workspaceHook?.modeInfo?.(exec) ?? null
|
|
31
36
|
const statusState = await tower.load()
|
|
@@ -46,6 +51,7 @@ export function registerStatusTool(ctx, deps) {
|
|
|
46
51
|
boardPath,
|
|
47
52
|
mode,
|
|
48
53
|
spawn,
|
|
54
|
+
components,
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
const state = await tower.load()
|
|
@@ -90,6 +96,7 @@ export function registerStatusTool(ctx, deps) {
|
|
|
90
96
|
boardPath,
|
|
91
97
|
mode,
|
|
92
98
|
spawn,
|
|
99
|
+
components,
|
|
93
100
|
}
|
|
94
101
|
if (isMarkdownFormat(args.format)) {
|
|
95
102
|
const state = await tower.load()
|
package/tools/teardown.js
CHANGED
|
@@ -8,13 +8,12 @@
|
|
|
8
8
|
// Pure eligibility decisions live in domain/integration.
|
|
9
9
|
// See docs/reference/tools.generated.md for the public shape.
|
|
10
10
|
// tower_teardown: remove eligible mission worktrees.
|
|
11
|
-
import { Tower } from '../app/tower.js'
|
|
12
11
|
import { SCHEMAS, output } from '../domain/contracts/outputs.js'
|
|
13
12
|
import { TeardownInput } from '../domain/contracts/tool-inputs/index.js'
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* @param {any} ctx
|
|
17
|
-
* @param {{ define: import('../types/index.js').DefineTool, actorFor: any, requireTowerActor: any, observeActivity: any }} deps
|
|
16
|
+
* @param {{ define: import('../types/index.js').DefineTool, openTower: any, actorFor: any, requireTowerActor: any, observeActivity: any }} deps
|
|
18
17
|
*/
|
|
19
18
|
export function registerTeardownTool(ctx, deps) {
|
|
20
19
|
const { define, actorFor, requireTowerActor, observeActivity } = deps
|
|
@@ -27,7 +26,7 @@ export function registerTeardownTool(ctx, deps) {
|
|
|
27
26
|
board: true,
|
|
28
27
|
output: output(SCHEMAS.teardown),
|
|
29
28
|
async execute(args, exec) {
|
|
30
|
-
const tower = await
|
|
29
|
+
const tower = await deps.openTower(args.repoRoot)
|
|
31
30
|
const actor = await actorFor(tower, exec)
|
|
32
31
|
requireTowerActor(actor, 'tear down worktrees')
|
|
33
32
|
const { activity } = await observeActivity(ctx, tower)
|
package/tools/test.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// tower_test: run declared worktree test discovery.
|
|
2
|
-
import { Tower } from '../app/tower.js'
|
|
3
2
|
import { SCHEMAS, output } from '../domain/contracts/outputs.js'
|
|
4
3
|
import { TowerToolError } from '../domain/errors.js'
|
|
5
4
|
import { TestInput } from '../domain/contracts/tool-inputs/index.js'
|
|
@@ -9,6 +8,7 @@ import { isReviewerRole, isWorkerRole } from '../domain/vocabulary.js'
|
|
|
9
8
|
* @param {any} ctx
|
|
10
9
|
* @param {{
|
|
11
10
|
* define: import('../types/index.js').DefineTool,
|
|
11
|
+
* openTower: any,
|
|
12
12
|
* actorFor: any,
|
|
13
13
|
* requireKnownActor: any,
|
|
14
14
|
* runMissionDiscovery: any,
|
|
@@ -25,7 +25,7 @@ export function registerTestTool(ctx, deps) {
|
|
|
25
25
|
requiresMissionId: true,
|
|
26
26
|
output: output(SCHEMAS.test),
|
|
27
27
|
async execute(args, exec) {
|
|
28
|
-
const tower = await
|
|
28
|
+
const tower = await deps.openTower(args.repoRoot)
|
|
29
29
|
const actor = await actorFor(tower, exec)
|
|
30
30
|
requireKnownActor(actor, 'tower_test')
|
|
31
31
|
const context = await tower.missionContext(args.missionId)
|
package/types/index.d.ts
CHANGED
|
@@ -6,16 +6,35 @@
|
|
|
6
6
|
// `tsc --noEmit` (checkJs). Keep this file free of value imports so consumers
|
|
7
7
|
// can load it without `allowJs`.
|
|
8
8
|
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Generated vocabulary (#115): one declaration per runtime constant family.
|
|
11
|
+
// `npm run types:generate` rewrites the marked block from ERROR_CODES,
|
|
12
|
+
// MISSION_STATUSES, REVIEW_STATUSES, MERGE_VERDICTS, MISSION_KINDS,
|
|
13
|
+
// REVIEW_POLICIES, FINDING_*, ROLES and PAUSE_CAUSES; the generated-types gate
|
|
14
|
+
// (`npm run docs:check:generated:types`) fails when the block drifts. Do not
|
|
15
|
+
// edit the block by hand.
|
|
16
|
+
// generated:vocabulary:start
|
|
9
17
|
export type MissionStatus = 'planned' | 'active' | 'completed' | 'blocked' | 'paused' | 'merged' | 'abandoned'
|
|
10
18
|
export type MissionKind = 'build' | 'survey'
|
|
11
|
-
export type ReviewStatus =
|
|
19
|
+
export type ReviewStatus =
|
|
20
|
+
| 'clean'
|
|
21
|
+
| 'p1-1items'
|
|
22
|
+
| 'p1-2items'
|
|
23
|
+
| 'p1-3items'
|
|
24
|
+
| 'p1-4items'
|
|
25
|
+
| 'p1-5items'
|
|
26
|
+
| 'p2-1items'
|
|
27
|
+
| 'p2-2items'
|
|
28
|
+
| 'p2-3items'
|
|
29
|
+
| 'p2-4items'
|
|
30
|
+
| 'p2-5items'
|
|
12
31
|
export type MergeVerdict = 'merge' | 'fix-then-merge' | 'hold'
|
|
13
32
|
export type ReviewPolicy = 'required-clean' | 'none'
|
|
14
33
|
export type FindingType = 'bug' | 'improve' | 'vuln' | 'idea'
|
|
15
34
|
export type FindingSeverity = 'low' | 'medium' | 'high' | 'critical'
|
|
16
35
|
export type FindingStatus = 'open' | 'triaged' | 'fixed' | 'wontfix'
|
|
17
36
|
export type Role = 'worker' | 'reviewer'
|
|
18
|
-
|
|
37
|
+
export type PauseCause = 'user' | 'failure' | 'resource' | 'dependency'
|
|
19
38
|
export type ErrorCode =
|
|
20
39
|
| 'invalid-argument'
|
|
21
40
|
| 'repo-invalid'
|
|
@@ -23,26 +42,22 @@ export type ErrorCode =
|
|
|
23
42
|
| 'mission-not-found'
|
|
24
43
|
| 'finding-not-found'
|
|
25
44
|
| 'invalid-transition'
|
|
45
|
+
| 'stale-base'
|
|
26
46
|
| 'state-locked'
|
|
27
47
|
| 'state-corrupt'
|
|
48
|
+
| 'state-version-unsupported'
|
|
49
|
+
| 'state-migration-failed'
|
|
28
50
|
| 'config-invalid'
|
|
29
51
|
| 'config-unknown-key'
|
|
52
|
+
| 'git-failed'
|
|
30
53
|
| 'git-timeout'
|
|
31
|
-
| '
|
|
32
|
-
| 'merge-hook-failed'
|
|
33
|
-
| 'merge-intent-pending'
|
|
34
|
-
| 'base-dirty'
|
|
35
|
-
| 'stale-base'
|
|
36
|
-
| 'tip-moved'
|
|
37
|
-
| 'out-of-scope'
|
|
38
|
-
| 'worker-busy'
|
|
39
|
-
| 'worker-stale'
|
|
54
|
+
| 'worktree-dirty'
|
|
40
55
|
| 'spawn-unavailable'
|
|
41
56
|
| 'provider-missing'
|
|
42
57
|
| 'provider-cooldown'
|
|
43
58
|
| 'preset-missing'
|
|
44
59
|
| 'reviewer-prerequisite'
|
|
45
|
-
| '
|
|
60
|
+
| 'not-authorized'
|
|
46
61
|
| 'workspace-busy'
|
|
47
62
|
| 'worker-limit'
|
|
48
63
|
| 'rate-limited'
|
|
@@ -56,6 +71,7 @@ export type ErrorCode =
|
|
|
56
71
|
| 'batch-invalid'
|
|
57
72
|
| 'legacy-invalid'
|
|
58
73
|
| 'tool-internal'
|
|
74
|
+
// generated:vocabulary:end
|
|
59
75
|
|
|
60
76
|
/** One machine-readable gate/completion blocker: a stable code plus a detail. */
|
|
61
77
|
export interface Blocker {
|
|
@@ -106,7 +122,7 @@ export interface CompletionReport {
|
|
|
106
122
|
checks?: CompletionCheck[]
|
|
107
123
|
}
|
|
108
124
|
|
|
109
|
-
/** One recorded test-discovery receipt (
|
|
125
|
+
/** One recorded test-discovery receipt (adapters/tests.js). */
|
|
110
126
|
export interface TestRun {
|
|
111
127
|
version?: number
|
|
112
128
|
ok?: boolean
|
package/ui-bundle/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Tower UI (optional Client bundle)
|
|
2
2
|
|
|
3
|
-
An **optional** Web Client bundle for the Host `dsh-tower` plugin. It never touches Tower state directly: everything it renders is derived from the conversation the user is already looking at, or from the tool results already in that conversation. There is no Host → Client remote channel, no `host.call`, no DOM manipulation, no iframe, and no import of Harness Client packages (the only module it requires is `react`
|
|
3
|
+
An **optional** Web Client bundle for the Host `dsh-tower` plugin. It never touches Tower state directly: everything it renders is derived from the conversation the user is already looking at, or from the tool results already in that conversation. There is no Host → Client remote channel, no `host.call`, no DOM manipulation, no iframe, and no import of Harness Client packages (the only browser module-table word it requires is `react`).
|
|
4
4
|
|
|
5
5
|
Version: **0.2.2** (private, not published). The Host plugin version is unchanged.
|
|
6
6
|
|
|
@@ -20,6 +20,39 @@ The cards are deliberately compact: mission status/counts, task checklist progre
|
|
|
20
20
|
|
|
21
21
|
The overlay strip is `pointer-events: none` end-to-end and uses `role="status"` / `aria-live="polite"`; it never blocks the composer or the page.
|
|
22
22
|
|
|
23
|
+
## Source layout (#71)
|
|
24
|
+
|
|
25
|
+
The bundle is split along the surfaces above. `client.js` is the entry: it owns
|
|
26
|
+
the diagnostics seam and the registrations, and loads the package-local chunks
|
|
27
|
+
through the DSH client module system's `require.async("./client.<surface>.js")`
|
|
28
|
+
(bundles execute as classic scripts, so the entry cannot require a sibling
|
|
29
|
+
synchronously). Every chunk registers through the same `window.__ModuleLoader__`
|
|
30
|
+
surface as `{ id: 'dsh-tower-ui', chunk: 'client.<surface>.js', factory }` and
|
|
31
|
+
receives the shared helpers/styles/locales through the `env` the entry composes;
|
|
32
|
+
no chunk reaches the Host or another chunk.
|
|
33
|
+
|
|
34
|
+
| File | Surface |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `client.js` | entry: diagnostics, `apply`, the five registrations |
|
|
37
|
+
| `client.toolview.js` | toolview card: lifecycle + `TowerToolCard` |
|
|
38
|
+
| `client.cards.js` | the per-tool card models behind the card |
|
|
39
|
+
| `client.dock.js` | composer dock: mode chip, unknown state, latest summary |
|
|
40
|
+
| `client.mode.js` | history-derived mode model + observable |
|
|
41
|
+
| `client.denied.js` | mode-aware `Tower mode · disabled` rows |
|
|
42
|
+
| `client.overlay.js` | transient `shell.overlay` status strip + notice store |
|
|
43
|
+
| `client.locales.js` | `en`/`zh` dictionaries and the `t`-seat helpers |
|
|
44
|
+
| `client.styles.js` | the `--dsw-alias-*` inline style table |
|
|
45
|
+
| `client.helpers.js` | tool vocabulary + defensive payload helpers |
|
|
46
|
+
| `index.js` | Host half (empty `apply`) |
|
|
47
|
+
|
|
48
|
+
The client tier is gated by `scripts/architecture.mjs` like any other source
|
|
49
|
+
root: `client-no-node` (no `node:*` imports; DOM globals are fine),
|
|
50
|
+
`client-size` (800-line ceiling per file) and `client-ambient` (no module-level
|
|
51
|
+
`let`; state lives in the `create*` closure), each with a seeded fixture in
|
|
52
|
+
`tests/architecture.test.mjs`. Every client source file stays under 400 lines
|
|
53
|
+
and the split preserves the pre-split line total within 10%
|
|
54
|
+
(`tests/ui-bundle.test.mjs`).
|
|
55
|
+
|
|
23
56
|
## Persistent Tower-mode indicator (#11)
|
|
24
57
|
|
|
25
58
|
Tower mode is a Host-side session stance and there is no Client RPC for it. The dock derives it from the durable session history that the Chat projection exposes in `useChat(snapshot.legacy)`:
|
|
@@ -38,7 +71,7 @@ The Host guard returns a plain tool error like `tower mode: <tool> is disabled w
|
|
|
38
71
|
|
|
39
72
|
While the displayed session is in Tower mode the bundle dynamically registers `tool.call.toolview` entries for:
|
|
40
73
|
|
|
41
|
-
- `tower_mode.deniedWhileActive` when the latest `tower_mode` output lists it, otherwise `
|
|
74
|
+
- `tower_mode.deniedWhileActive` when the latest `tower_mode` output lists it, otherwise `host/config.js` `DEFAULT_DENY_TOOLS` (`create_goal`, `todo_write`, `team_task_create`, `team_task_update`, `team_task_reopen`, `team_task_list`, `spawn_teammate`, `wait_agent`, `workflow`, `ralph`, `subagent`, `subagent_fork`);
|
|
42
75
|
- `update_goal` unconditionally, because the guard special-cases its `action: "resume"`.
|
|
43
76
|
|
|
44
77
|
Those entries register with `priority: -10`. The slot core renders the lowest priority for a key, so this shadows the native keyed views without replacing them; disposing the entries (mode off) restores the native views exactly. A denial result renders as a distinct **`Tower mode · disabled`** row (warn marker, label, the preserved denial message, and the `/tower off` hint) — not the generic error text styling. `update_goal` resume denials get the same row. A non-denial result that lands in one of those slots while the mode is on renders a defensive generic row (there is no native view to delegate to while the registration shadows it); ordinary tool errors are not misclassified as Tower-mode denials.
|
|
@@ -56,7 +89,7 @@ Verified **statically** against the installed DSH **0.2.1-rc.2** sources:
|
|
|
56
89
|
- Locale: `ctx.locale.register(ns, { en, zh })`, dictionaries are flat `key → template` maps with `{param}` interpolation; a slot declaration's `locale` option supplies the bound `t` seat.
|
|
57
90
|
- The bundle's `dsh.client.inject` orders activation after `ui-conversation` and `ui-tool`, the two packages that declare the slots it targets. `ctx.slots.inject` only installs a registration while its owner declaration exists; each injection is guarded and reported in diagnostics instead of crashing the whole Client half.
|
|
58
91
|
|
|
59
|
-
Automated offline coverage: `tests/ui-bundle.test.mjs` loads `client.js` through a fake `window.__ModuleLoader__
|
|
92
|
+
Automated offline coverage: `tests/ui-bundle.test.mjs` loads `client.js` through a fake `window.__ModuleLoader__` (and each split chunk through the same loader when the entry calls `require.async`), asserts the 12 keys / dock id / overlay id, en–zh dictionary parity, theme-token discipline, every file's loader format and the split-size budget, and renders every card plus the dock/overlay components with fake hooks. It also drives fake chat history through the dock to assert the off hint, the on chip (including refresh re-derivation), `tower_mode`/`tower_control`/command updates, dynamic registration for default and configured deny lists, disposal on mode off, `update_goal`, denial-row treatment, generic fallbacks, and locale parity — no browser required.
|
|
60
93
|
|
|
61
94
|
Live/browser verification is **opt-in** (`tests/live/ui-slots.cjs`) because it needs an isolated profile with the bundle installed. From a teammate/subagent session the Client `Slots` inspect query times out by design (the page answers only the agent whose session it displays), so this phase did not run a live inspection; the source-level contract above is the evidence. When the script runs against an installed profile it binds a session with a bare `/tower` command and asserts, through the read-only `window.__TOWER_UI_DIAG__` report, that apply() ran without errors, that all 12 toolview keys and the dock/overlay entries registered, that the dock outlet is visible, and — since the bind command turns the Host mode on — that the history-derived `Tower · on` chip rendered (#11). The chip can be inspected visually in the same run (`data-tower-ui="dock-mode"`).
|
|
62
95
|
|
|
@@ -65,6 +98,7 @@ Live/browser verification is **opt-in** (`tests/live/ui-slots.cjs`) because it n
|
|
|
65
98
|
```powershell
|
|
66
99
|
# offline, no browser
|
|
67
100
|
node --check ui-bundle/client.js
|
|
101
|
+
Get-ChildItem ui-bundle -Filter 'client*.js' | ForEach-Object { node --check $_.FullName }
|
|
68
102
|
node --test tests/ui-bundle.test.mjs
|
|
69
103
|
|
|
70
104
|
# opt-in live check (isolated profile only)
|