wyrm-mcp 7.2.1 → 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 (150) hide show
  1. package/LICENSE +26 -667
  2. package/NOTICE +14 -33
  3. package/dist/activation.js +1 -60
  4. package/dist/agent-daemon.js +4 -281
  5. package/dist/agent-loop.js +7 -332
  6. package/dist/analytics.js +13 -236
  7. package/dist/attribution.js +1 -49
  8. package/dist/audit.js +2 -457
  9. package/dist/auto-capture.js +3 -138
  10. package/dist/auto-orchestrator.js +1 -325
  11. package/dist/autoconfig.js +39 -840
  12. package/dist/buddy-runner.js +1 -109
  13. package/dist/buddy.js +14 -564
  14. package/dist/build-flags.js +1 -17
  15. package/dist/capabilities.js +3 -183
  16. package/dist/capture.js +1 -56
  17. package/dist/causality.js +6 -107
  18. package/dist/cli.js +20 -281
  19. package/dist/cloud/cli.js +5 -541
  20. package/dist/cloud/client.js +1 -221
  21. package/dist/cloud/crypto.js +1 -85
  22. package/dist/cloud/machine-id.js +2 -113
  23. package/dist/cloud/recovery.js +1 -60
  24. package/dist/cloud/sync-engine.js +7 -543
  25. package/dist/cloud-backup.js +5 -579
  26. package/dist/cloud-profile.js +1 -138
  27. package/dist/cloud-sync-entrypoint.js +1 -47
  28. package/dist/cloud-sync.js +2 -309
  29. package/dist/constellation.js +12 -168
  30. package/dist/context-build-budgeted.js +4 -144
  31. package/dist/context-ranking.js +1 -69
  32. package/dist/crypto.js +1 -179
  33. package/dist/daemon-write-endpoint.js +1 -290
  34. package/dist/daemon-writer.js +2 -406
  35. package/dist/database.js +43 -1110
  36. package/dist/deprecations.js +2 -162
  37. package/dist/design.js +13 -141
  38. package/dist/event-replication.js +1 -112
  39. package/dist/events-sse.js +7 -43
  40. package/dist/events.js +6 -238
  41. package/dist/failure-patterns.js +42 -659
  42. package/dist/federation.js +12 -236
  43. package/dist/goals.js +13 -101
  44. package/dist/golden.js +3 -355
  45. package/dist/handlers/agent.js +4 -165
  46. package/dist/handlers/alias-adapters.js +1 -129
  47. package/dist/handlers/aliases.js +1 -171
  48. package/dist/handlers/audit.js +1 -87
  49. package/dist/handlers/boundary.js +1 -221
  50. package/dist/handlers/capture.js +73 -1109
  51. package/dist/handlers/causality.js +7 -114
  52. package/dist/handlers/cloud.js +85 -382
  53. package/dist/handlers/companion.js +28 -459
  54. package/dist/handlers/datalake.js +7 -187
  55. package/dist/handlers/dispatch-context.js +0 -22
  56. package/dist/handlers/entity.js +25 -256
  57. package/dist/handlers/events.js +16 -335
  58. package/dist/handlers/failure.js +13 -340
  59. package/dist/handlers/goals.js +4 -296
  60. package/dist/handlers/intelligence.js +126 -674
  61. package/dist/handlers/invoicing.js +1 -70
  62. package/dist/handlers/mcpclient.js +6 -137
  63. package/dist/handlers/orchestration.js +40 -125
  64. package/dist/handlers/output-schemas.js +1 -24
  65. package/dist/handlers/presence.js +3 -99
  66. package/dist/handlers/project.js +28 -182
  67. package/dist/handlers/prompts.js +6 -157
  68. package/dist/handlers/quest.js +4 -224
  69. package/dist/handlers/recall.js +11 -218
  70. package/dist/handlers/registry.js +1 -167
  71. package/dist/handlers/resources.js +1 -288
  72. package/dist/handlers/review.js +11 -74
  73. package/dist/handlers/run.js +17 -487
  74. package/dist/handlers/search.js +15 -326
  75. package/dist/handlers/session.js +28 -615
  76. package/dist/handlers/share.js +8 -184
  77. package/dist/handlers/shims.js +1 -464
  78. package/dist/handlers/skill.js +67 -449
  79. package/dist/handlers/survivors.js +1 -120
  80. package/dist/handlers/symbols.js +8 -109
  81. package/dist/handlers/syncops.js +4 -302
  82. package/dist/handlers/types.js +1 -27
  83. package/dist/harvest.js +5 -191
  84. package/dist/hours.js +7 -156
  85. package/dist/http-auth.js +3 -321
  86. package/dist/http-fast.js +21 -1137
  87. package/dist/icons.js +1 -47
  88. package/dist/index.js +2 -924
  89. package/dist/indexer.js +4 -145
  90. package/dist/intelligence.js +31 -261
  91. package/dist/internal-dispatch.js +3 -212
  92. package/dist/keyset.js +1 -110
  93. package/dist/knowledge-graph.js +12 -176
  94. package/dist/license.js +2 -441
  95. package/dist/logger.js +2 -199
  96. package/dist/maintenance.js +2 -148
  97. package/dist/mcp-client.js +6 -262
  98. package/dist/memory-artifacts.js +30 -449
  99. package/dist/migrate-prompt.js +2 -124
  100. package/dist/migrations.js +40 -655
  101. package/dist/performance.js +1 -228
  102. package/dist/presence.js +11 -140
  103. package/dist/priority-embed.js +5 -164
  104. package/dist/providers/embedding-provider.js +1 -196
  105. package/dist/readonly-gate.js +1 -29
  106. package/dist/rehydration.js +9 -157
  107. package/dist/reindex.js +1 -88
  108. package/dist/render-target.js +21 -514
  109. package/dist/render.js +4 -280
  110. package/dist/repl-guard.js +1 -173
  111. package/dist/replication-daemon-entrypoint.js +1 -31
  112. package/dist/replication-daemon.js +2 -262
  113. package/dist/resilience.js +1 -591
  114. package/dist/reverse-bridge.js +5 -360
  115. package/dist/security.js +1 -244
  116. package/dist/session-seen.js +3 -51
  117. package/dist/setup.js +1 -260
  118. package/dist/skill-author.js +5 -168
  119. package/dist/spec-kit.js +1 -191
  120. package/dist/sqlite-busy.js +1 -154
  121. package/dist/statusline.js +11 -315
  122. package/dist/sub-agent.js +13 -262
  123. package/dist/summarizer.js +13 -139
  124. package/dist/symbols.js +7 -283
  125. package/dist/sync.js +5 -359
  126. package/dist/tasks-dispatch.js +1 -84
  127. package/dist/tasks.js +1 -282
  128. package/dist/token-budget.js +1 -143
  129. package/dist/tool-analytics.js +7 -129
  130. package/dist/tool-annotations.js +1 -365
  131. package/dist/tool-manifest-v2.json +1 -1
  132. package/dist/tool-manifest.json +1 -1
  133. package/dist/tool-profiles.js +1 -75
  134. package/dist/trace-harvest.js +6 -244
  135. package/dist/types.js +1 -30
  136. package/dist/ui-dashboard.js +41 -50
  137. package/dist/ulid.js +1 -81
  138. package/dist/validate.js +1 -129
  139. package/dist/vault.js +1 -534
  140. package/dist/vectors.js +3 -184
  141. package/dist/version-check.js +4 -136
  142. package/dist/visibility.js +19 -155
  143. package/dist/wyrm-cli.js +98 -2464
  144. package/dist/wyrm-guard.js +14 -424
  145. package/dist/wyrm-loop.js +3 -150
  146. package/dist/wyrm-manifest.json +1 -1
  147. package/dist/wyrm-statusline-daemon.js +1 -11
  148. package/dist/wyrm-statusline.js +4 -56
  149. package/dist/wyrm-ui.js +9 -77
  150. 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};