wyrm-mcp 9.2.0 → 9.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 (66) hide show
  1. package/README.md +2 -0
  2. package/dist/auto-runs.js +1 -0
  3. package/dist/autoconfig.js +3 -3
  4. package/dist/capabilities.js +3 -3
  5. package/dist/causality.js +13 -8
  6. package/dist/cli.js +25 -22
  7. package/dist/cloud/cli.js +3 -3
  8. package/dist/cloud/sync-daemon-entrypoint.js +2 -2
  9. package/dist/cloud/sync-daemon.js +1 -1
  10. package/dist/color.js +1 -0
  11. package/dist/database.js +3 -3
  12. package/dist/deprecations.js +2 -2
  13. package/dist/events.js +6 -6
  14. package/dist/federation.js +10 -10
  15. package/dist/handlers/arg-keys.js +1 -0
  16. package/dist/handlers/capture.js +58 -58
  17. package/dist/handlers/causality.js +8 -8
  18. package/dist/handlers/cloud.js +85 -83
  19. package/dist/handlers/companion.js +10 -10
  20. package/dist/handlers/entity.js +1 -1
  21. package/dist/handlers/events.js +12 -12
  22. package/dist/handlers/failure.js +12 -12
  23. package/dist/handlers/goals.js +4 -4
  24. package/dist/handlers/intelligence.js +63 -63
  25. package/dist/handlers/mcpclient.js +1 -1
  26. package/dist/handlers/presence.js +3 -3
  27. package/dist/handlers/project-arg.js +1 -0
  28. package/dist/handlers/project.js +1 -1
  29. package/dist/handlers/quest.js +5 -3
  30. package/dist/handlers/recall.js +18 -18
  31. package/dist/handlers/registry.js +1 -1
  32. package/dist/handlers/resources.js +1 -1
  33. package/dist/handlers/review.js +3 -3
  34. package/dist/handlers/run.js +3 -3
  35. package/dist/handlers/search.js +3 -3
  36. package/dist/handlers/session.js +24 -24
  37. package/dist/handlers/share.js +5 -5
  38. package/dist/handlers/shims.js +1 -1
  39. package/dist/handlers/thread.js +2 -2
  40. package/dist/http-auth.js +1 -1
  41. package/dist/http-fast.js +15 -15
  42. package/dist/index.js +2 -2
  43. package/dist/logger.js +2 -2
  44. package/dist/receipt.js +1 -1
  45. package/dist/rehydration.js +24 -9
  46. package/dist/render.js +3 -3
  47. package/dist/setup.js +2 -3
  48. package/dist/sqlite-busy.js +1 -1
  49. package/dist/stale-servers.js +2 -0
  50. package/dist/stdio-lifecycle.js +1 -0
  51. package/dist/storage/index.js +3 -3
  52. package/dist/tool-manifest-v2.json +1 -1
  53. package/dist/tool-manifest.json +1 -1
  54. package/dist/ui-dashboard.js +9 -8
  55. package/dist/validate.js +1 -1
  56. package/dist/vault-exec.js +4 -0
  57. package/dist/vault.js +1 -1
  58. package/dist/wyrm-cli.js +105 -142
  59. package/dist/wyrm-manifest.json +3 -3
  60. package/dist/wyrm-ui.js +14 -7
  61. package/dist/wyrm.js +2 -0
  62. package/package.json +6 -4
  63. package/scripts/hooks/wyrm-citation-audit.mjs +155 -46
  64. package/scripts/hooks/wyrm-prompt-context.mjs +300 -0
  65. package/scripts/hooks/wyrm-run-auto.mjs +148 -37
  66. package/scripts/hooks/wyrm-session-rehydrate.mjs +142 -24
