wyrm-mcp 7.2.0 → 7.2.2

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 (156) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.d.ts.map +1 -1
  4. package/dist/activation.js +1 -44
  5. package/dist/activation.js.map +1 -1
  6. package/dist/agent-daemon.js +4 -281
  7. package/dist/agent-loop.js +7 -332
  8. package/dist/analytics.js +13 -236
  9. package/dist/attribution.js +1 -49
  10. package/dist/audit.js +2 -457
  11. package/dist/auto-capture.js +3 -138
  12. package/dist/auto-orchestrator.js +1 -325
  13. package/dist/autoconfig.js +39 -840
  14. package/dist/buddy-runner.js +1 -109
  15. package/dist/buddy.js +14 -564
  16. package/dist/build-flags.js +1 -17
  17. package/dist/capabilities.js +3 -183
  18. package/dist/capture.js +1 -56
  19. package/dist/causality.js +6 -107
  20. package/dist/cli.js +20 -281
  21. package/dist/cloud/cli.js +5 -541
  22. package/dist/cloud/client.js +1 -221
  23. package/dist/cloud/crypto.js +1 -85
  24. package/dist/cloud/machine-id.js +2 -113
  25. package/dist/cloud/recovery.js +1 -60
  26. package/dist/cloud/sync-engine.js +7 -543
  27. package/dist/cloud-backup.js +5 -579
  28. package/dist/cloud-profile.js +1 -138
  29. package/dist/cloud-sync-entrypoint.js +1 -47
  30. package/dist/cloud-sync.js +2 -309
  31. package/dist/constellation.js +12 -168
  32. package/dist/context-build-budgeted.js +4 -144
  33. package/dist/context-ranking.js +1 -69
  34. package/dist/crypto.js +1 -179
  35. package/dist/daemon-write-endpoint.js +1 -290
  36. package/dist/daemon-writer.js +2 -406
  37. package/dist/database.js +43 -1110
  38. package/dist/deprecations.js +2 -162
  39. package/dist/design.js +13 -141
  40. package/dist/event-replication.js +1 -112
  41. package/dist/events-sse.js +7 -43
  42. package/dist/events.js +6 -238
  43. package/dist/failure-patterns.js +42 -659
  44. package/dist/federation.js +12 -236
  45. package/dist/goals.js +13 -101
  46. package/dist/golden.js +3 -355
  47. package/dist/handlers/agent.js +4 -165
  48. package/dist/handlers/alias-adapters.js +1 -129
  49. package/dist/handlers/aliases.js +1 -171
  50. package/dist/handlers/audit.js +1 -87
  51. package/dist/handlers/boundary.js +1 -221
  52. package/dist/handlers/capture.js +73 -1109
  53. package/dist/handlers/causality.js +7 -114
  54. package/dist/handlers/cloud.js +85 -382
  55. package/dist/handlers/companion.js +28 -459
  56. package/dist/handlers/datalake.js +7 -187
  57. package/dist/handlers/dispatch-context.js +0 -22
  58. package/dist/handlers/entity.js +25 -256
  59. package/dist/handlers/events.js +16 -335
  60. package/dist/handlers/failure.js +13 -340
  61. package/dist/handlers/goals.js +4 -296
  62. package/dist/handlers/intelligence.js +126 -674
  63. package/dist/handlers/invoicing.js +1 -70
  64. package/dist/handlers/mcpclient.js +6 -137
  65. package/dist/handlers/orchestration.js +40 -125
  66. package/dist/handlers/output-schemas.js +1 -24
  67. package/dist/handlers/presence.js +3 -99
  68. package/dist/handlers/project.js +28 -182
  69. package/dist/handlers/prompts.js +6 -157
  70. package/dist/handlers/quest.js +4 -224
  71. package/dist/handlers/recall.js +11 -218
  72. package/dist/handlers/registry.js +1 -167
  73. package/dist/handlers/resources.js +1 -288
  74. package/dist/handlers/review.js +11 -74
  75. package/dist/handlers/run.js +17 -487
  76. package/dist/handlers/search.js +15 -326
  77. package/dist/handlers/session.js +28 -615
  78. package/dist/handlers/share.js +8 -184
  79. package/dist/handlers/shims.js +1 -464
  80. package/dist/handlers/skill.js +67 -449
  81. package/dist/handlers/survivors.js +1 -120
  82. package/dist/handlers/symbols.js +8 -109
  83. package/dist/handlers/syncops.js +4 -302
  84. package/dist/handlers/types.js +1 -27
  85. package/dist/harvest.js +5 -191
  86. package/dist/hours.js +7 -156
  87. package/dist/http-auth.js +3 -321
  88. package/dist/http-fast.js +21 -1137
  89. package/dist/icons.js +1 -47
  90. package/dist/index.js +2 -924
  91. package/dist/indexer.js +4 -145
  92. package/dist/intelligence.js +31 -261
  93. package/dist/internal-dispatch.js +3 -212
  94. package/dist/keyset.js +1 -110
  95. package/dist/knowledge-graph.js +12 -176
  96. package/dist/license.d.ts +11 -0
  97. package/dist/license.d.ts.map +1 -1
  98. package/dist/license.js +2 -414
  99. package/dist/license.js.map +1 -1
  100. package/dist/logger.js +2 -199
  101. package/dist/maintenance.js +2 -148
  102. package/dist/mcp-client.js +6 -262
  103. package/dist/memory-artifacts.js +30 -449
  104. package/dist/migrate-prompt.js +2 -124
  105. package/dist/migrations.js +40 -655
  106. package/dist/performance.js +1 -228
  107. package/dist/presence.js +11 -140
  108. package/dist/priority-embed.js +5 -164
  109. package/dist/providers/embedding-provider.js +1 -196
  110. package/dist/readonly-gate.js +1 -29
  111. package/dist/rehydration.js +9 -157
  112. package/dist/reindex.js +1 -88
  113. package/dist/render-target.js +21 -514
  114. package/dist/render.js +4 -280
  115. package/dist/repl-guard.js +1 -173
  116. package/dist/replication-daemon-entrypoint.js +1 -31
  117. package/dist/replication-daemon.js +2 -262
  118. package/dist/resilience.js +1 -591
  119. package/dist/reverse-bridge.js +5 -360
  120. package/dist/security.js +1 -244
  121. package/dist/session-seen.js +3 -51
  122. package/dist/setup.js +1 -260
  123. package/dist/skill-author.js +5 -168
  124. package/dist/spec-kit.js +1 -191
  125. package/dist/sqlite-busy.js +1 -154
  126. package/dist/statusline.js +11 -315
  127. package/dist/sub-agent.js +13 -262
  128. package/dist/summarizer.js +13 -139
  129. package/dist/symbols.js +7 -283
  130. package/dist/sync.js +5 -359
  131. package/dist/tasks-dispatch.js +1 -84
  132. package/dist/tasks.js +1 -282
  133. package/dist/token-budget.js +1 -143
  134. package/dist/tool-analytics.js +7 -129
  135. package/dist/tool-annotations.js +1 -365
  136. package/dist/tool-manifest-v2.json +1 -1
  137. package/dist/tool-manifest.json +1 -1
  138. package/dist/tool-profiles.js +1 -75
  139. package/dist/trace-harvest.js +6 -244
  140. package/dist/types.js +1 -30
  141. package/dist/ui-dashboard.js +41 -50
  142. package/dist/ulid.js +1 -81
  143. package/dist/validate.js +1 -129
  144. package/dist/vault.js +1 -534
  145. package/dist/vectors.js +3 -184
  146. package/dist/version-check.js +4 -136
  147. package/dist/visibility.js +19 -155
  148. package/dist/wyrm-cli.js +98 -2451
  149. package/dist/wyrm-cli.js.map +1 -1
  150. package/dist/wyrm-guard.js +14 -424
  151. package/dist/wyrm-loop.js +3 -150
  152. package/dist/wyrm-manifest.json +1 -1
  153. package/dist/wyrm-statusline-daemon.js +1 -11
  154. package/dist/wyrm-statusline.js +4 -56
  155. package/dist/wyrm-ui.js +9 -77
  156. package/package.json +4 -2
