@treeseed/dsh-chapters 0.1.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.
Files changed (119) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +644 -0
  3. package/cordis.patch.yml +88 -0
  4. package/docs/architecture.md +460 -0
  5. package/docs/contract.md +375 -0
  6. package/docs/development.md +437 -0
  7. package/docs/host-compaction-seam.md +117 -0
  8. package/docs/knowledge-repo.md +730 -0
  9. package/docs/provider.md +98 -0
  10. package/docs/verify.md +200 -0
  11. package/lib/archive.d.ts +88 -0
  12. package/lib/archive.js +114 -0
  13. package/lib/archive.js.map +1 -0
  14. package/lib/arrival.d.ts +23 -0
  15. package/lib/arrival.js +86 -0
  16. package/lib/arrival.js.map +1 -0
  17. package/lib/artifact-query.d.ts +53 -0
  18. package/lib/artifact-query.js +130 -0
  19. package/lib/artifact-query.js.map +1 -0
  20. package/lib/client.js +234 -0
  21. package/lib/client.js.map +1 -0
  22. package/lib/commands.d.ts +28 -0
  23. package/lib/commands.js +305 -0
  24. package/lib/commands.js.map +1 -0
  25. package/lib/compose.d.ts +30 -0
  26. package/lib/compose.js +131 -0
  27. package/lib/compose.js.map +1 -0
  28. package/lib/continue-core.d.ts +153 -0
  29. package/lib/continue-core.js +252 -0
  30. package/lib/continue-core.js.map +1 -0
  31. package/lib/engine-core.d.ts +219 -0
  32. package/lib/engine-core.js +405 -0
  33. package/lib/engine-core.js.map +1 -0
  34. package/lib/engine.d.ts +211 -0
  35. package/lib/engine.js +571 -0
  36. package/lib/engine.js.map +1 -0
  37. package/lib/enrich-queue.d.ts +66 -0
  38. package/lib/enrich-queue.js +100 -0
  39. package/lib/enrich-queue.js.map +1 -0
  40. package/lib/enrich-store.d.ts +31 -0
  41. package/lib/enrich-store.js +103 -0
  42. package/lib/enrich-store.js.map +1 -0
  43. package/lib/enrich-wire.d.ts +73 -0
  44. package/lib/enrich-wire.js +209 -0
  45. package/lib/enrich-wire.js.map +1 -0
  46. package/lib/enrich.d.ts +73 -0
  47. package/lib/enrich.js +150 -0
  48. package/lib/enrich.js.map +1 -0
  49. package/lib/gitops.d.ts +107 -0
  50. package/lib/gitops.js +539 -0
  51. package/lib/gitops.js.map +1 -0
  52. package/lib/index.d.ts +81 -0
  53. package/lib/index.js +305 -0
  54. package/lib/index.js.map +1 -0
  55. package/lib/indexing.d.ts +85 -0
  56. package/lib/indexing.js +212 -0
  57. package/lib/indexing.js.map +1 -0
  58. package/lib/injections.d.ts +66 -0
  59. package/lib/injections.js +83 -0
  60. package/lib/injections.js.map +1 -0
  61. package/lib/notice.d.ts +60 -0
  62. package/lib/notice.js +84 -0
  63. package/lib/notice.js.map +1 -0
  64. package/lib/provider.d.ts +44 -0
  65. package/lib/provider.js +66 -0
  66. package/lib/provider.js.map +1 -0
  67. package/lib/redact.d.ts +24 -0
  68. package/lib/redact.js +85 -0
  69. package/lib/redact.js.map +1 -0
  70. package/lib/registry.d.ts +120 -0
  71. package/lib/registry.js +130 -0
  72. package/lib/registry.js.map +1 -0
  73. package/lib/render.d.ts +40 -0
  74. package/lib/render.js +280 -0
  75. package/lib/render.js.map +1 -0
  76. package/lib/repo.d.ts +78 -0
  77. package/lib/repo.js +184 -0
  78. package/lib/repo.js.map +1 -0
  79. package/lib/rules-commands.d.ts +24 -0
  80. package/lib/rules-commands.js +138 -0
  81. package/lib/rules-commands.js.map +1 -0
  82. package/lib/rules.d.ts +90 -0
  83. package/lib/rules.js +185 -0
  84. package/lib/rules.js.map +1 -0
  85. package/lib/search.d.ts +48 -0
  86. package/lib/search.js +152 -0
  87. package/lib/search.js.map +1 -0
  88. package/lib/signature.d.ts +28 -0
  89. package/lib/signature.js +149 -0
  90. package/lib/signature.js.map +1 -0
  91. package/lib/store.d.ts +130 -0
  92. package/lib/store.js +235 -0
  93. package/lib/store.js.map +1 -0
  94. package/lib/sync.d.ts +183 -0
  95. package/lib/sync.js +719 -0
  96. package/lib/sync.js.map +1 -0
  97. package/lib/tools.d.ts +84 -0
  98. package/lib/tools.js +581 -0
  99. package/lib/tools.js.map +1 -0
  100. package/lib/treedx/client.d.ts +57 -0
  101. package/lib/treedx/client.js +82 -0
  102. package/lib/treedx/client.js.map +1 -0
  103. package/lib/treedx/provider.d.ts +39 -0
  104. package/lib/treedx/provider.js +479 -0
  105. package/lib/treedx/provider.js.map +1 -0
  106. package/lib/treedx/state.d.ts +27 -0
  107. package/lib/treedx/state.js +65 -0
  108. package/lib/treedx/state.js.map +1 -0
  109. package/lib/types.d.ts +100 -0
  110. package/lib/types.js +19 -0
  111. package/lib/types.js.map +1 -0
  112. package/lib/vocabulary.d.ts +41 -0
  113. package/lib/vocabulary.js +118 -0
  114. package/lib/vocabulary.js.map +1 -0
  115. package/package.json +132 -0
  116. package/presets/chapters/agent.cordis.yml +272 -0
  117. package/presets/chapters/preset.yml +3 -0
  118. package/presets/probe-roots/agent.cordis.yml +11 -0
  119. package/presets/probe-roots/preset.yml +3 -0
