weir-harness 1.1.4 → 1.1.6

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.
Files changed (57) hide show
  1. package/cordis.patch.yml +48 -87
  2. package/icon.svg +1 -0
  3. package/lib/client.capability-badge.js +16 -16
  4. package/lib/client.capability-manager.js +87 -36
  5. package/lib/client.capability-model.js +151 -31
  6. package/lib/client.capability-presets.js +187 -39
  7. package/lib/client.edit-lock-maintenance.js +27 -12
  8. package/lib/client.edit-lock-panel.js +52 -19
  9. package/lib/client.js +270 -12
  10. package/lib/client.settings-page.js +13 -8
  11. package/lib/client.worktree-model.js +20 -1
  12. package/lib/client.worktree-view.js +32 -9
  13. package/package.json +4 -2
  14. package/skills/deep-work/SKILL.md +7 -3
  15. package/src/blackboard/durable.js +199 -0
  16. package/src/blackboard/index.js +62 -42
  17. package/src/blackboard/kernel.js +20 -1
  18. package/src/blackboard/remote.js +36 -28
  19. package/src/blackboard/storage.js +238 -0
  20. package/src/capabilities/capability-remote.js +122 -69
  21. package/src/capabilities/preset-library.js +113 -87
  22. package/src/capabilities/read-payloads.js +21 -4
  23. package/src/capabilities/skill-selection-plugin.js +188 -168
  24. package/src/capabilities/skill-selection-provider.js +13 -0
  25. package/src/capabilities/store/enumerate.js +46 -0
  26. package/src/capabilities/store/store.js +86 -1
  27. package/src/context-guard/index.js +222 -117
  28. package/src/context-guard/pressure.js +12 -3
  29. package/src/context-guard/recovery.js +25 -0
  30. package/src/core/doctrine.js +3 -3
  31. package/src/delegate/group-coordinator.js +2 -1
  32. package/src/delegate/supervision-mount.js +41 -2
  33. package/src/delegate/tool.js +11 -3
  34. package/src/edit-lock/admin-ledger.js +42 -6
  35. package/src/edit-lock/admin-recovery.js +4 -2
  36. package/src/edit-lock/binding-attestation.js +29 -0
  37. package/src/edit-lock/binding-guidance.js +43 -0
  38. package/src/edit-lock/domain.js +20 -5
  39. package/src/edit-lock/host.js +3 -3
  40. package/src/edit-lock/index.js +268 -57
  41. package/src/edit-lock/lifecycle.js +106 -47
  42. package/src/edit-lock/maintenance.js +12 -3
  43. package/src/edit-lock/manager.js +8 -3
  44. package/src/edit-lock/peer.js +2 -0
  45. package/src/edit-lock/publisher.js +57 -1
  46. package/src/edit-lock/remote.js +120 -52
  47. package/src/edit-lock/runtime.js +8 -0
  48. package/src/edit-lock/view.js +27 -12
  49. package/src/settings/sections.js +30 -6
  50. package/src/shared/audit.js +6 -0
  51. package/src/shared/delegate-edit-lifecycle.js +36 -0
  52. package/src/todo-driver/index.js +16 -1
  53. package/src/worktree/cards.js +47 -3
  54. package/src/worktree/command.js +29 -5
  55. package/src/worktree/git.js +39 -5
  56. package/src/worktree/lanes.js +83 -25
  57. package/src/worktree/prompts.js +2 -1
