@fractalsynapse/dsh-chapters 0.0.0-stage → 0.2.20
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/LICENSE +14 -0
- package/README.md +76 -2
- package/cordis.patch.yml +363 -0
- package/docs/architecture.md +692 -0
- package/docs/autorun-guide.md +248 -0
- package/docs/backlog.md +2022 -0
- package/docs/contract.md +938 -0
- package/docs/development.md +990 -0
- package/docs/host-compaction-seam.md +225 -0
- package/docs/host-protected-head-ask.md +80 -0
- package/docs/knowledge-repo.md +1332 -0
- package/docs/plans-carriage.md +571 -0
- package/docs/project-state.md +642 -0
- package/docs/provider.md +98 -0
- package/docs/superpowers/plans/2026-10-17-chapters-save.md +198 -0
- package/docs/superpowers/plans/2026-11-02-chapter-removal.md +151 -0
- package/docs/superpowers/specs/2026-11-02-chapter-removal-design.md +115 -0
- package/docs/verify.md +561 -0
- package/lib/archive.d.ts +161 -0
- package/lib/archive.js +229 -0
- package/lib/archive.js.map +1 -0
- package/lib/arrival.d.ts +23 -0
- package/lib/arrival.js +125 -0
- package/lib/arrival.js.map +1 -0
- package/lib/artifact-query.d.ts +62 -0
- package/lib/artifact-query.js +187 -0
- package/lib/artifact-query.js.map +1 -0
- package/lib/attach.d.ts +46 -0
- package/lib/attach.js +120 -0
- package/lib/attach.js.map +1 -0
- package/lib/auto-save.d.ts +145 -0
- package/lib/auto-save.js +334 -0
- package/lib/auto-save.js.map +1 -0
- package/lib/aux-message.d.ts +17 -0
- package/lib/aux-message.js +20 -0
- package/lib/aux-message.js.map +1 -0
- package/lib/client.js +1419 -0
- package/lib/client.js.map +1 -0
- package/lib/codegraph.d.ts +76 -0
- package/lib/codegraph.js +190 -0
- package/lib/codegraph.js.map +1 -0
- package/lib/commands.d.ts +52 -0
- package/lib/commands.js +946 -0
- package/lib/commands.js.map +1 -0
- package/lib/compose.d.ts +30 -0
- package/lib/compose.js +185 -0
- package/lib/compose.js.map +1 -0
- package/lib/continue-core.d.ts +245 -0
- package/lib/continue-core.js +405 -0
- package/lib/continue-core.js.map +1 -0
- package/lib/diag.d.ts +55 -0
- package/lib/diag.js +186 -0
- package/lib/diag.js.map +1 -0
- package/lib/engine-core.d.ts +301 -0
- package/lib/engine-core.js +531 -0
- package/lib/engine-core.js.map +1 -0
- package/lib/engine-note.d.ts +35 -0
- package/lib/engine-note.js +32 -0
- package/lib/engine-note.js.map +1 -0
- package/lib/engine.d.ts +329 -0
- package/lib/engine.js +1179 -0
- package/lib/engine.js.map +1 -0
- package/lib/enrich-queue.d.ts +66 -0
- package/lib/enrich-queue.js +126 -0
- package/lib/enrich-queue.js.map +1 -0
- package/lib/enrich-store.d.ts +31 -0
- package/lib/enrich-store.js +103 -0
- package/lib/enrich-store.js.map +1 -0
- package/lib/enrich-wire.d.ts +163 -0
- package/lib/enrich-wire.js +353 -0
- package/lib/enrich-wire.js.map +1 -0
- package/lib/enrich.d.ts +102 -0
- package/lib/enrich.js +267 -0
- package/lib/enrich.js.map +1 -0
- package/lib/gitops.d.ts +107 -0
- package/lib/gitops.js +539 -0
- package/lib/gitops.js.map +1 -0
- package/lib/index.d.ts +241 -0
- package/lib/index.js +1162 -0
- package/lib/index.js.map +1 -0
- package/lib/indexing.d.ts +98 -0
- package/lib/indexing.js +223 -0
- package/lib/indexing.js.map +1 -0
- package/lib/injections.d.ts +90 -0
- package/lib/injections.js +165 -0
- package/lib/injections.js.map +1 -0
- package/lib/last-resort.d.ts +24 -0
- package/lib/last-resort.js +27 -0
- package/lib/last-resort.js.map +1 -0
- package/lib/log-shape.d.ts +38 -0
- package/lib/log-shape.js +93 -0
- package/lib/log-shape.js.map +1 -0
- package/lib/models-command.d.ts +74 -0
- package/lib/models-command.js +100 -0
- package/lib/models-command.js.map +1 -0
- package/lib/notice.d.ts +74 -0
- package/lib/notice.js +92 -0
- package/lib/notice.js.map +1 -0
- package/lib/plans.d.ts +154 -0
- package/lib/plans.js +315 -0
- package/lib/plans.js.map +1 -0
- package/lib/preset.d.ts +186 -0
- package/lib/preset.js +212 -0
- package/lib/preset.js.map +1 -0
- package/lib/prompt-keys.d.ts +7 -0
- package/lib/prompt-keys.js +8 -0
- package/lib/prompt-keys.js.map +1 -0
- package/lib/prompt-loop.d.ts +73 -0
- package/lib/prompt-loop.js +147 -0
- package/lib/prompt-loop.js.map +1 -0
- package/lib/prompt-push.d.ts +47 -0
- package/lib/prompt-push.js +115 -0
- package/lib/prompt-push.js.map +1 -0
- package/lib/prompt-runner.d.ts +48 -0
- package/lib/prompt-runner.js +90 -0
- package/lib/prompt-runner.js.map +1 -0
- package/lib/prompt-state.d.ts +142 -0
- package/lib/prompt-state.js +101 -0
- package/lib/prompt-state.js.map +1 -0
- package/lib/prompt.d.ts +270 -0
- package/lib/prompt.js +408 -0
- package/lib/prompt.js.map +1 -0
- package/lib/provider.d.ts +44 -0
- package/lib/provider.js +66 -0
- package/lib/provider.js.map +1 -0
- package/lib/redact.d.ts +24 -0
- package/lib/redact.js +85 -0
- package/lib/redact.js.map +1 -0
- package/lib/registry.d.ts +187 -0
- package/lib/registry.js +235 -0
- package/lib/registry.js.map +1 -0
- package/lib/removal.d.ts +159 -0
- package/lib/removal.js +556 -0
- package/lib/removal.js.map +1 -0
- package/lib/render.d.ts +97 -0
- package/lib/render.js +530 -0
- package/lib/render.js.map +1 -0
- package/lib/repair-run.d.ts +76 -0
- package/lib/repair-run.js +506 -0
- package/lib/repair-run.js.map +1 -0
- package/lib/repair.d.ts +208 -0
- package/lib/repair.js +357 -0
- package/lib/repair.js.map +1 -0
- package/lib/repo.d.ts +78 -0
- package/lib/repo.js +184 -0
- package/lib/repo.js.map +1 -0
- package/lib/rules-commands.d.ts +46 -0
- package/lib/rules-commands.js +379 -0
- package/lib/rules-commands.js.map +1 -0
- package/lib/rules.d.ts +267 -0
- package/lib/rules.js +478 -0
- package/lib/rules.js.map +1 -0
- package/lib/save-core.d.ts +72 -0
- package/lib/save-core.js +220 -0
- package/lib/save-core.js.map +1 -0
- package/lib/search.d.ts +112 -0
- package/lib/search.js +282 -0
- package/lib/search.js.map +1 -0
- package/lib/session-rules.d.ts +222 -0
- package/lib/session-rules.js +454 -0
- package/lib/session-rules.js.map +1 -0
- package/lib/signature.d.ts +45 -0
- package/lib/signature.js +199 -0
- package/lib/signature.js.map +1 -0
- package/lib/store.d.ts +156 -0
- package/lib/store.js +368 -0
- package/lib/store.js.map +1 -0
- package/lib/sync.d.ts +220 -0
- package/lib/sync.js +810 -0
- package/lib/sync.js.map +1 -0
- package/lib/tools.d.ts +113 -0
- package/lib/tools.js +1090 -0
- package/lib/tools.js.map +1 -0
- package/lib/treedx/client.d.ts +57 -0
- package/lib/treedx/client.js +82 -0
- package/lib/treedx/client.js.map +1 -0
- package/lib/treedx/provider.d.ts +39 -0
- package/lib/treedx/provider.js +479 -0
- package/lib/treedx/provider.js.map +1 -0
- package/lib/treedx/state.d.ts +27 -0
- package/lib/treedx/state.js +65 -0
- package/lib/treedx/state.js.map +1 -0
- package/lib/types.d.ts +210 -0
- package/lib/types.js +31 -0
- package/lib/types.js.map +1 -0
- package/lib/version.d.ts +9 -0
- package/lib/version.js +10 -0
- package/lib/version.js.map +1 -0
- package/lib/vocabulary.d.ts +41 -0
- package/lib/vocabulary.js +118 -0
- package/lib/vocabulary.js.map +1 -0
- package/lib/warnings.d.ts +10 -0
- package/lib/warnings.js +18 -0
- package/lib/warnings.js.map +1 -0
- package/package.json +151 -4
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one boundary that decides which bytes of the durable log are CONVERSATION
|
|
3
|
+
* and which are host-injected context (knowledge-repo.md §15 amendment,
|
|
4
|
+
* "host-injected context is not conversation").
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: the harness injects project context — workspace instruction
|
|
7
|
+
* files (whatever a given project's AGENTS.md holds), the runtime-context
|
|
8
|
+
* snapshot, the skill catalog — and injected events arrive in the log either as
|
|
9
|
+
* their own `user/message` (source.kind `agent-instructions`, `plugin`, …) or
|
|
10
|
+
* riding INSIDE human messages as `<system-reminder>` spans. A target project's
|
|
11
|
+
* instruction text is that project's business: it must not be archived into the
|
|
12
|
+
* shared knowledge corpus, searched by other workspaces, re-quoted into
|
|
13
|
+
* enrichment input, or bloating a child's re-archive of its own TOC notice
|
|
14
|
+
* generation after generation.
|
|
15
|
+
*
|
|
16
|
+
* Precedents this rule follows, not invents:
|
|
17
|
+
* - `signature.ts` (r28) already excluded these events from per-turn topic
|
|
18
|
+
* signatures after measuring their poison: every signature gained the
|
|
19
|
+
* injected filenames and drowned the real topic signal.
|
|
20
|
+
* - `tests/e2e/model-proxy.ts` (user decision 2026-09-20, AGENTS-BLIND) already
|
|
21
|
+
* masks the same classes for tape matching, on the same reasoning:
|
|
22
|
+
* development artifacts, every real project ships its own.
|
|
23
|
+
*
|
|
24
|
+
* What is NOT touched: the durable session log (invariant: append-only, always —
|
|
25
|
+
* the originals live on in the parent session), and genuine conversation bytes
|
|
26
|
+
* (a human typing the words "AGENTS.md" in prose is conversation and stays
|
|
27
|
+
* verbatim; only the structural injected forms are removed).
|
|
28
|
+
*
|
|
29
|
+
* Pure module: no cordis, no fs, no network.
|
|
30
|
+
*/
|
|
31
|
+
/** A user/message is human-authored when it carries no source, or source.kind
|
|
32
|
+
* 'user' (unlabelled keeps working for seeded fixtures — same rule the
|
|
33
|
+
* signature layer has used since r28). Everything else — 'agent-instructions',
|
|
34
|
+
* 'plugin' (the TOC notice, runtime snapshots), 'tool', 'system' — is injected
|
|
35
|
+
* context, not conversation. Exported so signature.ts imports the SAME
|
|
36
|
+
* predicate; the two layers must never drift on what counts as a turn. */
|
|
37
|
+
export function isHumanAuthored(data) {
|
|
38
|
+
const source = data.source;
|
|
39
|
+
return source?.kind === undefined || source.kind === 'user';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The ONE targeted exception to the screen (human decision 2026-10-10): the harness's
|
|
43
|
+
* own mode-switch narration — `source: { kind: 'plugin', plugin: 'plan-mode',
|
|
44
|
+
* form: 'notice' }`, "The user switched this session to plan mode." — IS conversation.
|
|
45
|
+
*
|
|
46
|
+
* The screen exists so a project's private instruction files never travel in a shared
|
|
47
|
+
* corpus. A mode flip the human performed is not that: it is a fact about THIS session,
|
|
48
|
+
* and it is the seam that answers "was this approved before or after we started
|
|
49
|
+
* implementing?". Named by plugin AND form, so it cannot be widened by accident into
|
|
50
|
+
* the general plugin-source exemption — an instruction file or a runtime snapshot
|
|
51
|
+
* arriving with a different form still screens.
|
|
52
|
+
*/
|
|
53
|
+
export function isSessionStateNarration(data) {
|
|
54
|
+
const source = data.source;
|
|
55
|
+
// v4 (issue #43, measured against dsh-plan-mode/lib/index.js:395-410 on
|
|
56
|
+
// 0.2.0-rc.2): the producer owns the kind — `{ kind: 'plan-mode', form: 'notice' }`.
|
|
57
|
+
if (source?.kind === 'plan-mode' && source.form === 'notice')
|
|
58
|
+
return true;
|
|
59
|
+
// #61 Amendment B — a delegate's report is conversation about THIS session's work, not a project's
|
|
60
|
+
// private bytes. Measured shapes (0.2.x logs): `{ kind: 'agent-message', form: 'relay' }` carries
|
|
61
|
+
// "Agent <id> sent a message: <the result>", and `{ kind: 'subagent-settled', form: 'notice' }` is
|
|
62
|
+
// when that delegate finished. The old rule screened both, which cost 44 markers / 248,150 chars
|
|
63
|
+
// (~62K est tokens) in the first project built on this preset — delegated work vanished through BOTH
|
|
64
|
+
// doors meant to capture it, leaving chapters that recorded a delegation with no outcome.
|
|
65
|
+
// Named by producer AND form like the plan-mode arm above, so it cannot widen: the workspace
|
|
66
|
+
// instruction file (`{ kind: 'agent-instructions', form: 'instructions' }`) is still screened, and
|
|
67
|
+
// `isHumanAuthored` stays strict, because the topic-signature layer must not let a 2 KB report drag a
|
|
68
|
+
// signature toward the filenames it mentions (record r28).
|
|
69
|
+
if (source?.kind === 'agent-message' && source.form === 'relay')
|
|
70
|
+
return true;
|
|
71
|
+
if (source?.kind === 'subagent-settled' && source.form === 'notice')
|
|
72
|
+
return true;
|
|
73
|
+
// #113: a Suggested Next Step the human armed. It is conversation the human authorized — a chapter that
|
|
74
|
+
// records an autorun response without the prompt that produced it is unexplainable — so it is captured like
|
|
75
|
+
// the others. Deliberately NOT `kind: 'user'`: the host's goal tool decides "a direct human turn exists" by
|
|
76
|
+
// looking for exactly that (measured, dsh-tool-goal:49-65), and laundering our loop through it would let a
|
|
77
|
+
// suggestion authorize model-side goal operations. Named by producer AND form like every arm above: the TOC
|
|
78
|
+
// notice (`form: 'snapshot'`) and any other plugin's message stay screened.
|
|
79
|
+
if (source?.kind === 'plugin:dsh-chapters' && source.form === 'notice')
|
|
80
|
+
return true;
|
|
81
|
+
// v3 logs, still replayed from disk: the retired wrapper the exception was
|
|
82
|
+
// written against. Both arms stay named by producer AND form — neither widens.
|
|
83
|
+
return source?.kind === 'plugin' && source.plugin === 'plan-mode' && source.form === 'notice';
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Does this event belong in the archive as conversation?
|
|
87
|
+
*
|
|
88
|
+
* Deliberately NARROWER than `isHumanAuthored`: the topic-signature layer keeps using
|
|
89
|
+
* the strict predicate, because the r28 lesson was that injected text poisons topic
|
|
90
|
+
* merging (every signature gained the injected filenames). A mode narration is one
|
|
91
|
+
* short sentence, so archiving it is worth more than the risk of letting it move a
|
|
92
|
+
* chapter boundary — and the two uses are separate functions on purpose.
|
|
93
|
+
*/
|
|
94
|
+
export function isArchivableConversation(data) {
|
|
95
|
+
return isHumanAuthored(data) || isSessionStateNarration(data);
|
|
96
|
+
}
|
|
97
|
+
/** Label for the omission marker: kind, plus plugin name when the source
|
|
98
|
+
* carries one (so `plugin:dsh-chapters` is recognizable in a chapter). */
|
|
99
|
+
export function sourceLabel(data) {
|
|
100
|
+
const source = data.source;
|
|
101
|
+
if (source === undefined)
|
|
102
|
+
return 'unlabelled';
|
|
103
|
+
if (source.kind === 'plugin:dsh-chapters' && source.form === 'notice')
|
|
104
|
+
return 'suggested-prompt';
|
|
105
|
+
return source.kind === 'plugin' && typeof source.plugin === 'string'
|
|
106
|
+
? `plugin:${source.plugin}`
|
|
107
|
+
: String(source.kind ?? 'unknown');
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The structural injection span. Anchored on the exact harness tag, case
|
|
111
|
+
* sensitive (the tape's own skill-catalog rule is case sensitive for the same
|
|
112
|
+
* reason: ordinary prose quoting the tag is vanishingly rare, and the cost of a
|
|
113
|
+
* false positive is a marked, countable omission — never a silent rewrite of
|
|
114
|
+
* history, the original bytes stay in the session log). The unclosed-tail form
|
|
115
|
+
* `[\s\S]*?(</system-reminder>|$)` mirrors the tape's masking of truncated
|
|
116
|
+
* injections.
|
|
117
|
+
*/
|
|
118
|
+
/**
|
|
119
|
+
* The ONE predicate for "this text is host-injected context, not something the human wrote".
|
|
120
|
+
*
|
|
121
|
+
* `source.kind` remains the primary gate (`isHumanAuthored`, `screenConversation`) — but some consumers
|
|
122
|
+
* see only flattened provider text with NO source at all, first among them `deriveIdentity` choosing a
|
|
123
|
+
* chapter TITLE. Until #59 that path carried a private regex (`HARNESS_PROSE`) that had silently fallen
|
|
124
|
+
* behind the host's injected kinds, so a `time-context` snapshot became a chapter title, slug, FILENAME
|
|
125
|
+
* and a live TOC citation (`020-time-sampled-while-preparing-turn-11-step-24-202.md`, cited by a
|
|
126
|
+
* checkpoint and never written). AGENTS.md: one predicate per semantic decision, and a source-shape row
|
|
127
|
+
* fails if a second list reappears.
|
|
128
|
+
*
|
|
129
|
+
* Anchored at the START on purpose: a human quoting "time sampled while preparing" mid-sentence is
|
|
130
|
+
* conversation and stays verbatim — the same line the body screen draws.
|
|
131
|
+
*
|
|
132
|
+
* `this is an automatically generated checkpoint` joined 2026-10-23 (#87): it is the host's own
|
|
133
|
+
* in-place relief preamble — machine state that replaces earlier turns, never something a human asked.
|
|
134
|
+
* It had to join THIS list rather than get a second one, because #87's fix asks the same question
|
|
135
|
+
* ("is this a human turn?") at the compaction seam, and a parallel list is exactly the #59 drift this
|
|
136
|
+
* comment already records.
|
|
137
|
+
*/
|
|
138
|
+
const INJECTED_PROSE = /^\s*(<system-reminder>|<codegraph_context|system reminder\b|current runtime context\.|time sampled while preparing|browser time zone for this request|\[workspace instructions?\b|this session is running under|the following workspace instructions|agent [A-Za-z0-9_-]{8,} sent a message|background subagent [A-Za-z0-9_-]{8,} finished|this is an automatically generated checkpoint)/i;
|
|
139
|
+
export function isInjectedProse(text) {
|
|
140
|
+
return INJECTED_PROSE.test(text);
|
|
141
|
+
}
|
|
142
|
+
const REMINDER_SPAN = /<system-reminder>[\s\S]*?(?:<\/system-reminder>|$)/g;
|
|
143
|
+
/** Strip every injected span from a message's text; deterministic, idempotent
|
|
144
|
+
* (the marker we emit below cannot re-trigger this rule). */
|
|
145
|
+
export function stripReminderSpans(text) {
|
|
146
|
+
const removed = text.match(REMINDER_SPAN)?.join('').length ?? 0;
|
|
147
|
+
if (removed === 0)
|
|
148
|
+
return { text, removedChars: 0 };
|
|
149
|
+
return { text: text.replace(REMINDER_SPAN, '').trim(), removedChars: removed };
|
|
150
|
+
}
|
|
151
|
+
/** The visible, greppable, countable marker an omission leaves behind. One
|
|
152
|
+
* shape for every class so downstream assertions (and the sentinel integration
|
|
153
|
+
* test) pin a single prefix. */
|
|
154
|
+
export const omittedMarker = (what, chars) => `⟦omitted:host-injected ${what}, ${chars} chars — project state, not conversation⟧`;
|
|
155
|
+
export function screenConversation(data, rawText, role) {
|
|
156
|
+
if (role === 'user' && !isArchivableConversation(data)) {
|
|
157
|
+
return { text: '', omittedEvent: true, removedChars: rawText.length, sourceKind: sourceLabel(data) };
|
|
158
|
+
}
|
|
159
|
+
// Assistant text is model output, but a runtime snapshot has been observed
|
|
160
|
+
// appended to role user|system by the harness — screening assistant spans
|
|
161
|
+
// too costs one regex and can only ever mark, never invent, text.
|
|
162
|
+
const { text, removedChars } = stripReminderSpans(rawText);
|
|
163
|
+
return { text, omittedEvent: false, removedChars, sourceKind: 'user' };
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=injections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injections.js","sourceRoot":"","sources":["../src/injections.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The last-resort deferral's arithmetic (#78 option A) — kept apart from the engine so the rule that
|
|
3
|
+
* decides WHEN an oversized node may override the operator's arrival floor is testable without a
|
|
4
|
+
* session, a meter, or a model.
|
|
5
|
+
*
|
|
6
|
+
* The number is DERIVED, never invented: a node can only make relief impossible if it cannot fit
|
|
7
|
+
* beside the content relief is obliged to keep. That obligation is the base engine's own verbatim
|
|
8
|
+
* retain tail plus its reserved completion budget, both of which it already computes — so the
|
|
9
|
+
* ceiling is `window − retainBudget − reservedCompletion`, and no new tunable appears (AGENTS: no
|
|
10
|
+
* hardcoded tunables; `cordis.yml` can only change what config declares).
|
|
11
|
+
*/
|
|
12
|
+
export interface LastResortInputs {
|
|
13
|
+
windowTokens: number;
|
|
14
|
+
retainRatio: number;
|
|
15
|
+
retainTokens?: number;
|
|
16
|
+
reservedCompletionTokens: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The token size above which an inline `tool/result` cannot coexist with the mandatory tail, or
|
|
20
|
+
* `null` when nothing can be concluded. Returning `null` — rather than 0, or a guess — is the point:
|
|
21
|
+
* a wrong ceiling here silently overrides an operator's configured economy knob, which is the one
|
|
22
|
+
* thing this feature must never do.
|
|
23
|
+
*/
|
|
24
|
+
export declare function lastResortCeilingTokens(v: LastResortInputs): number | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The token size above which an inline `tool/result` cannot coexist with the mandatory tail, or
|
|
3
|
+
* `null` when nothing can be concluded. Returning `null` — rather than 0, or a guess — is the point:
|
|
4
|
+
* a wrong ceiling here silently overrides an operator's configured economy knob, which is the one
|
|
5
|
+
* thing this feature must never do.
|
|
6
|
+
*/
|
|
7
|
+
export function lastResortCeilingTokens(v) {
|
|
8
|
+
const { windowTokens, retainRatio, retainTokens, reservedCompletionTokens } = v;
|
|
9
|
+
const finite = (n) => typeof n === 'number' && Number.isFinite(n);
|
|
10
|
+
if (!finite(windowTokens) || !finite(reservedCompletionTokens))
|
|
11
|
+
return null;
|
|
12
|
+
if (windowTokens <= 0 || reservedCompletionTokens < 0)
|
|
13
|
+
return null;
|
|
14
|
+
// An explicit retainTokens wins over the ratio (both are base config the host may carry either way).
|
|
15
|
+
let retainBudget;
|
|
16
|
+
if (finite(retainTokens) && retainTokens > 0) {
|
|
17
|
+
retainBudget = retainTokens;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
if (!finite(retainRatio) || retainRatio <= 0 || retainRatio >= 1)
|
|
21
|
+
return null;
|
|
22
|
+
retainBudget = Math.floor(windowTokens * retainRatio);
|
|
23
|
+
}
|
|
24
|
+
const ceiling = windowTokens - retainBudget - reservedCompletionTokens;
|
|
25
|
+
return ceiling > 0 ? Math.floor(ceiling) : null;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=last-resort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"last-resort.js","sourceRoot":"","sources":["../src/last-resort.ts"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,CAAmB;IACzD,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,wBAAwB,EAAE,GAAG,CAAC,CAAA;IAC/E,MAAM,MAAM,GAAG,CAAC,CAAU,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;IACvF,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,wBAAwB,CAAC;QAAE,OAAO,IAAI,CAAA;IAC3E,IAAI,YAAY,IAAI,CAAC,IAAI,wBAAwB,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAClE,qGAAqG;IACrG,IAAI,YAAoB,CAAA;IACxB,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QAC7C,YAAY,GAAG,YAAY,CAAA;IAC7B,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,WAAW,IAAI,CAAC,IAAI,WAAW,IAAI,CAAC;YAAE,OAAO,IAAI,CAAA;QAC7E,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,WAAW,CAAC,CAAA;IACvD,CAAC;IACD,MAAM,OAAO,GAAG,YAAY,GAAG,YAAY,GAAG,wBAAwB,CAAA;IACtE,OAAO,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACjD,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event-shape readers — the single place that knows how DSH nests payloads in
|
|
3
|
+
* the durable log.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from `render.ts` (docs/plans-carriage.md § 1: the chapter renderer and the plan detector
|
|
6
|
+
* must agree on which `tool/result` answers which `tool/call`, or a plan's verdict could be read
|
|
7
|
+
* differently in two places). Shapes here were MEASURED from real logs, never guessed — see
|
|
8
|
+
* tests/unit/render.test.ts:18-22 for the fixture mirror and spikes/probe/FINDINGS.md.
|
|
9
|
+
*
|
|
10
|
+
* Pure module: no cordis, no fs, no DSH imports.
|
|
11
|
+
*/
|
|
12
|
+
import type { ContentBlock, SessionEventLike } from './types.ts';
|
|
13
|
+
export declare const blocksOf: (value: unknown) => ContentBlock[];
|
|
14
|
+
export declare const textOfBlocks: (value: unknown) => string;
|
|
15
|
+
/**
|
|
16
|
+
* Flatten a tool-result payload, which nests a content array inside a tool-result block.
|
|
17
|
+
* REAL shape (measured e2e, 2026-09-19): `message.content` blocks are
|
|
18
|
+
* `{ type: 'tool-result', toolCallId, isError, content: [{ type:'text', text }] }`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function toolResultText(data: Record<string, unknown>): string;
|
|
21
|
+
/** True when the paired tool-result carries an error flag (a failed call). */
|
|
22
|
+
export declare function toolResultIsError(data: Record<string, unknown>): boolean;
|
|
23
|
+
/** The callId a `tool/result` answers (measured: it rides on `message.source.callId`). */
|
|
24
|
+
export declare function resultCallId(ev: SessionEventLike): string | undefined;
|
|
25
|
+
/** The callId a `tool/call` carries. */
|
|
26
|
+
export declare const callIdOf: (ev: SessionEventLike) => string | undefined;
|
|
27
|
+
/** Does this result answer this call? Both sides must name a callId — never guess by adjacency. */
|
|
28
|
+
export declare function sameCall(call: SessionEventLike, result: SessionEventLike): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* The LAST result event for one callId, over an already-range-filtered list.
|
|
31
|
+
* Host redelivery is real (measured 2026-09-28: 25 calls carried TWO tool/result
|
|
32
|
+
* events), so the newest copy is the authoritative verdict.
|
|
33
|
+
*/
|
|
34
|
+
export declare function lastResultFor(results: readonly SessionEventLike[], callId: string): SessionEventLike | undefined;
|
|
35
|
+
/** `tool/call` arguments as one serialized string (object or pre-serialized string both occur). */
|
|
36
|
+
export declare function callArgsText(data: Record<string, unknown>): string;
|
|
37
|
+
/** One named argument from either shape (string JSON or object). */
|
|
38
|
+
export declare function callArg(data: Record<string, unknown>, name: string): string | undefined;
|
package/lib/log-shape.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export const blocksOf = (value) => Array.isArray(value) ? value : [];
|
|
2
|
+
export const textOfBlocks = (value) => blocksOf(value)
|
|
3
|
+
.filter((b) => b.type === 'text')
|
|
4
|
+
.map((b) => b.text ?? '')
|
|
5
|
+
.join('\n');
|
|
6
|
+
/**
|
|
7
|
+
* Flatten a tool-result payload, which nests a content array inside a tool-result block.
|
|
8
|
+
* REAL shape (measured e2e, 2026-09-19): `message.content` blocks are
|
|
9
|
+
* `{ type: 'tool-result', toolCallId, isError, content: [{ type:'text', text }] }`.
|
|
10
|
+
*/
|
|
11
|
+
export function toolResultText(data) {
|
|
12
|
+
const message = data.message;
|
|
13
|
+
const inner = blocksOf(message?.content ?? data.content);
|
|
14
|
+
const parts = [];
|
|
15
|
+
for (const block of inner) {
|
|
16
|
+
if (block.type === 'text')
|
|
17
|
+
parts.push(block.text ?? '');
|
|
18
|
+
else if (block.type === 'tool-result') {
|
|
19
|
+
const nested = block.content;
|
|
20
|
+
parts.push(typeof nested === 'string' ? nested : textOfBlocks(nested));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return parts.filter((p) => p.length > 0).join('\n');
|
|
24
|
+
}
|
|
25
|
+
/** True when the paired tool-result carries an error flag (a failed call). */
|
|
26
|
+
export function toolResultIsError(data) {
|
|
27
|
+
const message = data.message;
|
|
28
|
+
const inner = blocksOf(message?.content ?? data.content);
|
|
29
|
+
return inner.some((b) => b.isError === true);
|
|
30
|
+
}
|
|
31
|
+
/** The callId a `tool/result` answers (measured: it rides on `message.source.callId`). */
|
|
32
|
+
export function resultCallId(ev) {
|
|
33
|
+
const msg = ev.data?.message;
|
|
34
|
+
const cid = msg?.source?.callId;
|
|
35
|
+
if (typeof cid === 'string')
|
|
36
|
+
return cid;
|
|
37
|
+
const flat = ev.data?.callId;
|
|
38
|
+
return typeof flat === 'string' ? flat : undefined;
|
|
39
|
+
}
|
|
40
|
+
/** The callId a `tool/call` carries. */
|
|
41
|
+
export const callIdOf = (ev) => {
|
|
42
|
+
const cid = ev.data?.callId;
|
|
43
|
+
return typeof cid === 'string' ? cid : undefined;
|
|
44
|
+
};
|
|
45
|
+
/** Does this result answer this call? Both sides must name a callId — never guess by adjacency. */
|
|
46
|
+
export function sameCall(call, result) {
|
|
47
|
+
const cid = callIdOf(call);
|
|
48
|
+
const rcid = resultCallId(result);
|
|
49
|
+
return cid !== undefined && rcid !== undefined && String(cid) === String(rcid);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The LAST result event for one callId, over an already-range-filtered list.
|
|
53
|
+
* Host redelivery is real (measured 2026-09-28: 25 calls carried TWO tool/result
|
|
54
|
+
* events), so the newest copy is the authoritative verdict.
|
|
55
|
+
*/
|
|
56
|
+
export function lastResultFor(results, callId) {
|
|
57
|
+
let last;
|
|
58
|
+
for (const ev of results)
|
|
59
|
+
if (resultCallId(ev) === callId)
|
|
60
|
+
last = ev;
|
|
61
|
+
return last;
|
|
62
|
+
}
|
|
63
|
+
/** `tool/call` arguments as one serialized string (object or pre-serialized string both occur). */
|
|
64
|
+
export function callArgsText(data) {
|
|
65
|
+
const args = data.arguments;
|
|
66
|
+
if (typeof args === 'string')
|
|
67
|
+
return args;
|
|
68
|
+
try {
|
|
69
|
+
return JSON.stringify(args ?? {});
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return String(args);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** One named argument from either shape (string JSON or object). */
|
|
76
|
+
export function callArg(data, name) {
|
|
77
|
+
const args = data.arguments;
|
|
78
|
+
let obj;
|
|
79
|
+
if (typeof args === 'string') {
|
|
80
|
+
try {
|
|
81
|
+
obj = JSON.parse(args);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
obj = undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (args !== null && typeof args === 'object') {
|
|
88
|
+
obj = args;
|
|
89
|
+
}
|
|
90
|
+
const value = obj?.[name];
|
|
91
|
+
return typeof value === 'string' ? value : undefined;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=log-shape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-shape.js","sourceRoot":"","sources":["../src/log-shape.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #173 — the /chapters-models pure core: discovery for the three model settings.
|
|
3
|
+
*
|
|
4
|
+
* The three keys (`enrichment.model`, `rules.model`, the prompt model) all take a `provider/model`
|
|
5
|
+
* route, and before this command nothing in the product told a human which identifiers this host
|
|
6
|
+
* accepts — a wrong guess failed at call time as a routing error. This mirrors the host's own
|
|
7
|
+
* sanctioned discovery tool (`list_subagent_models` in
|
|
8
|
+
* examples/dsh/packages/subagent/tool-subagent/src/list-models.ts): `listProviders()` is SYNC,
|
|
9
|
+
* `listModels(provider)` is the ADVISORY catalog, and `resolveModelInfo(provider, model)` is the
|
|
10
|
+
* exact-route drill-down that adds what only the adapter knows (context window, reasoning efforts).
|
|
11
|
+
*
|
|
12
|
+
* Two honesty rules the rendering never drops: catalog membership is advisory — the host routes
|
|
13
|
+
* unlisted ids, so an empty catalog means "no selection offered", never "nothing works" — and a
|
|
14
|
+
* capability the adapter does not advertise is REPORTED ABSENT, not guessed. Like every
|
|
15
|
+
* /chapters-* command this is deterministic text: it never enters the model's context.
|
|
16
|
+
* Pure core; the thin registration lives in commands.ts and the wiring in index.ts.
|
|
17
|
+
*/
|
|
18
|
+
export interface ModelsProviderInfo {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly name: string;
|
|
21
|
+
}
|
|
22
|
+
/** Host-shaped `LlmModelInfo` (examples/dsh/packages/llm/llm/src/types.ts:348). */
|
|
23
|
+
export interface ModelsModelInfo {
|
|
24
|
+
readonly provider: string;
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly description?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
/** Host-shaped `LlmResolvedModelInfo` — the fields this command consumes, no more. */
|
|
30
|
+
export interface ModelsResolvedInfo extends ModelsModelInfo {
|
|
31
|
+
readonly context?: {
|
|
32
|
+
readonly contextWindow?: number;
|
|
33
|
+
} | undefined;
|
|
34
|
+
readonly reasoning?: {
|
|
35
|
+
readonly efforts?: readonly {
|
|
36
|
+
readonly id: string;
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly description?: string;
|
|
39
|
+
}[] | undefined;
|
|
40
|
+
readonly defaultEffort?: string | undefined;
|
|
41
|
+
} | undefined;
|
|
42
|
+
}
|
|
43
|
+
/** The `llm` service surface this command rides — stub it in the HOST's shape, never an easier one. */
|
|
44
|
+
export interface ModelsLlm {
|
|
45
|
+
listProviders(): readonly ModelsProviderInfo[];
|
|
46
|
+
listModels(provider: string): Promise<readonly ModelsModelInfo[]>;
|
|
47
|
+
resolveModelInfo(provider: string, model: string, signal?: AbortSignal): Promise<ModelsResolvedInfo>;
|
|
48
|
+
}
|
|
49
|
+
/** Where the value the human sees right now came from — rendered, because "set" and "fell back" differ. */
|
|
50
|
+
export type CurrentSource = 'settings' | 'config' | 'conversation-route' | 'rides-enrichment' | 'off';
|
|
51
|
+
export interface ModelsIo {
|
|
52
|
+
/** Absent on planes without the host llm service — the command refuses cleanly, never crashes. */
|
|
53
|
+
llm?: ModelsLlm | undefined;
|
|
54
|
+
/** The three settings' effective values, pre-resolved by the wiring (config → settings → fallback). */
|
|
55
|
+
current: {
|
|
56
|
+
enrichment: {
|
|
57
|
+
value: string;
|
|
58
|
+
source: CurrentSource;
|
|
59
|
+
};
|
|
60
|
+
rules: {
|
|
61
|
+
value: string;
|
|
62
|
+
source: CurrentSource;
|
|
63
|
+
};
|
|
64
|
+
prompt: {
|
|
65
|
+
value: string;
|
|
66
|
+
source: CurrentSource;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export interface ModelsReply {
|
|
71
|
+
kind: 'success' | 'error';
|
|
72
|
+
text: string;
|
|
73
|
+
}
|
|
74
|
+
export declare function modelsCommand(io: ModelsIo, args: string, signal?: AbortSignal): Promise<ModelsReply>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #173 — the /chapters-models pure core: discovery for the three model settings.
|
|
3
|
+
*
|
|
4
|
+
* The three keys (`enrichment.model`, `rules.model`, the prompt model) all take a `provider/model`
|
|
5
|
+
* route, and before this command nothing in the product told a human which identifiers this host
|
|
6
|
+
* accepts — a wrong guess failed at call time as a routing error. This mirrors the host's own
|
|
7
|
+
* sanctioned discovery tool (`list_subagent_models` in
|
|
8
|
+
* examples/dsh/packages/subagent/tool-subagent/src/list-models.ts): `listProviders()` is SYNC,
|
|
9
|
+
* `listModels(provider)` is the ADVISORY catalog, and `resolveModelInfo(provider, model)` is the
|
|
10
|
+
* exact-route drill-down that adds what only the adapter knows (context window, reasoning efforts).
|
|
11
|
+
*
|
|
12
|
+
* Two honesty rules the rendering never drops: catalog membership is advisory — the host routes
|
|
13
|
+
* unlisted ids, so an empty catalog means "no selection offered", never "nothing works" — and a
|
|
14
|
+
* capability the adapter does not advertise is REPORTED ABSENT, not guessed. Like every
|
|
15
|
+
* /chapters-* command this is deterministic text: it never enters the model's context.
|
|
16
|
+
* Pure core; the thin registration lives in commands.ts and the wiring in index.ts.
|
|
17
|
+
*/
|
|
18
|
+
const USAGE = 'usage: /chapters-models [<provider> [<model>]] — a paste-ready provider/model id works as one arg';
|
|
19
|
+
const ADVISORY = 'catalog membership is advisory: the host routes unlisted model ids too, and an empty catalog means no selection is offered — not that nothing works.';
|
|
20
|
+
function currentFooter(current) {
|
|
21
|
+
const line = (label, v, unset, setter) => ` ${label.padEnd(16)} ${v.value !== '' ? `${v.value} (from ${v.source})` : unset}\n ${''.padEnd(16)} set with: ${setter}`;
|
|
22
|
+
return 'current model settings:\n'
|
|
23
|
+
+ line('enrichment.model', current.enrichment, '(unset — rides the conversation route)', '/chapters-enrich model <provider/model> | clear') + '\n'
|
|
24
|
+
+ line('rules.model', current.rules, '(unset — mining rides the enrichment call)', '/chapters-rule model <provider/model> | clear') + '\n'
|
|
25
|
+
+ line('prompt model', current.prompt, 'OFF — setting a model IS the enable (§28)', '/chapters-prompt model <provider/model> | off');
|
|
26
|
+
}
|
|
27
|
+
function modelLine(provider, m) {
|
|
28
|
+
return ` ${provider}/${m.id} — ${m.name}${m.description === undefined || m.description === '' ? '' : `: ${m.description}`}`;
|
|
29
|
+
}
|
|
30
|
+
export async function modelsCommand(io, args, signal) {
|
|
31
|
+
const llm = io.llm;
|
|
32
|
+
if (llm === undefined) {
|
|
33
|
+
return { kind: 'error', text: `the llm service is unavailable on this plane — cannot enumerate models.\n${currentFooter(io.current)}` };
|
|
34
|
+
}
|
|
35
|
+
const parts = args.trim().split(/\s+/).filter((p) => p.length > 0);
|
|
36
|
+
// One arg carrying a slash is a paste-ready id — this command's own output format.
|
|
37
|
+
let provider;
|
|
38
|
+
let model;
|
|
39
|
+
if (parts.length === 1 && parts[0].includes('/')) {
|
|
40
|
+
const i = parts[0].indexOf('/');
|
|
41
|
+
provider = parts[0].slice(0, i);
|
|
42
|
+
model = parts[0].slice(i + 1);
|
|
43
|
+
}
|
|
44
|
+
else if (parts.length === 1) {
|
|
45
|
+
provider = parts[0];
|
|
46
|
+
}
|
|
47
|
+
else if (parts.length === 2) {
|
|
48
|
+
provider = parts[0];
|
|
49
|
+
model = parts[1];
|
|
50
|
+
}
|
|
51
|
+
else if (parts.length > 2) {
|
|
52
|
+
return { kind: 'error', text: USAGE };
|
|
53
|
+
}
|
|
54
|
+
const providers = llm.listProviders();
|
|
55
|
+
if (provider === undefined) {
|
|
56
|
+
const sections = [];
|
|
57
|
+
for (const p of providers) {
|
|
58
|
+
const models = await llm.listModels(p.id);
|
|
59
|
+
sections.push(`provider ${p.id} — ${p.name}\n`
|
|
60
|
+
+ (models.length === 0
|
|
61
|
+
? ` (no advertised models for ${p.id} — ${ADVISORY})`
|
|
62
|
+
: models.map((m) => modelLine(p.id, m)).join('\n')));
|
|
63
|
+
}
|
|
64
|
+
const body = providers.length === 0
|
|
65
|
+
? `(no LLM providers are registered on this host — ${ADVISORY})`
|
|
66
|
+
: sections.join('\n\n');
|
|
67
|
+
return { kind: 'success', text: `${body}\n\n${ADVISORY}\n\n${currentFooter(io.current)}` };
|
|
68
|
+
}
|
|
69
|
+
if (provider.length === 0 || (model !== undefined && model.length === 0)) {
|
|
70
|
+
return { kind: 'error', text: USAGE };
|
|
71
|
+
}
|
|
72
|
+
const known = providers.find((p) => p.id === provider);
|
|
73
|
+
if (known === undefined) {
|
|
74
|
+
const available = providers.map((p) => p.id).join(', ') || '(none)';
|
|
75
|
+
return { kind: 'error', text: `LLM provider "${provider}" is not registered; available providers: ${available}` };
|
|
76
|
+
}
|
|
77
|
+
if (model === undefined) {
|
|
78
|
+
const models = await llm.listModels(known.id);
|
|
79
|
+
const body = models.length === 0
|
|
80
|
+
? `(no advertised models for ${known.id} — ${ADVISORY})`
|
|
81
|
+
: `provider ${known.id} — ${known.name}\n${models.map((m) => modelLine(known.id, m)).join('\n')}\n\n${ADVISORY}`;
|
|
82
|
+
return { kind: 'success', text: `${body}\n\n${currentFooter(io.current)}` };
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const info = await llm.resolveModelInfo(known.id, model, signal);
|
|
86
|
+
const efforts = info.reasoning?.efforts ?? [];
|
|
87
|
+
const lines = [
|
|
88
|
+
`${known.id}/${info.id} — ${info.name}${info.description === undefined || info.description === '' ? '' : `: ${info.description}`}`,
|
|
89
|
+
`context window: ${info.context?.contextWindow !== undefined ? `${String(info.context.contextWindow)} tokens` : 'not advertised by this adapter'}`,
|
|
90
|
+
efforts.length === 0
|
|
91
|
+
? 'reasoning efforts: (no advertised reasoning efforts)'
|
|
92
|
+
: `reasoning efforts:\n${efforts.map((e) => ` ${e.id}${info.reasoning?.defaultEffort === e.id ? ' (default)' : ''} — ${e.name}${e.description === undefined || e.description === '' ? '' : `: ${e.description}`}`).join('\n')}`,
|
|
93
|
+
];
|
|
94
|
+
return { kind: 'success', text: `${lines.join('\n')}\n\n${currentFooter(io.current)}` };
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return { kind: 'error', text: `resolveModelInfo(${known.id}, ${model}) failed: ${String(error?.message ?? error).slice(0, 200)}` };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=models-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models-command.js","sourceRoot":"","sources":["../src/models-command.ts"],"names":[],"mappings":"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"}
|
package/lib/notice.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TOC notice: the single synthetic `user/message` that seeds a continuation.
|
|
3
|
+
*
|
|
4
|
+
* This module exists because of the 66-log lesson from `dsh-session-fork`
|
|
5
|
+
* (docs/development.md § L1): a message source outside the kernel's accepted
|
|
6
|
+
* vocabulary is not a cosmetic bug — stored logs that fail replay refuse to
|
|
7
|
+
* load, retroactively. The kernel's seed validator is not published as a
|
|
8
|
+
* callable, so the vocabulary here is pinned two ways and guarded by
|
|
9
|
+
* test/notice.test.ts + the v4 admission rows in tests/unit/host-seed-contract.test.ts:
|
|
10
|
+
*
|
|
11
|
+
* 1. Behaviorally, from the Phase 0 seed probes and the 2026-10-18 v4 drift
|
|
12
|
+
* (issue #43) — the retired forms are reproduced as refusals, not dropped:
|
|
13
|
+
* a string `source` was rejected as `invalid source`; session format v4
|
|
14
|
+
* rejects the bare `kind: 'plugin'` wrapper with the kernel's own words,
|
|
15
|
+
* "format v4 message requires a producer-owned source kind"
|
|
16
|
+
* (`@deepseek-ai/dsh-session-format-v3-to-v4/lib/index.js:126`,
|
|
17
|
+
* `dsh-session-persistence-jsonl/lib/worker.cjs:10902` on 0.2.0-rc.2).
|
|
18
|
+
* 2. Structurally, against the installed kernel: `role: 'user'`; a non-empty
|
|
19
|
+
* string `id`; array `content`; contiguous seed from `seq = 0`;
|
|
20
|
+
* `source.kind` a non-empty producer-owned string; `ContextForm` values
|
|
21
|
+
* `instructions | catalog | notice | relay | recall | snapshot`
|
|
22
|
+
* (`dsh-llm/lib/types/message.d.ts` — verified against 0.2.0-rc.2).
|
|
23
|
+
*
|
|
24
|
+
* v4 source attribution: each producer OWNS its kind. Third-party producers
|
|
25
|
+
* use the `plugin:<id>` form — measured live, the 0.2.0-rc.2 host writes its
|
|
26
|
+
* own logs with `plugin:dsh-codegraph` + `form: 'notice'`. So the notice
|
|
27
|
+
* carries `{ kind: 'plugin:dsh-chapters', form: 'snapshot', sections: [...] }`
|
|
28
|
+
* and NO `plugin` field (the v3→v4 migration's own `rewritePluginSource`
|
|
29
|
+
* drops it: "kind is the producer's own kind and whose `plugin` field is
|
|
30
|
+
* dropped"). `sourceLabel` renders `plugin:dsh-chapters` for BOTH the retired
|
|
31
|
+
* wrapper and the colon kind — byte-identically — so omission markers in
|
|
32
|
+
* already-archived chapters keep matching.
|
|
33
|
+
* INVARIANT 2: exactly one event, at seq 0. Never a slice of the parent's log.
|
|
34
|
+
*/
|
|
35
|
+
import type { SessionEventLike } from './types.ts';
|
|
36
|
+
/** Our producer id. Changing this orphans every already-seeded continuation. */
|
|
37
|
+
export declare const NOTICE_PLUGIN = "dsh-chapters";
|
|
38
|
+
/** The v4 producer-owned source kind (issue #43). `plugin:<producer>` is the
|
|
39
|
+
* form the 0.2.x kernel itself admits for third-party producers; the bare
|
|
40
|
+
* `plugin` wrapper it retired. */
|
|
41
|
+
export declare const NOTICE_SOURCE_KIND = "plugin:dsh-chapters";
|
|
42
|
+
/** The section name our TOC rides under inside the snapshot. */
|
|
43
|
+
export declare const NOTICE_SECTION = "chapters:toc";
|
|
44
|
+
/**
|
|
45
|
+
* The kinds this plugin may emit, pinned to what the installed v4 kernel
|
|
46
|
+
* admits (seed/load boundary + persistence row admission). The retired v3
|
|
47
|
+
* `plugin` wrapper is deliberately NOT in this list — it is refused below
|
|
48
|
+
* with the kernel's own refusal text.
|
|
49
|
+
*/
|
|
50
|
+
export declare const ACCEPTED_SOURCE_KINDS: readonly ["user", "plugin:dsh-chapters"];
|
|
51
|
+
/** `ContextForm` values, exactly as published (semantic, never visual). */
|
|
52
|
+
export declare const ACCEPTED_CONTEXT_FORMS: readonly ["instructions", "catalog", "snapshot", "notice", "relay", "recall"];
|
|
53
|
+
export interface TocNoticeOptions {
|
|
54
|
+
/** Fresh unique id per notice — the kernel rejects a seed message without one. */
|
|
55
|
+
newId: () => string;
|
|
56
|
+
time?: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build the one seed event for a continuation. Throws rather than emit an
|
|
60
|
+
* event the kernel would reject at seed time — a refusal here is cheap, a
|
|
61
|
+
* bricked log is not.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildTocNotice(tocText: string, opts: TocNoticeOptions): SessionEventLike;
|
|
64
|
+
/**
|
|
65
|
+
* Transcription of the kernel's seed-time checks (message-shape + v4 source
|
|
66
|
+
* admission + the recorded rejections). Kept as an exported predicate so tests
|
|
67
|
+
* pin our output against the same rules the host applies, and any future
|
|
68
|
+
* notice variant fails HERE, at the boundary of authorship. The v4 rows are
|
|
69
|
+
* drift-gated against the kernel's own admission function
|
|
70
|
+
* (`dsh-session-format-v3-to-v4` `assertV4RowAdmission`) in
|
|
71
|
+
* tests/unit/host-seed-contract.test.ts — this transcription is the fast lane,
|
|
72
|
+
* the kernel is the authority.
|
|
73
|
+
*/
|
|
74
|
+
export declare function assertKernelAcceptedUserMessage(event: SessionEventLike): void;
|