@@ -1,624 +1,37 @@
1
- /**
2
- * Session domain ToolSpec contract v2 (v7 F3 T026, hot-path extraction).
3
- *
4
- * wyrm_session_start / wyrm_session_update / wyrm_session_rehydrate /
5
- * wyrm_session_prime, moved VERBATIM from the index.ts dispatch switch +
6
- * buildAllTools(). The wyrm_session noun shim keeps routing action=start|
7
- * update|rehydrate onto these names (registry checked before switch — zero
8
- * shim changes), and wyrm_session_prime stays the first-class survivor.
9
- *
10
- * session_prime: the five 6.x sections (truths / scaffold / memory brief /
11
- * quests / vault advisory) are now the canonical structured body
12
- * (`sections[]` + the count fields the summary derives from); the text
13
- * channel is the same joined brief. Section CONTENT is institutional 6.x
14
- * prose built by the subsystems (incl. their emoji headers) — that is body
15
- * DATA, not renderer decoration, so it is byte-identical to 6.x; only the
16
- * summary line's brand glyph moved behind WYRM_FANCY (T019 policy).
17
- *
18
- * v7 F3 (T028) — FLEET MODE (spec FR-5, §7 criterion 8): when `run_id` is
19
- * given, the FIRST prime of a (run_id, role) compiles a byte-stable
20
- * role-sliced brief under a token budget (default 1,200; approx-4cpt, the
21
- * token-budget.ts convention) and CAS-caches it in the run_briefs table
22
- * (migration 23: INSERT OR IGNORE, then EVERY caller reads the row back), so
23
- * 12 simultaneous primes — across processes — return the byte-identical
24
- * cached prefix. Nondeterminism sources are pinned at compile time:
25
- * - NO staleness prefixes (computeStaleness reads Date.now());
26
- * - NO vault advisory section (vaultAdvisory reads host vault state);
27
- * - quest ordering carries an explicit `id` tiebreak;
28
- * - truth ordering is getCurrent's ORDER BY category, key (already stable).
29
- * Sections beyond the budget are STUBBED as plain-text `wyrm://` references —
30
- * the T034 resource layer will turn those into real resource links (the seam
31
- * is the STUB_REFS map below); 7.0 deliberately ships them as text only.
32
- *
33
- * FEATHERWEIGHT opt #2 — DISTRIBUTE-ONCE (`for_spawn:true`, advances T038 in
34
- * spirit: the role brief is RENDERED ONCE for OUT-OF-BAND delivery instead of
35
- * being re-sent to every agent over the wire). The orchestrator primes one
36
- * (run_id, role) with for_spawn:true and gets the brief back as clean
37
- * embeddable markdown (`brief`, byte-EQUAL to the agent-side prime's
38
- * content[0].text — same CAS compile, same primeTemplate render, just packaged
39
- * for embedding) + a `brief_hash` + the (run_id, role) cache key. It embeds
40
- * that brief in the spawn-prompt PREFIX of every same-role agent (where they
41
- * genuinely share a cacheable prefix), so those agents never call prime — the
42
- * cacheable share of the fleet's prime cost becomes NEVER-SENT bytes. A
43
- * forgetful orchestrator that skips for_spawn degrades to today's per-agent
44
- * prime against the SAME CAS row (the byte-stable fallback) — never below the
45
- * S3 floor. for_spawn is purely additive: same compile, alternate envelope.
46
- *
47
- * @copyright 2026 Ghost Protocol (Pvt) Ltd.
48
- * @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
49
- */
50
- import { basename } from 'path';
51
- import { createHash } from 'crypto';
52
- import { blockText } from './types.js';
53
- import { TOOL_ANNOTATIONS } from '../tool-annotations.js';
54
- import { renderResult, withGlyph, cacheKeyFor } from '../render.js';
55
- import { computeStaleness } from '../intelligence.js';
56
- import { ValidationError, asInt, asString } from '../validate.js';
57
- import { sanitizeActorId } from './boundary.js';
58
- import { makeEstimator } from '../token-budget.js';
59
- const CATEGORY_LABELS_SP = {
60
- tech_stack: '🛠️ Tech Stack', architecture: '🏗️ Architecture',
61
- constraint: '🚧 Constraints', decision: '📋 Decisions',
62
- contact: '👤 Contacts', other: '📌 Other Facts',
63
- };
64
- /** Section 1 — ground truths. `includeStaleness:false` is the T028 fleet pin:
65
- * computeStaleness() reads Date.now(), which would make the compiled brief
66
- * time-dependent. Output is byte-identical to 6.x when true. */
67
- function buildTruthsSectionText(currentTruths, includeStaleness) {
68
- let spTruthsText = '### 📚 Project Ground Truths _(treat as authoritative — do not infer or contradict)_\n';
69
- const spByCategory = new Map();
70
- for (const t of currentTruths) {
71
- if (!spByCategory.has(t.category))
72
- spByCategory.set(t.category, []);
73
- spByCategory.get(t.category).push(t);
74
- }
75
- for (const [cat, items] of spByCategory) {
76
- spTruthsText += `**${CATEGORY_LABELS_SP[cat] ?? cat}:**\n`;
77
- for (const t of items) {
78
- let stalePrefix = '';
79
- if (includeStaleness) {
80
- const staleness = computeStaleness(t);
81
- if (staleness !== null && staleness > 0.7)
82
- stalePrefix = '[⚠️ STALE] ';
83
- }
84
- spTruthsText += `- ${stalePrefix}**${t.key}:** ${t.value}${t.rationale ? ` _(${t.rationale})_` : ''}\n`;
85
- }
86
- }
87
- return spTruthsText;
88
- }
89
- // T028: explicit `id` tiebreak appended to the 6.x priority ordering — same
90
- // priority buckets, but ties are now deterministic (a fleet-brief pin that is
91
- // strictly-stable for the default path too; no consumer locks quest order).
92
- const ACTIVE_QUESTS_SQL = `
1
+ import{basename as K}from"path";import{createHash as z}from"crypto";import{blockText as U}from"./types.js";import{TOOL_ANNOTATIONS as v}from"../tool-annotations.js";import{renderResult as w,withGlyph as N,cacheKeyFor as q}from"../render.js";import{computeStaleness as X}from"../intelligence.js";import{ValidationError as Z,asInt as ee,asString as te}from"../validate.js";import{sanitizeActorId as se}from"./boundary.js";import{makeEstimator as ne}from"../token-budget.js";const oe={tech_stack:"\u{1F6E0}\uFE0F Tech Stack",architecture:"\u{1F3D7}\uFE0F Architecture",constraint:"\u{1F6A7} Constraints",decision:"\u{1F4CB} Decisions",contact:"\u{1F464} Contacts",other:"\u{1F4CC} Other Facts"};function G(e,t){let n=`### \u{1F4DA} Project Ground Truths _(treat as authoritative \u2014 do not infer or contradict)_
2
+ `;const s=new Map;for(const o of e)s.has(o.category)||s.set(o.category,[]),s.get(o.category).push(o);for(const[o,r]of s){n+=`**${oe[o]??o}:**
3
+ `;for(const i of r){let p="";if(t){const u=X(i);u!==null&&u>.7&&(p="[\u26A0\uFE0F STALE] ")}n+=`- ${p}**${i.key}:** ${i.value}${i.rationale?` _(${i.rationale})_`:""}
4
+ `}}return n}const J=`
93
5
  SELECT * FROM quests