@@ -11,20 +11,37 @@ window.__ModuleLoader__.load({
11
11
  // is a function over plain data so the DOM layer stays thin and the
12
12
  // node:test suite covers the semantics.
13
13
 
14
- /** Applied counts come from the SERVER receipt, never the optimistic draft (12.2). */
14
+ // Like the SEGMENT-regex mirror below, same-package sync require is
15
+ // impossible in the ModuleLoader. Mirror src/capabilities/skill-identity.js.
16
+ function skillIdentityKeyOf(identity) {
17
+ if (!identity || typeof identity !== "object" || Array.isArray(identity)) return null;
18
+ const { scope, root, name, provenance, opaqueId } = identity;
19
+ if (typeof scope !== "string" || typeof root !== "string" || typeof name !== "string") return null;
20
+ const sorted = provenance == null ? null : Object.fromEntries(Object.keys(provenance).sort().map(key => [key, provenance[key]]));
21
+ return JSON.stringify([scope, root, name, sorted, sorted === null ? (opaqueId ?? null) : null]);
22
+ }
23
+ function skillEntryKeyOf(entry) {
24
+ if (typeof entry === "string") return "name:" + entry;
25
+ const key = skillIdentityKeyOf(entry);
26
+ return key === null ? null : "id:" + key;
27
+ }
28
+ const sortSkills = (entries) => [...entries].sort((a, b) => {
29
+ const left = skillEntryKeyOf(a), right = skillEntryKeyOf(b);
30
+ return left < right ? -1 : left > right ? 1 : 0;
31
+ });
32
+
33
+ /** Only a complete, warning-free effective receipt proves applied counts. */
15
34
  function badgeStateOf(receipt, draft) {
16
- const applied = receipt && receipt.status === "applied"
17
- ? { skills: Array.isArray(receipt.effective?.skills) ? receipt.effective.skills.length : 0, mcpServers: Array.isArray(receipt.effective?.mcpServers) ? receipt.effective.mcpServers.length : 0 }
18
- : { skills: 0, mcpServers: 0 };
19
35
  const warnings = Array.isArray(receipt?.warnings) ? [...receipt.warnings] : [];
20
- const draftDirty = Boolean(draft && draft.dirty);
36
+ const known = receipt?.status === "applied" && warnings.length === 0
37
+ && Array.isArray(receipt?.effective?.skills) && Array.isArray(receipt?.effective?.mcpServers);
21
38
  return {
22
- applied,
39
+ applied: known ? { skills: receipt.effective.skills.length, mcpServers: receipt.effective.mcpServers.length } : null,
23
40
  warnings,
24
41
  unavailable: warnings.filter((warning) => typeof warning === "string" && warning.includes("unavailable")),
25
- draftDirty,
26
- // Distinct from a fresh/loading state (12.2): receipt absent ≠ applied zero.
27
- known: Boolean(receipt && receipt.status === "applied"),
42
+ draftDirty: Boolean(draft && draft.dirty),
43
+ known,
44
+ degraded: receipt?.status === "applied" && !known,
28
45
  };
29
46
  }
30
47
 
@@ -46,6 +63,7 @@ window.__ModuleLoader__.load({
46
63
  missing: status !== "parsed" && status !== "unknown",
47
64
  selected: Boolean(candidate?.selected),
48
65
  conflict: candidate?.conflict === true,
66
+ identity: candidate?.identity ?? null,
49
67
  };
50
68
  }
51
69
  function partitionManagerListing(listing) {
@@ -110,32 +128,110 @@ window.__ModuleLoader__.load({
110
128
  // ---- Draft editing (12.3 manager Apply surface) ----
111
129
  /** The edit draft seeded from the server receipt (CAS revision included). */
112
130
  function draftFromReceipt(receipt) {
113
- const skills = Array.isArray(receipt?.effective?.skills) ? [...receipt.effective.skills] : [];
131
+ const skills = Array.isArray(receipt?.effective?.skillIdentities) ? [...receipt.effective.skillIdentities]
132
+ : Array.isArray(receipt?.effective?.skills) ? [...receipt.effective.skills] : [];
114
133
  const mcpServers = Array.isArray(receipt?.effective?.mcpServers) ? [...receipt.effective.mcpServers] : [];
134
+ const warnings = Array.isArray(receipt?.warnings) ? receipt.warnings.filter(Boolean).map(String) : [];
135
+ const digest = receipt?.effective?.digest;
136
+ const provable = receipt?.status === "applied" && warnings.length === 0 && Number.isSafeInteger(receipt.revision) && receipt.revision >= 0
137
+ && Array.isArray(receipt?.effective?.skillIdentities) && Array.isArray(receipt?.effective?.mcpServers)
138
+ && typeof digest === "string" && /^[a-f0-9]{64}$/.test(digest);
115
139
  return {
116
- skills: [...skills].sort(),
140
+ skills: sortSkills(skills),
117
141
  mcpServers: [...mcpServers].sort(),
118
- applied: { skills: [...skills].sort(), mcpServers: [...mcpServers].sort() },
142
+ applied: { skills: sortSkills(skills), mcpServers: [...mcpServers].sort(), unresolvedRefs: Array.isArray(receipt?.effective?.unresolvedRefs) ? [...receipt.effective.unresolvedRefs] : [] },
143
+ unresolvedRefs: Array.isArray(receipt?.effective?.unresolvedRefs) ? [...receipt.effective.unresolvedRefs] : [],
144
+ ...(provable ? { appliedDigest: digest } : {}),
145
+ appliedSourceIssue: warnings.length ? "unavailable" : provable ? null : "receipt",
119
146
  revision: Number.isSafeInteger(receipt?.revision) ? receipt.revision : 0,
120
147
  dirty: false,
121
148
  };
122
149
  }
150
+ /** A degraded read is not permission to overwrite a user's staged selection.
151
+ * Keep the last CAS base (the server will reject a stale revision) and
152
+ * revoke applied-source proof until a complete refresh arrives. */
153
+ function draftAfterRefresh(current, receipt) {
154
+ if (!badgeStateOf(receipt, null).known) {
155
+ if (current) return { ...current, unknownBaseline: true, overwriteConfirmed: false, appliedSourceIssue: "unavailable", appliedDigest: undefined };
156
+ return receipt?.status === "applied" && Number.isSafeInteger(receipt.revision) && receipt.revision >= 0
157
+ ? { ...draftFromReceipt(receipt), unknownBaseline: true, overwriteConfirmed: false } : null;
158
+ }
159
+ const fresh = draftFromReceipt(receipt);
160
+ if (!current?.dirty) return fresh;
161
+ const sameBase = current.revision === fresh.revision
162
+ && JSON.stringify(current.applied.skills.map(skillEntryKeyOf)) === JSON.stringify(fresh.applied.skills.map(skillEntryKeyOf))
163
+ && JSON.stringify(current.applied.mcpServers) === JSON.stringify(fresh.applied.mcpServers);
164
+ return { ...current, unknownBaseline: current.unknownBaseline === true && !sameBase, overwriteConfirmed: false, appliedSourceIssue: sameBase ? fresh.appliedSourceIssue : "receipt", appliedDigest: sameBase ? fresh.appliedDigest : undefined };
165
+ }
123
166
 
124
- /** Toggle one name in the draft; dirty is recomputed against the applied sets. */
167
+ /** Toggle one entry in the draft; skills compare by identity, MCP by name. */
125
168
  function draftToggle(draft, kind, name) {
169
+ if (kind === "skills") {
170
+ const key = skillEntryKeyOf(name);
171
+ const list = new Map(draft.skills.map(entry => [skillEntryKeyOf(entry), entry]));
172
+ if (list.has(key)) list.delete(key); else list.set(key, name);
173
+ const next = { ...draft, overwriteConfirmed: false, skills: sortSkills([...list.values()]) };
174
+ next.dirty = draftDiffOf(next).any;
175
+ return next;
176
+ }
126
177
  const list = new Set(draft[kind]);
127
178
  if (list.has(name)) list.delete(name); else list.add(name);
128
- const next = { ...draft, [kind]: [...list].sort() };
179
+ const next = { ...draft, overwriteConfirmed: false, [kind]: [...list].sort() };
129
180
  const same = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
130
- next.dirty = !same(next.skills, draft.applied.skills) || !same(next.mcpServers, draft.applied.mcpServers);
181
+ next.dirty = !same(next.skills.map(skillEntryKeyOf), draft.applied.skills.map(skillEntryKeyOf)) || !same(next.mcpServers, draft.applied.mcpServers);
182
+ return next;
183
+ }
184
+ /** Preflight the complete draft against the unfiltered inventory. Strings are not identities. */
185
+ function skillDraftConflicts(draft, listing) {
186
+ const byName = new Map(), names = new Set();
187
+ for (const entry of Array.isArray(draft?.skills) ? draft.skills : []) {
188
+ if (typeof entry === "string") { if (entry) names.add(entry); continue; }
189
+ const key = skillIdentityKeyOf(entry);
190
+ if (key === null) continue;
191
+ if (!byName.has(entry.name)) byName.set(entry.name, new Map());
192
+ byName.get(entry.name).set(key, entry);
193
+ }
194
+ const selected = [...byName].filter(([, group]) => group.size > 1).map(([name, group]) => ({ name, identities: [...group.values()] }));
195
+ const rows = Array.isArray(listing?.skills) ? listing.skills : null;
196
+ const unresolved = [...names].map(name => {
197
+ if (!rows) return { name, state: "unavailable", candidates: [] };
198
+ const candidates = [...new Map(rows.filter(row => row?.status === "parsed" && row.name === name && skillIdentityKeyOf(row.identity) !== null)
199
+ .map(row => [skillIdentityKeyOf(row.identity), row.identity])).values()];
200
+ return { name, state: candidates.length > 1 ? "ambiguous" : candidates.length === 1 ? "unique" : "unknown", candidates };
201
+ });
202
+ return { selected, unresolved };
203
+ }
204
+
205
+ /** Explicit legacy-name recovery never consumes any other entry or CAS metadata. */
206
+ function resolveSkillName(draft, name, identity) {
207
+ if (!draft || typeof name !== "string" || !name || !draft.skills?.includes(name) || (identity !== null && (skillIdentityKeyOf(identity) === null || identity.name !== name))) return draft;
208
+ const entries = draft.skills.filter(entry => entry !== name);
209
+ if (identity && !entries.some(entry => skillEntryKeyOf(entry) === skillEntryKeyOf(identity))) entries.push(identity);
210
+ const next = { ...draft, overwriteConfirmed: false, skills: sortSkills(entries) };
211
+ next.dirty = draftDiffOf(next).any;
131
212
  return next;
132
213
  }
133
214
 
215
+ /** Scoped ordinal distinguishes same-scope roots without displaying paths or IDs. */
216
+ function skillSourceLabels(rows) {
217
+ const labels = new Map(), totals = new Map();
218
+ for (const row of Array.isArray(rows) ? rows : []) {
219
+ const key = skillIdentityKeyOf(row.identity);
220
+ if (key === null || labels.has(key)) continue;
221
+ const scope = SOURCE_LABELS[row.scopeKey ?? row.scope] ?? row.scopeKey ?? row.scope ?? "unknown";
222
+ const group = `${row.name}:${scope}`;
223
+ const number = (totals.get(group) ?? 0) + 1;
224
+ totals.set(group, number);
225
+ labels.set(key, { scope, group, number });
226
+ }
227
+ for (const [key, item] of labels) labels.set(key, totals.get(item.group) > 1 ? `${item.scope} · source ${item.number}` : item.scope);
228
+ return Object.fromEntries(labels);
229
+ }
134
230
  /**
135
231
  * Map the engine response onto the commit state machine (12.3): applied
136
232
  * refreshes; revision-conflict shows the current state for re-pick;
137
- * indeterminate stays queryable; missing names surface as
138
- * install/configure; anything else keeps the draft.
233
+ * indeterminate stays queryable; structured rejections preserve names;
234
+ * anything else keeps the draft.
139
235
  */
140
236
  function commitOutcomeOf(response) {
141
237
  if (!response || typeof response !== "object") return { phase: "failed", error: "no response", draftKept: true };
@@ -148,7 +244,8 @@ window.__ModuleLoader__.load({
148
244
  if (response.status === "applied") return { phase: "applied", revision: response.revision ?? null, skipped };
149
245
  if (response.status === "revision-conflict") return { phase: "revision-conflict", current: response.current ?? null, draftKept: true };
150
246
  if (response.status === "indeterminate") return { phase: "indeterminate", requestId: response.receipt?.requestId ?? null, queryable: true };
151
- if (response.status === "missing") return { phase: "install-or-configure", missing: Array.isArray(response.missing) ? response.missing : [] };
247
+ if (response.status === "missing") return { phase: "missing", names: Array.isArray(response.missing) ? response.missing.filter(name => typeof name === "string") : [], draftKept: true };
248
+ if (response.status === "rejected" && response.reason === "same-name-conflict") return { phase: "same-name-conflict", names: Array.isArray(response.conflicts) ? response.conflicts.map(item => item?.name).filter(name => typeof name === "string") : [], draftKept: true };
152
249
  return { phase: "failed", error: String(response.reason ?? response.status ?? "unknown"), draftKept: true };
153
250
  }
154
251
 
@@ -189,7 +286,7 @@ window.__ModuleLoader__.load({
189
286
  for (const name of baseSet) if (!currentSet.has(name)) removed += 1;
190
287
  return { added, removed };
191
288
  };
192
- const skills = count(draft.skills, draft.applied.skills);
289
+ const skills = count(draft.skills.map(skillEntryKeyOf), (draft.applied.skills ?? []).map(skillEntryKeyOf));
193
290
  const mcp = count(draft.mcpServers, draft.applied.mcpServers);
194
291
  return {
195
292
  skillsAdded: skills.added,
@@ -222,10 +319,11 @@ window.__ModuleLoader__.load({
222
319
  * carried for reporting; the CAS base (revision/applied) is kept; preset
223
320
  * metadata never produces an Apply by itself — dirty is recomputed
224
321
  * against the applied sets. */
225
- function draftFromPreset(document, current) {
322
+ function draftFromPreset(document, current, resolveName) {
226
323
  const selection = document?.selection ?? {};
227
324
  const clean = (value) => (Array.isArray(value) ? value.map(String) : []).sort();
228
- const skills = clean(selection.skills);
325
+ const skills = sortSkills((Array.isArray(selection.skills) ? selection.skills : []).map(entry =>
326
+ typeof entry === "string" ? (resolveName?.(entry) ?? entry) : entry));
229
327
  const mcpServers = clean(selection.mcpServers);
230
328
  const unresolvedRefs = Array.isArray(selection.unresolvedRefs) ? [...selection.unresolvedRefs] : [];
231
329
  const applied = current?.applied && Array.isArray(current.applied.skills) && Array.isArray(current.applied.mcpServers)
@@ -237,8 +335,12 @@ window.__ModuleLoader__.load({
237
335
  mcpServers,
238
336
  applied,
239
337
  revision: Number.isSafeInteger(current?.revision) ? current.revision : 0,
338
+ unknownBaseline: current?.unknownBaseline === true,
339
+ overwriteConfirmed: false,
340
+ ...(typeof current?.appliedDigest === "string" ? { appliedDigest: current.appliedDigest } : {}),
341
+ appliedSourceIssue: current?.appliedSourceIssue === null ? null : current?.appliedSourceIssue ?? "receipt",
240
342
  unresolvedRefs,
241
- dirty: !same(skills, [...applied.skills].sort()) || !same(mcpServers, [...applied.mcpServers].sort()),
343
+ dirty: !same(skills.map(skillEntryKeyOf), sortSkills(applied.skills).map(skillEntryKeyOf)) || !same(mcpServers, [...applied.mcpServers].sort()),
242
344
  };
243
345
  }
244
346
 
@@ -260,6 +362,12 @@ window.__ModuleLoader__.load({
260
362
  };
261
363
  }
262
364
 
365
+ /** A tombstone or incomplete document cannot be staged as an empty draft. */
366
+ function loadablePresetOf(response) {
367
+ const document = response?.document;
368
+ return response?.status === "ok" && document && document.deleted !== true && document.tombstone !== true
369
+ && Array.isArray(document.selection?.skills) && Array.isArray(document.selection?.mcpServers) && Array.isArray(document.selection?.unresolvedRefs);
370
+ }
263
371
  /** Preset listing grouped by namespace, sorted by display name. */
264
372
  function groupPresets(payload) {
265
373
  const rows = (Array.isArray(payload?.presets) ? payload.presets : []).map(presetRowOf);
@@ -277,7 +385,9 @@ window.__ModuleLoader__.load({
277
385
  if (!response || typeof response !== "object" || response.error === true) return { kind: "error" };
278
386
  if (response.status === "created" || response.status === "edited") return { kind: response.status, presetId: response.presetId ?? null, revision: response.revision ?? null };
279
387
  if (response.status === "deleted") return { kind: "deleted", revision: response.revision ?? null };
280
- if (response.status === "name-conflict" || response.status === "rename-required") return { kind: "name-conflict", with: response.with ?? null };
388
+ if (response.status === "name-conflict" || response.status === "rename-required") return { kind: "name-conflict", with: response.with ?? null, ...(Number.isSafeInteger(response.withRevision) && response.withRevision > 0 ? { withRevision: response.withRevision } : {}) };
389
+ if (response.status === "source-unavailable") return { kind: "source-unavailable", missing: Array.isArray(response.missing) ? response.missing.filter(name => typeof name === "string") : [] };
390
+ if (["locked", "unreadable", "unsupported", "source-changed", "absent", "deleted", "confirmation-required", "indeterminate"].includes(response.status)) return { kind: response.status };
281
391
  if (response.status === "revision-conflict") return { kind: "revision-conflict" };
282
392
  if (response.status === "exists") return { kind: "exists", presetId: response.presetId ?? null };
283
393
  if (response.status === "no-workspace") return { kind: "no-workspace" };
@@ -289,15 +399,16 @@ window.__ModuleLoader__.load({
289
399
  * failure. */
290
400
  function importFeedbackOf(response) {
291
401
  if (!response || typeof response !== "object" || response.error === true) return { kind: "error" };
292
- if (response.status === "created") {
402
+ if (response.status === "created" || response.status === "edited") {
293
403
  return {
294
- kind: "created",
404
+ kind: response.status,
295
405
  presetId: response.presetId ?? null,
296
406
  bound: Number.isSafeInteger(response.bound?.mcpServers) ? response.bound.mcpServers : 0,
297
407
  unresolved: Number.isSafeInteger(response.bound?.unresolvedRefs) ? response.bound.unresolvedRefs : 0,
298
408
  };
299
409
  }
300
- if (response.status === "name-conflict" || response.status === "rename-required") return { kind: "name-conflict", with: response.with ?? null };
410
+ if (response.status === "name-conflict" || response.status === "rename-required") return { kind: "name-conflict", with: response.with ?? null, ...(Number.isSafeInteger(response.withRevision) && response.withRevision > 0 ? { withRevision: response.withRevision } : {}) };
411
+ if (["locked", "unreadable", "unsupported", "revision-conflict", "confirmation-required", "indeterminate"].includes(response.status)) return { kind: response.status };
301
412
  if (response.status === "rejected") return { kind: "rejected", reason: String(response.reason ?? "rejected") };
302
413
  if (response.status === "no-workspace") return { kind: "no-workspace" };
303
414
  return { kind: "error", status: response.status ?? "unknown" };
@@ -420,11 +531,12 @@ window.__ModuleLoader__.load({
420
531
  * rollback count (no preset record was created), or a missing target
421
532
  * root. */
422
533
  function importConfirmOutcomeOf(response) {
423
- if (!response || typeof response !== "object" || response.error === true) return { kind: "error" };
424
- if (response.status === "created") {
534
+ if (!response || typeof response !== "object") return { kind: "error" };
535
+ if (response.error === true) return { kind: "error", ...(Array.isArray(response.installed) ? { installed: response.installed } : {}), ...(response.presetStatus === "unconfirmed" ? { presetStatus: "unconfirmed" } : {}) };
536
+ if (response.status === "created" || response.status === "edited") {
425
537
  const scopeOf = (value) => (typeof value === "string" && value !== "" ? value : "project");
426
538
  return {
427
- kind: "created",
539
+ kind: response.status,
428
540
  presetId: response.presetId ?? null,
429
541
  bound: Number.isSafeInteger(response.bound?.mcpServers) ? response.bound.mcpServers : 0,
430
542
  unresolved: Number.isSafeInteger(response.bound?.unresolvedRefs)
@@ -449,10 +561,11 @@ window.__ModuleLoader__.load({
449
561
  return { kind: "install-failed", reason: String(response.reason ?? "install-failed"), rolledBack: Number.isSafeInteger(response.rolledBack) && response.rolledBack >= 0 ? response.rolledBack : 0 };
450
562
  }
451
563
  if (response.status === "no-target-root") return { kind: "no-target-root", targetScope: String(response.targetScope ?? "unknown") };
452
- if (response.status === "name-conflict" || response.status === "rename-required") return { kind: "name-conflict", with: response.with ?? null };
564
+ if (response.status === "name-conflict" || response.status === "rename-required") return { kind: "name-conflict", with: response.with ?? null, ...(Number.isSafeInteger(response.withRevision) && response.withRevision > 0 ? { withRevision: response.withRevision } : {}) };
565
+ if (["locked", "unreadable", "unsupported", "revision-conflict", "confirmation-required", "indeterminate", "commit-failed", "write-failed", "preset-unconfirmed"].includes(response.status)) return { kind: response.status, installed: Array.isArray(response.installed) ? response.installed : [], ...(response.presetStatus === "unconfirmed" ? { presetStatus: "unconfirmed" } : {}) };
453
566
  if (response.status === "no-workspace") return { kind: "no-workspace" };
454
567
  if (response.status === "rejected") return { kind: "rejected", reason: String(response.reason ?? "rejected") };
455
- return { kind: "error", status: response.status ?? "unknown" };
568
+ return { kind: "error", status: response.status ?? "unknown", ...(Array.isArray(response.installed) ? { installed: response.installed } : {}), ...(response.presetStatus === "unconfirmed" ? { presetStatus: "unconfirmed" } : {}) };
456
569
  }
457
570
 
458
571
  /** Download filename for the exported package JSON: derived from the
@@ -468,6 +581,8 @@ window.__ModuleLoader__.load({
468
581
  return `${base}.json`;
469
582
  }
470
583
 
584
+ exports.skillIdentityKeyOf = skillIdentityKeyOf;
585
+ exports.skillEntryKeyOf = skillEntryKeyOf;
471
586
  exports.badgeStateOf = badgeStateOf;
472
587
  exports.skillRowOf = skillRowOf;
473
588
  exports.partitionManagerListing = partitionManagerListing;
@@ -476,8 +591,12 @@ window.__ModuleLoader__.load({
476
591
  exports.closeDirtyDraft = closeDirtyDraft;
477
592
  exports.frameRefreshesSession = frameRefreshesSession;
478
593
  exports.draftFromReceipt = draftFromReceipt;
594
+ exports.draftAfterRefresh = draftAfterRefresh;
479
595
  exports.draftToggle = draftToggle;
480
596
  exports.commitOutcomeOf = commitOutcomeOf;
597
+ exports.skillDraftConflicts = skillDraftConflicts;
598
+ exports.resolveSkillName = resolveSkillName;
599
+ exports.skillSourceLabels = skillSourceLabels;
481
600
  exports.convergenceHintOf = convergenceHintOf;
482
601
  exports.groupSkillRows = groupSkillRows;
483
602
  exports.filterSkillRows = filterSkillRows;
@@ -486,6 +605,7 @@ window.__ModuleLoader__.load({
486
605
  exports.draftFromPreset = draftFromPreset;
487
606
  exports.presetRowOf = presetRowOf;
488
607
  exports.groupPresets = groupPresets;
608
+ exports.loadablePresetOf = loadablePresetOf;
489
609
  exports.presetWriteOutcomeOf = presetWriteOutcomeOf;
490
610
  exports.importFeedbackOf = importFeedbackOf;
491
611
  exports.presetExportTextOf = presetExportTextOf;