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