@@ -1 +1 @@
1
- import{goalToolSpecs as n}from"./goals.js";import{captureToolSpecs as s}from"./capture.js";import{recallToolSpecs as l}from"./recall.js";import{searchToolSpecs as a}from"./search.js";import{failureToolSpecs as f}from"./failure.js";import{questToolSpecs as u}from"./quest.js";import{sessionToolSpecs as S}from"./session.js";import{reviewToolSpecs as T}from"./review.js";import{runToolSpecs as d}from"./run.js";import{declaredOutputSchemas as h}from"./output-schemas.js";import{projectToolSpecs as y}from"./project.js";import{eventsToolSpecs as g}from"./events.js";import{skillToolSpecs as O}from"./skill.js";import{datalakeToolSpecs as x}from"./datalake.js";import{entityToolSpecs as k}from"./entity.js";import{intelligenceToolSpecs as b}from"./intelligence.js";import{orchestrationToolSpecs as v}from"./orchestration.js";import{cloudToolSpecs as L}from"./cloud.js";import{companionToolSpecs as j}from"./companion.js";import{syncopsToolSpecs as w}from"./syncops.js";import{presenceToolSpecs as A}from"./presence.js";import{causalityToolSpecs as C}from"./causality.js";import{symbolsToolSpecs as D}from"./symbols.js";import{invoicingToolSpecs as N}from"./invoicing.js";import{auditToolSpecs as _}from"./audit.js";import{shareToolSpecs as q}from"./share.js";import{agentToolSpecs as E}from"./agent.js";import{mcpclientToolSpecs as I}from"./mcpclient.js";import{threadToolSpecs as K}from"./thread.js";const p=[...n,...s,...l,...a,...f,...u,...S,...T,...d,...y,...g,...O,...x,...k,...b,...v,...L,...j,...w,...A,...C,...D,...N,..._,...q,...E,...I,...K],fo=new Map(p.map(o=>[o.name,o.handler]));function uo(){return p.map(o=>({name:o.name,description:o.description,inputSchema:o.inputSchema,...o.outputSchema!==void 0?{outputSchema:o.outputSchema}:{}}))}function So(o){return o.map(t=>{const e=h[t.name];return!e||t.outputSchema!==void 0?t:{...t,outputSchema:e}})}const c=["name","description","inputSchema","outputSchema","annotations"];function To(o){return[...o].sort((t,e)=>t.name<e.name?-1:t.name>e.name?1:0).map(t=>{const e=t,m={};for(const r of c)r in e&&e[r]!==void 0&&(m[r]=e[r]);const i=Object.keys(e).filter(r=>!c.includes(r)&&e[r]!==void 0).sort();for(const r of i)m[r]=e[r];return m})}export{So as applyDeclaredOutputSchemas,To as buildStableToolList,fo as handlerRegistry,uo as registryToolDefs,p as toolSpecs};
1
+ import{goalToolSpecs as a}from"./goals.js";import{captureToolSpecs as u}from"./capture.js";import{recallToolSpecs as l}from"./recall.js";import{searchToolSpecs as f}from"./search.js";import{failureToolSpecs as S}from"./failure.js";import{questToolSpecs as T}from"./quest.js";import{sessionToolSpecs as d}from"./session.js";import{reviewToolSpecs as h}from"./review.js";import{runToolSpecs as y}from"./run.js";import{declaredOutputSchemas as n}from"./output-schemas.js";import{declareOutputSchemaTools as O,withoutErrorStructuredContent as i}from"../render.js";import{projectToolSpecs as g}from"./project.js";import{eventsToolSpecs as k}from"./events.js";import{skillToolSpecs as x}from"./skill.js";import{datalakeToolSpecs as b}from"./datalake.js";import{entityToolSpecs as C}from"./entity.js";import{intelligenceToolSpecs as j}from"./intelligence.js";import{orchestrationToolSpecs as v}from"./orchestration.js";import{cloudToolSpecs as w}from"./cloud.js";import{companionToolSpecs as E}from"./companion.js";import{syncopsToolSpecs as L}from"./syncops.js";import{presenceToolSpecs as A}from"./presence.js";import{causalityToolSpecs as D}from"./causality.js";import{symbolsToolSpecs as N}from"./symbols.js";import{invoicingToolSpecs as _}from"./invoicing.js";import{auditToolSpecs as q}from"./audit.js";import{shareToolSpecs as I}from"./share.js";import{agentToolSpecs as K}from"./agent.js";import{mcpclientToolSpecs as M}from"./mcpclient.js";import{threadToolSpecs as P}from"./thread.js";const p=[...a,...u,...l,...f,...S,...T,...d,...h,...y,...g,...k,...x,...b,...C,...j,...v,...w,...E,...L,...A,...D,...N,..._,...q,...I,...K,...M,...P];O([...p.filter(o=>o.outputSchema!==void 0).map(o=>o.name),...Object.keys(n)]);function R(o){return(r,e)=>{const m=o(r,e);return m instanceof Promise?m.then(i):i(m)}}const ho=new Map(p.map(o=>[o.name,o.outputSchema!==void 0?R(o.handler):o.handler]));function yo(){return p.map(o=>({name:o.name,description:o.description,inputSchema:o.inputSchema,...o.outputSchema!==void 0?{outputSchema:o.outputSchema}:{}}))}function Oo(o){return o.map(r=>{const e=n[r.name];return!e||r.outputSchema!==void 0?r:{...r,outputSchema:e}})}const c=["name","description","inputSchema","outputSchema","annotations"];function go(o){return[...o].sort((r,e)=>r.name<e.name?-1:r.name>e.name?1:0).map(r=>{const e=r,m={};for(const t of c)t in e&&e[t]!==void 0&&(m[t]=e[t]);const s=Object.keys(e).filter(t=>!c.includes(t)&&e[t]!==void 0).sort();for(const t of s)m[t]=e[t];return m})}export{Oo as applyDeclaredOutputSchemas,go as buildStableToolList,ho as handlerRegistry,yo as registryToolDefs,p as toolSpecs};
@@ -1 +1 @@
1
- import{getCapabilities as f,renderCapabilityBriefing as y}from"../capabilities.js";import{decodeCursor as g}from"../keyset.js";const o="wyrm://",c="application/json",l="text/markdown",T=["truths","failures","quests","memory","stats"];function R(r){return`${o}memory/${r}`}function $(r,t,e){const i=`${o}project/${r}/${t}`;return e?`${i}?cursor=${e}`:i}const h=`${o}capabilities`,j=`${o}stats`;function C(r,t,e){return{type:"resource_link",uri:r,name:t,...e?.description?{description:e.description}:{},...e?.mimeType?{mimeType:e.mimeType}:{}}}function d(r){if(!/^\d{1,15}$/.test(r))return null;const t=Number(r);return Number.isSafeInteger(t)&&t>=0?t:null}function E(r){if(typeof r!="string"||!r.startsWith(o))return null;let t=r.slice(o.length),e;const i=t.indexOf("?");if(i>=0){const s=t.slice(i+1);t=t.slice(0,i);const a=/^cursor=([A-Za-z0-9_-]{1,512})$/.exec(s);if(!a)return null;e=a[1]}if(/[\x00-\x1f\x7f]/.test(t)||t.includes(".."))return null;const n=t.split("/");if(n.some(s=>s.length===0))return null;if(n.length===1)return e!==void 0?null:n[0]==="capabilities"?{kind:"capabilities"}:n[0]==="stats"?{kind:"stats"}:null;if(n.length===2&&n[0]==="memory"){if(e!==void 0)return null;const s=d(n[1]);return s===null?null:{kind:"memory",id:s}}if(n.length===3&&n[0]==="project"){const s=d(n[1]);if(s===null)return null;const a=n[2];return T.includes(a)?e!==void 0&&a!=="memory"?null:{kind:"project",id:s,leaf:a,...e!==void 0?{cursor:e}:{}}:null}return null}function N(){return[{uri:h,name:"Wyrm capabilities",description:"Feature inventory + runtime state \u2014 what this memory system can do and which tool to reach for. The resource form of wyrm_capabilities.",mimeType:l},{uri:j,name:"Wyrm global stats",description:"Global database stats (projects, sessions, quests, data points, size). The resource form of wyrm_stats.",mimeType:c}]}function I(){return[{uriTemplate:`${o}memory/{id}`,name:"Memory artifact",description:"A single memory artifact (lesson/pattern/anti-pattern/heuristic/reasoning trace) by id.",mimeType:c},{uriTemplate:`${o}project/{id}/truths`,name:"Project ground truths",description:"A project's current ground truths (the authoritative facts).",mimeType:c},{uriTemplate:`${o}project/{id}/failures`,name:"Project failures",description:"A project's unresolved failure patterns (the negative-learning set).",mimeType:c},{uriTemplate:`${o}project/{id}/quests`,name:"Project quests",description:"A project's open quests (pending + in-progress).",mimeType:c},{uriTemplate:`${o}project/{id}/memory`,name:"Project memory",description:"A project's validated memory artifacts.",mimeType:c},{uriTemplate:`${o}project/{id}/stats`,name:"Project memory stats",description:"A project's memory artifact stats (totals by kind, avg confidence).",mimeType:c}]}const m=200;class p extends Error{uri;constructor(t,e){super(e),this.uri=t,this.name="ResourceNotFoundError"}}function u(r,t){return{uri:r,mimeType:c,text:JSON.stringify(t,null,2)}}function x(r,t){const e=E(r);if(!e)throw new p(r,`Unknown or malformed resource URI: ${r}`);switch(e.kind){case"capabilities":{const i=f(t.db.getDatabase(),t.WYRM_TOOL_COUNT,{hasEncryption:()=>!!process.env.WYRM_ENCRYPTION_KEY,isFederationEnabled:()=>!0,isAgentRunning:()=>{try{return t.agentDaemon.status().running}catch{return null}}});return{uri:r,mimeType:l,text:y(i)}}case"stats":return u(r,t.db.getStats());case"memory":{const i=t.memory.get(e.id);if(!i)throw new p(r,`Memory artifact not found: ${e.id}`);return u(r,i)}case"project":{if(!t.db.getProjectById(e.id))throw new p(r,`Project not found: ${e.id}`);switch(e.leaf){case"truths":return u(r,{project_id:e.id,truths:t.groundTruths.getCurrent(e.id)});case"failures":return u(r,{project_id:e.id,failures:t.failures.list(e.id,m)});case"quests":{const n=t.db.getDatabase().prepare("SELECT * FROM quests WHERE project_id = ? AND status IN ('pending','in_progress') ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, id LIMIT ?").all(e.id,m);return u(r,{project_id:e.id,quests:n})}case"memory":{const n=g(e.cursor),s=t.memory.listPage(e.id,{pageSize:m,after:n}),a=u(r,{project_id:e.id,artifacts:s.items,...s.nextCursor?{next_cursor:s.nextCursor}:{}});return s.nextCursor&&(a.nextCursor=s.nextCursor),a}case"stats":return u(r,{project_id:e.id,stats:t.memory.getStats(e.id)})}}}}export{h as CAPABILITIES_URI,p as ResourceNotFoundError,j as STATS_URI,o as WYRM_URI_SCHEME,I as listWyrmResourceTemplates,N as listWyrmResources,R as memoryUri,E as parseWyrmUri,$ as projectUri,x as readWyrmResource,C as resourceLink};
1
+ import{getCapabilities as f,renderCapabilityBriefing as y}from"../capabilities.js";import{decodeCursor as g}from"../keyset.js";const o="wyrm://",c="application/json",p="text/markdown",T=["truths","failures","quests","memory","stats"];function R(r){return`${o}memory/${r}`}function C(r,t,e){const i=`${o}project/${r}/${t}`;return e?`${i}?cursor=${e}`:i}const h=`${o}capabilities`,j=`${o}stats`;function $(r,t,e){return{type:"resource_link",uri:r,name:t,...e?.description?{description:e.description}:{},...e?.mimeType?{mimeType:e.mimeType}:{}}}function d(r){if(!/^\d{1,15}$/.test(r))return null;const t=Number(r);return Number.isSafeInteger(t)&&t>=0?t:null}function E(r){if(typeof r!="string"||!r.startsWith(o))return null;let t=r.slice(o.length),e;const i=t.indexOf("?");if(i>=0){const s=t.slice(i+1);t=t.slice(0,i);const a=/^cursor=([A-Za-z0-9_-]{1,512})$/.exec(s);if(!a)return null;e=a[1]}if(/[\x00-\x1f\x7f]/.test(t)||t.includes(".."))return null;const n=t.split("/");if(n.some(s=>s.length===0))return null;if(n.length===1)return e!==void 0?null:n[0]==="capabilities"?{kind:"capabilities"}:n[0]==="stats"?{kind:"stats"}:null;if(n.length===2&&n[0]==="memory"){if(e!==void 0)return null;const s=d(n[1]);return s===null?null:{kind:"memory",id:s}}if(n.length===3&&n[0]==="project"){const s=d(n[1]);if(s===null)return null;const a=n[2];return T.includes(a)?e!==void 0&&a!=="memory"?null:{kind:"project",id:s,leaf:a,...e!==void 0?{cursor:e}:{}}:null}return null}function N(){return[{uri:h,name:"Wyrm capabilities",description:"Feature inventory + runtime state \u2014 what this memory system can do and which tool to reach for. The resource form of wyrm_capabilities.",mimeType:p},{uri:j,name:"Wyrm global stats",description:"Global database stats (projects, sessions, quests, data points, size). The resource form of wyrm_stats.",mimeType:c}]}function I(){return[{uriTemplate:`${o}memory/{id}`,name:"Memory artifact",description:"A single memory artifact (lesson/pattern/anti-pattern/heuristic/reasoning trace) by id.",mimeType:c},{uriTemplate:`${o}project/{id}/truths`,name:"Project ground truths",description:"A project's current ground truths (the authoritative facts).",mimeType:c},{uriTemplate:`${o}project/{id}/failures`,name:"Project failures",description:"A project's unresolved failure patterns (the negative-learning set).",mimeType:c},{uriTemplate:`${o}project/{id}/quests`,name:"Project quests",description:"A project's open quests (pending + in-progress).",mimeType:c},{uriTemplate:`${o}project/{id}/memory`,name:"Project memory",description:"A project's validated memory artifacts.",mimeType:c},{uriTemplate:`${o}project/{id}/stats`,name:"Project memory stats",description:"A project's memory artifact stats (totals by kind, avg confidence).",mimeType:c}]}const m=200;class l extends Error{uri;constructor(t,e){super(e),this.uri=t,this.name="ResourceNotFoundError"}}function u(r,t){return{uri:r,mimeType:c,text:JSON.stringify(t,null,2)}}function A(r,t){const e=E(r);if(!e)throw new l(r,`Unknown or malformed resource URI: ${r}`);switch(e.kind){case"capabilities":{const i=t.toolCounts?.(),n=f(t.db.getDatabase(),i?.active??t.WYRM_TOOL_COUNT,{hasEncryption:()=>!!process.env.WYRM_ENCRYPTION_KEY,isFederationEnabled:()=>!0,isAgentRunning:()=>{try{return t.agentDaemon.status().running}catch{return null}}},i?.full);return{uri:r,mimeType:p,text:y(n)}}case"stats":return u(r,t.db.getStats());case"memory":{const i=t.memory.get(e.id);if(!i)throw new l(r,`Memory artifact not found: ${e.id}`);return u(r,i)}case"project":{if(!t.db.getProjectById(e.id))throw new l(r,`Project not found: ${e.id}`);switch(e.leaf){case"truths":return u(r,{project_id:e.id,truths:t.groundTruths.getCurrent(e.id)});case"failures":return u(r,{project_id:e.id,failures:t.failures.list(e.id,m)});case"quests":{const n=t.db.getDatabase().prepare("SELECT * FROM quests WHERE project_id = ? AND status IN ('pending','in_progress') ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, id LIMIT ?").all(e.id,m);return u(r,{project_id:e.id,quests:n})}case"memory":{const n=g(e.cursor),s=t.memory.listPage(e.id,{pageSize:m,after:n}),a=u(r,{project_id:e.id,artifacts:s.items,...s.nextCursor?{next_cursor:s.nextCursor}:{}});return s.nextCursor&&(a.nextCursor=s.nextCursor),a}case"stats":return u(r,{project_id:e.id,stats:t.memory.getStats(e.id)})}}}}export{h as CAPABILITIES_URI,l as ResourceNotFoundError,j as STATS_URI,o as WYRM_URI_SCHEME,I as listWyrmResourceTemplates,N as listWyrmResources,R as memoryUri,E as parseWyrmUri,C as projectUri,A as readWyrmResource,$ as resourceLink};
@@ -1,11 +1,11 @@
1
- import{TOOL_ANNOTATIONS as l}from"../tool-annotations.js";import{renderResult as f,withGlyph as p}from"../render.js";import{reverseBridgeSigFromTags as m,recordReverseBridgeRejection as u}from"../reverse-bridge.js";const g=[{name:"wyrm_review",description:"Use to process the review queue - approve or reject the pending memory candidates waiting from extraction, harvest, imports, capture conflicts, or session distillation. Approve activates the artifact for recall; reject permanently deletes it (unrecoverable).",inputSchema:{type:"object",properties:{artifactId:{type:"number"},approved:{type:"boolean",description:"true = activate, false = hard delete"},notes:{type:"string"}},required:["artifactId","approved"]},outputSchema:{type:"object",properties:{artifact_id:{type:"integer"},approved:{type:"boolean"},deleted:{type:"boolean"},problem:{type:"string"},notes:{type:["string","null"]}},required:["artifact_id","approved","deleted","problem","notes"]},annotations:l.wyrm_review,aliases:[],handler:(s,{memory:i,raw:a})=>{const{artifactId:t,approved:o,notes:c}=s,r=i.get(t);if(!r)return{content:[{type:"text",text:`Artifact #${t} not found.`}],isError:!0};if(o)i.approve(t);else{const e=m(r.tags);e&&u(a(),r.project_id,e),a().prepare("DELETE FROM memory_artifacts WHERE id = ?").run(t)}const d={artifact_id:t,approved:o,deleted:!o,problem:r.problem.slice(0,80),notes:c??null};return f(d,(e,n)=>e.approved?p(n.ok,`**Approved** -- Artifact #${e.artifact_id} is now active.`)+`
1
+ import{TOOL_ANNOTATIONS as m}from"../tool-annotations.js";import{renderResult as f,withGlyph as s}from"../render.js";import{reverseBridgeSigFromTags as u,recordReverseBridgeRejection as v}from"../reverse-bridge.js";import{asString as y,requireBool as g,requireInt as _}from"../validate.js";const I=[{name:"wyrm_review",description:"Use to process the review queue - approve or reject the pending memory candidates waiting from extraction, harvest, imports, capture conflicts, or session distillation. Approve activates the artifact for recall; reject permanently deletes it (unrecoverable).",inputSchema:{type:"object",properties:{artifactId:{type:"number"},approved:{type:"boolean",description:"true = activate, false = hard delete"},notes:{type:"string"}},required:["artifactId","approved"]},outputSchema:{type:"object",properties:{artifact_id:{type:"integer"},approved:{type:"boolean"},deleted:{type:"boolean"},problem:{type:"string"},notes:{type:["string","null"]}},required:["artifact_id","approved","deleted","problem","notes"]},annotations:m.wyrm_review,aliases:[],handler:(c,{memory:a,raw:n})=>{const o=c,t=_("artifactId",o.artifactId,{min:1}),i=g("approved",o.approved),d=y("notes",o.notes),r=a.get(t);if(!r)return{content:[{type:"text",text:`Artifact #${t} not found.`}],isError:!0};if(i)a.approve(t);else{const e=u(r.tags);e&&v(n(),r.project_id,e),n().prepare("DELETE FROM memory_artifacts WHERE id = ?").run(t)}const l={artifact_id:t,approved:i,deleted:!i,problem:r.problem.slice(0,80),notes:d??null};return f(l,(e,p)=>e.approved?s(p.ok,`**Approved** -- Artifact #${e.artifact_id} is now active.`)+`
2
2
 
3
3
  "${e.problem}"
4
4
 
5
5
  It will appear in future \`wyrm_recall\` and \`wyrm_context_build\` results.${e.notes?`
6
6
 
7
- Notes: ${e.notes}`:""}`:p(n.fail,`**Rejected** -- Artifact #${e.artifact_id} permanently deleted.`)+`
7
+ Notes: ${e.notes}`:""}`:s(p.fail,`**Rejected** -- Artifact #${e.artifact_id} permanently deleted.`)+`
8
8
 
9
9
  "${e.problem}"${e.notes?`
10
10
 
11
- Notes: ${e.notes}`:""}`)}}];export{g as reviewToolSpecs};
11
+ Notes: ${e.notes}`:""}`)}}];export{I as reviewToolSpecs};
@@ -6,7 +6,7 @@ import{TOOL_ANNOTATIONS as z}from"../tool-annotations.js";import{renderResult as
6
6
  `).run(n,s,r)}function S(e,n,s){return s?e.prepare("SELECT 1 FROM run_agents WHERE run_id = ? AND agent_id = ? AND role = 'orchestrator'").get(n,s)!==void 0:!1}function re(e,n,s){return e.prepare("SELECT 1 FROM run_agents WHERE run_id = ? AND agent_id = ?").get(n,s)!==void 0}function ne(e){return e.agent_id!==null&&e.source!=="client"&&e.source!=="legacy"}const C=(e,n)=>{e.prepare("UPDATE runs SET updated_at = datetime('now') WHERE run_id = ?").run(n)},ie=e=>({content:[{type:"text",text:`Run not found: ${e}`}],isError:!0}),le=[{name:"wyrm_run",description:"Use to manage a fleet run lifecycle from the orchestrator: action=start mints a ULID run_id and registers the orchestrator, join registers each subagent (agent_id, role), status reports agents, run-quarantined failure counts, and quest claims, debrief fans each agent's learnings through the LOCAL extractor (never a cloud LLM) into a run-scoped review queue for wyrm_review, and end writes the run summary, promotes or expires quarantined failures, and releases claims.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["start","join","status","debrief","end"]},run_id:{type:"string",description:"start mints if omitted"},agent_id:{type:"string",description:"default: ambient envelope"},role:{type:"string",description:"join"},orchestrator:{type:"string",description:"start"},parent_run_id:{type:"string",description:"start: nested fleets"},project_path:{type:"string",description:"debrief/end"},learnings:{type:"array",items:{type:"object"},description:"debrief: [{agent_id?, text}]"},status:{type:"string",enum:["completed","failed","abandoned"],description:"end (default completed)"}},required:["action"]},outputSchema:{type:"object",properties:{action:{type:"string"},run_id:{type:"string"},status:{type:"string"},orchestrator:{type:["string","null"]},agents:{type:"array",items:{type:"object",properties:{agent_id:{type:"string"},role:{type:["string","null"]}},required:["agent_id","role"]}},quarantined:{type:"integer"},resolved:{type:"integer"},claims:{type:"integer"},method:{type:"string"},queued:{type:"integer"},skipped:{type:"integer"},promoted:{type:"integer"},expired:{type:"integer"},claims_released:{type:"integer"},summary_artifact_id:{type:["integer","null"]}},required:["action","run_id","status"]},annotations:z.wyrm_run,aliases:[],handler:async(e,n)=>{const s=W("action",e.action,H);if(!s)throw new E("action",`must be one of: ${H.join(", ")}`);const r=n.raw(),_=B();if(s==="start"){const i=h("run_id",e.run_id),a=i??X(),d=w("orchestrator",e.orchestrator,{maxLen:200})??_.agent_id??null,c=h("agent_id",e.agent_id)??_.agent_id,o=h("parent_run_id",e.parent_run_id),p=O(r,a);if(p){if(!i||p.status!=="running")return{content:[{type:"text",text:`Run ${a} already exists (status: ${p.status}).`}],isError:!0};if(c){if(r.prepare("SELECT 1 FROM run_agents WHERE run_id = ? AND role = 'orchestrator' LIMIT 1").get(a)!==void 0&&!S(r,a,c))return{content:[{type:"text",text:`Run ${a} already has an orchestrator \u2014 re-start with the same agent_id, or join as a member instead.`}],isError:!0};q(r,a,c,"orchestrator")}C(r,a)}else{if(o&&!O(r,o))return{content:[{type:"text",text:`Parent run not found: ${o}`}],isError:!0};ee(r,a,o,d),c&&q(r,a,c,"orchestrator")}return R({action:"start",run_id:a,status:"running",parent_run_id:o,orchestrator:d,agents:c?[{agent_id:c,role:"orchestrator"}]:[]},(l,u)=>x(u.brand,`Run ${l.run_id} started${l.orchestrator?` by ${l.orchestrator}`:""}.`)+`
7
7
  Subagents join with: wyrm_run({ action: 'join', run_id: '${l.run_id}', agent_id, role })
8
8
  Export WYRM_RUN_ID=${l.run_id} so every write in the fleet carries run attribution.
9
- Prime ONCE per role: wyrm_session_prime({ run_id: '${l.run_id}', role, for_spawn: true }) -> embed the returned brief in each same-role agent's spawn prefix (don't re-prime per agent).`)}const t=h("run_id",e.run_id);if(!t)throw new E("run_id",`is required for action=${s}`);const g=O(r,t);if(!g)return ie(t);if(s==="join"){if(g.status!=="running")return{content:[{type:"text",text:`Run ${t} already ended (status: ${g.status}) \u2014 cannot join.`}],isError:!0};const i=h("agent_id",e.agent_id),a=i??_.agent_id;if(!a)throw new E("agent_id","no agent identity \u2014 pass agent_id or supply the actor envelope (actor param / _meta / WYRM_AGENT_ID)");const d=w("role",e.role,{maxLen:200})??null;if(i&&ne(_)&&i!==_.agent_id&&!S(r,t,_.agent_id))return{content:[{type:"text",text:`Joining run ${t} as '${i}' while attributed as '${_.agent_id}' is orchestrator-only \u2014 join as yourself, or have the orchestrator register the agent.`}],isError:!0};if(d==="orchestrator"&&!S(r,t,a)&&!S(r,t,_.agent_id))return{content:[{type:"text",text:`Role 'orchestrator' on run ${t} is reserved \u2014 it is registered at action=start. Join with a fleet role instead.`}],isError:!0};q(r,t,a,d),C(r,t);const c=v(r,t),o={action:"join",run_id:t,status:g.status,agents:c.map(p=>({agent_id:p.agent_id,role:p.role}))};return R(o,(p,m)=>x(m.brand,`${a} joined run ${p.run_id}${d?` as ${d}`:""} (${p.agents.length} agent(s) registered).`))}if(s==="status"){const i=v(r,t),a=r.prepare(`
9
+ Prime ONCE per role: wyrm_session_prime({ run_id: '${l.run_id}', role, for_spawn: true }) -> embed the returned brief in each same-role agent's spawn prefix (don't re-prime per agent).`)}const t=h("run_id",e.run_id);if(!t)throw new E("run_id",`is required for action=${s}`);const g=O(r,t);if(!g)return ie(t);if(s==="join"){if(g.status!=="running")return{content:[{type:"text",text:`Run ${t} already ended (status: ${g.status}) \u2014 cannot join.`}],isError:!0};const i=h("agent_id",e.agent_id),a=i??_.agent_id;if(!a)throw new E("agent_id","no agent identity; pass agent_id or supply the actor envelope (actor param / _meta / WYRM_AGENT_ID)");const d=w("role",e.role,{maxLen:200})??null;if(i&&ne(_)&&i!==_.agent_id&&!S(r,t,_.agent_id))return{content:[{type:"text",text:`Joining run ${t} as '${i}' while attributed as '${_.agent_id}' is orchestrator-only \u2014 join as yourself, or have the orchestrator register the agent.`}],isError:!0};if(d==="orchestrator"&&!S(r,t,a)&&!S(r,t,_.agent_id))return{content:[{type:"text",text:`Role 'orchestrator' on run ${t} is reserved \u2014 it is registered at action=start. Join with a fleet role instead.`}],isError:!0};q(r,t,a,d),C(r,t);const c=v(r,t),o={action:"join",run_id:t,status:g.status,agents:c.map(p=>({agent_id:p.agent_id,role:p.role}))};return R(o,(p,m)=>x(m.brand,`${a} joined run ${p.run_id}${d?` as ${d}`:""} (${p.agents.length} agent(s) registered).`))}if(s==="status"){const i=v(r,t),a=r.prepare(`
10
10
  SELECT
11
11
  SUM(CASE WHEN quarantine_scope = 'run' AND resolved = 0 THEN 1 ELSE 0 END) AS quarantined,
12
12
  SUM(CASE WHEN resolved = 1 THEN 1 ELSE 0 END) AS resolved
@@ -17,8 +17,8 @@ Orchestrator: ${o.orchestrator}`:""}
17
17
  Agents (${o.agents.length}):${m?`
