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
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// dsh-tower-ui 0.2.2 — the per-tool card models (toolview surface).
|
|
2
|
+
//
|
|
3
|
+
// One model per Tower tool plus the shared fallbacks for foreign/older result
|
|
4
|
+
// shapes. Package-local chunk loaded by client.js (#71).
|
|
5
|
+
window.__ModuleLoader__.load({
|
|
6
|
+
id: 'dsh-tower-ui',
|
|
7
|
+
chunk: 'client.cards.js',
|
|
8
|
+
factory() {
|
|
9
|
+
function createCards(env) {
|
|
10
|
+
const { h, translate, ST, helpers } = env
|
|
11
|
+
const {
|
|
12
|
+
isPlainObject,
|
|
13
|
+
truncate,
|
|
14
|
+
firstLine,
|
|
15
|
+
blockersOf,
|
|
16
|
+
toneForPayload,
|
|
17
|
+
countsOf,
|
|
18
|
+
missionLine,
|
|
19
|
+
toneForStatus,
|
|
20
|
+
taskCountsOf,
|
|
21
|
+
missionBlocksOf,
|
|
22
|
+
checkEntries,
|
|
23
|
+
checkSummary,
|
|
24
|
+
checkText,
|
|
25
|
+
shortCommit,
|
|
26
|
+
TOOL_SET,
|
|
27
|
+
} = helpers
|
|
28
|
+
|
|
29
|
+
function rowElement(t, key, row) {
|
|
30
|
+
const style =
|
|
31
|
+
row.tone === 'error'
|
|
32
|
+
? Object.assign({}, ST.row, ST.rowError)
|
|
33
|
+
: row.tone === 'warn'
|
|
34
|
+
? Object.assign({}, ST.row, ST.rowWarn)
|
|
35
|
+
: row.tone === 'ok'
|
|
36
|
+
? Object.assign({}, ST.row, ST.rowOk)
|
|
37
|
+
: ST.row
|
|
38
|
+
return h('div', { key, style }, row.text)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Owner props supplied by the Chat tool-call tree: callId, toolName,
|
|
42
|
+
// phase ('preparing' | 'start' | 'result'), block, cwd, home, openFile,
|
|
43
|
+
// loadImage, inspect, useDisclosure — plus the session-scope standard kit.
|
|
44
|
+
// `phase === 'result'` means `block` is the settled `tool/result` block.
|
|
45
|
+
function cardModel(toolName, payload, t) {
|
|
46
|
+
const rows = []
|
|
47
|
+
if (!isPlainObject(payload)) {
|
|
48
|
+
return { tone: 'ok', headline: translate(t, 'card.done'), rows }
|
|
49
|
+
}
|
|
50
|
+
if (payload.__error === true) {
|
|
51
|
+
const error = isPlainObject(payload.error) ? payload.error : {}
|
|
52
|
+
const name = typeof error.name === 'string' ? error.name : translate(t, 'card.error')
|
|
53
|
+
const code = typeof error.code === 'string' ? error.code : ''
|
|
54
|
+
const message = typeof error.message === 'string' ? error.message : ''
|
|
55
|
+
const line = truncate([name, code, message].filter((part) => part !== '').join(': '))
|
|
56
|
+
return { tone: 'error', headline: translate(t, 'card.failed'), rows: [{ text: line, tone: 'error' }] }
|
|
57
|
+
}
|
|
58
|
+
if (typeof payload.text === 'string' && payload.text.trim() !== '') {
|
|
59
|
+
return { tone: 'ok', headline: firstLine(payload.text), rows }
|
|
60
|
+
}
|
|
61
|
+
const blockers = blockersOf(payload.blockers)
|
|
62
|
+
let tone = toneForPayload(payload)
|
|
63
|
+
let headline = translate(t, 'card.done')
|
|
64
|
+
|
|
65
|
+
if (toolName === 'tower_status') {
|
|
66
|
+
const counts = countsOf(payload)
|
|
67
|
+
const openTasks = typeof payload.openTasks === 'number' ? payload.openTasks : null
|
|
68
|
+
if (counts !== null) {
|
|
69
|
+
headline = translate(t, 'card.statusHeadline', { total: counts.total, active: counts.active })
|
|
70
|
+
}
|
|
71
|
+
if (openTasks !== null) {
|
|
72
|
+
rows.push({
|
|
73
|
+
text: openTasks > 0 ? translate(t, 'card.openTasks', { count: openTasks }) : translate(t, 'card.noOpenTasks'),
|
|
74
|
+
tone: openTasks > 0 ? 'warn' : 'ok',
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
if (typeof payload.roster === 'number' || typeof payload.findings === 'number') {
|
|
78
|
+
rows.push({
|
|
79
|
+
text: translate(t, 'card.statusDetail', {
|
|
80
|
+
roster: typeof payload.roster === 'number' ? payload.roster : '?',
|
|
81
|
+
findings: typeof payload.findings === 'number' ? payload.findings : '?',
|
|
82
|
+
revision: typeof payload.revision === 'number' ? payload.revision : '?',
|
|
83
|
+
}),
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
if (Array.isArray(payload.missions)) {
|
|
87
|
+
for (const mission of payload.missions.slice(0, 3)) {
|
|
88
|
+
const line = missionLine(t, mission)
|
|
89
|
+
if (line !== '') rows.push({ text: line, tone: toneForStatus(isPlainObject(mission) ? mission.status : undefined) })
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (isPlainObject(payload.detail) && isPlainObject(payload.detail.mission)) {
|
|
93
|
+
const line = missionLine(t, payload.detail.mission)
|
|
94
|
+
if (line !== '') rows.push({ text: line, tone: toneForStatus(payload.detail.mission.status) })
|
|
95
|
+
}
|
|
96
|
+
if (headline === translate(t, 'card.done')) {
|
|
97
|
+
headline = translate(t, 'card.statusHeadline', { total: 0, active: 0 })
|
|
98
|
+
}
|
|
99
|
+
} else if (toolName === 'tower_roster') {
|
|
100
|
+
const roster = Array.isArray(payload.roster) ? payload.roster : []
|
|
101
|
+
let live = 0
|
|
102
|
+
let stale = 0
|
|
103
|
+
let missing = 0
|
|
104
|
+
let other = 0
|
|
105
|
+
for (const entry of roster) {
|
|
106
|
+
if (!isPlainObject(entry)) continue
|
|
107
|
+
const activity = entry.activity
|
|
108
|
+
if (activity === 'running') live += 1
|
|
109
|
+
else if (entry.stale === true) stale += 1
|
|
110
|
+
else if (activity === 'missing' || activity === 'unknown') missing += 1
|
|
111
|
+
else other += 1
|
|
112
|
+
}
|
|
113
|
+
headline = translate(t, 'card.rosterHeadline', { live, total: roster.length })
|
|
114
|
+
tone = live > 0 ? 'ok' : stale > 0 ? 'warn' : 'idle'
|
|
115
|
+
if (stale > 0) rows.push({ text: translate(t, 'card.staleCount', { count: stale }), tone: 'warn' })
|
|
116
|
+
if (missing + other > 0) rows.push({ text: translate(t, 'card.otherCount', { count: missing + other }) })
|
|
117
|
+
for (const entry of roster.slice(0, 4)) {
|
|
118
|
+
if (!isPlainObject(entry)) continue
|
|
119
|
+
rows.push({
|
|
120
|
+
text: translate(t, 'card.agentLine', {
|
|
121
|
+
name: typeof entry.name === 'string' ? entry.name : '?',
|
|
122
|
+
role: typeof entry.role === 'string' ? entry.role : '?',
|
|
123
|
+
mission: typeof entry.missionId === 'string' ? entry.missionId : '?',
|
|
124
|
+
activity: typeof entry.activity === 'string' ? entry.activity : '?',
|
|
125
|
+
}),
|
|
126
|
+
tone: entry.stale === true ? 'warn' : entry.activity === 'running' ? 'ok' : undefined,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
if (isPlainObject(payload.parentStatus)) {
|
|
130
|
+
const unavailable = Object.keys(payload.parentStatus).filter((key) => payload.parentStatus[key] !== 'ok')
|
|
131
|
+
if (unavailable.length > 0) {
|
|
132
|
+
rows.push({ text: 'parent catalog unavailable: ' + unavailable.length, tone: 'warn' })
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} else if (toolName === 'tower_spawn') {
|
|
136
|
+
headline = translate(t, 'card.spawnLine', {
|
|
137
|
+
role: typeof payload.role === 'string' ? payload.role : '?',
|
|
138
|
+
name: typeof payload.name === 'string' ? payload.name : '?',
|
|
139
|
+
mission: typeof payload.missionId === 'string' ? payload.missionId : '?',
|
|
140
|
+
})
|
|
141
|
+
tone = 'ok'
|
|
142
|
+
if (isPlainObject(payload.worktree) && typeof payload.worktree.branch === 'string') {
|
|
143
|
+
rows.push({ text: translate(t, 'card.worktreeLine', { branch: payload.worktree.branch }) })
|
|
144
|
+
}
|
|
145
|
+
if (payload.created === false) rows.push({ text: 'reused worktree' })
|
|
146
|
+
if (typeof payload.agentId === 'string') rows.push({ text: truncate(payload.agentId, 60) })
|
|
147
|
+
if (payload.timedOut === true) rows.push({ text: 'timed out', tone: 'warn' })
|
|
148
|
+
} else if (toolName === 'tower_mission') {
|
|
149
|
+
const mission = isPlainObject(payload.mission) ? payload.mission : null
|
|
150
|
+
headline = mission !== null ? missionLine(t, mission) : translate(t, 'card.done')
|
|
151
|
+
tone = mission !== null ? toneForStatus(mission.status) : 'ok'
|
|
152
|
+
if (mission !== null) {
|
|
153
|
+
const tasks = taskCountsOf(mission.tasks)
|
|
154
|
+
if (tasks !== null) rows.push({ text: translate(t, 'card.tasks', { done: tasks.done, total: tasks.total }) })
|
|
155
|
+
for (const blocker of missionBlocksOf(mission.blockers).slice(0, 3)) {
|
|
156
|
+
rows.push({ text: truncate(blocker), tone: 'error' })
|
|
157
|
+
}
|
|
158
|
+
if (typeof mission.reviewPolicy === 'string') {
|
|
159
|
+
rows.push({ text: translate(t, 'card.reviewPolicy', { policy: mission.reviewPolicy }) })
|
|
160
|
+
}
|
|
161
|
+
if (isPlainObject(mission.owner) && typeof mission.owner.name === 'string') {
|
|
162
|
+
rows.push({ text: translate(t, 'card.owner', { owner: mission.owner.name }) })
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} else if (toolName === 'tower_complete') {
|
|
166
|
+
const mission = isPlainObject(payload.mission) ? payload.mission : null
|
|
167
|
+
headline = translate(t, 'card.completeLine', {
|
|
168
|
+
mission: mission !== null && typeof mission.id === 'string' ? mission.id : '?',
|
|
169
|
+
commits: typeof payload.commits === 'number' ? payload.commits : 0,
|
|
170
|
+
})
|
|
171
|
+
tone = 'ok'
|
|
172
|
+
if (typeof payload.unread === 'number' && payload.unread > 0) {
|
|
173
|
+
rows.push({ text: translate(t, 'card.unread', { count: payload.unread }), tone: 'warn' })
|
|
174
|
+
}
|
|
175
|
+
if (mission !== null && isPlainObject(mission.completionReport)) {
|
|
176
|
+
const checks = checkEntries(mission.completionReport.checks)
|
|
177
|
+
if (checks.length > 0) {
|
|
178
|
+
rows.push({ text: translate(t, 'card.checks') + ': ' + checkSummary(t, checks) })
|
|
179
|
+
for (const entry of checks.slice(0, 3)) rows.push({ text: checkText(t, entry), tone: entry.ok === false ? 'error' : 'ok' })
|
|
180
|
+
}
|
|
181
|
+
if (typeof mission.completionReport.report === 'string') {
|
|
182
|
+
rows.push({ text: firstLine(mission.completionReport.report) })
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} else if (toolName === 'tower_review') {
|
|
186
|
+
const review = isPlainObject(payload.review) ? payload.review : {}
|
|
187
|
+
headline = translate(t, 'card.reviewLine', {
|
|
188
|
+
status: typeof review.status === 'string' ? review.status : '?',
|
|
189
|
+
merge: typeof review.mergeVerdict === 'string' ? review.mergeVerdict : '?',
|
|
190
|
+
round: typeof payload.round === 'number' ? payload.round : typeof review.round === 'number' ? review.round : '?',
|
|
191
|
+
})
|
|
192
|
+
tone = toneForStatus(review.status)
|
|
193
|
+
const checks = checkEntries(review.checks)
|
|
194
|
+
if (checks.length > 0) {
|
|
195
|
+
rows.push({ text: translate(t, 'card.checks') + ': ' + checks.slice(0, 4).map((entry) => checkText(t, entry)).join(translate(t, 'card.sep')) })
|
|
196
|
+
}
|
|
197
|
+
if (typeof review.findings === 'string' && review.findings.trim() !== '' && review.findings.trim().toLowerCase() !== 'none') {
|
|
198
|
+
rows.push({ text: firstLine(review.findings), tone: 'warn' })
|
|
199
|
+
}
|
|
200
|
+
if (typeof review.decision === 'string' && review.decision.trim() !== '') {
|
|
201
|
+
rows.push({ text: firstLine(review.decision) })
|
|
202
|
+
}
|
|
203
|
+
if (typeof payload.branchTip === 'string') rows.push({ text: 'tip ' + shortCommit(payload.branchTip) })
|
|
204
|
+
} else if (toolName === 'tower_preflight') {
|
|
205
|
+
headline =
|
|
206
|
+
payload.ok === true
|
|
207
|
+
? translate(t, 'card.mergeReady')
|
|
208
|
+
: translate(t, 'card.mergeBlocked', { count: blockers.length })
|
|
209
|
+
tone = payload.ok === true ? 'ok' : 'error'
|
|
210
|
+
if (Array.isArray(payload.changedFiles)) {
|
|
211
|
+
rows.push({ text: 'changed files ' + payload.changedFiles.length })
|
|
212
|
+
}
|
|
213
|
+
if (isPlainObject(payload.review)) {
|
|
214
|
+
rows.push({
|
|
215
|
+
text: translate(t, 'card.reviewLine', {
|
|
216
|
+
status: typeof payload.review.status === 'string' ? payload.review.status : '?',
|
|
217
|
+
merge: typeof payload.review.mergeVerdict === 'string' ? payload.review.mergeVerdict : '?',
|
|
218
|
+
round: typeof payload.review.round === 'number' ? payload.review.round : '?',
|
|
219
|
+
}),
|
|
220
|
+
tone: toneForStatus(payload.review.status),
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
} else if (toolName === 'tower_merge') {
|
|
224
|
+
if (payload.ok === true) {
|
|
225
|
+
headline = payload.alreadyMerged === true
|
|
226
|
+
? translate(t, 'card.alreadyMerged')
|
|
227
|
+
: translate(t, 'card.merged', { commit: shortCommit(payload.mergedCommit) })
|
|
228
|
+
tone = 'ok'
|
|
229
|
+
} else {
|
|
230
|
+
headline = translate(t, 'card.mergeBlocked', { count: blockers.length })
|
|
231
|
+
tone = 'error'
|
|
232
|
+
}
|
|
233
|
+
if (payload.recoveredFromFailure === true) rows.push({ text: 'recorded after a failed merge attempt', tone: 'warn' })
|
|
234
|
+
if (Array.isArray(payload.warnings)) {
|
|
235
|
+
for (const warning of payload.warnings.slice(0, 2)) rows.push({ text: truncate(warning), tone: 'warn' })
|
|
236
|
+
}
|
|
237
|
+
if (typeof payload.baseTip === 'string') rows.push({ text: 'base ' + shortCommit(payload.baseTip) })
|
|
238
|
+
} else if (toolName === 'tower_teardown') {
|
|
239
|
+
const removed = Array.isArray(payload.removed) ? payload.removed.length : 0
|
|
240
|
+
const decisions = Array.isArray(payload.decisions) ? payload.decisions : []
|
|
241
|
+
const kept = decisions.filter((entry) => isPlainObject(entry) && entry.action === 'keep').length
|
|
242
|
+
headline = translate(t, 'card.teardownLine', { removed, kept })
|
|
243
|
+
tone = payload.ok === false ? 'error' : 'ok'
|
|
244
|
+
if (payload.dryRun === true) rows.push({ text: translate(t, 'card.dryRun') })
|
|
245
|
+
for (const decision of decisions.slice(0, 3)) {
|
|
246
|
+
if (!isPlainObject(decision)) continue
|
|
247
|
+
const reason = typeof decision.reason === 'string' ? ' · ' + truncate(decision.reason, 60) : ''
|
|
248
|
+
rows.push({
|
|
249
|
+
text: (typeof decision.action === 'string' ? decision.action : '?') + ' ' + (typeof decision.missionId === 'string' ? decision.missionId : '?') + reason,
|
|
250
|
+
tone: decision.action === 'keep' ? 'warn' : 'ok',
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
} else if (toolName === 'tower_interrupt') {
|
|
254
|
+
tone = payload.interrupted === true ? 'warn' : 'idle'
|
|
255
|
+
headline =
|
|
256
|
+
payload.interrupted === true
|
|
257
|
+
? translate(t, 'card.interruptYes', { name: isPlainObject(payload.entry) && typeof payload.entry.name === 'string' ? payload.entry.name : '?' })
|
|
258
|
+
: translate(t, 'card.interruptNo', { name: isPlainObject(payload.entry) && typeof payload.entry.name === 'string' ? payload.entry.name : '?' })
|
|
259
|
+
if (typeof payload.previousActivity === 'string') {
|
|
260
|
+
rows.push({ text: translate(t, 'card.previousActivity', { activity: payload.previousActivity }) })
|
|
261
|
+
}
|
|
262
|
+
if (typeof payload.reason === 'string' && payload.reason !== '') rows.push({ text: truncate(payload.reason) })
|
|
263
|
+
} else if (toolName === 'tower_doctor') {
|
|
264
|
+
const checks = checkEntries(payload.checks)
|
|
265
|
+
const passed = checks.filter((entry) => entry.ok === true).length
|
|
266
|
+
const warned = checks.filter((entry) => entry.ok === null).length
|
|
267
|
+
const failed = checks.filter((entry) => entry.ok === false).length
|
|
268
|
+
headline = translate(t, 'card.doctorLine', { status: payload.ok === true ? 'ok' : 'issues' })
|
|
269
|
+
tone = payload.ok === true ? 'ok' : failed > 0 ? 'error' : 'warn'
|
|
270
|
+
if (checks.length > 0) {
|
|
271
|
+
rows.push({
|
|
272
|
+
text: translate(t, 'card.doctorChecks', { pass: passed, warn: warned, fail: failed }),
|
|
273
|
+
tone: failed > 0 ? 'error' : warned > 0 ? 'warn' : 'ok',
|
|
274
|
+
})
|
|
275
|
+
for (const entry of checks.filter((candidate) => candidate.ok !== true).slice(0, 4)) {
|
|
276
|
+
rows.push({ text: checkText(t, entry), tone: entry.ok === false ? 'error' : 'warn' })
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} else if (toolName === 'tower_batch') {
|
|
280
|
+
const summary = isPlainObject(payload.summary)
|
|
281
|
+
? payload.summary
|
|
282
|
+
: isPlainObject(payload.batch)
|
|
283
|
+
? payload.batch
|
|
284
|
+
: null
|
|
285
|
+
const status =
|
|
286
|
+
summary !== null && typeof summary.status === 'string'
|
|
287
|
+
? summary.status
|
|
288
|
+
: payload.ok === true
|
|
289
|
+
? 'ok'
|
|
290
|
+
: 'issues'
|
|
291
|
+
headline = translate(t, 'card.batchLine', { status })
|
|
292
|
+
tone = payload.ok === true ? 'ok' : 'warn'
|
|
293
|
+
if (summary !== null) {
|
|
294
|
+
if (Array.isArray(summary.members)) {
|
|
295
|
+
rows.push({
|
|
296
|
+
text: translate(t, 'card.batchMembers', {
|
|
297
|
+
count: summary.members.length,
|
|
298
|
+
status: typeof summary.status === 'string' ? summary.status : '?',
|
|
299
|
+
}),
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
if (Array.isArray(summary.missingChecks) && summary.missingChecks.length > 0) {
|
|
303
|
+
rows.push({ text: translate(t, 'card.batchMissing', { count: summary.missingChecks.length }), tone: 'warn' })
|
|
304
|
+
}
|
|
305
|
+
if (Array.isArray(summary.pendingChecks) && summary.pendingChecks.length > 0) {
|
|
306
|
+
rows.push({ text: translate(t, 'card.batchMissing', { count: summary.pendingChecks.length }), tone: 'warn' })
|
|
307
|
+
}
|
|
308
|
+
if (Array.isArray(summary.failedChecks) && summary.failedChecks.length > 0) {
|
|
309
|
+
rows.push({ text: translate(t, 'card.batchFailed', { count: summary.failedChecks.length }), tone: 'error' })
|
|
310
|
+
}
|
|
311
|
+
const checks = checkEntries(summary.checks)
|
|
312
|
+
if (checks.length > 0) {
|
|
313
|
+
rows.push({
|
|
314
|
+
text: translate(t, 'card.checks') + ': ' + checkSummary(t, checks),
|
|
315
|
+
tone: checks.some((entry) => entry.ok === false) ? 'error' : 'ok',
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
} else if (Array.isArray(payload.batches)) {
|
|
319
|
+
rows.push({ text: 'batches ' + payload.batches.length })
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
tone = toneForPayload(payload)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!TOOL_SET.has(toolName)) {
|
|
326
|
+
const checks = checkEntries(payload.checks)
|
|
327
|
+
if (checks.length > 0) {
|
|
328
|
+
rows.push({ text: translate(t, 'card.checks') + ': ' + checkSummary(t, checks), tone: checks.some((entry) => entry.ok === false) ? 'error' : 'ok' })
|
|
329
|
+
}
|
|
330
|
+
if (Array.isArray(payload.warnings) && payload.warnings.length > 0) {
|
|
331
|
+
rows.push({ text: translate(t, 'card.warnings') + ': ' + payload.warnings.slice(0, 2).map((item) => truncate(item, 60)).join(translate(t, 'card.sep')), tone: 'warn' })
|
|
332
|
+
}
|
|
333
|
+
const counts = countsOf(payload)
|
|
334
|
+
if (counts !== null) {
|
|
335
|
+
rows.push({ text: translate(t, 'card.statusHeadline', { total: counts.total, active: counts.active }) })
|
|
336
|
+
}
|
|
337
|
+
if (Array.isArray(payload.missions)) {
|
|
338
|
+
for (const mission of payload.missions.slice(0, 3)) {
|
|
339
|
+
const line = missionLine(t, mission)
|
|
340
|
+
if (line !== '') rows.push({ text: line, tone: toneForStatus(isPlainObject(mission) ? mission.status : undefined) })
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
for (const blocker of blockers.slice(0, 3)) {
|
|
346
|
+
rows.push({ text: truncate(blocker), tone: 'error' })
|
|
347
|
+
}
|
|
348
|
+
return { tone, headline, rows }
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return { rowElement, cardModel }
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return { createCards }
|
|
355
|
+
},
|
|
356
|
+
})
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// dsh-tower-ui 0.2.2 — the mode-aware denied-tool row surface (#12).
|
|
2
|
+
//
|
|
3
|
+
// The entries exist only while the session is in Tower mode and register at a
|
|
4
|
+
// negative priority so the native toolviews return when the mode turns off.
|
|
5
|
+
// Package-local chunk (#71).
|
|
6
|
+
window.__ModuleLoader__.load({
|
|
7
|
+
id: 'dsh-tower-ui',
|
|
8
|
+
chunk: 'client.denied.js',
|
|
9
|
+
factory() {
|
|
10
|
+
function createDenied(env) {
|
|
11
|
+
const { h, translate, seatT, ST, dotStyle, helpers } = env
|
|
12
|
+
const { isPlainObject, textOfBlock, truncate, firstLine, parsePayload, DEFAULT_DENIED_TOOLS } = helpers
|
|
13
|
+
|
|
14
|
+
function denialMessageOf(block) {
|
|
15
|
+
if (!isPlainObject(block)) return null
|
|
16
|
+
const candidates = []
|
|
17
|
+
if (isPlainObject(block.error) && typeof block.error.message === 'string') candidates.push(block.error.message)
|
|
18
|
+
const text = textOfBlock(block)
|
|
19
|
+
if (text !== '') candidates.push(text)
|
|
20
|
+
if (typeof block.text === 'string') candidates.push(block.text)
|
|
21
|
+
for (const raw of candidates) {
|
|
22
|
+
const message = normalizeDenialText(raw)
|
|
23
|
+
if (message !== null) return message
|
|
24
|
+
}
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
function normalizeDenialText(raw) {
|
|
28
|
+
let value = String(raw === undefined || raw === null ? '' : raw).trim()
|
|
29
|
+
if (value === '') return null
|
|
30
|
+
if (value[0] === '{') {
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(value)
|
|
33
|
+
if (isPlainObject(parsed) && typeof parsed.message === 'string') value = parsed.message.trim()
|
|
34
|
+
} catch (error) {
|
|
35
|
+
/* keep the raw text */
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
value = value.replace(/^Error:\s*/i, '')
|
|
39
|
+
const lower = value.toLowerCase()
|
|
40
|
+
if (!lower.includes('tower mode')) return null
|
|
41
|
+
const specific =
|
|
42
|
+
lower.includes('is disabled while tower mode is active') ||
|
|
43
|
+
lower.includes('goals cannot be resumed while tower mode is active')
|
|
44
|
+
if (!specific && !lower.includes('/tower off')) return null
|
|
45
|
+
return truncate(value, 400)
|
|
46
|
+
}
|
|
47
|
+
function deniedNames(configured) {
|
|
48
|
+
const source = Array.isArray(configured) && configured.length > 0 ? configured : DEFAULT_DENIED_TOOLS
|
|
49
|
+
const names = []
|
|
50
|
+
for (const entry of source) {
|
|
51
|
+
if (typeof entry !== 'string') continue
|
|
52
|
+
const name = entry.trim()
|
|
53
|
+
if (name === '' || names.includes(name)) continue
|
|
54
|
+
names.push(name)
|
|
55
|
+
}
|
|
56
|
+
// update_goal is denied only for action:"resume", but the guard special
|
|
57
|
+
// case exists regardless of the configured denyTools list.
|
|
58
|
+
if (!names.includes('update_goal')) names.push('update_goal')
|
|
59
|
+
return names
|
|
60
|
+
}
|
|
61
|
+
function DeniedToolRow(props) {
|
|
62
|
+
const t = seatT(props)
|
|
63
|
+
const toolName = props && typeof props.toolName === 'string' ? props.toolName : ''
|
|
64
|
+
const phase = props && typeof props.phase === 'string' ? props.phase : 'start'
|
|
65
|
+
if (phase !== 'result') {
|
|
66
|
+
return h(
|
|
67
|
+
'div',
|
|
68
|
+
{ 'data-tower-toolview': toolName, 'data-tower-denied-view': 'running', style: ST.denyCard },
|
|
69
|
+
h(
|
|
70
|
+
'div',
|
|
71
|
+
{ style: ST.header },
|
|
72
|
+
h('span', { 'aria-hidden': true, style: dotStyle('idle') }),
|
|
73
|
+
h('span', { style: ST.denyLabel }, translate(t, 'deny.label')),
|
|
74
|
+
h('span', { style: ST.dim }, '·'),
|
|
75
|
+
h('span', { style: ST.title }, toolName),
|
|
76
|
+
h('span', { style: ST.dim }, translate(t, 'deny.running')),
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
const denial = denialMessageOf(props ? props.block : undefined)
|
|
81
|
+
if (denial !== null) {
|
|
82
|
+
return h(
|
|
83
|
+
'div',
|
|
84
|
+
{
|
|
85
|
+
'data-tower-toolview': toolName,
|
|
86
|
+
'data-tower-denied-view': 'denied',
|
|
87
|
+
role: 'note',
|
|
88
|
+
style: ST.denyCard,
|
|
89
|
+
title: denial,
|
|
90
|
+
},
|
|
91
|
+
h(
|
|
92
|
+
'div',
|
|
93
|
+
{ style: ST.header },
|
|
94
|
+
h('span', { 'aria-hidden': true, style: dotStyle('warn') }),
|
|
95
|
+
h('span', { style: ST.denyLabel }, translate(t, 'deny.label')),
|
|
96
|
+
h('span', { style: ST.dim }, '·'),
|
|
97
|
+
h('span', { style: ST.title }, toolName),
|
|
98
|
+
),
|
|
99
|
+
h('div', { style: ST.row }, denial),
|
|
100
|
+
h('div', { style: ST.denyHint }, translate(t, 'deny.hint')),
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
// Defensive fallback: the slot is ours only while Tower mode is on, so
|
|
104
|
+
// a non-denial result here has no native view to delegate to.
|
|
105
|
+
const payload = parsePayload(props ? props.block : undefined)
|
|
106
|
+
const error =
|
|
107
|
+
isPlainObject(props) && isPlainObject(props.block) && isPlainObject(props.block.error) ? props.block.error : null
|
|
108
|
+
let text = translate(t, 'deny.generic', { tool: toolName })
|
|
109
|
+
let tone = 'ok'
|
|
110
|
+
if (isPlainObject(payload) && payload.__error === true) {
|
|
111
|
+
tone = 'error'
|
|
112
|
+
const reason =
|
|
113
|
+
error !== null && typeof error.message === 'string'
|
|
114
|
+
? error.message
|
|
115
|
+
: error !== null && typeof error.code === 'string'
|
|
116
|
+
? error.code
|
|
117
|
+
: 'error'
|
|
118
|
+
text = translate(t, 'deny.error') + ': ' + firstLine(reason)
|
|
119
|
+
} else if (isPlainObject(payload) && typeof payload.text === 'string' && payload.text.trim() !== '') {
|
|
120
|
+
text = translate(t, 'deny.generic', { tool: toolName }) + translate(t, 'card.sep') + firstLine(payload.text)
|
|
121
|
+
}
|
|
122
|
+
return h(
|
|
123
|
+
'div',
|
|
124
|
+
{ 'data-tower-toolview': toolName, 'data-tower-denied-view': 'generic', style: ST.denyCard },
|
|
125
|
+
h(
|
|
126
|
+
'div',
|
|
127
|
+
{ style: ST.header },
|
|
128
|
+
h('span', { 'aria-hidden': true, style: dotStyle(tone) }),
|
|
129
|
+
h('span', { style: ST.title }, translate(t, 'card.title')),
|
|
130
|
+
h('span', { style: ST.dim }, '·'),
|
|
131
|
+
h('span', { style: ST.title }, toolName),
|
|
132
|
+
h('span', { style: tone === 'error' ? ST.rowError : ST.dim }, truncate(text, 120)),
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { denialMessageOf, normalizeDenialText, deniedNames, DeniedToolRow }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { createDenied }
|
|
141
|
+
},
|
|
142
|
+
})
|