weir-harness 1.1.3 → 1.1.5
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/cordis.patch.yml +48 -87
- package/lib/client.capability-badge.js +16 -16
- package/lib/client.capability-manager.js +87 -36
- package/lib/client.capability-model.js +151 -31
- package/lib/client.capability-presets.js +187 -39
- package/lib/client.edit-lock-maintenance.js +27 -12
- package/lib/client.edit-lock-panel.js +49 -17
- package/lib/client.js +142 -5
- package/lib/client.settings-page.js +13 -8
- package/lib/client.worktree-model.js +20 -1
- package/lib/client.worktree-view.js +9 -1
- package/package.json +1 -1
- package/skills/deep-work/SKILL.md +7 -3
- package/src/blackboard/durable.js +199 -0
- package/src/blackboard/index.js +62 -42
- package/src/blackboard/kernel.js +20 -1
- package/src/blackboard/remote.js +36 -28
- package/src/blackboard/storage.js +238 -0
- package/src/capabilities/capability-remote.js +122 -69
- package/src/capabilities/preset-library.js +113 -87
- package/src/capabilities/read-payloads.js +21 -4
- package/src/capabilities/skill-selection-plugin.js +188 -168
- package/src/capabilities/skill-selection-provider.js +13 -0
- package/src/capabilities/store/enumerate.js +46 -0
- package/src/capabilities/store/store.js +86 -1
- package/src/context-guard/index.js +222 -117
- package/src/context-guard/pressure.js +12 -3
- package/src/context-guard/recovery.js +25 -0
- package/src/core/doctrine.js +3 -3
- package/src/delegate/group-coordinator.js +2 -1
- package/src/delegate/supervision-mount.js +41 -2
- package/src/delegate/tool.js +11 -3
- package/src/edit-lock/binding-attestation.js +29 -0
- package/src/edit-lock/binding-guidance.js +43 -0
- package/src/edit-lock/domain.js +19 -5
- package/src/edit-lock/host.js +3 -3
- package/src/edit-lock/index.js +205 -32
- package/src/edit-lock/lifecycle.js +89 -46
- package/src/edit-lock/maintenance.js +12 -3
- package/src/edit-lock/peer.js +2 -0
- package/src/edit-lock/publisher.js +57 -1
- package/src/edit-lock/remote.js +203 -25
- package/src/edit-lock/runtime.js +8 -0
- package/src/edit-lock/view.js +19 -7
- package/src/settings/sections.js +29 -5
- package/src/shared/audit.js +6 -0
- package/src/shared/delegate-edit-lifecycle.js +36 -0
- package/src/todo-driver/index.js +16 -1
- package/src/worktree/cards.js +47 -3
- package/src/worktree/command.js +29 -5
- package/src/worktree/git.js +39 -5
- package/src/worktree/lanes.js +83 -25
- package/src/worktree/pkgmgr.js +49 -10
- package/src/worktree/prompts.js +2 -1
- package/src/worktree/runner.js +3 -2
package/cordis.patch.yml
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
worktreeAutoSetup: true
|
|
41
41
|
worktreeWatchTimeoutMinutes: 360
|
|
42
42
|
worktreeAutoApprove: auto-clean
|
|
43
|
-
editLockEnabled:
|
|
43
|
+
editLockEnabled: true
|
|
44
44
|
# Message-driven auto-resume (edit-lock-auto-resume-on-message): a genuine
|
|
45
45
|
# user message resumes a stopped session and confirms its retained files.
|
|
46
46
|
# Read live per message, so toggling applies without a restart.
|
|
@@ -70,13 +70,12 @@
|
|
|
70
70
|
- id: weir-notify
|
|
71
71
|
name: 'weir-harness/notify'
|
|
72
72
|
# Capability read channel (silent-capability-reads): the plugin-owned
|
|
73
|
-
# typert remote serving
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
# (S27); the preset-layer skill-selection feeds its faces through the
|
|
77
|
-
# module-level bridge. Inert with a warning when typert is absent.
|
|
73
|
+
# typert remote serving all five Badge/manager reads without session-log
|
|
74
|
+
# writes. The host gateway resolves this service from the root (S27);
|
|
75
|
+
# preset feeds are labeled and routed by the sessionQuery projection.
|
|
78
76
|
- id: weir-capability-remote
|
|
79
77
|
name: 'weir-harness/capability-remote'
|
|
78
|
+
inject: [typert, sessionQuery]
|
|
80
79
|
# Session blackboard panel data channel (design D8): plugin-owned typert
|
|
81
80
|
# remote serving list/read/apply/write/remove so panel reads never touch
|
|
82
81
|
# the session log. Host layer for the same S27 reason as the capability
|
|
@@ -146,6 +145,7 @@
|
|
|
146
145
|
- id: weir-skill-selection
|
|
147
146
|
name: 'weir-harness/skill-selection'
|
|
148
147
|
config:
|
|
148
|
+
readPresetId: weir
|
|
149
149
|
# preset-skill-applicability (D2): profile-supplemental roots
|
|
150
150
|
# used ONLY to classify apply-missing names as known-but-
|
|
151
151
|
# inapplicable (skip with report) vs genuinely unknown — the
|
|
@@ -179,33 +179,13 @@
|
|
|
179
179
|
Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
|
|
180
180
|
|
|
181
181
|
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
|
182
|
-
- id:
|
|
182
|
+
- id: delegation
|
|
183
183
|
name: cordis:group
|
|
184
184
|
group: true
|
|
185
185
|
isolate:
|
|
186
186
|
compaction: true
|
|
187
187
|
toolResultPruner: true
|
|
188
|
-
|
|
189
|
-
- id: compaction-basic
|
|
190
|
-
name: '@deepseek-ai/dsh-compaction-basic'
|
|
191
|
-
- id: command-compact
|
|
192
|
-
name: '@deepseek-ai/dsh-command-compact'
|
|
193
|
-
- id: tool-result-pruner
|
|
194
|
-
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
195
|
-
config:
|
|
196
|
-
thresholdChars: 8192
|
|
197
|
-
headChars: 4096
|
|
198
|
-
tailChars: 1024
|
|
199
|
-
# F6 自动压缩:上下文压力阈值 + 模型择时压缩 + 边界强制 + 压缩后续推。
|
|
200
|
-
# `compaction` 服务被本组 isolate:消费者必须与提供者同 realm(否则
|
|
201
|
-
# inject 永久等待,预设加载失败)。
|
|
202
|
-
- id: context-guard
|
|
203
|
-
name: 'weir-harness/context-guard'
|
|
204
|
-
- id: delegation
|
|
205
|
-
name: cordis:group
|
|
206
|
-
group: true
|
|
207
|
-
isolate:
|
|
208
|
-
workflowEngine: true
|
|
188
|
+
weirCompactionCoordinator: true
|
|
209
189
|
# 编辑锁服务:提供者 edit-lock 与消费者 todo-driver / hashline-edit /
|
|
210
190
|
# lsp 同在本组。预设服务必须 isolate,否则预设加载以
|
|
211
191
|
# "Preset services require isolate realms" 失败。
|
|
@@ -217,29 +197,29 @@
|
|
|
217
197
|
# delegate write-contract consumer (slice 2) share this realm.
|
|
218
198
|
weirBlackboard: true
|
|
219
199
|
config:
|
|
200
|
+
- id: compaction
|
|
201
|
+
name: cordis:group
|
|
202
|
+
group: true
|
|
203
|
+
config:
|
|
204
|
+
- id: compaction-basic
|
|
205
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
206
|
+
- id: command-compact
|
|
207
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
208
|
+
- id: tool-result-pruner
|
|
209
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
210
|
+
config:
|
|
211
|
+
thresholdChars: 8192
|
|
212
|
+
headChars: 4096
|
|
213
|
+
tailChars: 1024
|
|
214
|
+
# F6 自动压缩:上下文压力阈值 + 模型择时压缩 + 边界强制 + 压缩后续推。
|
|
215
|
+
# `compaction` 服务被本组 isolate:消费者必须与提供者同 realm(否则
|
|
216
|
+
# inject 永久等待,预设加载失败)。
|
|
217
|
+
- id: context-guard
|
|
218
|
+
name: 'weir-harness/context-guard'
|
|
220
219
|
- id: tool-subagent-control
|
|
221
220
|
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
222
221
|
- id: tool-subagent-list-agents
|
|
223
222
|
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
224
|
-
- id: tool-subagent
|
|
225
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
226
|
-
config:
|
|
227
|
-
provider: spawn
|
|
228
|
-
toolName: subagent
|
|
229
|
-
modelSelectionSettings: true
|
|
230
|
-
backgroundMode: continuable
|
|
231
|
-
- id: tool-subagent-fork
|
|
232
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
233
|
-
config:
|
|
234
|
-
provider: fork
|
|
235
|
-
toolName: subagent_fork
|
|
236
|
-
backgroundMode: continuable
|
|
237
|
-
- id: workflow-ptc
|
|
238
|
-
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
239
|
-
config:
|
|
240
|
-
provider: spawn
|
|
241
|
-
- id: tool-workflow
|
|
242
|
-
name: '@deepseek-ai/dsh-tool-workflow'
|
|
243
223
|
- id: tool-ralph
|
|
244
224
|
name: '@deepseek-ai/dsh-tool-ralph'
|
|
245
225
|
disabled: true
|
|
@@ -262,8 +242,8 @@
|
|
|
262
242
|
# F4 todo 续推:空转续推 + 用户打断/供应商错误语义 + 逃生舱
|
|
263
243
|
- id: todo-driver
|
|
264
244
|
name: 'weir-harness/todo-driver'
|
|
265
|
-
#
|
|
266
|
-
#
|
|
245
|
+
# 编辑锁(默认开):显式保存 false 后重启才关闭;须在 hashline-edit / lsp 前挂载。
|
|
246
|
+
# 两个消费者挂载时捕获隔离组内的锁服务。
|
|
267
247
|
- id: edit-lock
|
|
268
248
|
name: 'weir-harness/edit-lock'
|
|
269
249
|
# F7 锚点编辑:read 锚点增强 + fail-closed hash_edit 工具
|
|
@@ -356,6 +336,7 @@
|
|
|
356
336
|
- id: weir-skill-selection
|
|
357
337
|
name: 'weir-harness/skill-selection'
|
|
358
338
|
config:
|
|
339
|
+
readPresetId: weir-creative
|
|
359
340
|
supplementalSkillDirs:
|
|
360
341
|
- !!js (() => { try { const p = process.getBuiltinModule('node:path'); return p.join(p.dirname(process.getBuiltinModule('node:module').createRequire(baseUrl).resolve('@deepseek-ai/dsh-agent-preset/package.json')), 'skills') } catch { return process.getBuiltinModule('node:path').join(process.getBuiltinModule('node:os').tmpdir(), 'dsh-agent-preset-skills-unavailable') } })()
|
|
361
342
|
customSkillDirs:
|
|
@@ -392,57 +373,37 @@
|
|
|
392
373
|
Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
|
|
393
374
|
|
|
394
375
|
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
|
395
|
-
- id:
|
|
376
|
+
- id: delegation
|
|
396
377
|
name: cordis:group
|
|
397
378
|
group: true
|
|
398
379
|
isolate:
|
|
399
380
|
compaction: true
|
|
400
381
|
toolResultPruner: true
|
|
401
|
-
|
|
402
|
-
- id: compaction-basic
|
|
403
|
-
name: '@deepseek-ai/dsh-compaction-basic'
|
|
404
|
-
- id: command-compact
|
|
405
|
-
name: '@deepseek-ai/dsh-command-compact'
|
|
406
|
-
- id: tool-result-pruner
|
|
407
|
-
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
408
|
-
config:
|
|
409
|
-
thresholdChars: 8192
|
|
410
|
-
headChars: 4096
|
|
411
|
-
tailChars: 1024
|
|
412
|
-
- id: context-guard
|
|
413
|
-
name: 'weir-harness/context-guard'
|
|
414
|
-
- id: delegation
|
|
415
|
-
name: cordis:group
|
|
416
|
-
group: true
|
|
417
|
-
isolate:
|
|
418
|
-
workflowEngine: true
|
|
382
|
+
weirCompactionCoordinator: true
|
|
419
383
|
weirEditLock: true
|
|
420
384
|
weirWorktreeLanes: true
|
|
421
385
|
weirBlackboard: true
|
|
422
386
|
config:
|
|
387
|
+
- id: compaction
|
|
388
|
+
name: cordis:group
|
|
389
|
+
group: true
|
|
390
|
+
config:
|
|
391
|
+
- id: compaction-basic
|
|
392
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
393
|
+
- id: command-compact
|
|
394
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
395
|
+
- id: tool-result-pruner
|
|
396
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
397
|
+
config:
|
|
398
|
+
thresholdChars: 8192
|
|
399
|
+
headChars: 4096
|
|
400
|
+
tailChars: 1024
|
|
401
|
+
- id: context-guard
|
|
402
|
+
name: 'weir-harness/context-guard'
|
|
423
403
|
- id: tool-subagent-control
|
|
424
404
|
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
425
405
|
- id: tool-subagent-list-agents
|
|
426
406
|
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
427
|
-
- id: tool-subagent
|
|
428
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
429
|
-
config:
|
|
430
|
-
provider: spawn
|
|
431
|
-
toolName: subagent
|
|
432
|
-
modelSelectionSettings: true
|
|
433
|
-
backgroundMode: continuable
|
|
434
|
-
- id: tool-subagent-fork
|
|
435
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
436
|
-
config:
|
|
437
|
-
provider: fork
|
|
438
|
-
toolName: subagent_fork
|
|
439
|
-
backgroundMode: continuable
|
|
440
|
-
- id: workflow-ptc
|
|
441
|
-
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
442
|
-
config:
|
|
443
|
-
provider: spawn
|
|
444
|
-
- id: tool-workflow
|
|
445
|
-
name: '@deepseek-ai/dsh-tool-workflow'
|
|
446
407
|
- id: tool-ralph
|
|
447
408
|
name: '@deepseek-ai/dsh-tool-ralph'
|
|
448
409
|
disabled: true
|
|
@@ -120,32 +120,35 @@ window.__ModuleLoader__.load({
|
|
|
120
120
|
const [panelOpen, setPanelOpen] = react.useState(false);
|
|
121
121
|
const [convergence, setConvergence] = react.useState(null);
|
|
122
122
|
const aliveRef = react.useRef(false);
|
|
123
|
+
const requestRef = react.useRef(0);
|
|
123
124
|
const refresh = react.useCallback(() => {
|
|
124
125
|
if (!sessionId || typeof props.fetchReceipt !== "function") return Promise.resolve();
|
|
126
|
+
const request = ++requestRef.current;
|
|
125
127
|
return props.fetchReceipt(sessionId).then((receipt) => {
|
|
126
|
-
if (aliveRef.current) { setState(model.badgeStateOf(receipt, null)); setSettled(true); }
|
|
127
|
-
}).catch(() => {
|
|
128
|
+
if (aliveRef.current && request === requestRef.current) { setState(model.badgeStateOf(receipt, null)); setSettled(true); }
|
|
129
|
+
}).catch(() => {
|
|
130
|
+
if (aliveRef.current && request === requestRef.current) { setState(null); setSettled(true); }
|
|
131
|
+
});
|
|
128
132
|
}, [sessionId]);
|
|
129
133
|
react.useEffect(() => {
|
|
134
|
+
++requestRef.current;
|
|
135
|
+
setState(null);
|
|
130
136
|
if (!sessionId || typeof props.fetchReceipt !== "function") {
|
|
131
|
-
setState(null);
|
|
132
137
|
setSettled(false);
|
|
133
138
|
return undefined;
|
|
134
139
|
}
|
|
135
140
|
aliveRef.current = true;
|
|
136
141
|
setSettled(false);
|
|
137
142
|
refresh();
|
|
138
|
-
// 12.5: a same-session frame refreshes this window's Badge; a
|
|
139
|
-
// missing subscription degrades to an explicit refresh hint.
|
|
140
143
|
if (typeof props.subscribeFrames === "function") {
|
|
141
144
|
setConvergence(model.convergenceHintOf({ subscribed: true }));
|
|
142
145
|
const stop = props.subscribeFrames((frame) => {
|
|
143
146
|
if (model.frameRefreshesSession(frame, sessionId)) refresh();
|
|
144
147
|
});
|
|
145
|
-
return () => { aliveRef.current = false; if (typeof stop === "function") stop(); };
|
|
148
|
+
return () => { aliveRef.current = false; ++requestRef.current; if (typeof stop === "function") stop(); };
|
|
146
149
|
}
|
|
147
150
|
setConvergence(model.convergenceHintOf({ subscribed: false }));
|
|
148
|
-
return () => { aliveRef.current = false; };
|
|
151
|
+
return () => { aliveRef.current = false; ++requestRef.current; };
|
|
149
152
|
}, [sessionId, refresh]);
|
|
150
153
|
// Close on outside click (edit-lock panel pattern): any pointerdown
|
|
151
154
|
// outside this entry's wrapper dismisses the panel. Clicks INSIDE
|
|
@@ -168,15 +171,12 @@ window.__ModuleLoader__.load({
|
|
|
168
171
|
const known = Boolean(state && state.known);
|
|
169
172
|
const label = known
|
|
170
173
|
? t("capability.badge", "{skills} skills · {mcp} MCP").replace("{skills}", String(state.applied.skills)).replace("{mcp}", String(state.applied.mcpServers))
|
|
171
|
-
: settled
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const compactLabel = known ? `${state.applied.skills} · ${state.applied.mcpServers}` : label;
|
|
178
|
-
const hasWarning = Boolean(state && state.unavailable && state.unavailable.length > 0);
|
|
179
|
-
const title = hasWarning ? state.unavailable.join("; ") : (convergence ?? (known ? label : undefined));
|
|
174
|
+
: settled && state?.degraded
|
|
175
|
+
? t("capability.effectiveUnknown", "Applied count unknown — refresh and check Skill sources")
|
|
176
|
+
: settled ? t("capability.unavailable.short", "Capabilities n/a") : t("capability.loading", "Capabilities…");
|
|
177
|
+
const compactLabel = known ? `${state.applied.skills} · ${state.applied.mcpServers}` : state?.degraded ? t("capability.effectiveUnknownShort", "Skills ? · MCP ?") : label;
|
|
178
|
+
const hasWarning = Boolean(state?.degraded);
|
|
179
|
+
const title = hasWarning ? `${label}: ${state.warnings.join("; ")}` : (convergence ?? (known ? label : undefined));
|
|
180
180
|
// D4: with the composition root's openPanel verb (a shell with a right
|
|
181
181
|
// sidebar) activation opens the dedicated panel and closes any open
|
|
182
182
|
// popover; without it the existing popover path runs unchanged.
|
|
@@ -539,6 +539,9 @@ window.__ModuleLoader__.load({
|
|
|
539
539
|
// recompute dirty, Apply goes through /capabilities apply with the
|
|
540
540
|
// draft's CAS revision and a fresh request ID.
|
|
541
541
|
const [draft, setDraft] = react.useState(null);
|
|
542
|
+
const [receiptState, setReceiptState] = react.useState(null);
|
|
543
|
+
const receiptRequest = react.useRef(0);
|
|
544
|
+
const applying = react.useRef(false);
|
|
542
545
|
const [commit, setCommit] = react.useState(null);
|
|
543
546
|
// Managed MCP add form (8.1/8.2): per-field errors are derived by the
|
|
544
547
|
// pure model (mcpAddErrorsOf) and shown after the first submit
|
|
@@ -546,13 +549,25 @@ window.__ModuleLoader__.load({
|
|
|
546
549
|
// return crashes with React #310.
|
|
547
550
|
const [addForm, setAddForm] = react.useState({ open: false, identity: "", label: "", command: "", args: "", showErrors: false, error: null, busy: false });
|
|
548
551
|
|
|
552
|
+
const refreshReceipt = () => {
|
|
553
|
+
if (typeof props.fetchReceipt !== "function") return;
|
|
554
|
+
const request = ++receiptRequest.current;
|
|
555
|
+
props.fetchReceipt(props.sessionId).then((receipt) => {
|
|
556
|
+
if (request !== receiptRequest.current) return;
|
|
557
|
+
setReceiptState(model.badgeStateOf(receipt, null));
|
|
558
|
+
setDraft(current => model.draftAfterRefresh(current, receipt));
|
|
559
|
+
}).catch(() => {
|
|
560
|
+
if (request === receiptRequest.current) {
|
|
561
|
+
setReceiptState(null);
|
|
562
|
+
setDraft(current => model.draftAfterRefresh(current, null));
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
};
|
|
549
566
|
const refreshAll = () => {
|
|
550
567
|
if (typeof props.fetchListing === "function") {
|
|
551
568
|
props.fetchListing(props.sessionId).then((data) => setListing(data ?? { error: true })).catch(() => setListing({ error: true }));
|
|
552
569
|
}
|
|
553
|
-
|
|
554
|
-
props.fetchReceipt(props.sessionId).then((receipt) => setDraft(receipt ? model.draftFromReceipt(receipt) : null)).catch(() => {});
|
|
555
|
-
}
|
|
570
|
+
refreshReceipt();
|
|
556
571
|
};
|
|
557
572
|
react.useEffect(() => {
|
|
558
573
|
let alive = true;
|
|
@@ -564,16 +579,16 @@ window.__ModuleLoader__.load({
|
|
|
564
579
|
if (typeof props.fetchConditions === "function") {
|
|
565
580
|
props.fetchConditions(props.sessionId).then((data) => { if (alive) setConditions(data); }).catch(() => {});
|
|
566
581
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
return () => { alive = false; };
|
|
582
|
+
refreshReceipt();
|
|
583
|
+
return () => { alive = false; ++receiptRequest.current; };
|
|
571
584
|
}, [props.sessionId]);
|
|
572
585
|
const toggle = (kind, name) => {
|
|
573
|
-
if (draft && commit?.phase !== "submitting") setDraft(model.draftToggle(draft, kind, name));
|
|
586
|
+
if (draft && commit?.phase !== "submitting") { setDraft(model.draftToggle(draft, kind, name)); setCommit(null); }
|
|
574
587
|
};
|
|
575
588
|
const applyDraft = () => {
|
|
576
|
-
|
|
589
|
+
const check = model.skillDraftConflicts(draft, listing);
|
|
590
|
+
if (applying.current || !draft || !draft.dirty || (draft.unknownBaseline && !draft.overwriteConfirmed) || check.selected.length || check.unresolved.some(item => item.state === "ambiguous") || typeof props.applySelection !== "function") return;
|
|
591
|
+
applying.current = true;
|
|
577
592
|
const requestId = crypto.randomUUID();
|
|
578
593
|
setCommit({ phase: "submitting", requestId });
|
|
579
594
|
props.applySelection(props.sessionId, {
|
|
@@ -582,18 +597,18 @@ window.__ModuleLoader__.load({
|
|
|
582
597
|
skills: draft.skills,
|
|
583
598
|
mcpServers: draft.mcpServers,
|
|
584
599
|
}).then((response) => {
|
|
600
|
+
applying.current = false;
|
|
585
601
|
const outcome = model.commitOutcomeOf(response);
|
|
586
602
|
setCommit(outcome);
|
|
587
603
|
if (outcome.phase === "applied") {
|
|
604
|
+
setDraft(current => current ? { ...current, applied: { skills: [...current.skills], mcpServers: [...current.mcpServers], unresolvedRefs: [...current.unresolvedRefs] }, revision: outcome.revision, dirty: false, appliedDigest: undefined, appliedSourceIssue: "receipt" } : null);
|
|
588
605
|
refreshAll();
|
|
589
|
-
// The Badge holds its own receipt state — tell it to re-pull
|
|
590
|
-
// so the composer counts follow the Apply immediately.
|
|
591
606
|
if (typeof props.onApplied === "function") props.onApplied();
|
|
592
607
|
}
|
|
593
|
-
}).catch(() => setCommit({ phase: "failed", error: "apply request failed", draftKept: true }));
|
|
608
|
+
}).catch(() => { applying.current = false; setCommit({ phase: "failed", error: "apply request failed", draftKept: true }); });
|
|
594
609
|
};
|
|
595
610
|
const discardDraft = () => {
|
|
596
|
-
if (draft) setDraft({ ...draft, skills: [...draft.applied.skills], mcpServers: [...draft.applied.mcpServers], dirty: false });
|
|
611
|
+
if (draft) setDraft({ ...draft, skills: [...draft.applied.skills], mcpServers: [...draft.applied.mcpServers], unresolvedRefs: [], dirty: false });
|
|
597
612
|
setCommit(null);
|
|
598
613
|
};
|
|
599
614
|
// Load-into-draft staging (preset view): only the preset's resolved
|
|
@@ -601,7 +616,11 @@ window.__ModuleLoader__.load({
|
|
|
601
616
|
// the session's applied set.
|
|
602
617
|
const stagePreset = (document) => {
|
|
603
618
|
if (commit?.phase === "submitting") return;
|
|
604
|
-
|
|
619
|
+
const resolveName = (name) => {
|
|
620
|
+
const matches = (listing?.skills ?? []).filter(row => row.status === "parsed" && row.name === name && row.identity);
|
|
621
|
+
return matches.length === 1 ? matches[0].identity : undefined;
|
|
622
|
+
};
|
|
623
|
+
setDraft(model.draftFromPreset(document, draft, resolveName));
|
|
605
624
|
setCommit(null);
|
|
606
625
|
};
|
|
607
626
|
// Managed MCP add form (8.1/8.2): registers a stdio server and mounts
|
|
@@ -670,7 +689,9 @@ window.__ModuleLoader__.load({
|
|
|
670
689
|
icon: "warn",
|
|
671
690
|
color: DANGER,
|
|
672
691
|
title: t("capability.error", "Capabilities unavailable for this session."),
|
|
673
|
-
description:
|
|
692
|
+
description: (draft?.skills ?? []).some(entry => typeof entry === "string")
|
|
693
|
+
? t("capability.inventoryUnavailableName", "The listing is unavailable; legacy names remain unresolved in the draft. Retry later; no source was selected.")
|
|
694
|
+
: t("capability.state.errorDesc", "The capability listing could not be loaded.")
|
|
674
695
|
})
|
|
675
696
|
});
|
|
676
697
|
}
|
|
@@ -697,6 +718,22 @@ window.__ModuleLoader__.load({
|
|
|
697
718
|
}
|
|
698
719
|
const partition = model.partitionManagerListing(listing);
|
|
699
720
|
const skillRows = model.groupSkillRows(model.filterSkillRows(partition.skills, query));
|
|
721
|
+
const skillKeys = new Set((draft?.skills ?? []).map(model.skillEntryKeyOf));
|
|
722
|
+
const check = model.skillDraftConflicts(draft, listing);
|
|
723
|
+
const sourceLabels = model.skillSourceLabels(partition.skills);
|
|
724
|
+
const sourceOf = identity => (sourceLabels[model.skillIdentityKeyOf(identity)] ?? t("capability.sourceUnknown", "unknown source"))
|
|
725
|
+
.replace(" · source ", t("capability.sourceNumber", " · source "));
|
|
726
|
+
const selectedKeys = new Set(check.selected.flatMap(item => item.identities.map(model.skillEntryKeyOf)));
|
|
727
|
+
const ambiguous = check.unresolved.filter(item => item.state === "ambiguous");
|
|
728
|
+
const unknown = check.unresolved.filter(item => item.state === "unknown");
|
|
729
|
+
const replaceName = (name, identity) => { if (commit?.phase !== "submitting") { setDraft(model.resolveSkillName(draft, name, identity)); setCommit(null); } };
|
|
730
|
+
const warningText = [
|
|
731
|
+
...check.selected.map(item => t("capability.sameNameWarning", "{name}: only one source can be enabled in this session. Uncheck one: {sources}.")
|
|
732
|
+
.replace("{name}", item.name).replace("{sources}", item.identities.map(sourceOf).join(", "))),
|
|
733
|
+
...ambiguous.map(item => t("capability.unresolvedNameWarning", "{name}: legacy name has no chosen source. Choose an exact source or remove the name: {sources}.")
|
|
734
|
+
.replace("{name}", item.name).replace("{sources}", item.candidates.map(sourceOf).join(", "))),
|
|
735
|
+
...unknown.map(item => t("capability.unknownLegacyName", "{name}: no matching Skill in the current listing. Refresh the listing or remove this legacy name.").replace("{name}", item.name))
|
|
736
|
+
];
|
|
700
737
|
const draftHas = (kind, name) => draft !== null && draft[kind].includes(name);
|
|
701
738
|
const selectionDisabled = draft === null || commit?.phase === "submitting";
|
|
702
739
|
const fieldError = (code) => code === "invalid"
|
|
@@ -752,8 +789,10 @@ window.__ModuleLoader__.load({
|
|
|
752
789
|
] })
|
|
753
790
|
] })
|
|
754
791
|
] }) : null;
|
|
755
|
-
const skillRow = (skill) => {
|
|
756
|
-
const selected =
|
|
792
|
+
const skillRow = (skill, index) => {
|
|
793
|
+
const selected = skillKeys.has(model.skillEntryKeyOf(skill.identity ?? skill.name));
|
|
794
|
+
const selectedConflict = selectedKeys.has(model.skillEntryKeyOf(skill.identity));
|
|
795
|
+
const source = skill.identity ? sourceOf(skill.identity) : skill.source;
|
|
757
796
|
const rest = selected ? tint(ACCENT, 8) : "transparent";
|
|
758
797
|
return react_jsx_runtime.jsxs("label", {
|
|
759
798
|
style: { ...S.row.base, background: rest },
|
|
@@ -765,20 +804,21 @@ window.__ModuleLoader__.load({
|
|
|
765
804
|
style: S.row.checkbox,
|
|
766
805
|
checked: selected,
|
|
767
806
|
disabled: selectionDisabled,
|
|
768
|
-
"aria-label": skill.name
|
|
769
|
-
|
|
807
|
+
"aria-label": `${skill.name} — ${source}`,
|
|
808
|
+
"aria-invalid": selectedConflict || undefined,
|
|
809
|
+
onChange: () => toggle("skills", skill.identity ?? skill.name)
|
|
770
810
|
}),
|
|
771
811
|
react_jsx_runtime.jsxs("span", { style: S.row.text, children: [
|
|
772
812
|
react_jsx_runtime.jsxs("span", { style: S.row.nameLine, children: [
|
|
773
813
|
react_jsx_runtime.jsx("span", { style: S.row.name, title: skill.name, children: skill.name }),
|
|
774
|
-
react_jsx_runtime.jsx("span", { style: { ...S.tag.base, ...S.tag.scope(skill.scopeKey) }, children:
|
|
775
|
-
skill.conflict ? react_jsx_runtime.jsx("span", { style: { ...S.tag.base, ...S.tag.warn }, children: t("capability.conflict", "conflict") }) : null,
|
|
814
|
+
react_jsx_runtime.jsx("span", { style: { ...S.tag.base, ...S.tag.scope(skill.scopeKey) }, children: source }),
|
|
815
|
+
selectedConflict || skill.conflict ? react_jsx_runtime.jsx("span", { style: { ...S.tag.base, ...S.tag.warn }, children: t("capability.conflict", "conflict") }) : null,
|
|
776
816
|
skill.missing ? react_jsx_runtime.jsx("span", { style: { ...S.tag.base, ...S.tag.danger }, title: skill.status, children: t("capability.missing", "missing") }) : null
|
|
777
817
|
] }),
|
|
778
818
|
skill.description ? react_jsx_runtime.jsx("span", { style: S.row.desc, title: skill.description, children: skill.description }) : null
|
|
779
819
|
] })
|
|
780
820
|
]
|
|
781
|
-
}, skill.name);
|
|
821
|
+
}, `${model.skillIdentityKeyOf(skill.identity) ?? `${skill.scopeKey}:${skill.name}`}:${index}`);
|
|
782
822
|
};
|
|
783
823
|
const mcpRow = (server) => {
|
|
784
824
|
const name = server.identity ?? server.serverName ?? "unknown";
|
|
@@ -819,11 +859,16 @@ window.__ModuleLoader__.load({
|
|
|
819
859
|
]
|
|
820
860
|
}, name);
|
|
821
861
|
};
|
|
862
|
+
const legacyActions = [...ambiguous, ...unknown].map(item => react_jsx_runtime.jsxs("div", { style: S.list.card, children: [
|
|
863
|
+
react_jsx_runtime.jsx("div", { style: S.form.title, children: t("capability.unresolvedNameTitle", "Choose a source for legacy name {name}").replace("{name}", item.name) }),
|
|
864
|
+
react_jsx_runtime.jsxs("div", { style: S.form.actions, children: [
|
|
865
|
+
...item.candidates.map(identity => react_jsx_runtime.jsx("button", { type: "button", style: S.form.ghost, ...ring, disabled: selectionDisabled, onClick: () => replaceName(item.name, identity), children: t("capability.replaceName", "Replace {name} with {source}").replace("{name}", item.name).replace("{source}", sourceOf(identity)) }, model.skillIdentityKeyOf(identity))),
|
|
866
|
+
react_jsx_runtime.jsx("button", { type: "button", style: S.form.ghost, ...ring, disabled: selectionDisabled, onClick: () => replaceName(item.name, null), children: t("capability.removeName", "Remove legacy name {name}").replace("{name}", item.name) })
|
|
867
|
+
] })
|
|
868
|
+
] }, item.name));
|
|
822
869
|
const body = tab === "skills"
|
|
823
|
-
? (
|
|
824
|
-
|
|
825
|
-
: react_jsx_runtime.jsxs(react_jsx_runtime.Fragment, {
|
|
826
|
-
children: skillRows.map((group) => react_jsx_runtime.jsxs("div", {
|
|
870
|
+
? react_jsx_runtime.jsxs(react_jsx_runtime.Fragment, {
|
|
871
|
+
children: [...(skillRows.length === 0 ? [react_jsx_runtime.jsx(StatePanel, { icon: "search", title: t("capability.noSkills", "No skills match.") })] : skillRows.map((group) => react_jsx_runtime.jsxs("div", {
|
|
827
872
|
style: S.list.group,
|
|
828
873
|
children: [
|
|
829
874
|
react_jsx_runtime.jsxs("div", {
|
|
@@ -835,8 +880,8 @@ window.__ModuleLoader__.load({
|
|
|
835
880
|
}),
|
|
836
881
|
...group.rows.map(skillRow)
|
|
837
882
|
]
|
|
838
|
-
}, group.key))
|
|
839
|
-
})
|
|
883
|
+
}, group.key))), ...legacyActions]
|
|
884
|
+
})
|
|
840
885
|
: tab === "mcp"
|
|
841
886
|
? react_jsx_runtime.jsxs(react_jsx_runtime.Fragment, {
|
|
842
887
|
children: [
|
|
@@ -895,7 +940,7 @@ window.__ModuleLoader__.load({
|
|
|
895
940
|
// Footer commit bar (12.3): the dirty diff summary (+n/−m) ahead of
|
|
896
941
|
// the same Apply/Discard/commit-phase semantics as before.
|
|
897
942
|
const diff = draft ? model.draftDiffOf(draft) : null;
|
|
898
|
-
const diffParts = diff && diff.any
|
|
943
|
+
const diffParts = diff && diff.any && !draft?.unknownBaseline
|
|
899
944
|
? [
|
|
900
945
|
diff.skillsAdded > 0 ? { added: true, text: t("capability.diff.addSkills", "+{n} skills").replace("{n}", String(diff.skillsAdded)) } : null,
|
|
901
946
|
diff.skillsRemoved > 0 ? { added: false, text: t("capability.diff.removeSkills", "−{n} skills").replace("{n}", String(diff.skillsRemoved)) } : null,
|
|
@@ -903,19 +948,19 @@ window.__ModuleLoader__.load({
|
|
|
903
948
|
diff.mcpRemoved > 0 ? { added: false, text: t("capability.diff.removeMcp", "−{n} MCP").replace("{n}", String(diff.mcpRemoved)) } : null
|
|
904
949
|
].filter(Boolean)
|
|
905
950
|
: null;
|
|
906
|
-
const applyDisabled = !draft || !draft.dirty || commit?.phase === "submitting";
|
|
951
|
+
const applyDisabled = !draft || !draft.dirty || (draft.unknownBaseline && !draft.overwriteConfirmed) || commit?.phase === "submitting" || check.selected.length > 0 || ambiguous.length > 0;
|
|
907
952
|
// Session state summary (sidebar header only): the APPLIED counts,
|
|
908
953
|
// same template the composer Badge renders.
|
|
909
|
-
const appliedSummary =
|
|
910
|
-
? t("capability.badge", "{skills} skills · {mcp} MCP").replace("{skills}", String(
|
|
911
|
-
: null;
|
|
954
|
+
const appliedSummary = receiptState?.known
|
|
955
|
+
? t("capability.badge", "{skills} skills · {mcp} MCP").replace("{skills}", String(receiptState.applied.skills)).replace("{mcp}", String(receiptState.applied.mcpServers))
|
|
956
|
+
: receiptState?.degraded ? t("capability.effectiveUnknownShort", "Skills ? · MCP ?") : null;
|
|
912
957
|
return react_jsx_runtime.jsxs("div", { style: frameStyle, children: [
|
|
913
958
|
react_jsx_runtime.jsxs("div", { style: S.header.row, children: [
|
|
914
959
|
react_jsx_runtime.jsx("button", { type: "button", style: S.tabs.segment(tab === "skills", "first"), ...ring, onClick: () => setTab("skills"), "aria-pressed": tab === "skills", children: t("capability.tab.skills", "Skills") }),
|
|
915
960
|
react_jsx_runtime.jsx("button", { type: "button", style: S.tabs.segment(tab === "mcp", "middle"), ...ring, onClick: () => setTab("mcp"), "aria-pressed": tab === "mcp", children: t("capability.tab.mcp", "MCP") }),
|
|
916
961
|
react_jsx_runtime.jsx("button", { type: "button", style: S.tabs.segment(tab === "presets", "last"), ...ring, onClick: () => setTab("presets"), "aria-pressed": tab === "presets", children: t("capability.tab.presets", "Presets") }),
|
|
917
962
|
props.shell === "sidebar" && appliedSummary
|
|
918
|
-
? react_jsx_runtime.jsx("span", { style: S.header.summary, children: appliedSummary })
|
|
963
|
+
? react_jsx_runtime.jsx("span", { style: S.header.summary, title: receiptState?.degraded ? t("capability.effectiveUnknown", "Applied count unknown — refresh and check Skill sources") : undefined, children: appliedSummary })
|
|
919
964
|
: null,
|
|
920
965
|
react_jsx_runtime.jsxs("span", {
|
|
921
966
|
style: S.search.wrap,
|
|
@@ -944,8 +989,12 @@ window.__ModuleLoader__.load({
|
|
|
944
989
|
...(props.shell === "sidebar"
|
|
945
990
|
? [react_jsx_runtime.jsxs("div", { style: S.list.scroll, children: [addBlock, body] })]
|
|
946
991
|
: [addBlock, body]),
|
|
992
|
+
receiptState?.degraded ? react_jsx_runtime.jsx("div", { role: "status", "aria-live": "polite", style: { color: WARN_TEXT, lineHeight: "16px", padding: "6px" }, children: t("capability.effectiveUnknownDetail", "Accepted revision; effective capabilities cannot be verified. Refresh and check Skill sources. Draft kept; only Apply changes the session.") }) : null,
|
|
947
993
|
draft !== null ? react_jsx_runtime.jsxs("div", { style: S.footer.bar, children: [
|
|
948
994
|
diffParts ? react_jsx_runtime.jsx("span", { style: S.footer.diff, "data-weir-capability-diff": "", children: diffParts.map((part, index) => react_jsx_runtime.jsx("span", { style: S.footer.diffBadge(part.added), children: part.text }, index)) }) : null,
|
|
995
|
+
warningText.length ? react_jsx_runtime.jsx("div", { role: "alert", "aria-live": "assertive", style: { width: "100%", color: WARN_TEXT, lineHeight: "16px" }, children: warningText.join(" ") }) : null,
|
|
996
|
+
draft.unknownBaseline ? react_jsx_runtime.jsx("div", { role: "alert", style: { width: "100%", color: WARN_TEXT, lineHeight: "16px" }, children: t("capability.unknownOverwriteRisk", "Previous selection is unknown. Applying this draft may replace other accepted Skills and MCP servers. Review the full selection before confirming.") }) : null,
|
|
997
|
+
draft.unknownBaseline && draft.dirty && !draft.overwriteConfirmed ? react_jsx_runtime.jsx("button", { type: "button", style: S.footer.discard, ...ring, disabled: commit?.phase === "submitting", onClick: () => setDraft(current => current ? { ...current, overwriteConfirmed: true } : null), children: t("capability.confirmUnknownOverwrite", "Confirm replacing unknown selection") }) : null,
|
|
949
998
|
react_jsx_runtime.jsx("button", {
|
|
950
999
|
type: "button",
|
|
951
1000
|
style: applyDisabled ? { ...S.footer.apply, ...S.footer.disabled } : S.footer.apply,
|
|
@@ -956,11 +1005,13 @@ window.__ModuleLoader__.load({
|
|
|
956
1005
|
children: commit?.phase === "submitting" ? t("capability.applying", "Applying…") : t("capability.apply", "Apply")
|
|
957
1006
|
}),
|
|
958
1007
|
draft.dirty ? react_jsx_runtime.jsx("button", { type: "button", style: S.footer.discard, ...ring, ...hover(HOVER_BG, "none"), onClick: discardDraft, children: t("capability.discard", "Discard") }) : null,
|
|
959
|
-
commit?.phase === "applied" ? react_jsx_runtime.jsx("span", { style: S.footer.chip(SUCCESS), children: t("capability.applied", "Applied") }) : null,
|
|
1008
|
+
commit?.phase === "applied" ? react_jsx_runtime.jsx("span", { style: S.footer.chip(receiptState?.degraded ? WARN : SUCCESS), children: receiptState?.degraded ? t("capability.acceptedUnknown", "Accepted · effective count unknown") : t("capability.applied", "Applied") }) : null,
|
|
960
1009
|
commit?.phase === "applied" && Array.isArray(commit.skipped) && commit.skipped.length > 0
|
|
961
1010
|
? react_jsx_runtime.jsx("span", { style: S.footer.chip(WARN), title: commit.skipped.map(entry => `${entry.name} — ${entry.reason}`).join("\n"), children: t("capability.appliedSkipped", "skipped {n} (not applicable here)").replace("{n}", String(commit.skipped.length)) })
|
|
962
1011
|
: null,
|
|
963
|
-
commit?.phase === "failed" ? react_jsx_runtime.jsx("span", { style: S.footer.chip(DANGER),
|
|
1012
|
+
commit?.phase === "failed" ? react_jsx_runtime.jsx("span", { style: S.footer.chip(DANGER), role: "alert", children: t("capability.failed", "Failed — draft kept") }) : null,
|
|
1013
|
+
commit?.phase === "same-name-conflict" ? react_jsx_runtime.jsx("span", { style: S.footer.chip(DANGER), role: "alert", children: t("capability.serverSameName", "Server rejected same-name Skill {names}. Uncheck one source, or refresh the listing; draft kept.").replace("{names}", commit.names.join(", ") || t("capability.unknownName", "unknown name")) }) : null,
|
|
1014
|
+
commit?.phase === "missing" ? react_jsx_runtime.jsx("span", { style: S.footer.chip(WARN), role: "alert", children: t("capability.serverMissing", "Server could not resolve {names}. Refresh the listing and choose an exact source or remove the name; draft kept.").replace("{names}", commit.names.join(", ") || t("capability.unknownName", "unknown name")) }) : null,
|
|
964
1015
|
commit?.phase === "revision-conflict" ? react_jsx_runtime.jsx("span", { style: S.footer.chip(WARN), children: t("capability.conflictState", "Changed elsewhere — review current state") }) : null,
|
|
965
1016
|
commit?.phase === "install-or-configure" ? react_jsx_runtime.jsx("span", { style: S.footer.chip(ACCENT), title: (commit.missing ?? []).join(", "), children: t("capability.missingAction", "Missing items need install/configure") }) : null,
|
|
966
1017
|
commit?.phase === "indeterminate" ? react_jsx_runtime.jsx("span", { style: S.footer.chip(MUTED), children: t("capability.pending", "Result pending — query the receipt") }) : null
|