18
18
  ${m}`:" none"}
19
19
  Failures: ${o.quarantined} run-quarantined unresolved, ${o.resolved} resolved
20
- Quest claims held: ${o.claims}`})}if(s==="debrief"){const i=w("project_path",e.project_path,{maxLen:500});if(!i)throw new E("project_path","is required for action=debrief (the project the candidates file under)");const a=n.store.getProject(i);if(!a)return{content:[{type:"text",text:`Project not found: ${i}`}],isError:!0};const d=e.learnings;if(!Array.isArray(d)||d.length===0)throw new E("learnings","is required for action=debrief \u2014 an array of { agent_id?, text }");if(d.length>64)throw new E("learnings","too many entries (max 64 per debrief call)");for(const u of d){const f=u&&typeof u=="object"?u.text:void 0;typeof f=="string"&&w("learnings[].text",f,{maxLen:32e3})}let c=0,o=0,p=0;const m=new Set;for(const u of d){const f=u&&typeof u=="object"?u.text:void 0;if(typeof f!="string"||f.trim().length<20){o++;continue}const F=f,P=h("learnings[].agent_id",u.agent_id)??_.agent_id,{candidates:M,method:U}=await J(F);m.add(U),p+=M.length;for(const k of M){const y=Q(k),Y=y.tags[y.tags.length-1];if(r.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(a.id,"%"+K(Y)+"%")){o++;continue}G({agent_id:P,run_id:t,source:"param"},()=>n.memory.add(a.id,{kind:y.kind,problem:y.problem,tags:[...y.tags,`run:${t}`],confidence:y.confidence,needsReview:1,createdBy:"run-debrief"})),c++}}C(r,t),c>0&&n.cache.invalidate();const l={action:"debrief",run_id:t,status:g.status,method:m.size===1?[...m][0]:m.size===0?"none":"mixed",submitted:d.length,candidates:p,queued:c,skipped:o};return R(l,(u,f)=>x(f.brand,`Run ${u.run_id} debrief (${u.method}): ${u.submitted} learning(s) -> ${u.queued} candidate(s) queued for review, ${u.skipped} skipped.`)+`
21
- Approve/reject with wyrm_review (run-scoped tag run:${u.run_id}).`)}if(g.status!=="running")return{content:[{type:"text",text:`Run ${t} already ended (status: ${g.status}).`}],isError:!0};const T=h("agent_id",e.agent_id)??_.agent_id;if(!T)throw new E("agent_id","no agent identity \u2014 action=end is member-only; pass agent_id or supply the actor envelope (actor param / _meta / WYRM_AGENT_ID)");if(!re(r,t,T))return{content:[{type:"text",text:`'${T}' is not a registered member of run ${t} \u2014 only run members may end it (action=join first).`}],isError:!0};const $=W("status",e.status,Z,"completed"),A=w("project_path",e.project_path,{maxLen:500}),j=A?n.store.getProject(A):void 0;if(A&&!j)return{content:[{type:"text",text:`Project not found: ${A}`}],isError:!0};const I=r.prepare(`
20
+ Quest claims held: ${o.claims}`})}if(s==="debrief"){const i=w("project_path",e.project_path,{maxLen:500});if(!i)throw new E("project_path","is required for action=debrief (the project the candidates file under)");const a=n.store.getProject(i);if(!a)return{content:[{type:"text",text:`Project not found: ${i}`}],isError:!0};const d=e.learnings;if(!Array.isArray(d)||d.length===0)throw new E("learnings","is required for action=debrief: an array of { agent_id?, text }");if(d.length>64)throw new E("learnings","too many entries (max 64 per debrief call)");for(const u of d){const f=u&&typeof u=="object"?u.text:void 0;typeof f=="string"&&w("learnings[].text",f,{maxLen:32e3})}let c=0,o=0,p=0;const m=new Set;for(const u of d){const f=u&&typeof u=="object"?u.text:void 0;if(typeof f!="string"||f.trim().length<20){o++;continue}const F=f,P=h("learnings[].agent_id",u.agent_id)??_.agent_id,{candidates:M,method:U}=await J(F);m.add(U),p+=M.length;for(const k of M){const y=Q(k),Y=y.tags[y.tags.length-1];if(r.prepare("SELECT 1 FROM memory_artifacts WHERE project_id = ? AND tags LIKE ? ESCAPE '\\' LIMIT 1").get(a.id,"%"+K(Y)+"%")){o++;continue}G({agent_id:P,run_id:t,source:"param"},()=>n.memory.add(a.id,{kind:y.kind,problem:y.problem,tags:[...y.tags,`run:${t}`],confidence:y.confidence,needsReview:1,createdBy:"run-debrief"})),c++}}C(r,t),c>0&&n.cache.invalidate();const l={action:"debrief",run_id:t,status:g.status,method:m.size===1?[...m][0]:m.size===0?"none":"mixed",submitted:d.length,candidates:p,queued:c,skipped:o};return R(l,(u,f)=>x(f.brand,`Run ${u.run_id} debrief (${u.method}): ${u.submitted} learning(s) -> ${u.queued} candidate(s) queued for review, ${u.skipped} skipped.`)+`
21
+ Approve/reject with wyrm_review (run-scoped tag run:${u.run_id}).`)}if(g.status!=="running")return{content:[{type:"text",text:`Run ${t} already ended (status: ${g.status}).`}],isError:!0};const T=h("agent_id",e.agent_id)??_.agent_id;if(!T)throw new E("agent_id","no agent identity: action=end is member-only; pass agent_id or supply the actor envelope (actor param / _meta / WYRM_AGENT_ID)");if(!re(r,t,T))return{content:[{type:"text",text:`'${T}' is not a registered member of run ${t} \u2014 only run members may end it (action=join first).`}],isError:!0};const $=W("status",e.status,Z,"completed"),A=w("project_path",e.project_path,{maxLen:500}),j=A?n.store.getProject(A):void 0;if(A&&!j)return{content:[{type:"text",text:`Project not found: ${A}`}],isError:!0};const I=r.prepare(`
22
22
  SELECT id FROM failure_patterns
23
23
  WHERE run_id = ? AND quarantine_scope = 'run' AND resolved = 0
24
24
  `).all(t).map(i=>i.id);te(r,t,$),n.failures.sweepRunQuarantine();let N=0,L=0;if(I.length>0){const i=r.prepare("SELECT quarantine_scope, resolved FROM failure_patterns WHERE id = ?");for(const a of I){const d=i.get(a);d&&(d.quarantine_scope!=="run"?N++:d.resolved===1&&L++)}}const D=n.presence.releaseRunClaims(t);try{r.prepare("DELETE FROM run_briefs WHERE run_id = ?").run(t)}catch{}let b=null;if(j){const i=v(r,t);b=n.memory.add(j.id,{kind:"reasoning_trace",problem:`Fleet run ${t} ended ${$}: ${i.length} agent(s), ${N} quarantined failure(s) promoted, ${L} expired, ${D} quest claim(s) released.`,outcome:$==="completed"?"positive":$==="failed"?"negative":"neutral",tags:["run-summary",`run:${t}`],confidence:1,createdBy:"wyrm_run"}).id,r.prepare("UPDATE runs SET debrief_artifact_id = ? WHERE run_id = ?").run(b,t)}return n.cache.invalidate(),R({action:"end",run_id:t,status:$,promoted:N,expired:L,claims_released:D,summary_artifact_id:b},(i,a)=>x(a.brand,`Run ${i.run_id} ended (${i.status}): ${i.promoted} failure(s) promoted, ${i.expired} expired, ${i.claims_released} claim(s) released.`)+(i.summary_artifact_id!==null?`
@@ -1,4 +1,4 @@
1
- import{TOOL_ANNOTATIONS as A}from"../tool-annotations.js";import{renderResult as I,withGlyph as q,cacheKeyFor as C}from"../render.js";import{sanitizeFtsQuery as Q}from"../security.js";import{constellationQuery as E}from"../constellation.js";const B=[{name:"wyrm_search",description:"Use to find anything by keyword or meaning across all projects - find past sessions that mention a topic, look up everything we have about a service, or ask have I solved this problem before in any other project (cross-project constellation). FTS/vector/hybrid; where a function or symbol is defined across repos lives here too (wyrm_symbol_search).",inputSchema:{type:"object",properties:{query:{type:"string"},type:{type:"string",enum:["all","sessions","quests","data","artifacts","entities"]},mode:{type:"string",enum:["lexical","semantic","hybrid"],description:"Default hybrid, else lexical"},projectPath:{type:"string",description:"Limit to one project"}},required:["query"]},outputSchema:{type:"object",properties:{query:{type:"string"},mode:{type:"string",enum:["lexical","semantic","hybrid"]},sections:{type:"array",items:{type:"object",properties:{label:{type:"string"},count:{type:"integer"},items:{type:"array",items:{type:"string"}}},required:["label","count","items"]}},skills:{type:"array"},entities:{type:"array"}},required:["query","mode","sections","skills","entities"]},annotations:A.wyrm_search,aliases:[],handler:async(j,{store:l,graph:$,vectors:b,cache:w,decrypt:S})=>{const{query:d,type:_,mode:k,projectPath:m}=j,y=b(),u=Q(d),n=(m?l.getProject(m):void 0)?.id,i=_||"all",p=k||(y?"hybrid":"lexical"),c={};if((i==="all"||i==="sessions")&&p!=="semantic"){const e=l.searchSessions(u,n);c.session=e.map((t,s)=>({id:t.id,snippet:`${t.date}: ${(t.objectives||t.completed||"No info").slice(0,80)}`,rank:s+1}))}if((i==="all"||i==="quests")&&p!=="semantic"){const e=l.searchQuests(u);c.quest=e.map((t,s)=>({id:t.id,snippet:`#${t.id}: ${t.title}`,rank:s+1}))}if((i==="all"||i==="data")&&p!=="semantic"){const e=l.searchData(u,n);c.note=e.map((t,s)=>({id:t.id,snippet:`${t.category}/${t.key}: ${S(t.value).slice(0,60)}`,rank:s+1}))}if((i==="all"||i==="artifacts")&&p!=="semantic"){const e=l.searchArtifacts(u,n);c.artifact=e.map((t,s)=>({id:t.id,snippet:`#${t.id}: ${(t.problem||"").slice(0,80)}`,rank:s+1}))}let x=[];if(y&&p!=="lexical"&&(i==="all"||i==="data"||i==="sessions"||i==="quests"))try{x=await y.search(d,20,n)}catch{}const T=60,f=new Map,D=(e,t)=>{t.forEach((s,r)=>{const a=`${e}:${s.id}`,h=f.get(a),g=1/(T+(r+1));h?h.score+=g:f.set(a,{score:g,snippet:s.snippet,type:e,id:s.id})})};for(const[e,t]of Object.entries(c))D(e,t);const v={};for(const e of x)v[e.content_type]||(v[e.content_type]=[]),v[e.content_type].push(e);for(const[e,t]of Object.entries(v))t.forEach((s,r)=>{const a=`${e}:${s.content_id}`,h=f.get(a),g=1/(T+(r+1));h?(h.score+=g,h.snippet+=` (semantic: ${(s.similarity*100).toFixed(0)}%)`):f.set(a,{score:g,snippet:`${e} #${s.content_id} (semantic: ${(s.similarity*100).toFixed(0)}%)`,type:e,id:s.content_id})});const R=Array.from(f.values()).sort((e,t)=>t.score-e.score),P=[];if(R.length>0){const e={};for(const t of R)e[t.type]||(e[t.type]=[]),e[t.type].push(t);for(const[t,s]of Object.entries(e)){const r=t==="note"?"Data":t.charAt(0).toUpperCase()+t.slice(1)+"s";P.push({label:r,count:s.length,items:s.slice(0,10).map(a=>a.snippet)})}}let N=[];(i==="all"||i==="skills")&&(N=l.searchSkills(u,10).map(e=>({name:e.name,category:e.category||"uncategorized",active:!!e.is_active,description:e.description})));let F=[];(i==="all"||i==="entities")&&n!==void 0&&(F=$.searchEntities(n,d,10).map(e=>({name:e.name,type:e.type,relationships:$.getRelationships(e.id,"both").length})));const O=I({query:d,mode:p,sections:P,skills:N,entities:F},(e,t)=>{let s=q(t.brand,`**Search Results for "${e.query}"** _(mode: ${e.mode})_`)+`
1
+ import{TOOL_ANNOTATIONS as D}from"../tool-annotations.js";import{renderResult as C,withGlyph as v,cacheKeyFor as L}from"../render.js";import{sanitizeFtsQuery as M}from"../security.js";import{asEnum as E,requireString as z}from"../validate.js";import{constellationQuery as K}from"../constellation.js";const V=[{name:"wyrm_search",description:"Use to find anything by keyword or meaning across all projects - find past sessions that mention a topic, look up everything we have about a service, or ask have I solved this problem before in any other project (cross-project constellation). FTS/vector/hybrid; where a function or symbol is defined across repos: type=symbols.",inputSchema:{type:"object",properties:{query:{type:"string"},type:{type:"string",enum:["all","sessions","quests","data","artifacts","skills","entities","symbols"]},mode:{type:"string",enum:["lexical","semantic","hybrid"],description:"Default hybrid, else lexical"},projectPath:{type:"string",description:"Limit to one project"}},required:["query"]},outputSchema:{type:"object",properties:{query:{type:"string"},mode:{type:"string",enum:["lexical","semantic","hybrid"]},sections:{type:"array",items:{type:"object",properties:{label:{type:"string"},count:{type:"integer"},items:{type:"array",items:{type:"string"}}},required:["label","count","items"]}},skills:{type:"array"},entities:{type:"array"}},required:["query","mode","sections","skills","entities"]},annotations:D.wyrm_search,aliases:[],handler:async(b,{store:p,graph:k,vectors:_,cache:w,decrypt:S,symbols:x})=>{const u=b,{projectPath:h}=u,y=z("query",u.query),f=E("type",u.type,["all","sessions","quests","data","artifacts","skills","entities","symbols"]),T=E("mode",u.mode,["lexical","semantic","hybrid"]),n=_(),o=M(y),l=(h?p.getProject(h):void 0)?.id,i=f||"all",d=T||(n?"hybrid":"lexical"),g={};if((i==="all"||i==="sessions")&&d!=="semantic"){const e=p.searchSessions(o,l);g.session=e.map((t,s)=>({id:t.id,snippet:`${t.date}: ${(t.objectives||t.completed||"No info").slice(0,80)}`,rank:s+1}))}if((i==="all"||i==="quests")&&d!=="semantic"){const e=p.searchQuests(o);g.quest=e.map((t,s)=>({id:t.id,snippet:`#${t.id}: ${t.title}`,rank:s+1}))}if((i==="all"||i==="data")&&d!=="semantic"){const e=p.searchData(o,l);g.note=e.map((t,s)=>({id:t.id,snippet:`${t.category}/${t.key}: ${S(t.value).slice(0,60)}`,rank:s+1}))}if((i==="all"||i==="artifacts")&&d!=="semantic"){const e=p.searchArtifacts(o,l);g.artifact=e.map((t,s)=>({id:t.id,snippet:`#${t.id}: ${(t.problem||"").slice(0,80)}`,rank:s+1}))}let P=[];if(n&&d!=="lexical"&&(i==="all"||i==="data"||i==="sessions"||i==="quests"))try{P=await n.search(y,20,l)}catch{}const N=60,j=new Map,Q=(e,t)=>{t.forEach((s,r)=>{const a=`${e}:${s.id}`,m=j.get(a),$=1/(N+(r+1));m?m.score+=$:j.set(a,{score:$,snippet:s.snippet,type:e,id:s.id})})};for(const[e,t]of Object.entries(g))Q(e,t);const q={};for(const e of P)q[e.content_type]||(q[e.content_type]=[]),q[e.content_type].push(e);for(const[e,t]of Object.entries(q))t.forEach((s,r)=>{const a=`${e}:${s.content_id}`,m=j.get(a),$=1/(N+(r+1));m?(m.score+=$,m.snippet+=` (semantic: ${(s.similarity*100).toFixed(0)}%)`):j.set(a,{score:$,snippet:`${e} #${s.content_id} (semantic: ${(s.similarity*100).toFixed(0)}%)`,type:e,id:s.content_id})});const F=Array.from(j.values()).sort((e,t)=>t.score-e.score),R=[];if(F.length>0){const e={};for(const t of F)e[t.type]||(e[t.type]=[]),e[t.type].push(t);for(const[t,s]of Object.entries(e)){const r=t==="note"?"Data":t.charAt(0).toUpperCase()+t.slice(1)+"s";R.push({label:r,count:s.length,items:s.slice(0,10).map(a=>a.snippet)})}}if(i==="symbols"){const e=x.search(y,{projectId:l,limit:50});e.length>0&&R.push({label:"Symbols",count:e.length,items:e.slice(0,10).map(t=>`[${t.language}] ${t.kind} ${t.symbol} -- ${t.file_path}:${t.line}`)})}let O=[];(i==="all"||i==="skills")&&(O=p.searchSkills(o,10).map(e=>({name:e.name,category:e.category||"uncategorized",active:!!e.is_active,description:e.description})));let A=[];(i==="all"||i==="entities")&&l!==void 0&&(A=k.searchEntities(l,y,10).map(e=>({name:e.name,type:e.type,relationships:k.getRelationships(e.id,"both").length})));const I=C({query:y,mode:d,sections:R,skills:O,entities:A},(e,t)=>{let s=v(t.brand,`**Search Results for "${e.query}"** _(mode: ${e.mode})_`)+`
2
2
 
3
3
  `;for(const r of e.sections){s+=`## ${r.label} (${r.count})
4
4
  `;for(const a of r.items)s+=`${t.bullet} ${a}
@@ -11,5 +11,5 @@ import{TOOL_ANNOTATIONS as A}from"../tool-annotations.js";import{renderResult as
11
11
  `;for(const r of e.entities)s+=`${t.bullet} **${r.name}** _(${r.type})_ -- ${r.relationships} relationship${r.relationships!==1?"s":""}
12
12
  `;s+=`
13
13
  `}return e.sections.length===0&&e.skills.length===0&&e.entities.length===0&&(s+=`_No results found._
14
- `),s},{summary:(e,t)=>{const s=e.sections.reduce((r,a)=>r+a.count,0)+e.skills.length+e.entities.length;return q(t.brand,`**Search Results for "${e.query}"** _(mode: ${e.mode})_ -- ${s} result${s!==1?"s":""}`)}});return w.set(C("wyrm_search",JSON.stringify(j)),O,2e4),O}},{name:"wyrm_constellation",description:"Cross-project memory query \u2014 search every registered Wyrm project at once for matching truths, artifacts, quests, or design references. Returns candidates grouped by project; the calling AI does semantic ranking on its side. Use this when the operator asks 'have I solved this before?', 'where else have I used X?', or any portfolio-spanning question. Honours per-row cross_project_visibility ('within' = invisible across projects, 'org' = shared, 'public' = anywhere).",inputSchema:{type:"object",properties:{query:{type:"string",description:"Search keywords. Sanitised; safe FTS5 subset only."},kinds:{type:"array",items:{type:"string",enum:["truth","artifact","quest","decision","reference"]},description:"Restrict to specific kinds. Default: all kinds."},visibility:{type:"array",items:{type:"string",enum:["within","org","public"]},description:"Which visibility levels to include. Default: ['org','public'] \u2014 strict cross-project gate."},per_project_limit:{type:"number",description:"Max hits per project (default 5, max 50)."},caller_project_path:{type:"string",description:"Caller's current project path \u2014 excluded from results unless 'within' is in visibility."}},required:["query"]},outputSchema:{type:"object",properties:{query:{type:"string"},count:{type:"integer"},projects:{type:"array",items:{type:"object",properties:{project:{type:"string"},hits:{type:"array",items:{type:"object",properties:{kind:{type:"string"},id:{type:"integer"},title:{type:"string"},visibility:{type:"string"},snippet:{type:["string","null"]}},required:["kind","id","title","visibility","snippet"]}}},required:["project","hits"]}}},required:["query","count","projects"]},annotations:A.wyrm_constellation,aliases:[],handler:(j,{store:l,raw:$})=>{const{query:b,kinds:w,visibility:S,per_project_limit:d,caller_project_path:_}=j;let k;_&&(k=l.getProject(_)?.id);const m=E($(),{query:b,kinds:w,visibility:S,perProjectLimit:d,callerProjectId:k}),y=new Map;for(const o of m){const n=y.get(o.projectName)??[];n.push(o),y.set(o.projectName,n)}const u={query:b,count:m.length,projects:Array.from(y.entries()).map(([o,n])=>({project:o,hits:n.map(i=>({kind:i.kind,id:i.id,title:i.title,visibility:i.visibility,snippet:i.snippet??null}))}))};return I(u,(o,n)=>{if(o.count===0)return q(n.brand,`No constellation matches for *${o.query}* across registered projects.`)+"\n\n_Set `cross_project_visibility` to `'org'` or `'public'` on the items you want surfaced here. Default is `'within'` -- within-project only._";const i=[q(n.brand,`**Constellation** -- ${o.count} match(es) for *${o.query}* across ${o.projects.length} project(s)`)];for(const p of o.projects){i.push(""),i.push(`### ${p.project}`);for(const c of p.hits)i.push(`${n.bullet} \`${c.kind}:${c.id}\` **${c.title}** (${c.visibility})`),c.snippet&&i.push(` ${c.snippet}`)}return i.join(`
15
- `)})}}];export{B as searchToolSpecs};
14
+ `),s},{summary:(e,t)=>{const s=e.sections.reduce((r,a)=>r+a.count,0)+e.skills.length+e.entities.length;return v(t.brand,`**Search Results for "${e.query}"** _(mode: ${e.mode})_ -- ${s} result${s!==1?"s":""}`)}});return w.set(L("wyrm_search",JSON.stringify(b)),I,2e4),I}},{name:"wyrm_constellation",description:"Cross-project memory query \u2014 search every registered Wyrm project at once for matching truths, artifacts, quests, or design references. Returns candidates grouped by project; the calling AI does semantic ranking on its side. Use this when the operator asks 'have I solved this before?', 'where else have I used X?', or any portfolio-spanning question. Honours per-row cross_project_visibility ('within' = invisible across projects, 'org' = shared, 'public' = anywhere).",inputSchema:{type:"object",properties:{query:{type:"string",description:"Search keywords. Sanitised; safe FTS5 subset only."},kinds:{type:"array",items:{type:"string",enum:["truth","artifact","quest","decision","reference"]},description:"Restrict to specific kinds. Default: all kinds."},visibility:{type:"array",items:{type:"string",enum:["within","org","public"]},description:"Which visibility levels to include. Default: ['org','public'] \u2014 strict cross-project gate."},per_project_limit:{type:"number",description:"Max hits per project (default 5, max 50)."},caller_project_path:{type:"string",description:"Caller's current project path \u2014 excluded from results unless 'within' is in visibility."}},required:["query"]},outputSchema:{type:"object",properties:{query:{type:"string"},count:{type:"integer"},projects:{type:"array",items:{type:"object",properties:{project:{type:"string"},hits:{type:"array",items:{type:"object",properties:{kind:{type:"string"},id:{type:"integer"},title:{type:"string"},visibility:{type:"string"},snippet:{type:["string","null"]}},required:["kind","id","title","visibility","snippet"]}}},required:["project","hits"]}}},required:["query","count","projects"]},annotations:D.wyrm_constellation,aliases:[],handler:(b,{store:p,raw:k})=>{const{query:_,kinds:w,visibility:S,per_project_limit:x,caller_project_path:u}=b;let h;u&&(h=p.getProject(u)?.id);const y=K(k(),{query:_,kinds:w,visibility:S,perProjectLimit:x,callerProjectId:h}),f=new Map;for(const n of y){const o=f.get(n.projectName)??[];o.push(n),f.set(n.projectName,o)}const T={query:_,count:y.length,projects:Array.from(f.entries()).map(([n,o])=>({project:n,hits:o.map(c=>({kind:c.kind,id:c.id,title:c.title,visibility:c.visibility,snippet:c.snippet??null}))}))};return C(T,(n,o)=>{if(n.count===0)return v(o.brand,`No constellation matches for *${n.query}* across registered projects.`)+"\n\n_Set `cross_project_visibility` to `'org'` or `'public'` on the items you want surfaced here. Default is `'within'` -- within-project only._";const c=[v(o.brand,`**Constellation** -- ${n.count} match(es) for *${n.query}* across ${n.projects.length} project(s)`)];for(const l of n.projects){c.push(""),c.push(`### ${l.project}`);for(const i of l.hits)c.push(`${o.bullet} \`${i.kind}:${i.id}\` **${i.title}** (${i.visibility})`),i.snippet&&c.push(` ${i.snippet}`)}return c.join(`
15
+ `)})}}];export{V as searchToolSpecs};
@@ -1,26 +1,26 @@
1
- import{basename as ce}from"path";import{createHash as ae}from"crypto";import{blockText as K}from"./types.js";import{TOOL_ANNOTATIONS as R}from"../tool-annotations.js";import{renderResult as I,withGlyph as P,cacheKeyFor as A}from"../render.js";import{computeStaleness as de}from"../intelligence.js";import{ValidationError as ue,asInt as pe,asString as le}from"../validate.js";import{sanitizeActorId as me,getActor as _e}from"./boundary.js";import{makeEstimator as fe}from"../token-budget.js";import{refreshThread as z}from"../thread-assemble.js";import{autoEnrollProject as ye,resolveProjectForPath as he}from"../project-resolve.js";import{resolvePolicy as X,policyHash as Z}from"../recall-policy.js";function ee(e){return!e||!e.thread.trim()?null:`### \u{1F9F5} Where We Left Off _(the living thread \u2014 what we were working on and thinking about)_
1
+ import{basename as ue}from"path";import{createHash as pe}from"crypto";import{blockText as X}from"./types.js";import{TOOL_ANNOTATIONS as C}from"../tool-annotations.js";import{renderResult as P,withGlyph as N,cacheKeyFor as L}from"../render.js";import{computeStaleness as le}from"../intelligence.js";import{ValidationError as me,asInt as _e,asString as F}from"../validate.js";import{sanitizeActorId as fe,getActor as ye}from"./boundary.js";import{makeEstimator as he}from"../token-budget.js";import{refreshThread as Z}from"../thread-assemble.js";import{autoEnrollProject as ge,resolveProjectForPath as ee}from"../project-resolve.js";import{resolvePolicy as te,policyHash as se}from"../recall-policy.js";function ne(e){return!e||!e.thread.trim()?null:`### \u{1F9F5} Where We Left Off _(the living thread \u2014 what we were working on and thinking about)_
2
2
  `+`_updated ${e.updated_at}${e.updated_by?" \xB7 "+e.updated_by:""} \xB7 ${e.turn_count} turn(s)_`+`
3
3
 
4
- `+e.thread.slice(-1800)}const ge={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 te(e,t){let s=`### \u{1F4DA} Project Ground Truths _(treat as authoritative \u2014 do not infer or contradict)_
5
- `;const o=new Map;for(const i of e)o.has(i.category)||o.set(i.category,[]),o.get(i.category).push(i);for(const[i,c]of o){s+=`**${ge[i]??i}:**
6
- `;for(const n of c){let l="";if(t){const d=de(n);d!==null&&d>.7&&(l="[\u26A0\uFE0F STALE] ")}s+=`- ${l}**${n.key}:** ${n.value}${n.rationale?` _(${n.rationale})_`:""}
7
- `}}return s}const se=`
4
+ `+e.thread.slice(-1800)}const je={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 oe(e,t){let s=`### \u{1F4DA} Project Ground Truths _(treat as authoritative \u2014 do not infer or contradict)_
5
+ `;const o=new Map;for(const r of e)o.has(r.category)||o.set(r.category,[]),o.get(r.category).push(r);for(const[r,i]of o){s+=`**${je[r]??r}:**
6
+ `;for(const n of i){let u="";if(t){const d=le(n);d!==null&&d>.7&&(u="[\u26A0\uFE0F STALE] ")}s+=`- ${u}**${n.key}:** ${n.value}${n.rationale?` _(${n.rationale})_`:""}
7
+ `}}return s}const re=`
8
8
  SELECT * FROM quests
9
9
  WHERE project_id = ? AND status = 'pending'
10
10
  ORDER BY
11
11
  CASE priority WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 ELSE 4 END,
12
12
  id
13
13
  LIMIT 5
14
- `;async function ne(){try{const{resolveEmbeddingState:e,tierForResolution:t}=await import("../providers/embedding-provider.js");return t(e())}catch{return{n:1,label:"keyword"}}}function oe(e){const t={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F7E2}"};let s=`### \u{1F3AF} Active Quests
14
+ `;async function ie(){try{const{resolveEmbeddingState:e,tierForResolution:t}=await import("../providers/embedding-provider.js");return t(e())}catch{return{n:1,label:"keyword"}}}function ce(e){const t={critical:"\u{1F534}",high:"\u{1F7E0}",medium:"\u{1F7E1}",low:"\u{1F7E2}"};let s=`### \u{1F3AF} Active Quests
15
15
  `;for(const o of e)s+=`- ${t[o.priority]??"\u2022"} #${o.id}: ${o.title}
16
- `;return s}function L(e,t,s,o){if(s)return e.getProjectById(s);if(o)return t.prepare("SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1").get(`%${o}%`);const{id:i}=he(t,process.cwd());if(i!=null)return e.getProjectById(i);const c=t.prepare(`
16
+ `;return s}function D(e,t,s,o,r){if(s)return e.getProjectById(s);if(o)return t.prepare("SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1").get(`%${o}%`);if(r){const d=e.getProject(r);if(d)return d;const{id:p}=ee(t,r);return p!=null?e.getProjectById(p):void 0}const{id:i}=ee(t,process.cwd());if(i!=null)return e.getProjectById(i);const n=t.prepare(`
17
17
  SELECT p.* FROM projects p
18
18
  JOIN sessions s ON s.project_id = p.id
19
19
  ORDER BY s.created_at DESC LIMIT 1
20
- `).get();if(c)return c;const n=ye(e);return n?e.getProjectById(n.id):void 0}function F(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:
20
+ `).get();if(n)return n;const u=ge(e);return u?e.getProjectById(u.id):void 0}function M(e){const t=e.getAllProjects(20);return t.length===0?{content:[{type:"text",text:"No projects found. Register one first (wyrm_project action=scan)."}],isError:!0}:{content:[{type:"text",text:`Project not found. Available projects:
21
21
  ${t.map(o=>`- ${o.name} (ID: ${o.id})`).join(`
22
- `)}`}],isError:!0}}const ie=(e,t)=>{const s=e.session_id!==null?`
23
- Session logged: #${e.session_id}`:"";return P(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"}${s}`)},je=(e,t)=>{const s=typeof e.tier_n=="number"&&e.tier_label?`tier ${e.tier_n}/3 (${e.tier_label})`:"tier n/a (cached brief)";return P(t.brand,`Wyrm ${t.bullet} ${s} ${t.bullet} ${e.memory_count} memor${e.memory_count!==1?"ies":"y"} on file ${t.bullet} auto-capture: hooks on Claude Code, model-driven elsewhere`)},D=(e,t)=>{const s=ie(e,t),o=e.sections.length>0?e.sections.map(i=>i.text).join(`
22
+ `)}`}],isError:!0}}const ae=(e,t)=>{const s=e.session_id!==null?`
23
+ 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"}${s}`)},be=(e,t)=>{const s=typeof e.tier_n=="number"&&e.tier_label?`tier ${e.tier_n}/3 (${e.tier_label})`:"tier n/a (cached brief)";return N(t.brand,`Wyrm ${t.bullet} ${s} ${t.bullet} ${e.memory_count} memor${e.memory_count!==1?"ies":"y"} on file ${t.bullet} auto-capture: hooks on Claude Code, model-driven elsewhere`)},B=(e,t)=>{const s=ae(e,t),o=e.sections.length>0?e.sections.map(r=>r.text).join(`
24
24
 
25
25
  ---
26
26
 
@@ -28,19 +28,19 @@ Session logged: #${e.session_id}`:"";return P(t.brand,`Wyrm primed for **${e.pro
28
28
 
29
29
  `+s:s+`
30
30
 
31
- _No memory stored yet -- start working and Wyrm will learn._`;return je(e,t)+`
31
+ _No memory stored yet -- start working and Wyrm will learn._`;return be(e,t)+`
32
32
 
33
- `+o},M={summary:ie};async function B(e,t,s){try{const{logSavings:o}=await import("../statusline.js"),i=K(s.content[0]).length,c=Math.round(i/4),n=e.prepare("SELECT id FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(t);o(e,"wyrm_session_prime","recovered_context",c,n?.id)}catch{}}const be={continuity:{path:"continuity",tool:"wyrm_session_prime"},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"}},Se=48,ve=1200;async function Ee(e,{store:t,raw:s,memory:o,truths:i,scaffolds:c,cache:n}){const{project_id:l,project_name:d,task:m,log_session:u}=e,r=me(e.run_id);if(r===null)throw new ue("run_id",'must be <=64 printable-ASCII chars (no ";")');const h=le("role",e.role,{maxLen:200})??"",b=pe("token_budget",e.token_budget,{min:1})??ve,g=s(),p=X(g,r),x=p.mode==="full"&&p.mutedIds.length===0&&p.challengedTruthIds.length===0?h:`${h}::${Z(p)}`,$=()=>g.prepare("SELECT body_json FROM run_briefs WHERE run_id = ? AND role = ?").get(r,x),S=l||d?L(t,g,l,d):void 0;if((l||d)&&!S)return F(t);let v=$();if(v&&S){const a=JSON.parse(v.body_json);if(a.project_id!==S.id)return{content:[{type:"text",text:`Fleet brief for (run ${r}, role '${h}') is pinned to project '${a.project_name}' (ID: ${a.project_id}) \u2014 refusing to serve it for requested project '${S.name}' (ID: ${S.id}). Use a distinct role for a second project, or prime without run_id.`}],isError:!0}}if(!v){const a=S??L(t,g,l,d);if(!a)return F(t);const y=[],k=ee(t.getContinuity(a.id));k&&y.push({kind:"continuity",text:k});const _=m??(h||void 0),f=i.getCurrent(a.id),E=o.buildContextBrief(a.id,_??"general",{maxItems:10,minConfidence:.3,runId:r,groundTruths:f}),H=E.groundTruths??f;if(H.length>0&&y.push({kind:"truths",text:te(H,!1).slice(0,1200)}),_){const w=c.findBest(_,a.id,.3);w&&y.push({kind:"scaffold",text:c.formatForContext(w,1500)})}E.text&&y.push({kind:"memory",text:E.text.slice(0,2e3)});const N=g.prepare(se).all(a.id);N.length>0&&y.push({kind:"quests",text:oe(N).slice(0,800)});const U=fe(),O=[];let q=Se;for(const w of y){const J=U.count(w.text)+2;if(q+J<=b)O.push(w),q+=J;else{const V=be[w.kind]??{path:w.kind,tool:"wyrm_search"},Y=`[beyond token_budget] wyrm://project/${a.id}/${V.path} -- fetch via ${V.tool}`;O.push({kind:w.kind,text:Y}),q+=U.count(Y)+2}}let Q=null;u&&(Q=t.createSession(a.id,{objectives:m??"Session started via wyrm_session_prime",summary:""}).id);const G=await ne(),re={project_id:a.id,project_name:a.name,truth_count:i.getStats(a.id).current,quest_count:N.length,memory_count:o.getStats(a.id).total,session_id:Q,sections:O,run_id:r,role:h,token_budget:b,tier_n:G.n,tier_label:G.label};g.prepare("INSERT OR IGNORE INTO run_briefs (run_id, role, body_json, token_budget) VALUES (?, ?, ?, ?)").run(r,x,JSON.stringify(re),b),v=$()}const j=JSON.parse(v.body_json),T=I(j,D,M);if(e.for_spawn===!0){const a=I(j,D,{...M,exemptStructured:!0}),y=K(a.content[0]),k=ae("sha256").update(y,"utf8").digest("hex"),_={project_id:j.project_id,project_name:j.project_name,session_id:j.session_id??null,sections:j.sections,for_spawn:!0,run_id:r,role:h,brief:y,brief_hash:k,brief_chars:y.length,cache_key:{table:"run_briefs",run_id:r,role:x}};await B(g,j.project_id,a);const f={content:[{type:"text",text:y}],structuredContent:_};return n.set(A("wyrm_session_prime",JSON.stringify(e)),f,3e4),f}return await B(g,j.project_id,T),n.set(A("wyrm_session_prime",JSON.stringify(e)),T,3e4),T}const Ae=[{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:R.wyrm_session_start,aliases:[],handler:(e,{store:t,raw:s})=>{const{projectPath:o,objectives:i}=e;let c=t.getProject(o);c||(c=t.registerProject(ce(o),o));let n=t.getTodaySession(c.id);n?i&&(n=t.updateSession(n.id,{objectives:n.objectives?`${n.objectives}
34
- ${i}`:i})):n=t.createSession(c.id,{objectives:i||""}),t.archiveOldSessions(c.id,10),z(s(),c.id);const l={session_id:n.id,project:c.name,date:n.date,objectives:n.objectives||null};return I(l,(d,m)=>P(m.brand,`Session ${d.session_id} for ${d.project}`)+`
33
+ `+o},W={summary:ae};async function H(e,t,s){try{const{logSavings:o}=await import("../statusline.js"),r=X(s.content[0]).length,i=Math.round(r/4),n=e.prepare("SELECT id FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(t);o(e,"wyrm_session_prime","recovered_context",i,n?.id)}catch{}}const Se={continuity:{path:"continuity",tool:"wyrm_session_prime"},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"}},ve=48,we=1200;async function Ee(e,{store:t,raw:s,memory:o,truths:r,scaffolds:i,cache:n}){const{project_id:u,project_name:d,task:p,log_session:l}=e,m=fe(e.run_id);if(m===null)throw new me("run_id",'must be <=64 printable-ASCII chars (no ";")');const c=F("role",e.role,{maxLen:200})??"",w=_e("token_budget",e.token_budget,{min:1})??we,y=s(),E=te(y,m),T=E.mode==="full"&&E.mutedIds.length===0&&E.challengedTruthIds.length===0?c:`${c}::${se(E)}`,R=()=>y.prepare("SELECT body_json FROM run_briefs WHERE run_id = ? AND role = ?").get(m,T),x=F("projectPath",e.projectPath),b=u||d||x?D(t,y,u,d,x):void 0;if((u||d||x)&&!b)return M(t);let S=R();if(S&&b){const a=JSON.parse(S.body_json);if(a.project_id!==b.id)return{content:[{type:"text",text:`Fleet brief for (run ${m}, role '${c}') is pinned to project '${a.project_name}' (ID: ${a.project_id}) \u2014 refusing to serve it for requested project '${b.name}' (ID: ${b.id}). Use a distinct role for a second project, or prime without run_id.`}],isError:!0}}if(!S){const a=b??D(t,y,u,d,x);if(!a)return M(t);const h=[],I=ne(t.getContinuity(a.id));I&&h.push({kind:"continuity",text:I});const _=p??(c||void 0),f=r.getCurrent(a.id),v=o.buildContextBrief(a.id,_??"general",{maxItems:10,minConfidence:.3,runId:m,groundTruths:f}),Q=v.groundTruths??f;if(Q.length>0&&h.push({kind:"truths",text:oe(Q,!1).slice(0,1200)}),_){const $=i.findBest(_,a.id,.3);$&&h.push({kind:"scaffold",text:i.formatForContext($,1500)})}v.text&&h.push({kind:"memory",text:v.text.slice(0,2e3)});const O=y.prepare(re).all(a.id);O.length>0&&h.push({kind:"quests",text:ce(O).slice(0,800)});const G=he(),q=[];let A=ve;for(const $ of h){const Y=G.count($.text)+2;if(A+Y<=w)q.push($),A+=Y;else{const K=Se[$.kind]??{path:$.kind,tool:"wyrm_search"},z=`[beyond token_budget] wyrm://project/${a.id}/${K.path} -- fetch via ${K.tool}`;q.push({kind:$.kind,text:z}),A+=G.count(z)+2}}let J=null;l&&(J=t.createSession(a.id,{objectives:p??"Session started via wyrm_session_prime",summary:""}).id);const V=await ie(),de={project_id:a.id,project_name:a.name,truth_count:r.getStats(a.id).current,quest_count:O.length,memory_count:o.getStats(a.id).total,session_id:J,sections:q,run_id:m,role:c,token_budget:w,tier_n:V.n,tier_label:V.label};y.prepare("INSERT OR IGNORE INTO run_briefs (run_id, role, body_json, token_budget) VALUES (?, ?, ?, ?)").run(m,T,JSON.stringify(de),w),S=R()}const j=JSON.parse(S.body_json),k=P(j,B,W);if(e.for_spawn===!0){const a=P(j,B,{...W,exemptStructured:!0}),h=X(a.content[0]),I=pe("sha256").update(h,"utf8").digest("hex"),_={project_id:j.project_id,project_name:j.project_name,session_id:j.session_id??null,sections:j.sections,for_spawn:!0,run_id:m,role:c,brief:h,brief_hash:I,brief_chars:h.length,cache_key:{table:"run_briefs",run_id:m,role:T}};await H(y,j.project_id,a);const f={content:[{type:"text",text:h}],structuredContent:_};return n.set(L("wyrm_session_prime",JSON.stringify(e)),f,3e4),f}return await H(y,j.project_id,k),n.set(L("wyrm_session_prime",JSON.stringify(e)),k,3e4),k}const Le=[{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:C.wyrm_session_start,aliases:[],handler:(e,{store:t,raw:s})=>{const{projectPath:o,objectives:r}=e;let i=t.getProject(o);i||(i=t.registerProject(ue(o),o));let n=t.getTodaySession(i.id);n?r&&(n=t.updateSession(n.id,{objectives:n.objectives?`${n.objectives}
34
+ ${r}`:r})):n=t.createSession(i.id,{objectives:r||""}),t.archiveOldSessions(i.id,10),Z(s(),i.id);const u={session_id:n.id,project:i.name,date:n.date,objectives:n.objectives||null};return P(u,(d,p)=>N(p.brand,`Session ${d.session_id} for ${d.project}`)+`
35
35
  **Date:** ${d.date}
36
- **Objectives:** ${d.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:R.wyrm_session_update,aliases:[],handler:(e,{store:t,indexing:s,raw:o})=>{const{projectPath:i,completed:c,issues:n,commits:l,notes:d}=e,m=t.getProject(i);if(!m)return{content:[{type:"text",text:"Project not found"}],isError:!0};let u=t.getTodaySession(m.id);u||(u=t.createSession(m.id,{}));const r={};c&&(r.completed=u.completed?`${u.completed}
37
- ${c}`:c),n&&(r.issues=u.issues?`${u.issues}
38
- ${n}`:n),l&&(r.commits=u.commits?`${u.commits}
39
- ${l}`:l),d&&(r.notes=u.notes?`${u.notes}
40
- ${d}`:d),u=t.updateSession(u.id,r);const h=[c,n,d].filter(Boolean).join(" \xB7 ");h&&t.updateContinuity(m.id,h,"session_update"),s()?.enqueue("session",u.id,m.id),z(o(),m.id);const b={session_id:u.id,project:m.name,updated:Object.keys(r)};return I(b,(g,p)=>P(p.brand,`Session updated for ${g.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:R.wyrm_session_rehydrate,aliases:[],handler:(e,{rehydration:t})=>{const{session_id:s,include_artifacts:o,include_failures:i,max_truth_chars:c}=e,n=t.rehydrate(s,{include_artifacts:o,include_failures:i,max_truth_chars:c});return n?I({...n},l=>l.briefing_markdown,{exemptStructured:!0}):{content:[{type:"text",text:`Session #${s} 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:R.wyrm_session_prime,aliases:[],handler:async(e,{store:t,raw:s,memory:o,truths:i,scaffolds:c,cache:n})=>{const{project_id:l,project_name:d,task:m,log_session:u}=e;if(e.run_id!==void 0&&e.run_id!==null)return Ee(e,{store:t,raw:s,memory:o,truths:i,scaffolds:c,cache:n});const r=L(t,s(),l,d);if(!r)return F(t);const h=_e().run_id,b=X(s(),h),g=b.mode==="full"&&b.mutedIds.length===0&&b.challengedTruthIds.length===0,p=[],C=ee(t.getContinuity(r.id));C&&p.push({kind:"continuity",text:C});const x=i.getCurrent(r.id),$=o.buildContextBrief(r.id,m??"general",{maxItems:10,minConfidence:.3,runId:h,groundTruths:x}),S=$.groundTruths??x;if(S.length>0&&p.push({kind:"truths",text:te(S,!0).slice(0,1200)}),m){const _=c.findBest(m,r.id,.3);_&&p.push({kind:"scaffold",text:c.formatForContext(_,1500)})}$.text&&p.push({kind:"memory",text:$.text.slice(0,2e3)});const v=s().prepare(se).all(r.id);v.length>0&&p.push({kind:"quests",text:oe(v).slice(0,800)});try{const _=s().prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE project_id = ? AND needs_review = 1").get(r.id).n;_>0&&p.push({kind:"review_queue",text:`### \u{1F4E5} Review Queue
41
- ${_} captured memor${_===1?"y awaits":"ies await"} review (wyrm_review) \u2014 queued memory is invisible to recall until approved.`})}catch{}try{const{resolveEmbeddingState:_}=await import("../providers/embedding-provider.js"),f=_();let E=-1;try{E=s().prepare("SELECT COUNT(*) AS n FROM vectors").get().n}catch{E=0}f.resolved==="none"?p.push({kind:"vector_health",text:`### [!] Semantic Recall is OFF
42
- No embedding provider is active (${f.reason||"no provider"}) - recall is FTS5 keyword-only (~59.9% vs 72.2% hybrid recall@10). Fix: ${f.fix}`}):f.resolved==="local"?p.push({kind:"vector_health",text:`### [!] Semantic Recall is NON-SEMANTIC
43
- Provider 'local' (hash-384) is a test-grade fallback, not a real embedder - recall quality is at the FTS floor. Fix: ${f.fix}`}):E===0&&p.push({kind:"vector_health",text:`### [!] Vector Index is EMPTY
44
- Provider '${f.resolved}' (${f.model}) is configured but nothing is indexed - recall is running FTS5-only until the index is built. Fix: wyrm index rebuild`})}catch{}try{const{vaultAdvisory:_}=await import("../vault.js"),f=_();f.length>0&&p.push({kind:"vault",text:`### \u{1F510} Credential Vault
45
- `+f.map(E=>`- ${E}`).join(`
46
- `)})}catch{}let j=null;u&&(j=t.createSession(r.id,{objectives:m??"Session started via wyrm_session_prime",summary:""}).id);const T=await ne(),W={project_id:r.id,project_name:r.name,truth_count:i.getStats(r.id).current,quest_count:v.length,memory_count:o.getStats(r.id).total,session_id:j,sections:p,tier_n:T.n,tier_label:T.label},a=I(W,D,M);await B(s(),r.id,a);const y=A("wyrm_session_prime",JSON.stringify(e)),k=g?y:`${y}::${Z(b)}`;return n.set(k,a,3e4),a}}];export{Ae as sessionToolSpecs};
36
+ **Objectives:** ${d.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:C.wyrm_session_update,aliases:[],handler:(e,{store:t,indexing:s,raw:o})=>{const{projectPath:r,completed:i,issues:n,commits:u,notes:d}=e,p=t.getProject(r);if(!p)return{content:[{type:"text",text:"Project not found"}],isError:!0};let l=t.getTodaySession(p.id);l||(l=t.createSession(p.id,{}));const m={};i&&(m.completed=l.completed?`${l.completed}
37
+ ${i}`:i),n&&(m.issues=l.issues?`${l.issues}
38
+ ${n}`:n),u&&(m.commits=l.commits?`${l.commits}
39
+ ${u}`:u),d&&(m.notes=l.notes?`${l.notes}
40
+ ${d}`:d),l=t.updateSession(l.id,m);const c=[i,n,d].filter(Boolean).join(" \xB7 ");c&&t.updateContinuity(p.id,c,"session_update"),s()?.enqueue("session",l.id,p.id),Z(o(),p.id);const w={session_id:l.id,project:p.name,updated:Object.keys(m)};return P(w,(y,E)=>N(E.brand,`Session updated for ${y.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:C.wyrm_session_rehydrate,aliases:[],handler:(e,{rehydration:t})=>{const{session_id:s,include_artifacts:o,include_failures:r,max_truth_chars:i}=e,n=t.rehydrate(s,{include_artifacts:o,include_failures:r,max_truth_chars:i});return n?P({...n},u=>u.briefing_markdown,{exemptStructured:!0}):{content:[{type:"text",text:`Session #${s} 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"},projectPath:{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:C.wyrm_session_prime,aliases:[],handler:async(e,{store:t,raw:s,memory:o,truths:r,scaffolds:i,cache:n})=>{const{project_id:u,project_name:d,task:p,log_session:l}=e;if(e.run_id!==void 0&&e.run_id!==null)return Ee(e,{store:t,raw:s,memory:o,truths:r,scaffolds:i,cache:n});const m=F("projectPath",e.projectPath),c=D(t,s(),u,d,m);if(!c)return M(t);const w=ye().run_id,y=te(s(),w),E=y.mode==="full"&&y.mutedIds.length===0&&y.challengedTruthIds.length===0,g=[],T=ne(t.getContinuity(c.id));T&&g.push({kind:"continuity",text:T});const R=r.getCurrent(c.id),x=o.buildContextBrief(c.id,p??"general",{maxItems:10,minConfidence:.3,runId:w,groundTruths:R}),b=x.groundTruths??R;if(b.length>0&&g.push({kind:"truths",text:oe(b,!0).slice(0,1200)}),p){const _=i.findBest(p,c.id,.3);_&&g.push({kind:"scaffold",text:i.formatForContext(_,1500)})}x.text&&g.push({kind:"memory",text:x.text.slice(0,2e3)});const S=s().prepare(re).all(c.id);S.length>0&&g.push({kind:"quests",text:ce(S).slice(0,800)});try{const _=s().prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE project_id = ? AND needs_review = 1").get(c.id).n;_>0&&g.push({kind:"review_queue",text:`### \u{1F4E5} Review Queue
41
+ ${_} captured memor${_===1?"y awaits":"ies await"} review (wyrm_review) \u2014 queued memory is invisible to recall until approved.`})}catch{}try{const{resolveEmbeddingState:_}=await import("../providers/embedding-provider.js"),f=_();let v=-1;try{v=s().prepare("SELECT COUNT(*) AS n FROM vectors").get().n}catch{v=0}f.resolved==="none"?g.push({kind:"vector_health",text:`### [!] Semantic Recall is OFF
42
+ No embedding provider is active (${f.reason||"no provider"}) - recall is FTS5 keyword-only (~59.9% vs 72.2% hybrid recall@10). Fix: ${f.fix}`}):f.resolved==="local"?g.push({kind:"vector_health",text:`### [!] Semantic Recall is NON-SEMANTIC
43
+ Provider 'local' (hash-384) is a test-grade fallback, not a real embedder - recall quality is at the FTS floor. Fix: ${f.fix}`}):v===0&&g.push({kind:"vector_health",text:`### [!] Vector Index is EMPTY
44
+ Provider '${f.resolved}' (${f.model}) is configured but nothing is indexed - recall is running FTS5-only until the index is built. Fix: wyrm index rebuild`})}catch{}try{const{vaultAdvisory:_}=await import("../vault.js"),f=_();f.length>0&&g.push({kind:"vault",text:`### \u{1F510} Credential Vault
45
+ `+f.map(v=>`- ${v}`).join(`
46
+ `)})}catch{}let j=null;l&&(j=t.createSession(c.id,{objectives:p??"Session started via wyrm_session_prime",summary:""}).id);const k=await ie(),U={project_id:c.id,project_name:c.name,truth_count:r.getStats(c.id).current,quest_count:S.length,memory_count:o.getStats(c.id).total,session_id:j,sections:g,tier_n:k.n,tier_label:k.label},a=P(U,B,W);await H(s(),c.id,a);const h=L("wyrm_session_prime",JSON.stringify(e)),I=E?h:`${h}::${se(y)}`;return n.set(I,a,3e4),a}}];export{Le as sessionToolSpecs};
@@ -1,8 +1,8 @@
1
- import{TOOL_ANNOTATIONS as u}from"../tool-annotations.js";const h=[{name:"wyrm_ask",description:"Wyrm answers a natural-language question using its own data as context. Assembles relevant ground truths, sessions, artifacts, failures, symbols, then generates an answer. BACKEND POLICY (9.0.0): Ollama (local, auto-detected on localhost:11434) is the only backend used by default, because the context sent to a backend is the operator's own memory. Cloud backends \u2014 Claude (ANTHROPIC_API_KEY), NVIDIA NIM (NVIDIA_API_KEY), OpenAI (OPENAI_API_KEY) \u2014 are OPT-IN: they run only when the operator sets WYRM_ASK_ALLOW_CLOUD=1 or the caller names one via model_override. Having an API key set is NOT consent to send memory off the machine. If no permitted backend is reachable, returns the assembled raw context (degraded mode \u2014 explicit, not silent) with an error naming the remedy.",inputSchema:{type:"object",properties:{query:{type:"string",description:"Question in natural language"},projectPath:{type:"string",description:"Scope context to one project (optional)"},max_context_chars:{type:"number",description:"Soft cap on assembled context (default 12000, max 50000)"},model_override:{type:"string",description:"Force a backend/model: 'ollama:llama3.2', 'anthropic:claude-opus-4-8' (alias 'claude'), 'nim:nvidia/nemotron-3-ultra-550b' (alias 'nemotron'), or 'openai:gpt-4o-mini'"}},required:["query"]},annotations:u.wyrm_ask,aliases:[],handler:async(a,s)=>{const{db:i,subAgent:n}=s,{query:t,projectPath:e,max_context_chars:c,model_override:r}=a,p=e?i.getProject(e):null,o=await n.ask({query:t,project_id:p?.id??null,max_context_chars:c,model_override:r});return{content:[{type:"text",text:(o.degraded?`\u{F115D} Wyrm (${o.model}, ${o.context_chars} ctx chars, ${o.latency_ms}ms) \u2014 DEGRADED MODE: ${o.error}
1
+ import{TOOL_ANNOTATIONS as p}from"../tool-annotations.js";import{ValidationError as h,asEnum as g,requireInt as _}from"../validate.js";const m=["session","quest","truth","artifact","edge"];function y(r){const s=g("kind",r.kind,m);if(s===void 0)throw new h("kind",`is required (one of: ${m.join(", ")})`);return{kind:s,id:_("id",r.id,{min:1})}}const w=[{name:"wyrm_ask",description:"Wyrm answers a natural-language question using its own data as context. Assembles relevant ground truths, sessions, artifacts, failures, symbols, then generates an answer. BACKEND POLICY (9.0.0): Ollama (local, auto-detected on localhost:11434) is the only backend used by default, because the context sent to a backend is the operator's own memory. Cloud backends \u2014 Claude (ANTHROPIC_API_KEY), NVIDIA NIM (NVIDIA_API_KEY), OpenAI (OPENAI_API_KEY) \u2014 are OPT-IN: they run only when the operator sets WYRM_ASK_ALLOW_CLOUD=1 or the caller names one via model_override. Having an API key set is NOT consent to send memory off the machine. If no permitted backend is reachable, returns the assembled raw context (degraded mode \u2014 explicit, not silent) with an error naming the remedy.",inputSchema:{type:"object",properties:{query:{type:"string",description:"Question in natural language"},projectPath:{type:"string",description:"Scope context to one project (optional)"},max_context_chars:{type:"number",description:"Soft cap on assembled context (default 12000, max 50000)"},model_override:{type:"string",description:"Force a backend/model: 'ollama:llama3.2', 'anthropic:claude-opus-4-8' (alias 'claude'), 'nim:nvidia/nemotron-3-ultra-550b' (alias 'nemotron'), or 'openai:gpt-4o-mini'"}},required:["query"]},annotations:p.wyrm_ask,aliases:[],handler:async(r,s)=>{const{db:c,subAgent:o}=s,{query:t,projectPath:e,max_context_chars:a,model_override:i}=r,u=e?c.getProject(e):null,n=await o.ask({query:t,project_id:u?.id??null,max_context_chars:a,model_override:i});return{content:[{type:"text",text:(n.degraded?`\u{F115D} Wyrm (${n.model}, ${n.context_chars} ctx chars, ${n.latency_ms}ms) \u2014 DEGRADED MODE: ${n.error}
2
2
 
3
- `:`\u{F115D} Wyrm (${o.model}, ${o.context_chars} ctx chars, ${o.latency_ms}ms${o.tokens_in?`, ${o.tokens_in}+${o.tokens_out} tok`:""}):
3
+ `:`\u{F115D} Wyrm (${n.model}, ${n.context_chars} ctx chars, ${n.latency_ms}ms${n.tokens_in?`, ${n.tokens_in}+${n.tokens_out} tok`:""}):
4
4
 
5
- `)+o.answer}]}}},{name:"wyrm_share",description:"Use to share a finding with the rest of the team - marks a row (session/quest/truth/artifact/edge) is_shared for the next sync push; make a record private again with wyrm_unshare when it must not leave this machine.",inputSchema:{type:"object",properties:{kind:{type:"string",enum:["session","quest","truth","artifact","edge"]},id:{type:"number"}},required:["kind","id"]},annotations:u.wyrm_share,aliases:[],handler:async(a,s)=>{const{federation:i}=s,{kind:n,id:t}=a;return{content:[{type:"text",text:i.share(n,t)?`\u{F115D} ${n}#${t} marked shared.`:`\u{F115D} ${n}#${t} not found.`}]}}},{name:"wyrm_unshare",description:"Revoke team visibility for a row. Local row stays; future syncs won't include it.",inputSchema:{type:"object",properties:{kind:{type:"string",enum:["session","quest","truth","artifact","edge"]},id:{type:"number"}},required:["kind","id"]},annotations:u.wyrm_unshare,aliases:[],handler:async(a,s)=>{const{federation:i}=s,{kind:n,id:t}=a;return{content:[{type:"text",text:i.unshare(n,t)?`\u{F115D} ${n}#${t} unshared.`:`\u{F115D} ${n}#${t} not found.`}]}}},{name:"wyrm_sync_conflicts",description:"List unresolved sync conflicts \u2014 rows where a remote push landed on top of a newer local edit. Each conflict has been auto-converted into a quest (tag 'sync-conflict') so the operator sees it on next session.",inputSchema:{type:"object",properties:{limit:{type:"number",description:"Max conflicts (default 50)"}}},annotations:u.wyrm_sync_conflicts,aliases:[],handler:async(a,s)=>{const{federation:i,sync:n}=s,{limit:t}=a,e=i.unresolvedConflicts(Math.min(Math.max(1,t??50),500));if(e.length===0)return{content:[{type:"text",text:"\u{F115D} No unresolved sync conflicts."}]};const c=e.map(r=>`\u2022 #${r.id} ${r.local_kind}#${r.local_id} from ${r.remote_origin??"?"} at ${r.detected_at}`+(r.quest_id?` \u2192 quest #${r.quest_id}`:"")).join(`
5
+ `)+n.answer}]}}},{name:"wyrm_share",description:"Use to share a finding with the rest of the team - marks a row (session/quest/truth/artifact/edge) is_shared for the next sync push; revoke with wyrm_replication action=unshare.",inputSchema:{type:"object",properties:{kind:{type:"string",enum:["session","quest","truth","artifact","edge"]},id:{type:"number"}},required:["kind","id"]},annotations:p.wyrm_share,aliases:[],handler:async(r,s)=>{const{federation:c}=s,{kind:o,id:t}=y(r),e=c.shareOutcome(o,t);if(e.ok)return{content:[{type:"text",text:`\u{F115D} ${o}#${t} marked shared.`}]};if(e.reason==="private_grove"){const a=e.projectId!=null?String(e.projectId):"<project>";return{content:[{type:"text",text:`\u{F115D} ${o}#${t} was not shared: it belongs to a private grove (project ${a}), and private groves never leave this machine. To share it, change the grove's lane first: \`wyrm grove policy ${a} cloud\` (your own cloud) or \`wyrm grove policy ${a} team\` (federates to a team Wyrm).`}],isError:!0}}return{content:[{type:"text",text:`\u{F115D} ${o}#${t} not found.`}],isError:!0}}},{name:"wyrm_unshare",description:"Revoke team visibility for a row. Local row stays; future syncs won't include it.",inputSchema:{type:"object",properties:{kind:{type:"string",enum:["session","quest","truth","artifact","edge"]},id:{type:"number"}},required:["kind","id"]},annotations:p.wyrm_unshare,aliases:[],handler:async(r,s)=>{const{federation:c}=s,{kind:o,id:t}=y(r);return c.unshare(o,t)?{content:[{type:"text",text:`\u{F115D} ${o}#${t} unshared.`}]}:{content:[{type:"text",text:`\u{F115D} ${o}#${t} not found.`}],isError:!0}}},{name:"wyrm_sync_conflicts",description:"List unresolved sync conflicts \u2014 rows where a remote push landed on top of a newer local edit. Each conflict has been auto-converted into a quest (tag 'sync-conflict') so the operator sees it on next session.",inputSchema:{type:"object",properties:{limit:{type:"number",description:"Max conflicts (default 50)"}}},annotations:p.wyrm_sync_conflicts,aliases:[],handler:async(r,s)=>{const{federation:c,sync:o}=s,{limit:t}=r,e=c.unresolvedConflicts(Math.min(Math.max(1,t??50),500));if(e.length===0)return{content:[{type:"text",text:"\u{F115D} No unresolved sync conflicts."}]};const a=e.map(i=>`\u2022 #${i.id} ${i.local_kind}#${i.local_id} from ${i.remote_origin??"?"} at ${i.detected_at}`+(i.quest_id?` \u2192 quest #${i.quest_id}`:"")).join(`
6
6
  `);return{content:[{type:"text",text:`\u{F115D} ${e.length} unresolved conflict(s):
7
- ${c}`}]}}},{name:"wyrm_sync_resolve",description:"Resolve a sync conflict. Mode: 'kept_local' (drop remote), 'kept_remote' (overwrite local with remote payload), 'merged' (apply a hand-merged payload supplied by the caller).",inputSchema:{type:"object",properties:{conflict_id:{type:"number"},mode:{type:"string",enum:["kept_local","kept_remote","merged"]},merged_payload:{type:"object",description:"Required when mode='merged' \u2014 full row payload to write"}},required:["conflict_id","mode"]},annotations:u.wyrm_sync_resolve,aliases:[],handler:async(a,s)=>{const{federation:i}=s,{conflict_id:n,mode:t,merged_payload:e}=a;return t==="merged"&&!e?{content:[{type:"text",text:"merged_payload required for mode=merged"}],isError:!0}:{content:[{type:"text",text:i.resolveConflict(n,t,e)?`\u{F115D} Conflict #${n} resolved (${t}).`:`\u{F115D} Conflict #${n} not found or already resolved.`}]}}},{name:"wyrm_act",description:"Use to pursue a goal autonomously - the OODA loop: observe Wyrm context, orient, decide one whitelisted tool call, act, loop. Pass goal_id for an existing goal, or query+title to create and pursue ad-hoc.",inputSchema:{type:"object",properties:{goal_id:{type:"number"},query:{type:"string",description:"Ad-hoc"},title:{type:"string",description:"Ad-hoc goal"},projectPath:{type:"string"},max_steps:{type:"number",description:"Default 5, cap 50"},model_override:{type:"string",description:"ollama:model"}}},annotations:u.wyrm_act,aliases:[],handler:async(a,s)=>{const{agentLoop:i,db:n,goals:t}=s,e=a;let c=e.goal_id,r=!1;if(!c){if(!e.query)return{content:[{type:"text",text:"Either goal_id or query required"}],isError:!0};const l=e.projectPath?n.getProject(e.projectPath):null;c=t.set({project_id:l?.id??null,title:(e.title??e.query).slice(0,140),description:e.query,priority:"medium",max_iterations:Math.min(50,e.max_steps??5)+2}).id,r=!0}const p=await i.pursue(c,{max_steps:e.max_steps,model_override:e.model_override});if(p.length===0)return{content:[{type:"text",text:`Goal #${c} not active or already at max_iterations.`}],isError:!0};const o=p[p.length-1],d=[];d.push(`\u{F115D} Agent ran ${p.length} iteration(s) on goal #${c}${r?" (ad-hoc)":""}.`),d.push(`Final outcome: **${o.outcome}** (model: ${o.model}${o.degraded?", DEGRADED":""}, ${o.latency_ms}ms)`),d.push("");for(const l of p)d.push(`### Iter #${l.iteration_num} \u2014 ${l.outcome}`),l.decided.thought&&d.push(`> ${l.decided.thought}`),d.push(`Action: \`${l.decided.action}\``+(l.decided.args?` args=${JSON.stringify(l.decided.args).slice(0,200)}`:"")),d.push(`Result: ${l.action_result.slice(0,400)}`),d.push("");return{content:[{type:"text",text:d.join(`
8
- `)}]}}}];export{h as shareToolSpecs};
7
+ ${a}`}]}}},{name:"wyrm_sync_resolve",description:"Resolve a sync conflict. Mode: 'kept_local' (drop remote), 'kept_remote' (overwrite local with remote payload), 'merged' (apply a hand-merged payload supplied by the caller).",inputSchema:{type:"object",properties:{conflict_id:{type:"number"},mode:{type:"string",enum:["kept_local","kept_remote","merged"]},merged_payload:{type:"object",description:"Required when mode='merged' \u2014 full row payload to write"}},required:["conflict_id","mode"]},annotations:p.wyrm_sync_resolve,aliases:[],handler:async(r,s)=>{const{federation:c}=s,{conflict_id:o,mode:t,merged_payload:e}=r;return t==="merged"&&!e?{content:[{type:"text",text:"merged_payload required for mode=merged"}],isError:!0}:{content:[{type:"text",text:c.resolveConflict(o,t,e)?`\u{F115D} Conflict #${o} resolved (${t}).`:`\u{F115D} Conflict #${o} not found or already resolved.`}]}}},{name:"wyrm_act",description:"Use to pursue a goal autonomously - the OODA loop: observe Wyrm context, orient, decide one whitelisted tool call, act, loop. Pass goal_id for an existing goal, or query+title to create and pursue ad-hoc.",inputSchema:{type:"object",properties:{goal_id:{type:"number"},query:{type:"string",description:"Ad-hoc"},title:{type:"string",description:"Ad-hoc goal"},projectPath:{type:"string"},max_steps:{type:"number",description:"Default 5, cap 50"},model_override:{type:"string",description:"ollama:model"}}},annotations:p.wyrm_act,aliases:[],handler:async(r,s)=>{const{agentLoop:c,db:o,goals:t}=s,e=r;let a=e.goal_id,i=!1;if(!a){if(!e.query)return{content:[{type:"text",text:"Either goal_id or query required"}],isError:!0};const l=e.projectPath?o.getProject(e.projectPath):null;a=t.set({project_id:l?.id??null,title:(e.title??e.query).slice(0,140),description:e.query,priority:"medium",max_iterations:Math.min(50,e.max_steps??5)+2}).id,i=!0}const u=await c.pursue(a,{max_steps:e.max_steps,model_override:e.model_override});if(u.length===0)return{content:[{type:"text",text:`Goal #${a} not active or already at max_iterations.`}],isError:!0};const n=u[u.length-1],d=[];d.push(`\u{F115D} Agent ran ${u.length} iteration(s) on goal #${a}${i?" (ad-hoc)":""}.`),d.push(`Final outcome: **${n.outcome}** (model: ${n.model}${n.degraded?", DEGRADED":""}, ${n.latency_ms}ms)`),d.push("");for(const l of u)d.push(`### Iter #${l.iteration_num} \u2014 ${l.outcome}`),l.decided.thought&&d.push(`> ${l.decided.thought}`),d.push(`Action: \`${l.decided.action}\``+(l.decided.args?` args=${JSON.stringify(l.decided.args).slice(0,200)}`:"")),d.push(`Result: ${l.action_result.slice(0,400)}`),d.push("");return{content:[{type:"text",text:d.join(`
8
+ `)}]}}}];export{w as shareToolSpecs};
@@ -1 +1 @@
1
- import{ValidationError as a}from"../validate.js";const m={wyrm_quest:{discriminator:"action",ownCase:!1,routes:{add:"wyrm_quest_add",complete:"wyrm_quest_complete",claim:"wyrm_quest_claim",release:"wyrm_quest_release",list:"wyrm_all_quests"}},wyrm_session:{discriminator:"action",ownCase:!1,routes:{start:"wyrm_session_start",update:"wyrm_session_update",rehydrate:"wyrm_session_rehydrate"}},wyrm_skill:{discriminator:"action",ownCase:!1,routes:{list:"wyrm_skill_list",search:"wyrm_skill_search",get:"wyrm_skill_get",register:"wyrm_skill_register",create:"wyrm_skill_create",activate:"wyrm_skill_activate",deactivate:"wyrm_skill_deactivate",graph:"wyrm_skill_graph",stats:"wyrm_skill_stats",delete:"wyrm_skill_delete"}},wyrm_entity:{discriminator:"action",ownCase:!1,routes:{search:"wyrm_entity_search",add:"wyrm_entity_add",link:"wyrm_entity_link",merge:"wyrm_entity_merge",path:"wyrm_entity_path",graph:"wyrm_entity_graph"}},wyrm_decision_trace:{discriminator:"action",ownCase:!1,routes:{upstream:"wyrm_decision_upstream",downstream:"wyrm_decision_downstream",invalidate:"wyrm_decision_invalidate"}},wyrm_goal:{discriminator:"action",ownCase:!1,routes:{list:"wyrm_goal_list",set:"wyrm_goal_set",complete:"wyrm_goal_complete",pause:"wyrm_goal_pause",resume:"wyrm_goal_resume",abandon:"wyrm_goal_abandon",iterations:"wyrm_goal_iterations"}},wyrm_presence:{discriminator:"action",ownCase:!1,routes:{list:"wyrm_presence_list",announce:"wyrm_presence_announce",release:"wyrm_presence_release"}},wyrm_mcp:{discriminator:"action",ownCase:!1,routes:{list:"wyrm_mcp_list",register:"wyrm_mcp_register",tools:"wyrm_mcp_tools",disable:"wyrm_mcp_disable"}},wyrm_audit:{discriminator:"action",ownCase:!1,routes:{log:"wyrm_audit_log",export:"wyrm_audit_export",verify:"wyrm_audit_verify"}},wyrm_project:{discriminator:"action",ownCase:!1,routes:{list:"wyrm_list_projects",context:"wyrm_project_context",scan:"wyrm_scan_projects",sync:"wyrm_sync"}},wyrm_design_token:{discriminator:"action",ownCase:!1,routes:{get:"wyrm_token_get",set:"wyrm_token_set",delete:"wyrm_token_delete"}},wyrm_reference:{discriminator:"action",ownCase:!1,routes:{list:"wyrm_reference_list",add:"wyrm_reference_add",search:"wyrm_reference_search"}},wyrm_replication:{discriminator:"action",ownCase:!0,routes:{conflicts:"wyrm_sync_conflicts",resolve:"wyrm_sync_resolve",publish:"wyrm_events_publish",since:"wyrm_events_since",subscribe:"wyrm_events_subscribe",replicate:"wyrm_events_replicate"}},wyrm_capture:{discriminator:"mode",ownCase:!0,routes:{classify:"wyrm_capture",direct:"wyrm_remember",extract:"wyrm_auto_capture",trace:"wyrm_capture_trace",session:"wyrm_distill",artifacts:"wyrm_harvest",import:{key:"source",routes:{git:"wyrm_import_git",pr:"wyrm_import_pr",rules:"wyrm_import_rules"}},spec:"wyrm_spec_register",checklist:"wyrm_scaffold_save"}}},c=[{name:"wyrm_quest",description:"Use for project tasks and todos - action=add (add a todo or work item), complete (mark a task as done), claim (lock a quest so other agents don't duplicate the work; TTL lease stamped with agent_id/run_id), release, list (what's still open on my plate across all projects). The fleet coordination primitive.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["add","complete","claim","release","list"]},projectPath:{type:"string",description:"add"},title:{type:"string",description:"add"},description:{type:"string"},priority:{type:"string",description:"critical|high|medium|low"},tags:{type:"string",description:"add"},questId:{type:"number",description:"complete"},quest_id:{type:"number",description:"claim/release"},agent_id:{type:"string",description:"claim/release"},ttl_seconds:{type:"number",description:"claim"}},required:["action"]}},{name:"wyrm_session",description:"Use to track working sessions - action=start (start tracking a new working session on a repo), update (log what you just finished: completed work, issues, commits), rehydrate (the full briefing from a past session so a fresh agent can take over).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["start","update","rehydrate"]},projectPath:{type:"string",description:"start/update"},objectives:{type:"string"},completed:{type:"string",description:"update"},issues:{type:"string"},commits:{type:"string",description:"update"},notes:{type:"string"},session_id:{type:"number",description:"rehydrate"}},required:["action"]}},{name:"wyrm_skill",description:"Use for reusable playbooks (skills) - do we already have a playbook for this? action=list (the playbooks and capabilities registered on this machine), search, get, register, create (package what you just learned into a reusable skill), activate, deactivate, graph (governance tree), stats, delete.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","search","get","register","create","activate","deactivate","graph","stats","delete"]},name:{type:"string",description:"skill name"},description:{type:"string",description:"register/create"},skillPath:{type:"string",description:"register"},body:{type:"string",description:"create"},query:{type:"string"},category:{type:"string",description:"list/register/create"},tier:{type:"string",description:"atomic|mega|god|fable"},limit:{type:"number",description:"search"}},required:["action"]}},{name:"wyrm_entity",description:"Use for the knowledge graph - action=add (a person, vendor, service, or concept), link (record that Alice maintains the deploy pipeline: who maintains, uses, or depends on what), search, merge (destructive), path (how two entities or services are related to each other), graph (show everything connected to an entity, N hops).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["search","add","link","merge","path","graph"]},projectPath:{type:"string"},name:{type:"string",description:"add"},type:{type:"string",description:"add/search"},source:{type:"string",description:"link/merge/path"},target:{type:"string",description:"link/merge/path"},relationship:{type:"string",description:"link: uses|depends_on"},query:{type:"string"},entity:{type:"string",description:"graph"},depth:{type:"number",description:"graph"},limit:{type:"number",description:"search"}},required:["action"]}},{name:"wyrm_decision_trace",description:"Use to trace decision causality - action=upstream (why did we pick this? walk me through the reasoning chain back to its sources), downstream (if that assumption turns out to be wrong, what else breaks - the blast radius), invalidate (a foundational fact was wrong: flag everything we built on top of it, cascading).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["upstream","downstream","invalidate"]},kind:{type:"string",enum:["truth","artifact","quest","session","data"]},id:{type:"number"},reason:{type:"string",description:"invalidate"}},required:["action"]}},{name:"wyrm_goal",description:"Use for persistent goals the wyrm-loop daemon pursues autonomously - action=set (define with success criteria), list, complete, pause, resume, abandon, iterations (the OODA pursuit history).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","set","complete","pause","resume","abandon","iterations"]},title:{type:"string"},description:{type:"string"},success_criteria:{type:"string",description:"set: definition of done"},priority:{type:"string",description:"set: critical|high|medium|low"},projectPath:{type:"string"},id:{type:"number",description:"complete/pause/resume/abandon"},goal_id:{type:"number",description:"iterations"}},required:["action"]}},{name:"wyrm_presence",description:"Use for multi-agent presence - who else is working on this right now? action=list, announce (I'm online and working on X; a TTL heartbeat orchestrators and other agents see), release (clean exit: clears presence and drops any quest claims held).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","announce","release"]},agent_id:{type:"string"},agent_kind:{type:"string",description:"announce: claude|codex|human"},projectPath:{type:"string"},current_file:{type:"string",description:"announce"},ttl_seconds:{type:"number",description:"announce"}},required:["action"]}},{name:"wyrm_mcp",description:"Use to manage outbound MCP servers Wyrm can call - action=register (command, args, env), list, tools (one-shot tools/list), disable. Call a registered server's tools via wyrm_call_external.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","register","tools","disable"]},server_name:{type:"string"},command:{type:"string",description:"register: executable"},args:{type:"array",items:{type:"string"},description:"register: argv"},env:{type:"object",description:"register"}},required:["action"]}},{name:"wyrm_audit",description:"Use for the tamper-evident audit chain - action=log (append a tamper-evident, hash-chained record of an admin action), export (the audit history as an offline-verifiable bundle for a compliance review), verify (prove the audit trail hasn't been tampered with).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["log","export","verify"]},event_kind:{type:"string",description:"log: tool_call etc"},actor:{type:"string"},projectPath:{type:"string"},payload:{type:"object",description:"log"},range_start:{type:"string",description:"export"},range_end:{type:"string",description:"export"}},required:["action"]}},{name:"wyrm_project",description:"Use for the project registry and cross-project landscape - action=list, context (one project's memory overview), scan (discover and register git repos under a directory), sync (DB rows to per-project .wyrm folders).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","context","scan","sync"]},search:{type:"string",description:"list"},limit:{type:"number"},projectPath:{type:"string",description:"context/sync"},projectName:{type:"string",description:"context"},path:{type:"string",description:"scan"},watch:{type:"boolean",description:"scan"},recursive:{type:"boolean"},direction:{type:"string",description:"sync: import|export|both"}},required:["action"]}},{name:"wyrm_design_token",description:"Use for the project's design system - store and read the values the brand uses: color, type, spacing, motion, shadow, radius, breakpoint. action=set (upsert a primary color or spacing value), get (what values does this design system use?), delete.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["get","set","delete"]},projectPath:{type:"string"},category:{type:"string",enum:["color","type","spacing","motion","shadow","radius","breakpoint","custom"]},key:{type:"string",description:"set/delete"},value:{type:"string",description:"set"}},required:["action"]}},{name:"wyrm_reference",description:"Use for design references and visual inspiration - action=add (clip a website, image, palette, or snippet to keep for later projects), search (find that inspiration link I saved a while back), list.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","add","search"]},projectPath:{type:"string",description:"Omit for global"},kind:{type:"string",enum:["url","image","palette","snippet"],description:"add"},location:{type:"string",description:"add: URL/path/inline"},title:{type:"string"},tags:{type:"string",description:"add"},query:{type:"string",description:"search"},limit:{type:"number"}},required:["action"]}}];function g(){return c.map(r=>({...r}))}const w=c.map(r=>r.name).sort();function h(r,i){const e=m[r];if(!e)return{tool:r,args:i};const s=i[e.discriminator],t=typeof s=="string"?e.routes[s]:void 0;if(t===void 0){if(e.ownCase)return{tool:r,args:i};throw new a(e.discriminator,`must be one of: ${Object.keys(e.routes).join(", ")}`)}if(typeof t=="string"){const{[e.discriminator]:u,...d}=i;return{tool:t,args:d}}const n=i[t.key],o=typeof n=="string"?t.routes[n]:void 0;if(o===void 0)throw new a(t.key,`${e.discriminator}=${String(s)} requires ${t.key} \u2014 one of: ${Object.keys(t.routes).join(", ")}`);const{[e.discriminator]:y,[t.key]:l,...p}=i;return{tool:o,args:p}}export{m as SHIM_ROUTES,w as SHIM_TOOL_NAMES,h as resolveShimCall,g as shimToolDefs};
1
+ import{ValidationError as a}from"../validate.js";const m={wyrm_quest:{discriminator:"action",ownCase:!1,routes:{add:"wyrm_quest_add",complete:"wyrm_quest_complete",claim:"wyrm_quest_claim",release:"wyrm_quest_release",list:"wyrm_all_quests"}},wyrm_session:{discriminator:"action",ownCase:!1,routes:{start:"wyrm_session_start",update:"wyrm_session_update",rehydrate:"wyrm_session_rehydrate"}},wyrm_skill:{discriminator:"action",ownCase:!1,routes:{list:"wyrm_skill_list",search:"wyrm_skill_search",get:"wyrm_skill_get",register:"wyrm_skill_register",create:"wyrm_skill_create",activate:"wyrm_skill_activate",deactivate:"wyrm_skill_deactivate",graph:"wyrm_skill_graph",stats:"wyrm_skill_stats",delete:"wyrm_skill_delete"}},wyrm_entity:{discriminator:"action",ownCase:!1,routes:{search:"wyrm_entity_search",add:"wyrm_entity_add",link:"wyrm_entity_link",merge:"wyrm_entity_merge",path:"wyrm_entity_path",graph:"wyrm_entity_graph"}},wyrm_decision_trace:{discriminator:"action",ownCase:!1,routes:{upstream:"wyrm_decision_upstream",downstream:"wyrm_decision_downstream",invalidate:"wyrm_decision_invalidate"}},wyrm_goal:{discriminator:"action",ownCase:!1,routes:{list:"wyrm_goal_list",set:"wyrm_goal_set",complete:"wyrm_goal_complete",pause:"wyrm_goal_pause",resume:"wyrm_goal_resume",abandon:"wyrm_goal_abandon",iterations:"wyrm_goal_iterations"}},wyrm_presence:{discriminator:"action",ownCase:!1,routes:{list:"wyrm_presence_list",announce:"wyrm_presence_announce",release:"wyrm_presence_release"}},wyrm_mcp:{discriminator:"action",ownCase:!1,routes:{list:"wyrm_mcp_list",register:"wyrm_mcp_register",tools:"wyrm_mcp_tools",disable:"wyrm_mcp_disable"}},wyrm_audit:{discriminator:"action",ownCase:!1,routes:{log:"wyrm_audit_log",export:"wyrm_audit_export",verify:"wyrm_audit_verify"}},wyrm_project:{discriminator:"action",ownCase:!1,routes:{list:"wyrm_list_projects",context:"wyrm_project_context",scan:"wyrm_scan_projects",sync:"wyrm_sync"}},wyrm_design_token:{discriminator:"action",ownCase:!1,routes:{get:"wyrm_token_get",set:"wyrm_token_set",delete:"wyrm_token_delete"}},wyrm_reference:{discriminator:"action",ownCase:!1,routes:{list:"wyrm_reference_list",add:"wyrm_reference_add",search:"wyrm_reference_search"}},wyrm_replication:{discriminator:"action",ownCase:!0,routes:{conflicts:"wyrm_sync_conflicts",resolve:"wyrm_sync_resolve",publish:"wyrm_events_publish",since:"wyrm_events_since",subscribe:"wyrm_events_subscribe",replicate:"wyrm_events_replicate",unshare:"wyrm_unshare"}},wyrm_capture:{discriminator:"mode",ownCase:!0,routes:{classify:"wyrm_capture",direct:"wyrm_remember",extract:"wyrm_auto_capture",trace:"wyrm_capture_trace",session:"wyrm_distill",artifacts:"wyrm_harvest",import:{key:"source",routes:{git:"wyrm_import_git",pr:"wyrm_import_pr",rules:"wyrm_import_rules"}},spec:"wyrm_spec_register",checklist:"wyrm_scaffold_save",feedback:"wyrm_feedback"}}},c=[{name:"wyrm_quest",description:"Use for project tasks and todos - action=add (add a todo or work item), complete (mark a task as done), claim (lock a quest so other agents don't duplicate the work; TTL lease stamped with agent_id/run_id), release, list (what's still open on my plate across all projects). The fleet coordination primitive.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["add","complete","claim","release","list"]},projectPath:{type:"string",description:"add"},title:{type:"string",description:"add"},description:{type:"string"},priority:{type:"string",enum:["critical","high","medium","low"]},tags:{type:"string",description:"add"},quest_id:{type:"number"},agent_id:{type:"string",description:"claim/release"},ttl_seconds:{type:"number",description:"claim"},limit:{type:"number",description:"list (default 50)"}},required:["action"]}},{name:"wyrm_session",description:"Use to track working sessions - action=start (start tracking a new working session on a repo), update (log what you just finished: completed work, issues, commits), rehydrate (the full briefing from a past session so a fresh agent can take over).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["start","update","rehydrate"]},projectPath:{type:"string",description:"start/update"},objectives:{type:"string"},completed:{type:"string",description:"update"},issues:{type:"string"},commits:{type:"string",description:"update"},notes:{type:"string"},session_id:{type:"number",description:"rehydrate"}},required:["action"]}},{name:"wyrm_skill",description:"Use for reusable playbooks (skills) - do we already have a playbook for this? action=list (the playbooks and capabilities registered on this machine), search, get, register, create (package what you just learned into a reusable skill), activate, deactivate, graph (governance tree), stats, delete.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","search","get","register","create","activate","deactivate","graph","stats","delete"]},name:{type:"string",description:"skill name"},description:{type:"string",description:"register/create"},skillPath:{type:"string",description:"register"},body:{type:"string",description:"create"},query:{type:"string"},category:{type:"string",description:"list/register/create"},tier:{type:"string",description:"atomic|mega|god|fable"},limit:{type:"number",description:"search"}},required:["action"]}},{name:"wyrm_entity",description:"Use for the knowledge graph - action=add (a person, vendor, service, or concept), link (record that Alice maintains the deploy pipeline: who maintains, uses, or depends on what), search, merge (destructive), path (how two entities or services are related to each other), graph (show everything connected to an entity, N hops).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["search","add","link","merge","path","graph"]},projectPath:{type:"string"},name:{type:"string",description:"add"},type:{type:"string",description:"add/search"},source:{type:"string",description:"link/merge/path"},target:{type:"string",description:"link/merge/path"},relationship:{type:"string",description:"link: uses|depends_on"},query:{type:"string"},entity:{type:"string",description:"graph"},depth:{type:"number",description:"graph"},limit:{type:"number",description:"search"}},required:["action"]}},{name:"wyrm_decision_trace",description:"Use to trace decision causality - action=upstream (why did we pick this? walk me through the reasoning chain back to its sources), downstream (if that assumption turns out to be wrong, what else breaks - the blast radius), invalidate (a foundational fact was wrong: flag everything we built on top of it, cascading).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["upstream","downstream","invalidate"]},kind:{type:"string",enum:["truth","artifact","quest","session","data"]},id:{type:"number"},reason:{type:"string",description:"invalidate"}},required:["action"]}},{name:"wyrm_goal",description:"Use for persistent goals the wyrm-loop daemon pursues autonomously - action=set (define with success criteria), list, complete, pause, resume, abandon, iterations (the OODA pursuit history).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","set","complete","pause","resume","abandon","iterations"]},title:{type:"string"},description:{type:"string"},success_criteria:{type:"string",description:"set: definition of done"},priority:{type:"string",description:"set: critical|high|medium|low"},projectPath:{type:"string"},id:{type:"number"}},required:["action"]}},{name:"wyrm_presence",description:"Use for multi-agent presence - who else is working on this right now? action=list, announce (I'm online and working on X; a TTL heartbeat orchestrators and other agents see), release (clean exit: clears presence and drops any quest claims held).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","announce","release"]},agent_id:{type:"string"},agent_kind:{type:"string",description:"announce: claude|codex|human"},projectPath:{type:"string"},current_file:{type:"string",description:"announce"},ttl_seconds:{type:"number",description:"announce"}},required:["action"]}},{name:"wyrm_mcp",description:"Use to manage outbound MCP servers Wyrm can call - action=register (command, args, env), list, tools (one-shot tools/list), disable. Call a registered server's tools via wyrm_call_external.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","register","tools","disable"]},server_name:{type:"string"},command:{type:"string",description:"register: executable"},args:{type:"array",items:{type:"string"},description:"register: argv"},env:{type:"object",description:"register"}},required:["action"]}},{name:"wyrm_audit",description:"Use for the tamper-evident audit chain - action=log (append a tamper-evident, hash-chained record of an admin action), export (the audit history as an offline-verifiable bundle for a compliance review), verify (prove the audit trail hasn't been tampered with).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["log","export","verify"]},event_kind:{type:"string",description:"log: tool_call etc"},actor:{type:"string"},projectPath:{type:"string"},payload:{type:"object",description:"log"},range_start:{type:"string",description:"export"},range_end:{type:"string",description:"export"}},required:["action"]}},{name:"wyrm_project",description:"Use for the project registry and cross-project landscape - action=list, context (one project's memory overview), scan (discover and register git repos under a directory), sync (DB rows to per-project .wyrm folders).",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","context","scan","sync"]},search:{type:"string",description:"list"},limit:{type:"number"},projectPath:{type:"string",description:"context/sync"},projectName:{type:"string",description:"context"},path:{type:"string",description:"scan"},watch:{type:"boolean",description:"scan"},recursive:{type:"boolean"},direction:{type:"string",description:"sync: import|export|both"}},required:["action"]}},{name:"wyrm_design_token",description:"Use for the project's design system - store and read the values the brand uses: color, type, spacing, motion, shadow, radius, breakpoint. action=set (upsert a primary color or spacing value), get (what values does this design system use?), delete.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["get","set","delete"]},projectPath:{type:"string"},category:{type:"string",enum:["color","type","spacing","motion","shadow","radius","breakpoint","custom"]},key:{type:"string",description:"set/delete"},value:{type:"string",description:"set"}},required:["action"]}},{name:"wyrm_reference",description:"Use for design references and visual inspiration - action=add (clip a website, image, palette, or snippet to keep for later projects), search (find that inspiration link I saved a while back), list.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["list","add","search"]},projectPath:{type:"string",description:"Omit for global"},kind:{type:"string",enum:["url","image","palette","snippet"],description:"add"},location:{type:"string",description:"add: URL/path/inline"},title:{type:"string"},tags:{type:"string",description:"add"},query:{type:"string",description:"search"},limit:{type:"number"}},required:["action"]}}];function g(){return c.map(r=>({...r}))}const w=c.map(r=>r.name).sort();function h(r,i){const e=m[r];if(!e)return{tool:r,args:i};const s=i[e.discriminator],t=typeof s=="string"?e.routes[s]:void 0;if(t===void 0){if(e.ownCase)return{tool:r,args:i};throw new a(e.discriminator,`must be one of: ${Object.keys(e.routes).join(", ")}`)}if(typeof t=="string"){const{[e.discriminator]:u,...d}=i;return{tool:t,args:d}}const n=i[t.key],o=typeof n=="string"?t.routes[n]:void 0;if(o===void 0)throw new a(t.key,`${e.discriminator}=${String(s)} requires ${t.key}, one of: ${Object.keys(t.routes).join(", ")}`);const{[e.discriminator]:y,[t.key]:l,...p}=i;return{tool:o,args:p}}export{m as SHIM_ROUTES,w as SHIM_TOOL_NAMES,h as resolveShimCall,g as shimToolDefs};
@@ -1,2 +1,2 @@
1
- import{TOOL_ANNOTATIONS as c}from"../tool-annotations.js";import{assembleThread as d}from"../thread-assemble.js";import{putThread as l}from"../thread-store.js";const y=["get","nudge"];function a(e){const t=[];if(e.objective&&t.push(`Objective: ${e.objective}`),e.inProgress&&t.push(`In progress: ${e.inProgress}`),e.nextStep&&t.push(`Next step: ${e.nextStep}`),e.lastDecisions.length>0){t.push("Last decisions:");for(const o of e.lastDecisions)t.push(o.source?`- ${o.text} (${o.source})`:`- ${o.text}`)}if(e.openLoops.length>0){t.push("Open loops:");for(const o of e.openLoops)t.push(`- ${o.text}`)}if(e.inPlay.length>0){t.push("In play:");for(const o of e.inPlay)t.push(`- ${o.label} (${o.kind})`)}return t.length>0?t.join(`
2
- `):"No active thread yet."}function m(e){return!e.objective&&!e.inProgress&&!e.nextStep&&e.openLoops.length===0&&e.lastDecisions.length===0&&e.inPlay.length===0?"":a(e)}function h(e){const t=e.trim(),o=/^loop:\s*(.*)$/i.exec(t);if(o){const n=o[1].trim();return n?{openLoops:[{text:n}]}:{}}return{inProgress:t}}const x=[{name:"wyrm_thread",description:"Use to get or nudge the project's working thread: a compact, always-current snapshot (objective, in-progress work, last decisions, open loops, next step, what's in play) assembled live from Wyrm's signals and persisted to the threads table. action=get assembles, persists, and renders the current thread. action=nudge additionally merges an advisory note first: a plain note seeds in-progress (only if no session signal already covers it), while a note prefixed 'loop:' appends one open loop. The signal-derived open loops, decisions, and in-play are never replaced by a nudge.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["get","nudge"]},projectPath:{type:"string",description:"Project path"},note:{type:"string",description:"nudge only: a plain note seeds in-progress; prefix with 'loop:' to append an open loop instead"}},required:["action","projectPath"]},annotations:c.wyrm_thread,aliases:[],handler:(e,{projects:t,raw:o})=>{const n=e,s=t.getProject(n.projectPath);if(!s)return{content:[{type:"text",text:`Project not found: ${n.projectPath}`}],isError:!0};const r=o(),i=n.action==="nudge"&&n.note?h(n.note):void 0,p=d(r,s.id,i?{nudge:i}:{});return l(r,p),{content:[{type:"text",text:a(p)}]}}}];export{a as renderThread,m as renderThreadForContext,x as threadToolSpecs};
1
+ import{TOOL_ANNOTATIONS as u}from"../tool-annotations.js";import{assembleThread as h}from"../thread-assemble.js";import{putThread as f}from"../thread-store.js";import{ValidationError as g,asEnum as m,asString as y}from"../validate.js";import{requireProjectFromArg as x}from"./project-arg.js";const l=["get","nudge"];function d(e){const t=[];if(e.objective&&t.push(`Objective: ${e.objective}`),e.inProgress&&t.push(`In progress: ${e.inProgress}`),e.nextStep&&t.push(`Next step: ${e.nextStep}`),e.lastDecisions.length>0){t.push("Last decisions:");for(const o of e.lastDecisions)o.text?t.push(o.source?`- ${o.text} (${o.source})`:`- ${o.text}`):t.push(`- ${o.source??"(unlabelled)"}`)}if(e.openLoops.length>0){t.push("Open loops:");for(const o of e.openLoops)t.push(`- ${o.text}`)}if(e.inPlay.length>0){t.push("In play:");for(const o of e.inPlay)t.push(`- ${o.label} (${o.kind})`)}return t.length>0?t.join(`
2
+ `):"No active thread yet."}function L(e){return!e.objective&&!e.inProgress&&!e.nextStep&&e.openLoops.length===0&&e.lastDecisions.length===0&&e.inPlay.length===0?"":d(e)}function b(e){const t=e.trim(),o=/^loop:\s*(.*)$/i.exec(t);if(o){const n=o[1].trim();return n?{openLoops:[{text:n}]}:{}}return{inProgress:t}}const E={truth:"SELECT key AS label FROM ground_truths WHERE id = ?",quest:"SELECT title AS label FROM quests WHERE id = ?",artifact:"SELECT problem AS label FROM memory_artifacts WHERE id = ?"};function j(e,t){if(!t.lastDecisions.some(n=>!n.text))return t;const o=t.lastDecisions.map(n=>{if(n.text)return n;const r=/^([a-z]+)#(\d+)$/.exec(n.source??""),s=r?E[r[1]]:void 0;if(!r||!s)return n;try{const a=e.prepare(s).get(Number(r[2]))?.label?.trim();return a?{...n,text:a.slice(0,120)}:n}catch{return n}});return{...t,lastDecisions:o}}const v=[{name:"wyrm_thread",description:"Use to get or nudge the project's working thread: a compact, always-current snapshot (objective, in-progress work, last decisions, open loops, next step, what's in play) assembled live from Wyrm's signals and persisted to the threads table. action=get assembles, persists, and renders the current thread. action=nudge additionally merges an advisory note first: a plain note seeds in-progress (only if no session signal already covers it), while a note prefixed 'loop:' appends one open loop. The signal-derived open loops, decisions, and in-play are never replaced by a nudge.",inputSchema:{type:"object",properties:{action:{type:"string",enum:["get","nudge"]},projectPath:{type:"string",description:"Project path"},note:{type:"string",description:"nudge only: a plain note seeds in-progress; prefix with 'loop:' to append an open loop instead"}},required:["action","projectPath"]},annotations:u.wyrm_thread,aliases:[],handler:(e,{projects:t,raw:o})=>{const n=e,r=m("action",n.action,l);if(r===void 0)throw new g("action",`is required (one of: ${l.join(", ")})`);const s=y("note",n.note),i=o(),a=x(t,i,n.projectPath);if(!a)return{content:[{type:"text",text:`Project not found: ${String(n.projectPath)}`}],isError:!0};const p=r==="nudge"&&s?b(s):void 0,c=j(i,h(i,a.id,p?{nudge:p}:{}));return f(i,c),{content:[{type:"text",text:d(c)}]}}}];export{j as labelDecisions,d as renderThread,L as renderThreadForContext,v as threadToolSpecs};
package/dist/http-auth.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import{existsSync as O,readFileSync as R,writeFileSync as l,mkdirSync as I}from"fs";import{join as S}from"path";import{homedir as N}from"os";import{randomBytes as p,createHash as T,timingSafeEqual as M}from"crypto";import{WyrmLogger as P}from"./logger.js";const A=S(N(),".wyrm"),a=S(A,"http-config.json"),L={enabled:!0,requests:100,windowMs:6e4},x=["http://localhost:3333","http://127.0.0.1:3333"],u=new Map;let c=null;const i=new P;function y(e){return T("sha256").update(e).digest("hex")}function k(e,t){if(e.length!==t.length)return!1;let r=0;for(let o=0;o<e.length;o++)r|=e.charCodeAt(o)^t.charCodeAt(o);return r===0}function H(){if(O(A)||I(A,{recursive:!0,mode:448}),O(a))try{const r=JSON.parse(R(a,"utf-8"));return{apiKeyHash:r.apiKeyHash,allowedOrigins:r.allowedOrigins||x,rateLimit:{...L,...r.rateLimit},requireAuth:r.requireAuth??!0,devMode:process.env.WYRM_DEV==="true"}}catch(r){i.error("Failed to load HTTP config, regenerating",{error:r.message})}const e=p(32).toString("hex");i.info("Generated new API key for HTTP server"),console.log(`
2
2
  `+"\u2550".repeat(60)),console.log("\u{1F510} WYRM API KEY (save this securely, shown once):"),console.log(""),console.log(` ${e}`),console.log(""),console.log(" Use with: Authorization: Bearer <key>"),console.log("\u2550".repeat(60)+`
3
- `);const t={apiKeyHash:y(e),allowedOrigins:x,rateLimit:L,requireAuth:!0,devMode:process.env.WYRM_DEV==="true"};return l(a,JSON.stringify(t,null,2),{mode:384}),t}function s(){return c||(c=H()),c}function v(e){const t=s(),r=e.socket.remoteAddress??"",o=r==="127.0.0.1"||r==="::1"||r==="::ffff:127.0.0.1",n=(e.headers.host??"").split(":")[0].toLowerCase(),d=n==="localhost"||n==="127.0.0.1"||n==="::1"||n==="[::1]";if(t.devMode&&o&&d)return!0;const h=e.headers["x-wyrm-nonce"];if(o&&d&&typeof h=="string"&&K(h))return!0;if(!t.requireAuth)return i.warn("Authentication disabled - API is open"),!0;const g=e.headers.authorization;if(!g||!g.startsWith("Bearer "))return!1;const m=g.slice(7);if(!m||m.length<32)return!1;const C=y(m);return k(C,t.apiKeyHash)}function D(e){const t=s();if(!t.rateLimit.enabled)return{allowed:!0,remaining:1/0,resetAt:0};const r=e.socket.remoteAddress||"unknown",o=Date.now();if(u.size>1e4)for(const[d,h]of u)h.resetAt<o&&u.delete(d);let n=u.get(r);return(!n||n.resetAt<o)&&(n={count:0,resetAt:o+t.rateLimit.windowMs},u.set(r,n)),n.count++,{allowed:n.count<=t.rateLimit.requests,remaining:Math.max(0,t.rateLimit.requests-n.count),resetAt:n.resetAt}}function E(e){const t=s(),r=e.headers.origin;return r&&t.allowedOrigins.includes(r)?r:t.allowedOrigins[0]}function w(e){return{"Access-Control-Allow-Origin":E(e),"Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Authorization","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","Cache-Control":"no-store","Content-Security-Policy":"default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'","Referrer-Policy":"no-referrer","Permissions-Policy":"geolocation=(), microphone=(), camera=()","X-Robots-Tag":"noindex, nofollow"}}const b=["/health","/auth/status","/ui"];function U(e,t){if(e.method==="OPTIONS"){const n=w(e);return t.writeHead(204,n),t.end(),{error:!0}}const r=D(e);if(!r.allowed){const n=w(e);return t.writeHead(429,{...n,"X-RateLimit-Remaining":"0","X-RateLimit-Reset":String(r.resetAt),"Retry-After":String(Math.ceil((r.resetAt-Date.now())/1e3))}),t.end(JSON.stringify({error:"Rate limit exceeded"})),i.warn("Rate limit exceeded",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}const o=new URL(e.url||"/","http://localhost").pathname;if(b.includes(o))return{error:!1};if(!v(e)){const n=w(e);return t.writeHead(401,{...n,"WWW-Authenticate":'Bearer realm="Wyrm API"'}),t.end(JSON.stringify({error:"Unauthorized"})),i.warn("Authentication failed",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}return{error:!1}}function z(){const e=p(32).toString("hex"),t=s();return t.apiKeyHash=y(e),l(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("API key regenerated"),e}function G(e){const t=s();t.allowedOrigins=e,l(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("CORS origins updated",{origins:e})}function X(e,t){const r=s();r.rateLimit={enabled:!0,requests:e,windowMs:t},l(a,JSON.stringify(r,null,2),{mode:384}),c=r,i.info("Rate limit updated",{requests:e,windowMs:t})}function Y(e){const t=s();t.requireAuth=e,l(a,JSON.stringify(t,null,2),{mode:384}),c=t,e||i.warn("Authentication disabled - API is now open")}function j(){const e=s();e.devMode=!0}let f=null;function V(){return f===null&&(f=p(32).toString("hex")),f}function K(e){if(f===null)return!1;const t=Buffer.from(e,"utf8"),r=Buffer.from(f,"utf8");return t.length!==r.length?!1:M(t,r)}function $(){const e=s();return{requireAuth:e.requireAuth,devMode:e.devMode,rateLimit:e.rateLimit,allowedOrigins:e.allowedOrigins}}export{U as authMiddleware,v as authenticate,D as checkRateLimit,j as enableDevMode,$ as getAuthStatus,E as getCorsOrigin,w as getSecurityHeaders,V as getUiNonce,z as regenerateApiKey,G as setAllowedOrigins,X as setRateLimit,Y as setRequireAuth};
3
+ `);const t={apiKeyHash:y(e),allowedOrigins:x,rateLimit:L,requireAuth:!0,devMode:process.env.WYRM_DEV==="true"};return l(a,JSON.stringify(t,null,2),{mode:384}),t}function s(){return c||(c=H()),c}function v(e){const t=s(),r=e.socket.remoteAddress??"",o=r==="127.0.0.1"||r==="::1"||r==="::ffff:127.0.0.1",n=(e.headers.host??"").split(":")[0].toLowerCase(),d=n==="localhost"||n==="127.0.0.1"||n==="::1"||n==="[::1]";if(t.devMode&&o&&d)return!0;const h=e.headers["x-wyrm-nonce"];if(o&&d&&typeof h=="string"&&K(h))return!0;if(!t.requireAuth)return i.warn("Authentication disabled - API is open"),!0;const g=e.headers.authorization;if(!g||!g.startsWith("Bearer "))return!1;const m=g.slice(7);if(!m||m.length<32)return!1;const C=y(m);return k(C,t.apiKeyHash)}function D(e){const t=s();if(!t.rateLimit.enabled)return{allowed:!0,remaining:1/0,resetAt:0};const r=e.socket.remoteAddress||"unknown",o=Date.now();if(u.size>1e4)for(const[d,h]of u)h.resetAt<o&&u.delete(d);let n=u.get(r);return(!n||n.resetAt<o)&&(n={count:0,resetAt:o+t.rateLimit.windowMs},u.set(r,n)),n.count++,{allowed:n.count<=t.rateLimit.requests,remaining:Math.max(0,t.rateLimit.requests-n.count),resetAt:n.resetAt}}function E(e){const t=s(),r=e.headers.origin;return r&&t.allowedOrigins.includes(r)?r:t.allowedOrigins[0]}function w(e){return{"Access-Control-Allow-Origin":E(e),"Access-Control-Allow-Methods":"GET, POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Authorization","X-Content-Type-Options":"nosniff","X-Frame-Options":"DENY","Cache-Control":"no-store","Content-Security-Policy":"default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'","Referrer-Policy":"no-referrer","Permissions-Policy":"geolocation=(), microphone=(), camera=()","X-Robots-Tag":"noindex, nofollow"}}const b=["/health","/auth/status","/ui"];function U(e,t){if(e.method==="OPTIONS"){const n=w(e);return t.writeHead(204,n),t.end(),{error:!0}}const r=D(e);if(!r.allowed){const n=w(e);return t.writeHead(429,{...n,"X-RateLimit-Remaining":"0","X-RateLimit-Reset":String(r.resetAt),"Retry-After":String(Math.ceil((r.resetAt-Date.now())/1e3))}),t.end(JSON.stringify({error:"Rate limit exceeded"})),i.warn("Rate limit exceeded",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}const o=new URL(e.url||"/","http://localhost").pathname;if(b.includes(o))return{error:!1};if(!v(e)){const n=w(e);return t.writeHead(401,{...n,"WWW-Authenticate":'Bearer realm="Wyrm API"'}),t.end(JSON.stringify({error:"Unauthorized"})),i.warn("Authentication failed",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}return{error:!1}}function z(){const e=p(32).toString("hex"),t=s();return t.apiKeyHash=y(e),l(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("API key regenerated"),e}function G(e){const t=s();t.allowedOrigins=e,l(a,JSON.stringify(t,null,2),{mode:384}),c=t,i.info("CORS origins updated",{origins:e})}function X(e,t){const r=s();r.rateLimit={enabled:!0,requests:e,windowMs:t},l(a,JSON.stringify(r,null,2),{mode:384}),c=r,i.info("Rate limit updated",{requests:e,windowMs:t})}function Y(e){const t=s();t.requireAuth=e,l(a,JSON.stringify(t,null,2),{mode:384}),c=t,e||i.warn("Authentication disabled - API is now open")}function j(){const e=s();e.devMode=!0}let f=null;function V(){return f===null&&(f=p(32).toString("hex")),f}function K(e){if(f===null)return!1;const t=Buffer.from(e,"utf8"),r=Buffer.from(f,"utf8");return t.length!==r.length?!1:M(t,r)}function $(){const e=s();return{requireAuth:e.requireAuth,devMode:e.devMode,rateLimit:e.rateLimit,allowedOrigins:e.allowedOrigins}}export{U as authMiddleware,v as authenticate,D as checkRateLimit,j as enableDevMode,$ as getAuthStatus,E as getCorsOrigin,w as getSecurityHeaders,V as getUiNonce,z as regenerateApiKey,G as setAllowedOrigins,X as setRateLimit,Y as setRequireAuth};