94
6
  WHERE project_id = ? AND status = 'pending'
95
7
  ORDER BY
96
8
  CASE priority WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 ELSE 4 END,
97
9
  id
98
10
  LIMIT 5
99
- `;
100
- /** Section 4 active quests (byte-identical to the 6.x block). */
101
- function buildQuestsSectionText(activeQuests) {
102
- const priorityEmoji = { critical: '🔴', high: '🟠', medium: '🟡', low: '🟢' };
103
- let questsBlock = '### 🎯 Active Quests\n';
104
- for (const q of activeQuests) {
105
- questsBlock += `- ${priorityEmoji[q.priority] ?? '•'} #${q.id}: ${q.title}\n`;
106
- }
107
- return questsBlock;
108
- }
109
- /** The 6.x three-branch project resolution (by_id / by_name / latest-active),
110
- * shared verbatim by the default and fleet paths. */
111
- function resolvePrimeProject(store, db, spId, spName) {
112
- if (spId)
113
- return store.getProjectById(spId);
114
- if (spName) {
115
- return db.prepare('SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1').get(`%${spName}%`);
116
- }
117
- return db.prepare(`
11
+ `;function Q(e){const t={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F7E2}"};let n=`### \u{1F3AF} Active Quests
12
+ `;for(const s of e)n+=`- ${t[s.priority]??"\u2022"} #${s.id}: ${s.title}
13
+ `;return n}function P(e,t,n,s){return n?e.getProjectById(n):s?t.prepare("SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1").get(`%${s}%`):t.prepare(`
118
14
  SELECT p.* FROM projects p
119
15
  JOIN sessions s ON s.project_id = p.id
120
16
  ORDER BY s.created_at DESC LIMIT 1
121
- `).get();
122
- }
123
- /** The 6.x not-found advisory (isError per the T026 goals precedent). */
124
- function primeProjectNotFound(store) {
125
- const allProjects = store.getAllProjects(20);
126
- if (allProjects.length === 0) {
127
- return { content: [{ type: "text", text: `No projects found. Run \`wyrm_scan_projects\` first.` }], isError: true };
128
- }
129
- const candidates = allProjects.map(p => `- ${p.name} (ID: ${p.id})`).join('\n');
130
- return { content: [{ type: "text", text: `Project not found. Available projects:\n${candidates}` }], isError: true };
131
- }
132
- /** The session_prime summary line the one-line roll-up the WYRM_CHANNEL=
133
- * structured seam keeps on the text channel; the section bodies (the actual
134
- * knowledge) ride structuredContent (`body.sections`) unchanged. */
135
- const primeSummary = (b, g) => {
136
- const sessionNote = b.session_id !== null ? `\nSession logged: #${b.session_id}` : '';
137
- return withGlyph(g.brand, `Wyrm primed for **${b.project_name}** | ${b.truth_count} truth${b.truth_count !== 1 ? 's' : ''} - ` +
138
- `${b.quest_count} quest${b.quest_count !== 1 ? 's' : ''} - ` +
139
- `${b.memory_count} memor${b.memory_count !== 1 ? 'ies' : 'y'}${sessionNote}`);
140
- };
141
- /** The session_prime text template pure (body, glyphs), shared by both
142
- * paths so cached fleet bodies re-render to the exact same text channel. */
143
- const primeTemplate = (b, g) => {
144
- const summaryLine = primeSummary(b, g);
145
- return b.sections.length > 0
146
- ? b.sections.map((s) => s.text).join('\n\n---\n\n') + '\n\n' + summaryLine
147
- : summaryLine + '\n\n_No memory stored yet -- start working and Wyrm will learn._';
148
- };
149
- /** Shared render-options for both session_prime emit paths. */
150
- const PRIME_RENDER_OPTS = { summary: primeSummary };
151
- /** Spec 018 recovered-context savings accounting (both paths, best-effort). */
152
- async function logPrimeSavings(db, projectId, response) {
153
- try {
154
- const { logSavings } = await import('../statusline.js');
155
- const recoveredChars = blockText(response.content[0]).length;
156
- // ~4 chars/token is a conservative English estimate.
157
- const recoveredTokens = Math.round(recoveredChars / 4);
158
- // Use last active session for this project if available.
159
- const lastSession = db.prepare(`SELECT id FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1`).get(projectId);
160
- logSavings(db, 'wyrm_session_prime', 'recovered_context', recoveredTokens, lastSession?.id);
161
- }
162
- catch { /* best-effort */ }
163
- }
164
- // ── T028 fleet mode ─────────────────────────────────────────────────────────
165
- /** Over-budget sections collapse to a plain-text `wyrm://` reference naming
166
- * the follow-on tool. THE T034 SEAM: when the resource layer lands, these
167
- * become real `resourceLink` returns — 7.0 ships them as text on purpose. */
168
- const STUB_REFS = {
169
- truths: { path: 'truths', tool: 'wyrm_truth_get' },
170
- scaffold: { path: 'scaffolds', tool: 'wyrm_context_build' },
171
- memory: { path: 'memory', tool: 'wyrm_recall' },
172
- quests: { path: 'quests', tool: 'wyrm_all_quests' },
173
- };
174
- /** Tokens reserved for the summary line + section separators before any
175
- * section is admitted (measured worst-case summary ≈40 tokens at 4cpt). */
176
- const PRIME_SUMMARY_RESERVE = 48;
177
- const DEFAULT_PRIME_BUDGET = 1200;
178
- /**
179
- * Fleet-mode session_prime (T028). Compile-once, CAS-insert, read-back-winner:
180
- * byte-equality across N concurrent primes of one (run_id, role) holds even
181
- * across processes, because every caller renders the row that WON the
182
- * INSERT OR IGNORE — a loser's own compile is discarded, never returned.
183
- * First-caller-wins applies to the args too (task/project/log_session/
184
- * token_budget shape only the compiled brief); later callers with different
185
- * args still receive the cached prefix — that IS the stability contract.
186
- */
187
- async function fleetPrime(args, { store, raw, memory, truths, scaffolds, cache }) {
188
- const { project_id: spId, project_name: spName, task: spTask, log_session: spLog } = args;
189
- const runId = sanitizeActorId(args.run_id);
190
- if (runId === null)
191
- throw new ValidationError('run_id', 'must be <=64 printable-ASCII chars (no ";")');
192
- // role is half the cache key — '' is the role-less slice.
193
- const role = asString('role', args.role, { maxLen: 200 }) ?? '';
194
- const budget = asInt('token_budget', args.token_budget, { min: 1 }) ?? DEFAULT_PRIME_BUDGET;
195
- const db = raw();
196
- const readBrief = () => db.prepare('SELECT body_json FROM run_briefs WHERE run_id = ? AND role = ?').get(runId, role);
197
- // Security pass #1 (confirmed finding): the cache key is (run_id, role) —
198
- // a cache hit previously skipped project resolution ENTIRELY, so a prime
199
- // naming a DIFFERENT project under the same key silently received the
200
- // first project's ground truths, memory, and quests. An EXPLICITLY
201
- // requested project is now resolved BEFORE the cache read and checked
202
- // against the project_id the winning compile baked into body_json; a
203
- // mismatch is a visible error, never a cross-project leak. Project-LESS
204
- // primes still take the cached row untouched — first-caller-wins applies
205
- // to the args (incl. the project), which IS the byte-stability contract,
206
- // and resolving the latest-active-session branch on a hit would itself
207
- // be a mid-run nondeterminism source.
208
- const requested = (spId || spName) ? resolvePrimeProject(store, db, spId, spName) : undefined;
209
- if ((spId || spName) && !requested)
210
- return primeProjectNotFound(store);
211
- let row = readBrief();
212
- if (row && requested) {
213
- const cached = JSON.parse(row.body_json);
214
- if (cached.project_id !== requested.id) {
215
- return {
216
- content: [{
217
- type: 'text',
218
- text: `Fleet brief for (run ${runId}, role '${role}') is pinned to project '${cached.project_name}' (ID: ${cached.project_id}) — refusing to serve it for requested project '${requested.name}' (ID: ${requested.id}). Use a distinct role for a second project, or prime without run_id.`,
219
- }],
220
- isError: true,
221
- };
222
- }
223
- }
224
- if (!row) {
225
- const project = requested ?? resolvePrimeProject(store, db, spId, spName);
226
- if (!project)
227
- return primeProjectNotFound(store); // never cached — a later prime may resolve
228
- // Same section compilation as the default path, nondeterminism pinned
229
- // (no staleness prefixes, no vault advisory — see the module header).
230
- const compiled = [];
231
- const currentTruths = truths.getCurrent(project.id);
232
- if (currentTruths.length > 0) {
233
- compiled.push({ kind: 'truths', text: buildTruthsSectionText(currentTruths, false).slice(0, 1200) });
234
- }
235
- // The ROLE SLICE: role drives scaffold + memory selection when the
236
- // orchestrator didn't pass an explicit task.
237
- const fleetTask = spTask ?? (role || undefined);
238
- if (fleetTask) {
239
- const scaffoldMatch = scaffolds.findBest(fleetTask, project.id, 0.3);
240
- if (scaffoldMatch)
241
- compiled.push({ kind: 'scaffold', text: scaffolds.formatForContext(scaffoldMatch, 1500) });
242
- }
243
- const spBrief = memory.buildContextBrief(project.id, fleetTask ?? 'general', { maxItems: 10, minConfidence: 0.3 });
244
- if (spBrief.text)
245
- compiled.push({ kind: 'memory', text: spBrief.text.slice(0, 2000) });
246
- const activeQuests = db.prepare(ACTIVE_QUESTS_SQL).all(project.id);
247
- if (activeQuests.length > 0) {
248
- compiled.push({ kind: 'quests', text: buildQuestsSectionText(activeQuests).slice(0, 800) });
249
- }
250
- // Token budget (approx-4cpt, the token-budget.ts convention): greedy
251
- // in section order; an over-budget section becomes its wyrm:// stub.
252
- // Stubs always emit (a pathological budget floors at stubs + summary —
253
- // the brief never silently loses a whole section).
254
- const est = makeEstimator();
255
- const sections = [];
256
- let used = PRIME_SUMMARY_RESERVE;
257
- for (const s of compiled) {
258
- const cost = est.count(s.text) + 2; // +2 ≈ the '\n\n---\n\n' separator
259
- if (used + cost <= budget) {
260
- sections.push(s);
261
- used += cost;
262
- }
263
- else {
264
- const ref = STUB_REFS[s.kind] ?? { path: s.kind, tool: 'wyrm_search' };
265
- const stub = `[beyond token_budget] wyrm://project/${project.id}/${ref.path} -- fetch via ${ref.tool}`;
266
- sections.push({ kind: s.kind, text: stub });
267
- used += est.count(stub) + 2;
268
- }
269
- }
270
- // log_session fires on the COMPILING call only — the logged session_id is
271
- // baked into the cached body; cache hits never insert another row.
272
- let sessionId = null;
273
- if (spLog) {
274
- sessionId = store.createSession(project.id, {
275
- objectives: spTask ?? 'Session started via wyrm_session_prime',
276
- summary: '',
277
- }).id;
278
- }
279
- const body = {
280
- project_id: project.id,
281
- project_name: project.name,
282
- truth_count: truths.getStats(project.id).current,
283
- quest_count: activeQuests.length,
284
- memory_count: memory.getStats(project.id).total,
285
- session_id: sessionId,
286
- sections,
287
- run_id: runId,
288
- role,
289
- token_budget: budget,
290
- };
291
- db.prepare('INSERT OR IGNORE INTO run_briefs (run_id, role, body_json, token_budget) VALUES (?, ?, ?, ?)').run(runId, role, JSON.stringify(body), budget);
292
- row = readBrief();
293
- }
294
- // Render the WINNER's body — text re-derives through the shared template,
295
- // so the text channel is byte-identical for every caller too.
296
- const winner = JSON.parse(row.body_json);
297
- const response = renderResult(winner, primeTemplate, PRIME_RENDER_OPTS);
298
- // FEATHERWEIGHT opt #2 — for_spawn: same CAS row, same compile, packaged for
299
- // OUT-OF-BAND embedding (advances T038 in spirit). The brief is the payload
300
- // the orchestrator embeds RAW into each same-role agent's spawn prefix —
301
- // there is NO structuredContent channel in a spawn prompt, so the brief MUST
302
- // carry the FULL prime (truths/scaffold/memory/quests), never the renderer's
303
- // WYRM_CHANNEL=structured summary collapse. We therefore render a dedicated
304
- // exemptStructured response for the brief (mirroring session_rehydrate, whose
305
- // text IS its payload): under WYRM_CHANNEL=both this is byte-identical to the
306
- // agent-side prime's content[0].text (the distribute-once == fallback
307
- // guarantee); under WYRM_CHANNEL=structured the agent-side fallback collapses
308
- // its text channel but still ships the full body on structuredContent, while
309
- // the embedded brief — which has no such body to fall back on — stays full.
310
- const forSpawn = args.for_spawn === true;
311
- if (forSpawn) {
312
- const briefResponse = renderResult(winner, primeTemplate, { ...PRIME_RENDER_OPTS, exemptStructured: true });
313
- const brief = blockText(briefResponse.content[0]);
314
- const briefHash = createHash('sha256').update(brief, 'utf8').digest('hex');
315
- // The for_spawn envelope is a SUPERSET of the declared session_prime
316
- // outputSchema (project_id/project_name/session_id/sections required) — the
317
- // schema-required fields stay so a strict client validates it, and the
318
- // distribute-once extras (brief / brief_hash / cache_key) ride alongside
319
- // per the T022 lean-union rule (undeclared extras on structuredContent).
320
- const spawnBody = {
321
- project_id: winner.project_id,
322
- project_name: winner.project_name,
323
- session_id: winner.session_id ?? null,
324
- sections: winner.sections,
325
- for_spawn: true,
326
- run_id: runId,
327
- role,
328
- brief,
329
- brief_hash: briefHash,
330
- brief_chars: brief.length,
331
- cache_key: { table: 'run_briefs', run_id: runId, role },
332
- };
333
- await logPrimeSavings(db, winner.project_id, briefResponse);
334
- // The for_spawn envelope is per-(args) like any read; publish under the
335
- // same dispatcher key convention so a repeat for_spawn read is cache-hot.
336
- const spawnResult = {
337
- content: [{ type: 'text', text: brief }],
338
- structuredContent: spawnBody,
339
- };
340
- cache.set(cacheKeyFor('wyrm_session_prime', JSON.stringify(args)), spawnResult, 30000);
341
- return spawnResult;
342
- }
343
- await logPrimeSavings(db, winner.project_id, response);
344
- // READ_ONLY_TOOLS cache publish — dispatcher key convention.
345
- cache.set(cacheKeyFor('wyrm_session_prime', JSON.stringify(args)), response, 30000);
346
- return response;
347
- }
348
- export const sessionToolSpecs = [
349
- {
350
- name: "wyrm_session_start",
351
- description: "Start or continue a session for a project",
352
- inputSchema: {
353
- type: "object",
354
- properties: {
355
- projectPath: { type: "string", description: "Project path" },
356
- objectives: { type: "string", description: "Session objectives" },
357
- },
358
- required: ["projectPath"],
359
- },
360
- outputSchema: {
361
- type: "object",
362
- properties: {
363
- session_id: { type: "integer" },
364
- project: { type: "string" },
365
- date: { type: "string" },
366
- objectives: { type: ["string", "null"] },
367
- },
368
- required: ["session_id", "project", "date", "objectives"],
369
- },
370
- annotations: TOOL_ANNOTATIONS.wyrm_session_start,
371
- aliases: [],
372
- handler: (args, { store }) => {
373
- const { projectPath, objectives } = args;
374
- let project = store.getProject(projectPath);
375
- if (!project) {
376
- // Auto-register project
377
- project = store.registerProject(basename(projectPath), projectPath);
378
- }
379
- let session = store.getTodaySession(project.id);
380
- if (!session) {
381
- session = store.createSession(project.id, { objectives: objectives || '' });
382
- }
383
- else if (objectives) {
384
- session = store.updateSession(session.id, {
385
- objectives: session.objectives ? `${session.objectives}\n${objectives}` : objectives,
386
- });
387
- }
388
- // Archive old sessions
389
- store.archiveOldSessions(project.id, 10);
390
- const body = {
391
- session_id: session.id,
392
- project: project.name,
393
- date: session.date,
394
- objectives: session.objectives || null,
395
- };
396
- return renderResult(body, (b, g) => withGlyph(g.brand, `Session ${b.session_id} for ${b.project}`) + '\n' +
397
- `**Date:** ${b.date}\n` +
398
- `**Objectives:** ${b.objectives ?? 'None set'}`);
399
- },
400
- },
401
- {
402
- name: "wyrm_session_update",
403
- description: "Update the current session with completed work, issues, or notes",
404
- inputSchema: {
405
- type: "object",
406
- properties: {
407
- projectPath: { type: "string" },
408
- completed: { type: "string", description: "What was completed" },
409
- issues: { type: "string" },
410
- commits: { type: "string", description: "Git commits made" },
411
- notes: { type: "string" },
412
- },
413
- required: ["projectPath"],
414
- },
415
- outputSchema: {
416
- type: "object",
417
- properties: {
418
- session_id: { type: "integer" },
419
- project: { type: "string" },
420
- updated: { type: "array", items: { type: "string", enum: ["completed", "issues", "commits", "notes"] } },
421
- },
422
- required: ["session_id", "project", "updated"],
423
- },
424
- annotations: TOOL_ANNOTATIONS.wyrm_session_update,
425
- aliases: [],
426
- handler: (args, { store, indexing }) => {
427
- const { projectPath, completed, issues, commits, notes } = args;
428
- const project = store.getProject(projectPath);
429
- if (!project) {
430
- // T026: was a non-error "Project not found" text in 6.x — domain
431
- // not-founds are isError per the goals precedent (schema-exempt).
432
- return { content: [{ type: "text", text: "Project not found" }], isError: true };
433
- }
434
- let session = store.getTodaySession(project.id);
435
- if (!session) {
436
- session = store.createSession(project.id, {});
437
- }
438
- const updates = {};
439
- if (completed)
440
- updates.completed = session.completed ? `${session.completed}\n${completed}` : completed;
441
- if (issues)
442
- updates.issues = session.issues ? `${session.issues}\n${issues}` : issues;
443
- if (commits)
444
- updates.commits = session.commits ? `${session.commits}\n${commits}` : commits;
445
- if (notes)
446
- updates.notes = session.notes ? `${session.notes}\n${notes}` : notes;
447
- session = store.updateSession(session.id, updates);
448
- indexing()?.enqueue('session', session.id, project.id);
449
- const body = { session_id: session.id, project: project.name, updated: Object.keys(updates) };
450
- return renderResult(body, (b, g) => withGlyph(g.brand, `Session updated for ${b.project}`));
451
- },
452
- },
453
- {
454
- name: "wyrm_session_rehydrate",
455
- description: "Lossless session rehydration. Given a past session ID, returns a complete briefing markdown a fresh AI agent can ingest to inherit prior state: objectives, completed work, notes, summary, current ground truths, open quests, validated patterns (artifacts), unresolved failure patterns. Cross-session continuity no other AI memory tool offers.",
456
- inputSchema: {
457
- type: "object",
458
- properties: {
459
- session_id: { type: "number", description: "ID of the session to rehydrate" },
460
- include_artifacts: { type: "boolean", description: "Include memory artifacts (default true)" },
461
- include_failures: { type: "boolean", description: "Include unresolved failure patterns (default true)" },
462
- max_truth_chars: { type: "number", description: "Cap on chars used for ground truths (default 2000)" },
463
- },
464
- required: ["session_id"],
465
- },
466
- outputSchema: {
467
- type: "object",
468
- properties: {
469
- session_id: { type: "integer" },
470
- project_name: { type: "string" },
471
- project_path: { type: "string" },
472
- briefing_markdown: { type: "string" },
473
- context_chars: { type: "integer" },
474
- attached: {
475
- type: "object",
476
- properties: {
477
- quests: { type: "integer" },
478
- truths: { type: "integer" },
479
- artifacts: { type: "integer" },
480
- failures: { type: "integer" },
481
- },
482
- required: ["quests", "truths", "artifacts", "failures"],
483
- },
484
- },
485
- required: ["session_id", "project_name", "project_path", "briefing_markdown", "context_chars", "attached"],
486
- },
487
- annotations: TOOL_ANNOTATIONS.wyrm_session_rehydrate,
488
- aliases: [],
489
- handler: (args, { rehydration }) => {
490
- const { session_id, include_artifacts, include_failures, max_truth_chars } = args;
491
- const brief = rehydration.rehydrate(session_id, {
492
- include_artifacts, include_failures, max_truth_chars,
493
- });
494
- if (!brief) {
495
- return { content: [{ type: "text", text: `Session #${session_id} not found.` }], isError: true };
496
- }
497
- // The briefing markdown IS the text channel (6.x behavior, byte-
498
- // identical); the body carries it plus the structured brief identity.
499
- // EXEMPT from WYRM_CHANNEL=structured shrink: the text channel here is
500
- // the payload (the body just wraps the same markdown), so collapsing it
501
- // to a summary line would drop the knowledge the model reads.
502
- return renderResult({ ...brief }, (b) => b.briefing_markdown, { exemptStructured: true });
503
- },
504
- },
505
- {
506
- name: "wyrm_session_prime",
507
- description: "Use first at session start - load everything you need to know before starting work, and catch me up on where we left off last time: ground truths, memory brief, reasoning scaffold, and active quests in one call. First call every agent makes in a run.",
508
- inputSchema: {
509
- type: "object",
510
- properties: {
511
- project_id: { type: "number" },
512
- project_name: { type: "string" },
513
- task: { type: "string", description: "Drives scaffold + memory selection" },
514
- log_session: { type: "boolean", description: "Also log a session row" },
515
- // T028 fleet mode (descriptions omitted under the 8K default-surface
516
- // pin — semantics in the tool description + run_id's frozen meaning).
517
- run_id: { type: "string", description: "Fleet: brief cached per (run_id, role)" },
518
- role: { type: "string" },
519
- token_budget: { type: "number" },
520
- for_spawn: { type: "boolean" },
521
- },
522
- },
523
- // T028: the truth/quest/memory count properties were dropped from the
524
- // DECLARED schema to fund the fleet-mode input params under the 8K pin
525
- // (the T027 handoff earmarked them); the body still carries the counts —
526
- // undeclared extras ride structuredContent (the T022 lean-union rule).
527
- outputSchema: {
528
- type: "object",
529
- properties: {
530
- project_id: { type: "integer" },
531
- project_name: { type: "string" },
532
- session_id: { type: ["integer", "null"] },
533
- sections: {
534
- type: "array",
535
- items: {
536
- type: "object",
537
- properties: {
538
- kind: { type: "string" },
539
- text: { type: "string" },
540
- },
541
- required: ["kind", "text"],
542
- },
543
- },
544
- },
545
- required: ["project_id", "project_name", "session_id", "sections"],
546
- },
547
- annotations: TOOL_ANNOTATIONS.wyrm_session_prime,
548
- aliases: [],
549
- handler: async (args, { store, raw, memory, truths, scaffolds, cache }) => {
550
- const { project_id: spId, project_name: spName, task: spTask, log_session: spLog } = args;
551
- // T028 fleet mode: run_id present → the byte-stable cached-brief path.
552
- if (args.run_id !== undefined && args.run_id !== null) {
553
- return fleetPrime(args, { store, raw, memory, truths, scaffolds, cache });
554
- }
555
- // Project resolution (shared 3-branch helper — T028 hoist, verbatim).
556
- const spProject = resolvePrimeProject(store, raw(), spId, spName);
557
- if (!spProject) {
558
- // T026: not-found paths are isError (goals precedent; was non-error
559
- // prose in 6.x — recorded deviation).
560
- return primeProjectNotFound(store);
561
- }
562
- const sections = [];
563
- // Section 1 — Ground Truths (up to 1200 chars), prefix stale truths
564
- const spCurrentTruths = truths.getCurrent(spProject.id);
565
- if (spCurrentTruths.length > 0) {
566
- sections.push({ kind: 'truths', text: buildTruthsSectionText(spCurrentTruths, true).slice(0, 1200) });
567
- }
568
- // Section 2 — Reasoning Scaffold (up to 1500 chars)
569
- if (spTask) {
570
- const scaffoldMatchSP = scaffolds.findBest(spTask, spProject.id, 0.3);
571
- if (scaffoldMatchSP) {
572
- sections.push({ kind: 'scaffold', text: scaffolds.formatForContext(scaffoldMatchSP, 1500) });
573
- }
574
- }
575
- // Section 3 — Memory Brief (up to 2000 chars)
576
- const spBrief = memory.buildContextBrief(spProject.id, spTask ?? 'general', { maxItems: 10, minConfidence: 0.3 });
577
- if (spBrief.text)
578
- sections.push({ kind: 'memory', text: spBrief.text.slice(0, 2000) });
579
- // Section 4 — Active Quests (up to 800 chars)
580
- const spActiveQuests = raw().prepare(ACTIVE_QUESTS_SQL).all(spProject.id);
581
- if (spActiveQuests.length > 0) {
582
- sections.push({ kind: 'quests', text: buildQuestsSectionText(spActiveQuests).slice(0, 800) });
583
- }
584
- // Section 5 — Credential vault advisory (global; only when actionable, so it
585
- // self-resolves once `wyrm vault setup` has been run). Lets Wyrm AUTOMATICALLY
586
- // guide the connecting AI to set credential storage up properly.
587
- try {
588
- const { vaultAdvisory } = await import('../vault.js');
589
- const vaultLines = vaultAdvisory();
590
- if (vaultLines.length > 0) {
591
- sections.push({ kind: 'vault', text: '### 🔐 Credential Vault\n' + vaultLines.map(l => `- ${l}`).join('\n') });
592
- }
593
- }
594
- catch { /* vault optional */ }
595
- // Optionally log session
596
- let sessionId = null;
597
- if (spLog) {
598
- const session = store.createSession(spProject.id, {
599
- objectives: spTask ?? 'Session started via wyrm_session_prime',
600
- summary: '',
601
- });
602
- sessionId = session.id;
603
- }
604
- const body = {
605
- project_id: spProject.id,
606
- project_name: spProject.name,
607
- truth_count: truths.getStats(spProject.id).current,
608
- quest_count: spActiveQuests.length,
609
- memory_count: memory.getStats(spProject.id).total,
610
- session_id: sessionId,
611
- sections,
612
- };
613
- const spResponse = renderResult(body, primeTemplate, PRIME_RENDER_OPTS);
614
- // Spec 018: log recovered-context savings. The text we just assembled
615
- // is context the AI would have had to re-elicit from the operator
616
- // turn by turn — counting it as ~recovered tokens (conservative).
617
- await logPrimeSavings(raw(), spProject.id, spResponse);
618
- // READ_ONLY_TOOLS cache publish — dispatcher key convention.
619
- cache.set(cacheKeyFor('wyrm_session_prime', JSON.stringify(args)), spResponse, 30000);
620
- return spResponse;
621
- },
622
- },
623
- ];
624
- //# sourceMappingURL=session.js.map
17
+ `).get()}function C(e){const t=e.getAllProjects(20);return t.length===0?{content:[{type:"text",text:"No projects found. Run `wyrm_scan_projects` first."}],isError:!0}:{content:[{type:"text",text:`Project not found. Available projects:
18
+ ${t.map(s=>`- ${s.name} (ID: ${s.id})`).join(`
19
+ `)}`}],isError:!0}}const V=(e,t)=>{const n=e.session_id!==null?`
20
+ Session logged: #${e.session_id}`:"";return N(t.brand,`Wyrm primed for **${e.project_name}** | ${e.truth_count} truth${e.truth_count!==1?"s":""} - ${e.quest_count} quest${e.quest_count!==1?"s":""} - ${e.memory_count} memor${e.memory_count!==1?"ies":"y"}${n}`)},L=(e,t)=>{const n=V(e,t);return e.sections.length>0?e.sections.map(s=>s.text).join(`
21
+
22
+ ---
23
+
24
+ `)+`
25
+
26
+ `+n:n+`
27
+
28
+ _No memory stored yet -- start working and Wyrm will learn._`},A={summary:V};async function D(e,t,n){try{const{logSavings:s}=await import("../statusline.js"),o=U(n.content[0]).length,r=Math.round(o/4),i=e.prepare("SELECT id FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(t);s(e,"wyrm_session_prime","recovered_context",r,i?.id)}catch{}}const re={truths:{path:"truths",tool:"wyrm_truth_get"},scaffold:{path:"scaffolds",tool:"wyrm_context_build"},memory:{path:"memory",tool:"wyrm_recall"},quests:{path:"quests",tool:"wyrm_all_quests"}},ie=48,ce=1200;async function ae(e,{store:t,raw:n,memory:s,truths:o,scaffolds:r,cache:i}){const{project_id:p,project_name:u,task:c,log_session:_}=e,a=se(e.run_id);if(a===null)throw new Z("run_id",'must be <=64 printable-ASCII chars (no ";")');const m=te("role",e.role,{maxLen:200})??"",g=ee("token_budget",e.token_budget,{min:1})??ce,f=n(),E=()=>f.prepare("SELECT body_json FROM run_briefs WHERE run_id = ? AND role = ?").get(a,m),h=p||u?P(t,f,p,u):void 0;if((p||u)&&!h)return C(t);let b=E();if(b&&h){const d=JSON.parse(b.body_json);if(d.project_id!==h.id)return{content:[{type:"text",text:`Fleet brief for (run ${a}, role '${m}') is pinned to project '${d.project_name}' (ID: ${d.project_id}) \u2014 refusing to serve it for requested project '${h.name}' (ID: ${h.id}). Use a distinct role for a second project, or prime without run_id.`}],isError:!0}}if(!b){const d=h??P(t,f,p,u);if(!d)return C(t);const y=[],k=o.getCurrent(d.id);k.length>0&&y.push({kind:"truths",text:G(k,!1).slice(0,1200)});const x=c??(m||void 0);if(x){const S=r.findBest(x,d.id,.3);S&&y.push({kind:"scaffold",text:r.formatForContext(S,1500)})}const $=s.buildContextBrief(d.id,x??"general",{maxItems:10,minConfidence:.3});$.text&&y.push({kind:"memory",text:$.text.slice(0,2e3)});const I=f.prepare(J).all(d.id);I.length>0&&y.push({kind:"quests",text:Q(I).slice(0,800)});const M=ne(),R=[];let O=ie;for(const S of y){const F=M.count(S.text)+2;if(O+F<=g)R.push(S),O+=F;else{const H=re[S.kind]??{path:S.kind,tool:"wyrm_search"},W=`[beyond token_budget] wyrm://project/${d.id}/${H.path} -- fetch via ${H.tool}`;R.push({kind:S.kind,text:W}),O+=M.count(W)+2}}let B=null;_&&(B=t.createSession(d.id,{objectives:c??"Session started via wyrm_session_prime",summary:""}).id);const Y={project_id:d.id,project_name:d.name,truth_count:o.getStats(d.id).current,quest_count:I.length,memory_count:s.getStats(d.id).total,session_id:B,sections:R,run_id:a,role:m,token_budget:g};f.prepare("INSERT OR IGNORE INTO run_briefs (run_id, role, body_json, token_budget) VALUES (?, ?, ?, ?)").run(a,m,JSON.stringify(Y),g),b=E()}const l=JSON.parse(b.body_json),j=w(l,L,A);if(e.for_spawn===!0){const d=w(l,L,{...A,exemptStructured:!0}),y=U(d.content[0]),k=z("sha256").update(y,"utf8").digest("hex"),x={project_id:l.project_id,project_name:l.project_name,session_id:l.session_id??null,sections:l.sections,for_spawn:!0,run_id:a,role:m,brief:y,brief_hash:k,brief_chars:y.length,cache_key:{table:"run_briefs",run_id:a,role:m}};await D(f,l.project_id,d);const $={content:[{type:"text",text:y}],structuredContent:x};return i.set(q("wyrm_session_prime",JSON.stringify(e)),$,3e4),$}return await D(f,l.project_id,j),i.set(q("wyrm_session_prime",JSON.stringify(e)),j,3e4),j}const je=[{name:"wyrm_session_start",description:"Start or continue a session for a project",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project path"},objectives:{type:"string",description:"Session objectives"}},required:["projectPath"]},outputSchema:{type:"object",properties:{session_id:{type:"integer"},project:{type:"string"},date:{type:"string"},objectives:{type:["string","null"]}},required:["session_id","project","date","objectives"]},annotations:v.wyrm_session_start,aliases:[],handler:(e,{store:t})=>{const{projectPath:n,objectives:s}=e;let o=t.getProject(n);o||(o=t.registerProject(K(n),n));let r=t.getTodaySession(o.id);r?s&&(r=t.updateSession(r.id,{objectives:r.objectives?`${r.objectives}
29
+ ${s}`:s})):r=t.createSession(o.id,{objectives:s||""}),t.archiveOldSessions(o.id,10);const i={session_id:r.id,project:o.name,date:r.date,objectives:r.objectives||null};return w(i,(p,u)=>N(u.brand,`Session ${p.session_id} for ${p.project}`)+`
30
+ **Date:** ${p.date}
31
+ **Objectives:** ${p.objectives??"None set"}`)}},{name:"wyrm_session_update",description:"Update the current session with completed work, issues, or notes",inputSchema:{type:"object",properties:{projectPath:{type:"string"},completed:{type:"string",description:"What was completed"},issues:{type:"string"},commits:{type:"string",description:"Git commits made"},notes:{type:"string"}},required:["projectPath"]},outputSchema:{type:"object",properties:{session_id:{type:"integer"},project:{type:"string"},updated:{type:"array",items:{type:"string",enum:["completed","issues","commits","notes"]}}},required:["session_id","project","updated"]},annotations:v.wyrm_session_update,aliases:[],handler:(e,{store:t,indexing:n})=>{const{projectPath:s,completed:o,issues:r,commits:i,notes:p}=e,u=t.getProject(s);if(!u)return{content:[{type:"text",text:"Project not found"}],isError:!0};let c=t.getTodaySession(u.id);c||(c=t.createSession(u.id,{}));const _={};o&&(_.completed=c.completed?`${c.completed}
32
+ ${o}`:o),r&&(_.issues=c.issues?`${c.issues}
33
+ ${r}`:r),i&&(_.commits=c.commits?`${c.commits}
34
+ ${i}`:i),p&&(_.notes=c.notes?`${c.notes}
35
+ ${p}`:p),c=t.updateSession(c.id,_),n()?.enqueue("session",c.id,u.id);const a={session_id:c.id,project:u.name,updated:Object.keys(_)};return w(a,(m,g)=>N(g.brand,`Session updated for ${m.project}`))}},{name:"wyrm_session_rehydrate",description:"Lossless session rehydration. Given a past session ID, returns a complete briefing markdown a fresh AI agent can ingest to inherit prior state: objectives, completed work, notes, summary, current ground truths, open quests, validated patterns (artifacts), unresolved failure patterns. Cross-session continuity no other AI memory tool offers.",inputSchema:{type:"object",properties:{session_id:{type:"number",description:"ID of the session to rehydrate"},include_artifacts:{type:"boolean",description:"Include memory artifacts (default true)"},include_failures:{type:"boolean",description:"Include unresolved failure patterns (default true)"},max_truth_chars:{type:"number",description:"Cap on chars used for ground truths (default 2000)"}},required:["session_id"]},outputSchema:{type:"object",properties:{session_id:{type:"integer"},project_name:{type:"string"},project_path:{type:"string"},briefing_markdown:{type:"string"},context_chars:{type:"integer"},attached:{type:"object",properties:{quests:{type:"integer"},truths:{type:"integer"},artifacts:{type:"integer"},failures:{type:"integer"}},required:["quests","truths","artifacts","failures"]}},required:["session_id","project_name","project_path","briefing_markdown","context_chars","attached"]},annotations:v.wyrm_session_rehydrate,aliases:[],handler:(e,{rehydration:t})=>{const{session_id:n,include_artifacts:s,include_failures:o,max_truth_chars:r}=e,i=t.rehydrate(n,{include_artifacts:s,include_failures:o,max_truth_chars:r});return i?w({...i},p=>p.briefing_markdown,{exemptStructured:!0}):{content:[{type:"text",text:`Session #${n} not found.`}],isError:!0}}},{name:"wyrm_session_prime",description:"Use first at session start - load everything you need to know before starting work, and catch me up on where we left off last time: ground truths, memory brief, reasoning scaffold, and active quests in one call. First call every agent makes in a run.",inputSchema:{type:"object",properties:{project_id:{type:"number"},project_name:{type:"string"},task:{type:"string",description:"Drives scaffold + memory selection"},log_session:{type:"boolean",description:"Also log a session row"},run_id:{type:"string",description:"Fleet: brief cached per (run_id, role)"},role:{type:"string"},token_budget:{type:"number"},for_spawn:{type:"boolean"}}},outputSchema:{type:"object",properties:{project_id:{type:"integer"},project_name:{type:"string"},session_id:{type:["integer","null"]},sections:{type:"array",items:{type:"object",properties:{kind:{type:"string"},text:{type:"string"}},required:["kind","text"]}}},required:["project_id","project_name","session_id","sections"]},annotations:v.wyrm_session_prime,aliases:[],handler:async(e,{store:t,raw:n,memory:s,truths:o,scaffolds:r,cache:i})=>{const{project_id:p,project_name:u,task:c,log_session:_}=e;if(e.run_id!==void 0&&e.run_id!==null)return ae(e,{store:t,raw:n,memory:s,truths:o,scaffolds:r,cache:i});const a=P(t,n(),p,u);if(!a)return C(t);const m=[],g=o.getCurrent(a.id);if(g.length>0&&m.push({kind:"truths",text:G(g,!0).slice(0,1200)}),c){const j=r.findBest(c,a.id,.3);j&&m.push({kind:"scaffold",text:r.formatForContext(j,1500)})}const f=s.buildContextBrief(a.id,c??"general",{maxItems:10,minConfidence:.3});f.text&&m.push({kind:"memory",text:f.text.slice(0,2e3)});const E=n().prepare(J).all(a.id);E.length>0&&m.push({kind:"quests",text:Q(E).slice(0,800)});try{const{vaultAdvisory:j}=await import("../vault.js"),T=j();T.length>0&&m.push({kind:"vault",text:`### \u{1F510} Credential Vault
36
+ `+T.map(d=>`- ${d}`).join(`
37
+ `)})}catch{}let h=null;_&&(h=t.createSession(a.id,{objectives:c??"Session started via wyrm_session_prime",summary:""}).id);const b={project_id:a.id,project_name:a.name,truth_count:o.getStats(a.id).current,quest_count:E.length,memory_count:s.getStats(a.id).total,session_id:h,sections:m},l=w(b,L,A);return await D(n(),a.id,l),i.set(q("wyrm_session_prime",JSON.stringify(e)),l,3e4),l}}];export{je as sessionToolSpecs};