package/lib/arrival.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Arrival-time artifacting (record architecture.md amendment, 2026-09-19):
3
+ * tool results at or above the floor never get to become context. The blob
4
+ * goes to the content-addressed artifact store; the surface node is replaced
5
+ * IN PLACE (the pruner's proven single-node form: a shadowing event + a
6
+ * replacement append carrying `surfaceOp: replace`) with a stub that cites
7
+ * the path and the query tool. Runs at the head of compactIfNeeded/compactNow,
8
+ * i.e. inside the awaited `agent/pre-step` chain, BEFORE the next request is
9
+ * composed — so the blob is prefilled zero times, ever.
10
+ *
11
+ * A plain factory over structural shims, per house rule (the r28 lesson:
12
+ * the LOGIC must be L0-testable against the real host Session shape).
13
+ */
14
+ import { artifactPath, sha256 } from "./render.js";
15
+ import fs from 'node:fs/promises';
16
+ import path from 'node:path';
17
+ export const ARRIVAL_MARKER = '[dsh:artifact';
18
+ const kb = (n) => n < 1024 ? `${n} B` : `${(n / 1024).toFixed(1)} KB`;
19
+ export async function applyArrivalStubs(session, opts) {
20
+ let stubbed = 0;
21
+ for (const seq of [...session.surface.nodes]) {
22
+ const event = session.eventAt(seq);
23
+ if (event?.type !== 'tool/result')
24
+ continue;
25
+ const data = event.data;
26
+ if (data?.message === undefined)
27
+ continue;
28
+ // REAL shape (measured e2e, 2026-09-19): message.content blocks are
29
+ // { type: 'tool-result', toolCallId, isError, content: [{type:'text',text}] }
30
+ // — the text is nested one level below the wrapper. A unit fixture that
31
+ // invented a flat shape hid this for a whole run; the fake mirrors the
32
+ // real from now on.
33
+ const blocks = Array.isArray(data.message.content) ? data.message.content : [];
34
+ const wrappers = blocks.filter((b) => b.type === 'tool-result');
35
+ if (wrappers.length === 0)
36
+ continue;
37
+ const textOf = (w) => Array.isArray(w.content)
38
+ ? w.content.filter((c) => c.type === 'text').map((c) => c.text ?? '').join('')
39
+ : typeof w.content === 'string' ? w.content : '';
40
+ const text = wrappers.map(textOf).join('');
41
+ if (text.length === 0 || text.includes(ARRIVAL_MARKER))
42
+ continue;
43
+ const tokens = opts.estimate(data.message);
44
+ if (tokens < opts.floorTokens)
45
+ continue;
46
+ const hash = sha256(text);
47
+ const rel = artifactPath(hash);
48
+ const abs = path.join(opts.cwd, opts.storeRoot, rel);
49
+ try {
50
+ await fs.mkdir(path.dirname(abs), { recursive: true, mode: 0o700 });
51
+ // content-addressed: rewriting identical bytes is a no-op by definition
52
+ await fs.writeFile(abs, text, 'utf8');
53
+ }
54
+ catch (error) {
55
+ // the blob stays inline — arrival artifacting is an optimization, and a
56
+ // disk problem must never eat the conversation (loud via detail).
57
+ return { stubbed, detail: `artifact write failed (${String(error?.message ?? error)}); ${stubbed} node(s) stubbed before the failure` };
58
+ }
59
+ const stub = `${ARRIVAL_MARKER} ${hash.slice(0, 12)}] ${kb(text.length)} ≈ ${tokens} tok stored at ${rel} — query it with chapters_artifact (path '${rel}' or the sha): action 'toc' for the heading map with line numbers, 'search' for term hits as small blocks, 'read' for exact line ranges. The full result is NOT elsewhere in this context; re-running the original tool only recreates it.`;
60
+ const stubbedWrappers = wrappers.map((w, i) => ({
61
+ ...w, content: [{ type: 'text', text: i === 0 ? stub : '' }],
62
+ }));
63
+ const message = {
64
+ ...data.message,
65
+ content: [...blocks.filter((b) => b.type !== 'tool-result'), ...stubbedWrappers],
66
+ };
67
+ try {
68
+ session.append('compaction/prune', {
69
+ shadowedRange: { start: seq, end: seq },
70
+ shadowedSeqs: [seq],
71
+ shadowedTokenCount: tokens,
72
+ by: 'dsh-chapters-arrival',
73
+ });
74
+ session.append('tool/result', { ...data, message }, {
75
+ surfaceOp: { op: 'replace', startSeq: seq, endSeq: seq },
76
+ sourceEventSeqs: [seq],
77
+ });
78
+ stubbed++;
79
+ }
80
+ catch (error) {
81
+ return { stubbed, detail: `surface replace refused at seq ${seq} (${String(error?.message ?? error)})` };
82
+ }
83
+ }
84
+ return { stubbed };
85
+ }
86
+ //# sourceMappingURL=arrival.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arrival.js","sourceRoot":"","sources":["../src/arrival.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ export type TocEntry = {
2
+ line: number;
3
+ depth: number;
4
+ title: string;
5
+ };
6
+ /**
7
+ * Resolve a user/model-supplied reference to an absolute artifact path INSIDE
8
+ * the store. Accepts a 64-hex sha, 'artifacts/xx/<sha>.txt', or an absolute
9
+ * path. Throws on anything escaping the store — this tool is read-only, the
10
+ * containment is the whole security story.
11
+ */
12
+ export declare function resolveArtifactPath(storeDir: string, ref: string): string;
13
+ /**
14
+ * Markdown heading map with 1-based line numbers — a 300-page book arrives
15
+ * with its own table of contents (fenced code blocks are skipped so '#
16
+ * comments' inside them never masquerade as headings).
17
+ */
18
+ export declare function buildToc(text: string, maxEntries?: number): {
19
+ entries: TocEntry[];
20
+ truncated: boolean;
21
+ };
22
+ export type SearchBlock = {
23
+ start: number;
24
+ end: number;
25
+ lines: string[];
26
+ };
27
+ /**
28
+ * Case-insensitive query over lines: regex when it compiles, literal phrase
29
+ * otherwise. Matches expand to ±context lines and overlapping windows merge;
30
+ * the pack stops at maxTokens and REPORTS what it left behind.
31
+ */
32
+ export declare function searchArtifact(text: string, query: string, opts?: {
33
+ maxTokens?: number;
34
+ contextLines?: number;
35
+ }): {
36
+ total: number;
37
+ blocks: SearchBlock[];
38
+ truncated: boolean;
39
+ remaining: number;
40
+ };
41
+ /** Exact line window (1-based inclusive), capped per call; bounds reported. */
42
+ export declare function readLines(text: string, offset: number, limit: number, hardCap?: number): {
43
+ start: number;
44
+ end: number;
45
+ of: number;
46
+ lines: string[];
47
+ };
48
+ /** Load an artifact with a size guard; returns text + totals for the pack. */
49
+ export declare function loadArtifact(absPath: string): {
50
+ text: string;
51
+ bytes: number;
52
+ lines: number;
53
+ };
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Artifact query surface (arrival-time artifacting plan, Phase 3): pure,
3
+ * bounded readers over content-addressed artifact files. The point of this
4
+ * module is that a stored blob is USEFUL without ever being loaded whole —
5
+ * toc/search/read are the small-block windows the RLM result calls an
6
+ * 'external environment', implemented with files and line numbers instead
7
+ * of a code sandbox. Every function is deterministic and refuses rather
8
+ * than silently clipping (bounds reported, never applied by truncation
9
+ * without a flag).
10
+ */
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ /** Rough token estimate consistent with the est-token convention elsewhere (chars/4). */
14
+ const estTokens = (s) => Math.ceil(s.length / 4);
15
+ /**
16
+ * Resolve a user/model-supplied reference to an absolute artifact path INSIDE
17
+ * the store. Accepts a 64-hex sha, 'artifacts/xx/<sha>.txt', or an absolute
18
+ * path. Throws on anything escaping the store — this tool is read-only, the
19
+ * containment is the whole security story.
20
+ */
21
+ export function resolveArtifactPath(storeDir, ref) {
22
+ const clean = ref.trim();
23
+ if (/^[0-9a-f]{64}$/.test(clean))
24
+ return path.join(storeDir, 'artifacts', clean.slice(0, 2), `${clean}.txt`);
25
+ const abs = path.isAbsolute(clean) ? path.normalize(clean) : path.normalize(path.join(storeDir, clean));
26
+ const root = path.resolve(storeDir);
27
+ if (abs !== root && !abs.startsWith(root + path.sep))
28
+ throw new Error(`artifact reference escapes the store: ${ref}`);
29
+ return abs;
30
+ }
31
+ /**
32
+ * Markdown heading map with 1-based line numbers — a 300-page book arrives
33
+ * with its own table of contents (fenced code blocks are skipped so '#
34
+ * comments' inside them never masquerade as headings).
35
+ */
36
+ export function buildToc(text, maxEntries = 400) {
37
+ const entries = [];
38
+ let inFence = false;
39
+ let fenceMarker = '';
40
+ let lineNo = 0;
41
+ for (const line of text.split('\n')) {
42
+ lineNo++;
43
+ const fence = /^\s{0,3}(`{3,}|~{3,})/.test(line);
44
+ if (fence) {
45
+ const marker = line.trim().slice(0, 3);
46
+ if (!inFence) {
47
+ inFence = true;
48
+ fenceMarker = marker[0];
49
+ }
50
+ else if (marker[0] === fenceMarker) {
51
+ inFence = false;
52
+ }
53
+ continue;
54
+ }
55
+ if (inFence)
56
+ continue;
57
+ const m = /^(#{1,6})\s+(.*)$/.exec(line);
58
+ if (m === null)
59
+ continue;
60
+ if (entries.length >= maxEntries)
61
+ return { entries, truncated: true };
62
+ entries.push({ line: lineNo, depth: m[1].length, title: m[2].trim().slice(0, 120) });
63
+ }
64
+ return { entries, truncated: false };
65
+ }
66
+ /**
67
+ * Case-insensitive query over lines: regex when it compiles, literal phrase
68
+ * otherwise. Matches expand to ±context lines and overlapping windows merge;
69
+ * the pack stops at maxTokens and REPORTS what it left behind.
70
+ */
71
+ export function searchArtifact(text, query, opts = {}) {
72
+ const maxTokens = opts.maxTokens ?? 400;
73
+ const context = opts.contextLines ?? 2;
74
+ const lines = text.split('\n');
75
+ let re = null;
76
+ try {
77
+ re = new RegExp(query, 'i');
78
+ }
79
+ catch {
80
+ re = null;
81
+ }
82
+ const hit = (l) => re !== null ? re.test(l) : l.toLowerCase().includes(query.trim().toLowerCase());
83
+ const matched = [];
84
+ for (let i = 0; i < lines.length; i++)
85
+ if (hit(lines[i]))
86
+ matched.push(i);
87
+ const blocks = [];
88
+ let used = 0;
89
+ for (const idx of matched) {
90
+ const start = Math.max(0, idx - context);
91
+ const end = Math.min(lines.length - 1, idx + context);
92
+ const last = blocks[blocks.length - 1];
93
+ if (last !== undefined && start <= last.end + 1) {
94
+ if (end > last.end) {
95
+ const add = lines.slice(last.end + 1, end + 1).map((l, k) => `${last.end + 2 + k}: ${l.slice(0, 400)}`).join('\n');
96
+ const addTokens = estTokens(add);
97
+ if (used + addTokens > maxTokens)
98
+ break;
99
+ last.lines.push(...lines.slice(last.end + 1, end + 1).map((l, k) => `${last.end + 2 + k}: ${l.slice(0, 400)}`));
100
+ last.end = end;
101
+ used += addTokens;
102
+ }
103
+ continue;
104
+ }
105
+ const chunk = lines.slice(start, end + 1).map((l, k) => `${start + k + 1}: ${l.slice(0, 400)}`);
106
+ const tokens = estTokens(chunk.join('\n'));
107
+ if (used + tokens > maxTokens)
108
+ break;
109
+ blocks.push({ start: start + 1, end: end + 1, lines: chunk });
110
+ used += tokens;
111
+ }
112
+ const lastIncluded = blocks.length === 0 ? -1 : blocks[blocks.length - 1].end - 1;
113
+ const remaining = matched.filter((i) => i > lastIncluded).length;
114
+ return { total: matched.length, blocks, truncated: remaining > 0, remaining };
115
+ }
116
+ /** Exact line window (1-based inclusive), capped per call; bounds reported. */
117
+ export function readLines(text, offset, limit, hardCap = 400) {
118
+ const lines = text.split('\n');
119
+ const start = Math.max(1, Math.floor(offset));
120
+ const want = Math.max(1, Math.floor(limit));
121
+ const take = Math.min(want, hardCap);
122
+ const end = Math.min(lines.length, start + take - 1);
123
+ return { start, end, of: lines.length, lines: lines.slice(start - 1, end).map((l, k) => `${start + k}: ${l.slice(0, 1000)}`) };
124
+ }
125
+ /** Load an artifact with a size guard; returns text + totals for the pack. */
126
+ export function loadArtifact(absPath) {
127
+ const st = fs.statSync(absPath);
128
+ return { text: fs.readFileSync(absPath, 'utf8'), bytes: st.size, lines: 0 };
129
+ }
130
+ //# sourceMappingURL=artifact-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-query.js","sourceRoot":"","sources":["../src/artifact-query.ts"],"names":[],"mappings":"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"}
package/lib/client.js ADDED
@@ -0,0 +1,234 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@treeseed/dsh-chapters",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ //#region src/client/index.ts
10
+ /**
11
+ * dsh-chapters client entry: the per-message fork action.
12
+ *
13
+ * Registers one button into the assistant action row
14
+ * (`conversation.chat.assistant-actions` — the slot the official feedback
15
+ * package populates beside copy/branch). Clicking it executes `/chapters-fork`
16
+ * through `remote.commands` — the exact wire the composer's input line rides —
17
+ * then SWITCHES the app to the fresh branch:
18
+ *
19
+ * 1. `sessions.open(child)` — the client sessions service carries open() on
20
+ * its prototype (Playwright-verified against this host; a keys-dump alone
21
+ * hid it, which is why an earlier round wrongly gave up on this path);
22
+ * 2. fallback: click the branch's sidebar row after expanding collapsed
23
+ * groups (plugin children can render under collapsed 'Ungrouped' heads);
24
+ * 3. last resort: an inline note — the branch is durable either way.
25
+ *
26
+ * There is deliberately NO second implementation of fork logic here: the
27
+ * button is a mouth for the host command (watermark, segmentation, budget,
28
+ * titles, refusal-with-numbers all server-side, all tested). The purity gate
29
+ * in tsdown.client.config.ts keeps this file cross-plugin-free.
30
+ *
31
+ * @module dsh-chapters/src/client
32
+ */
33
+ const inject = [
34
+ "slots",
35
+ "remote",
36
+ "remote.commands",
37
+ "sessions"
38
+ ];
39
+ /** Pull the child id and branch title out of the command's durable texts. */
40
+ const CHILD_ID = /is session (ch-[\w-]+)/;
41
+ const BRANCH_TITLE = /branch \u201C([^\u201D]+)\u201D is session/;
42
+ function openByService(sessions, sessionId) {
43
+ return (async () => {
44
+ const svc = sessions;
45
+ if (typeof svc?.open !== "function") return false;
46
+ try {
47
+ await svc.refresh?.();
48
+ } catch {}
49
+ const started = Date.now();
50
+ for (;;) try {
51
+ svc.open(sessionId);
52
+ return true;
53
+ } catch {
54
+ if (Date.now() - started > 4e3) return false;
55
+ await new Promise((r) => setTimeout(r, 250));
56
+ }
57
+ })();
58
+ }
59
+ /**
60
+ * Fallback path: expand collapsed groups / show-more buttons, then click the
61
+ * newest treeitem whose label contains the branch title (newest-updated sorts
62
+ * first, so the first match is the fresh branch).
63
+ */
64
+ async function openBranchRow(title, budgetMs = 4e3) {
65
+ if (typeof document === "undefined") return false;
66
+ const needle = title.toLowerCase();
67
+ const tryClick = () => {
68
+ const hit = Array.from(document.querySelectorAll("[role=\"treeitem\"]")).find((row) => (row.textContent ?? "").toLowerCase().includes(needle));
69
+ if (hit === void 0) return false;
70
+ hit.click();
71
+ return true;
72
+ };
73
+ const clickMore = () => {
74
+ const buttons = Array.from(document.querySelectorAll("button")).filter((b) => /more sessions|ungrouped/i.test(b.textContent ?? "") && b.getAttribute("aria-expanded") !== "true");
75
+ for (const b of buttons) b.click();
76
+ return buttons.length;
77
+ };
78
+ const started = Date.now();
79
+ let expansions = 0;
80
+ while (Date.now() - started < budgetMs) {
81
+ if (tryClick()) return true;
82
+ if (expansions < 4 && clickMore() > 0) {
83
+ expansions += 1;
84
+ await new Promise((r) => setTimeout(r, 250));
85
+ continue;
86
+ }
87
+ await new Promise((resolve) => setTimeout(resolve, 150));
88
+ }
89
+ return tryClick();
90
+ }
91
+ function makeForker(remote, sessions) {
92
+ return async function fork(sessionId) {
93
+ try {
94
+ if (remote.commands?.execute === void 0) return {
95
+ ok: false,
96
+ message: "commands remote unavailable"
97
+ };
98
+ const res = await remote.commands.execute(sessionId, "/chapters-fork", []);
99
+ if (res.ok !== true) return {
100
+ ok: false,
101
+ message: (res.error?.message ?? "the fork call failed").slice(0, 90)
102
+ };
103
+ const result = res.value?.result;
104
+ const text = typeof result?.text === "string" ? result.text : "";
105
+ if (result?.kind !== "success") return {
106
+ ok: false,
107
+ message: (text || "the fork was refused — see the command result").slice(0, 90)
108
+ };
109
+ const child = CHILD_ID.exec(text)?.[1];
110
+ if (child === void 0) return {
111
+ ok: true,
112
+ message: "forked — the branch is appearing in the sidebar"
113
+ };
114
+ if (await openByService(sessions, child)) return {
115
+ ok: true,
116
+ message: ""
117
+ };
118
+ const title = BRANCH_TITLE.exec(text)?.[1];
119
+ if (title !== void 0 && await openBranchRow(title)) return {
120
+ ok: true,
121
+ message: ""
122
+ };
123
+ return {
124
+ ok: true,
125
+ message: "forked — open the branch in the sidebar"
126
+ };
127
+ } catch (error) {
128
+ return {
129
+ ok: false,
130
+ message: String(error?.message ?? error).slice(0, 90)
131
+ };
132
+ }
133
+ };
134
+ }
135
+ /**
136
+ * Style parity with the native IconActions row: rules copied verbatim from the
137
+ * host's MessageIconActions module (`._xzv4MW_action` in dsh-client-ui-chat's
138
+ * compiled client + the feedback package's :disabled) — same metrics, tokens,
139
+ * hover. Update this string if the host restyles its action row.
140
+ *
141
+ * The final rule hides the native branch action IN THE MESSAGE ROW only
142
+ * (user-approved cosmetic decision, docs/architecture.md): built-in chrome, so
143
+ * the selector is its accessible label scoped to action-row buttons; both
144
+ * locale spellings covered. If the host rewords the label the button simply
145
+ * reappears — fails open, visibly.
146
+ */
147
+ const CSS = [
148
+ ".dsh-chapters_forkAction{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}",
149
+ ".dsh-chapters_forkAction svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px))}",
150
+ ".dsh-chapters_forkAction:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}",
151
+ ".dsh-chapters_forkAction:disabled{cursor:default;opacity:.4}",
152
+ ".dsh-chapters_forkNote{color:var(--dsw-alias-label-tertiary);padding-left:4px;font-size:13px;line-height:20px}",
153
+ "button[class*=\"_action\"][aria-label=\"Branch into a new conversation\"],button[class*=\"_action\"][aria-label=\"在新对话中分支\"]{display:none !important}"
154
+ ].join("\n");
155
+ const CSS_TAG_ID = "dsh-chapters/assistant-action.css";
156
+ function injectStyles() {
157
+ try {
158
+ if (typeof document === "undefined" || document.querySelector(`style[data-plugin-css=${JSON.stringify(CSS_TAG_ID)}]`) !== null) return;
159
+ const tag = document.createElement("style");
160
+ tag.dataset.plugin = "dsh-chapters";
161
+ tag.dataset.pluginCss = CSS_TAG_ID;
162
+ tag.textContent = CSS;
163
+ document.head.appendChild(tag);
164
+ } catch {}
165
+ }
166
+ function ChaptersForkAction({ fork }) {
167
+ const [busy, setBusy] = (0, react.useState)(false);
168
+ const [note, setNote] = (0, react.useState)(null);
169
+ const alive = (0, react.useRef)(true);
170
+ (0, react.useEffect)(() => () => {
171
+ alive.current = false;
172
+ }, []);
173
+ const onClick = (0, react.useCallback)(() => {
174
+ if (busy) return;
175
+ setBusy(true);
176
+ setNote(null);
177
+ fork().then((result) => {
178
+ if (!alive.current) return;
179
+ setBusy(false);
180
+ setNote(result.message === "" ? null : result.message);
181
+ setTimeout(() => {
182
+ if (alive.current) setNote(null);
183
+ }, 8e3);
184
+ });
185
+ }, [busy, fork]);
186
+ const button = (0, react.createElement)("button", {
187
+ type: "button",
188
+ "aria-label": "Fork with chapters",
189
+ disabled: busy,
190
+ onClick,
191
+ className: "dsh-chapters_forkAction"
192
+ }, (0, react.createElement)("svg", {
193
+ viewBox: "0 0 16 16",
194
+ width: "15",
195
+ height: "15",
196
+ fill: "currentColor",
197
+ "aria-hidden": "true"
198
+ }, (0, react.createElement)("path", { d: "M5 3.25a2.25 2.25 0 1 0-1.5 2.12v5.26a2.25 2.25 0 1 0 1.5 0V9h5a2.25 2.25 0 0 0 2.25-2.25v-1.4a2.25 2.25 0 1 0-1.5 0v1.4A.75.75 0 0 1 10 7.5H5V5.37A2.25 2.25 0 0 0 5 3.25Z" })));
199
+ return (0, react.createElement)("span", { style: {
200
+ display: "inline-flex",
201
+ alignItems: "center",
202
+ gap: "4px"
203
+ } }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
204
+ label: "Fork with chapters",
205
+ side: "bottom",
206
+ children: button
207
+ }), note !== null ? (0, react.createElement)("span", { className: "dsh-chapters_forkNote" }, note) : null);
208
+ }
209
+ function apply(ctx) {
210
+ injectStyles();
211
+ const slots = ctx.slots;
212
+ const remote = ctx.get("remote") ?? {};
213
+ let sessions;
214
+ try {
215
+ sessions = ctx.get("sessions");
216
+ } catch {
217
+ sessions = void 0;
218
+ }
219
+ const fork = makeForker(remote, sessions);
220
+ slots.inject("conversation.chat.assistant-actions", () => slots.register({
221
+ name: "conversation.chat.assistant-actions",
222
+ id: "chapters-fork",
223
+ order: 30,
224
+ inject: (sessionId) => ({ fork: () => fork(sessionId) })
225
+ }, ChaptersForkAction));
226
+ }
227
+ //#endregion
228
+ exports.apply = apply;
229
+ exports.inject = inject;
230
+ return module.exports;
231
+ }
232
+ });
233
+
234
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":["useState","useRef","useCallback","h","Tooltip"],"sources":["../src/client/index.ts"],"sourcesContent":["/**\n * dsh-chapters client entry: the per-message fork action.\n *\n * Registers one button into the assistant action row\n * (`conversation.chat.assistant-actions` — the slot the official feedback\n * package populates beside copy/branch). Clicking it executes `/chapters-fork`\n * through `remote.commands` — the exact wire the composer's input line rides —\n * then SWITCHES the app to the fresh branch:\n *\n * 1. `sessions.open(child)` — the client sessions service carries open() on\n * its prototype (Playwright-verified against this host; a keys-dump alone\n * hid it, which is why an earlier round wrongly gave up on this path);\n * 2. fallback: click the branch's sidebar row after expanding collapsed\n * groups (plugin children can render under collapsed 'Ungrouped' heads);\n * 3. last resort: an inline note — the branch is durable either way.\n *\n * There is deliberately NO second implementation of fork logic here: the\n * button is a mouth for the host command (watermark, segmentation, budget,\n * titles, refusal-with-numbers all server-side, all tested). The purity gate\n * in tsdown.client.config.ts keeps this file cross-plugin-free.\n *\n * @module dsh-chapters/src/client\n */\n\nimport { createElement as h, useCallback, useEffect, useRef, useState, type ReactElement } from 'react'\n// The host's own tooltip bubble — a platform module served by the loader table,\n// external like react. Every action in this row labels through it.\nimport { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'\n\n/** Structural slice of the aggregated remote service (@deepseek-ai/dsh-api-remotes).\n * `remote.commands` must ALSO appear in inject — the federation gates each\n * sub-service individually (the live feedback package declares\n * remote.messageFeedback beside remote for exactly this reason). */\ninterface ClientRemote {\n commands?: {\n execute(sessionId: string, line: string, attachments: readonly unknown[]): Promise<{\n ok?: boolean\n value?: { commandId?: string; result?: { kind?: string; text?: string } }\n error?: { code?: string; message?: string }\n }>\n }\n}\n\ninterface ClientSlots {\n inject(name: string, factory: () => unknown): void\n register(registration: Record<string, unknown>, component: unknown): unknown\n}\n\n/** The client sessions service (dsh-client-runtime): open() lives on the\n * prototype; everything else on the surface stays untrusted and unused. */\ninterface ClientSessions {\n open(sessionId: string): unknown\n}\n\ninterface Ctx {\n slots: ClientSlots\n get(name: string): unknown\n}\n\nexport const inject = ['slots', 'remote', 'remote.commands', 'sessions'] as const\n\n/** Pull the child id and branch title out of the command's durable texts. */\nconst CHILD_ID = /is session (ch-[\\w-]+)/\nconst BRANCH_TITLE = /branch \\u201C([^\\u201D]+)\\u201D is session/\n\nfunction openByService(sessions: ClientSessions | undefined, sessionId: string): Promise<boolean> {\n return (async () => {\n const svc = sessions as unknown as { open?: (id: string) => unknown; refresh?: () => unknown } | undefined\n if (typeof svc?.open !== 'function') return false\n try { await svc.refresh?.() } catch { /* refresh best-effort */ }\n // The store must know the session before open() resolves it; a child\n // created seconds ago is only server-known until refresh lands. Retry\n // open briefly across that window.\n const started = Date.now()\n for (;;) {\n try { svc.open(sessionId); return true } catch {\n if (Date.now() - started > 4_000) return false\n await new Promise((r) => setTimeout(r, 250))\n }\n }\n })()\n}\n\n/**\n * Fallback path: expand collapsed groups / show-more buttons, then click the\n * newest treeitem whose label contains the branch title (newest-updated sorts\n * first, so the first match is the fresh branch).\n */\nasync function openBranchRow(title: string, budgetMs = 4_000): Promise<boolean> {\n if (typeof document === 'undefined') return false\n const needle = title.toLowerCase()\n const tryClick = (): boolean => {\n const rows = Array.from(document.querySelectorAll<HTMLElement>('[role=\"treeitem\"]'))\n const hit = rows.find((row) => (row.textContent ?? '').toLowerCase().includes(needle))\n if (hit === undefined) return false\n hit.click()\n return true\n }\n const clickMore = (): number => {\n const buttons = Array.from(document.querySelectorAll<HTMLElement>('button'))\n .filter((b) => /more sessions|ungrouped/i.test(b.textContent ?? '') && b.getAttribute('aria-expanded') !== 'true')\n for (const b of buttons) b.click()\n return buttons.length\n }\n const started = Date.now()\n let expansions = 0\n while (Date.now() - started < budgetMs) {\n if (tryClick()) return true\n if (expansions < 4 && clickMore() > 0) { expansions += 1; await new Promise((r) => setTimeout(r, 250)); continue }\n await new Promise((resolve) => setTimeout(resolve, 150))\n }\n return tryClick()\n}\n\nfunction makeForker(remote: ClientRemote, sessions: ClientSessions | undefined) {\n return async function fork(sessionId: string): Promise<{ ok: boolean; message: string }> {\n try {\n if (remote.commands?.execute === undefined) return { ok: false, message: 'commands remote unavailable' }\n const res = await remote.commands.execute(sessionId, '/chapters-fork', [])\n if (res.ok !== true) return { ok: false, message: (res.error?.message ?? 'the fork call failed').slice(0, 90) }\n const result = res.value?.result\n const text = typeof result?.text === 'string' ? result.text : ''\n if (result?.kind !== 'success') return { ok: false, message: (text || 'the fork was refused — see the command result').slice(0, 90) }\n const child = CHILD_ID.exec(text)?.[1]\n if (child === undefined) return { ok: true, message: 'forked — the branch is appearing in the sidebar' }\n const opened = await openByService(sessions, child)\n if (opened) return { ok: true, message: '' }\n const title = BRANCH_TITLE.exec(text)?.[1]\n if (title !== undefined && await openBranchRow(title)) return { ok: true, message: '' }\n return { ok: true, message: 'forked — open the branch in the sidebar' }\n } catch (error) {\n return { ok: false, message: String((error as Error)?.message ?? error).slice(0, 90) }\n }\n }\n}\n\n/** Props composed by the slot framework: owner (`messageId`) + our injected verb. */\ninterface ForkActionProps {\n messageId: string\n fork: () => Promise<{ ok: boolean; message: string }>\n}\n\n/**\n * Style parity with the native IconActions row: rules copied verbatim from the\n * host's MessageIconActions module (`._xzv4MW_action` in dsh-client-ui-chat's\n * compiled client + the feedback package's :disabled) — same metrics, tokens,\n * hover. Update this string if the host restyles its action row.\n *\n * The final rule hides the native branch action IN THE MESSAGE ROW only\n * (user-approved cosmetic decision, docs/architecture.md): built-in chrome, so\n * the selector is its accessible label scoped to action-row buttons; both\n * locale spellings covered. If the host rewords the label the button simply\n * reappears — fails open, visibly.\n */\nconst CSS = [\n '.dsh-chapters_forkAction{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}',\n '.dsh-chapters_forkAction svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px))}',\n '.dsh-chapters_forkAction:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}',\n '.dsh-chapters_forkAction:disabled{cursor:default;opacity:.4}',\n '.dsh-chapters_forkNote{color:var(--dsw-alias-label-tertiary);padding-left:4px;font-size:13px;line-height:20px}',\n 'button[class*=\"_action\"][aria-label=\"Branch into a new conversation\"],button[class*=\"_action\"][aria-label=\"\\u5728\\u65b0\\u5bf9\\u8bdd\\u4e2d\\u5206\\u652f\"]{display:none !important}',\n].join('\\n')\nconst CSS_TAG_ID = 'dsh-chapters/assistant-action.css'\n\nfunction injectStyles(): void {\n try {\n if (typeof document === 'undefined' || document.querySelector(`style[data-plugin-css=${JSON.stringify(CSS_TAG_ID)}]`) !== null) return\n const tag = document.createElement('style')\n tag.dataset.plugin = 'dsh-chapters'\n tag.dataset.pluginCss = CSS_TAG_ID\n tag.textContent = CSS\n document.head.appendChild(tag)\n } catch { /* styling is cosmetic; the action still works without it */ }\n}\n\nfunction ChaptersForkAction({ fork }: ForkActionProps) {\n const [busy, setBusy] = useState(false)\n const [note, setNote] = useState<string | null>(null)\n const alive = useRef(true)\n useEffect(() => () => { alive.current = false }, [])\n const onClick = useCallback(() => {\n if (busy) return\n setBusy(true)\n setNote(null)\n void fork().then((result) => {\n if (!alive.current) return\n setBusy(false)\n setNote(result.message === '' ? null : result.message)\n setTimeout(() => { if (alive.current) setNote(null) }, 8000)\n })\n }, [busy, fork])\n const button = h('button', {\n type: 'button',\n 'aria-label': 'Fork with chapters',\n disabled: busy,\n onClick,\n className: 'dsh-chapters_forkAction',\n },\n h('svg', { viewBox: '0 0 16 16', width: '15', height: '15', fill: 'currentColor', 'aria-hidden': 'true' },\n h('path', { d: 'M5 3.25a2.25 2.25 0 1 0-1.5 2.12v5.26a2.25 2.25 0 1 0 1.5 0V9h5a2.25 2.25 0 0 0 2.25-2.25v-1.4a2.25 2.25 0 1 0-1.5 0v1.4A.75.75 0 0 1 10 7.5H5V5.37A2.25 2.25 0 0 0 5 3.25Z' })))\n return h('span', { style: { display: 'inline-flex', alignItems: 'center', gap: '4px' } },\n h(Tooltip, { label: 'Fork with chapters', side: 'bottom' as const, children: button as ReactElement<Record<string, unknown>> }),\n note !== null\n ? h('span', { className: 'dsh-chapters_forkNote' }, note)\n : null)\n}\n\nexport function apply(ctx: Ctx): void {\n injectStyles()\n const slots = ctx.slots\n const remote = (ctx.get('remote') ?? {}) as ClientRemote\n let sessions: ClientSessions | undefined\n try { sessions = ctx.get('sessions') as ClientSessions | undefined } catch { sessions = undefined }\n // No early return on missing descriptors: the button registers regardless\n // and the click reports the gap. A missing button is worse than a button\n // that explains itself.\n const fork = makeForker(remote, sessions)\n slots.inject('conversation.chat.assistant-actions', () => slots.register({\n name: 'conversation.chat.assistant-actions',\n id: 'chapters-fork',\n order: 30,\n inject: (sessionId: string) => ({ fork: () => fork(sessionId) }),\n }, ChaptersForkAction))\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2DA,MAAa,SAAS;GAAC;GAAS;GAAU;GAAmB;EAAU;;EAGvE,MAAM,WAAW;EACjB,MAAM,eAAe;EAErB,SAAS,cAAc,UAAsC,WAAqC;GAChG,QAAQ,YAAY;IAClB,MAAM,MAAM;IACZ,IAAI,OAAO,KAAK,SAAS,YAAY,OAAO;IAC5C,IAAI;KAAE,MAAM,IAAI,UAAU;IAAE,QAAQ,CAA4B;IAIhE,MAAM,UAAU,KAAK,IAAI;IACzB,SACE,IAAI;KAAE,IAAI,KAAK,SAAS;KAAG,OAAO;IAAK,QAAQ;KAC7C,IAAI,KAAK,IAAI,IAAI,UAAU,KAAO,OAAO;KACzC,MAAM,IAAI,SAAS,MAAM,WAAW,GAAG,GAAG,CAAC;IAC7C;GAEJ,EAAA,CAAG;EACL;;;;;;EAOA,eAAe,cAAc,OAAe,WAAW,KAAyB;GAC9E,IAAI,OAAO,aAAa,aAAa,OAAO;GAC5C,MAAM,SAAS,MAAM,YAAY;GACjC,MAAM,iBAA0B;IAE9B,MAAM,MADO,MAAM,KAAK,SAAS,iBAA8B,qBAAmB,CACnE,CAAC,CAAC,MAAM,SAAS,IAAI,eAAe,GAAA,CAAI,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC;IACrF,IAAI,QAAQ,KAAA,GAAW,OAAO;IAC9B,IAAI,MAAM;IACV,OAAO;GACT;GACA,MAAM,kBAA0B;IAC9B,MAAM,UAAU,MAAM,KAAK,SAAS,iBAA8B,QAAQ,CAAC,CAAC,CACzE,QAAQ,MAAM,2BAA2B,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,aAAa,eAAe,MAAM,MAAM;IACnH,KAAK,MAAM,KAAK,SAAS,EAAE,MAAM;IACjC,OAAO,QAAQ;GACjB;GACA,MAAM,UAAU,KAAK,IAAI;GACzB,IAAI,aAAa;GACjB,OAAO,KAAK,IAAI,IAAI,UAAU,UAAU;IACtC,IAAI,SAAS,GAAG,OAAO;IACvB,IAAI,aAAa,KAAK,UAAU,IAAI,GAAG;KAAE,cAAc;KAAG,MAAM,IAAI,SAAS,MAAM,WAAW,GAAG,GAAG,CAAC;KAAG;IAAS;IACjH,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;GACzD;GACA,OAAO,SAAS;EAClB;EAEA,SAAS,WAAW,QAAsB,UAAsC;GAC9E,OAAO,eAAe,KAAK,WAA8D;IACvF,IAAI;KACF,IAAI,OAAO,UAAU,YAAY,KAAA,GAAW,OAAO;MAAE,IAAI;MAAO,SAAS;KAA8B;KACvG,MAAM,MAAM,MAAM,OAAO,SAAS,QAAQ,WAAW,kBAAkB,CAAC,CAAC;KACzE,IAAI,IAAI,OAAO,MAAM,OAAO;MAAE,IAAI;MAAO,UAAU,IAAI,OAAO,WAAW,uBAAA,CAAwB,MAAM,GAAG,EAAE;KAAE;KAC9G,MAAM,SAAS,IAAI,OAAO;KAC1B,MAAM,OAAO,OAAO,QAAQ,SAAS,WAAW,OAAO,OAAO;KAC9D,IAAI,QAAQ,SAAS,WAAW,OAAO;MAAE,IAAI;MAAO,UAAU,QAAQ,gDAAA,CAAiD,MAAM,GAAG,EAAE;KAAE;KACpI,MAAM,QAAQ,SAAS,KAAK,IAAI,CAAC,GAAG;KACpC,IAAI,UAAU,KAAA,GAAW,OAAO;MAAE,IAAI;MAAM,SAAS;KAAkD;KAEvG,IAAI,MADiB,cAAc,UAAU,KAAK,GACtC,OAAO;MAAE,IAAI;MAAM,SAAS;KAAG;KAC3C,MAAM,QAAQ,aAAa,KAAK,IAAI,CAAC,GAAG;KACxC,IAAI,UAAU,KAAA,KAAa,MAAM,cAAc,KAAK,GAAG,OAAO;MAAE,IAAI;MAAM,SAAS;KAAG;KACtF,OAAO;MAAE,IAAI;MAAM,SAAS;KAA0C;IACxE,SAAS,OAAO;KACd,OAAO;MAAE,IAAI;MAAO,SAAS,OAAQ,OAAiB,WAAW,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;KAAE;IACvF;GACF;EACF;;;;;;;;;;;;;EAoBA,MAAM,MAAM;GACV;GACA;GACA;GACA;GACA;GACA;EACF,CAAC,CAAC,KAAK,IAAI;EACX,MAAM,aAAa;EAEnB,SAAS,eAAqB;GAC5B,IAAI;IACF,IAAI,OAAO,aAAa,eAAe,SAAS,cAAc,yBAAyB,KAAK,UAAU,UAAU,EAAE,EAAE,MAAM,MAAM;IAChI,MAAM,MAAM,SAAS,cAAc,OAAO;IAC1C,IAAI,QAAQ,SAAS;IACrB,IAAI,QAAQ,YAAY;IACxB,IAAI,cAAc;IAClB,SAAS,KAAK,YAAY,GAAG;GAC/B,QAAQ,CAA+D;EACzE;EAEA,SAAS,mBAAmB,EAAE,QAAyB;GACrD,MAAM,CAAC,MAAM,YAAA,GAAWA,MAAAA,SAAAA,CAAS,KAAK;GACtC,MAAM,CAAC,MAAM,YAAA,GAAWA,MAAAA,SAAAA,CAAwB,IAAI;GACpD,MAAM,SAAA,GAAQC,MAAAA,OAAAA,CAAO,IAAI;GACzB,CAAA,GAAA,MAAA,UAAA,aAAsB;IAAE,MAAM,UAAU;GAAM,GAAG,CAAC,CAAC;GACnD,MAAM,WAAA,GAAUC,MAAAA,YAAAA,OAAkB;IAChC,IAAI,MAAM;IACV,QAAQ,IAAI;IACZ,QAAQ,IAAI;IACZ,KAAU,CAAC,CAAC,MAAM,WAAW;KAC3B,IAAI,CAAC,MAAM,SAAS;KACpB,QAAQ,KAAK;KACb,QAAQ,OAAO,YAAY,KAAK,OAAO,OAAO,OAAO;KACrD,iBAAiB;MAAE,IAAI,MAAM,SAAS,QAAQ,IAAI;KAAE,GAAG,GAAI;IAC7D,CAAC;GACH,GAAG,CAAC,MAAM,IAAI,CAAC;GACf,MAAM,UAAA,GAASC,MAAAA,cAAAA,CAAE,UAAU;IACzB,MAAM;IACN,cAAc;IACd,UAAU;IACV;IACA,WAAW;GACb,IAAA,GACEA,MAAAA,cAAAA,CAAE,OAAO;IAAE,SAAS;IAAa,OAAO;IAAM,QAAQ;IAAM,MAAM;IAAgB,eAAe;GAAO,IAAA,GACtGA,MAAAA,cAAAA,CAAE,QAAQ,EAAE,GAAG,8KAA8K,CAAC,CAAC,CAAC;GACpM,QAAA,GAAOA,MAAAA,cAAAA,CAAE,QAAQ,EAAE,OAAO;IAAE,SAAS;IAAe,YAAY;IAAU,KAAK;GAAM,EAAE,IAAA,GACrFA,MAAAA,cAAAA,CAAEC,sCAAAA,SAAS;IAAE,OAAO;IAAsB,MAAM;IAAmB,UAAU;GAAgD,CAAC,GAC9H,SAAS,QAAA,GACLD,MAAAA,cAAAA,CAAE,QAAQ,EAAE,WAAW,wBAAwB,GAAG,IAAI,IACtD,IAAI;EACZ;EAEA,SAAgB,MAAM,KAAgB;GACpC,aAAa;GACb,MAAM,QAAQ,IAAI;GAClB,MAAM,SAAU,IAAI,IAAI,QAAQ,KAAK,CAAC;GACtC,IAAI;GACJ,IAAI;IAAE,WAAW,IAAI,IAAI,UAAU;GAAgC,QAAQ;IAAE,WAAW,KAAA;GAAU;GAIlG,MAAM,OAAO,WAAW,QAAQ,QAAQ;GACxC,MAAM,OAAO,6CAA6C,MAAM,SAAS;IACvE,MAAM;IACN,IAAI;IACJ,OAAO;IACP,SAAS,eAAuB,EAAE,YAAY,KAAK,SAAS,EAAE;GAChE,GAAG,kBAAkB,CAAC;EACxB"}
@@ -0,0 +1,28 @@
1
+ import { type SyncScheduler } from './sync.ts';
2
+ import type { DomainLike } from './store.ts';
3
+ import type { ToolsCtx } from './tools.ts';
4
+ /** Structural slice of EnrichWiring the commands need (keeps queue types out). */
5
+ export interface EnrichCommandSurface {
6
+ runNow(cwd?: string): Promise<{
7
+ processed: number;
8
+ remaining: number;
9
+ }>;
10
+ setModelOverride(v: string | null): Promise<void>;
11
+ modelOverride(): string | null;
12
+ statusLine(): string;
13
+ pendingCount(): Promise<number>;
14
+ }
15
+ export interface HostCommandsConfig {
16
+ artifactStoreRoot: string;
17
+ harnessId: string;
18
+ /** The sync scheduler to arm after a link (record §5). */
19
+ scheduler?: SyncScheduler;
20
+ /** P2 enrichment command surface (host plane; manual-drain semantics). */
21
+ enrich?: EnrichCommandSurface;
22
+ /** P3 rule lifecycle family, bound to a workspace (S2 module underneath). */
23
+ rules?: (cwd: string, args: string) => Promise<{
24
+ kind: 'success' | 'error';
25
+ text: string;
26
+ }>;
27
+ }
28
+ export declare function registerHostCommands(ctx: ToolsCtx, domain: DomainLike, config: HostCommandsConfig): () => void;