wyrm-mcp 9.2.2 → 9.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activation.js +1 -1
- package/dist/agent-loop.js +2 -2
- package/dist/audit.js +2 -2
- package/dist/autoconfig.js +6 -6
- package/dist/backup-cli.js +6 -0
- package/dist/backup.js +1 -0
- package/dist/bridge.js +1 -1
- package/dist/buddy-runner.js +1 -1
- package/dist/buddy.js +4 -4
- package/dist/capabilities.js +1 -1
- package/dist/causality.js +3 -3
- package/dist/cloud/cli.js +3 -3
- package/dist/cloud/sync-daemon-entrypoint.js +1 -1
- package/dist/cloud/sync-engine.js +8 -7
- package/dist/cloud-backup.js +4 -4
- package/dist/cloud-sync-entrypoint.js +1 -1
- package/dist/cloud-sync.js +1 -1
- package/dist/connectors/ingest.js +2 -2
- package/dist/constellation.js +1 -1
- package/dist/context-build-budgeted.js +4 -4
- package/dist/context-ranking.js +1 -1
- package/dist/daemon-writer.js +1 -1
- package/dist/database.js +41 -23
- package/dist/design.js +1 -1
- package/dist/erasure-cli.js +3 -0
- package/dist/erasure-log.js +8 -0
- package/dist/erasure.js +1 -0
- package/dist/event-replication.js +1 -1
- package/dist/events.js +11 -11
- package/dist/failure-patterns.js +67 -44
- package/dist/federation.js +11 -10
- package/dist/governance.js +1 -0
- package/dist/handlers/agent.js +4 -4
- package/dist/handlers/alias-adapters.js +1 -1
- package/dist/handlers/audit.js +1 -1
- package/dist/handlers/boundary.js +1 -1
- package/dist/handlers/capture.js +54 -52
- package/dist/handlers/causality.js +2 -2
- package/dist/handlers/cloud.js +9 -9
- package/dist/handlers/companion.js +4 -4
- package/dist/handlers/entity.js +6 -6
- package/dist/handlers/events.js +4 -4
- package/dist/handlers/failure.js +15 -12
- package/dist/handlers/goals.js +2 -2
- package/dist/handlers/intelligence.js +81 -74
- package/dist/handlers/mcpclient.js +3 -3
- package/dist/handlers/presence.js +1 -1
- package/dist/handlers/prompts.js +2 -2
- package/dist/handlers/quest.js +11 -4
- package/dist/handlers/recall.js +10 -10
- package/dist/handlers/resources.js +1 -1
- package/dist/handlers/review.js +3 -3
- package/dist/handlers/run.js +3 -3
- package/dist/handlers/search.js +1 -1
- package/dist/handlers/session.js +27 -25
- package/dist/handlers/share.js +3 -3
- package/dist/handlers/skill.js +2 -2
- package/dist/handlers/symbols.js +5 -5
- package/dist/handlers/syncops.js +2 -2
- package/dist/harvest.js +5 -5
- package/dist/hours.js +1 -1
- package/dist/http-auth.js +2 -2
- package/dist/http-fast.js +17 -17
- package/dist/http-projects.js +4 -0
- package/dist/http-rate-limit.js +1 -0
- package/dist/index-health.js +52 -0
- package/dist/index.js +2 -2
- package/dist/indexer.js +4 -4
- package/dist/intelligence.js +36 -29
- package/dist/lane-provenance.js +39 -0
- package/dist/license.js +1 -1
- package/dist/lineage.js +15 -0
- package/dist/live-embedding-state.js +2 -2
- package/dist/maintenance.js +2 -2
- package/dist/mcp-integrity.js +2 -2
- package/dist/memory-artifacts.js +39 -37
- package/dist/metabolize.js +9 -7
- package/dist/metrics.js +1 -1
- package/dist/migrate-prompt.js +1 -1
- package/dist/migrations.js +236 -45
- package/dist/priority-embed.js +1 -1
- package/dist/providers/embedding-provider.js +1 -1
- package/dist/readonly-gate.js +1 -1
- package/dist/redact-backfill.js +1 -0
- package/dist/rehydration.js +30 -20
- package/dist/reindex.js +1 -1
- package/dist/render-target.js +6 -6
- package/dist/repl-guard.js +1 -1
- package/dist/replication-daemon-entrypoint.js +1 -1
- package/dist/replication-daemon.js +1 -1
- package/dist/retention.js +1 -0
- package/dist/reverse-bridge.js +6 -6
- package/dist/review-sample.js +31 -0
- package/dist/security.js +1 -1
- package/dist/self-maintenance.js +4 -4
- package/dist/served-records.js +8 -2
- package/dist/setup-wizard.js +1 -1
- package/dist/setup.js +1 -1
- package/dist/skill-author.js +2 -2
- package/dist/store-open.js +1 -0
- package/dist/store-readonly.js +1 -0
- package/dist/sub-agent.js +2 -2
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/truth-freshness.js +8 -0
- package/dist/ui-dashboard.js +4 -4
- package/dist/vault.js +1 -1
- package/dist/vector-init.js +1 -1
- package/dist/vectors.js +4 -3
- package/dist/visibility.js +12 -12
- package/dist/write-guard.js +1 -0
- package/dist/wyrm-cli.js +108 -92
- package/dist/wyrm-guard.js +13 -12
- package/dist/wyrm-loop.js +3 -3
- package/dist/wyrm-manifest.json +1 -1
- package/dist/wyrm-memo.js +5 -5
- package/package.json +1 -1
- package/scripts/hooks/wyrm-session-capture.mjs +3 -0
package/dist/handlers/review.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import{TOOL_ANNOTATIONS as
|
|
1
|
+
import{TOOL_ANNOTATIONS as f}from"../tool-annotations.js";import{renderResult as m,withGlyph as p}from"../render.js";import{reverseBridgeSigFromTags as v,recordReverseBridgeRejection as u}from"../reverse-bridge.js";import{asString as y,requireBool as g,requireInt as w}from"../validate.js";const j=[{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:f.wyrm_review,aliases:[],handler:(s,{memory:o,raw:c})=>{const i=s,t=w("artifactId",i.artifactId,{min:1}),n=g("approved",i.approved),d=y("notes",i.notes),r=o.get(t);if(!r)return{content:[{type:"text",text:`Artifact #${t} not found.`}],isError:!0};if(n)o.approve(t);else{const e=v(r.tags);e&&u(c(),r.project_id,e),o.reject(t)}const l={artifact_id:t,approved:n,deleted:!n,problem:r.problem.slice(0,80),notes:d??null};return m(l,(e,a)=>e.approved?p(a.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}`:""}`:
|
|
7
|
+
Notes: ${e.notes}`:""}`:p(a.fail,`**Rejected** -- Artifact #${e.artifact_id} permanently deleted.`)+`
|
|
8
8
|
|
|
9
9
|
"${e.problem}"${e.notes?`
|
|
10
10
|
|
|
11
|
-
Notes: ${e.notes}`:""}`)}}];export{
|
|
11
|
+
Notes: ${e.notes}`:""}`)}}];export{j as reviewToolSpecs};
|
package/dist/handlers/run.js
CHANGED
|
@@ -3,10 +3,10 @@ import{TOOL_ANNOTATIONS as z}from"../tool-annotations.js";import{renderResult as
|
|
|
3
3
|
ON CONFLICT(run_id, agent_id) DO UPDATE SET role =
|
|
4
4
|
CASE WHEN run_agents.role = 'orchestrator' THEN run_agents.role
|
|
5
5
|
ELSE COALESCE(excluded.role, run_agents.role) END
|
|
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
|
|
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!=="writer"&&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; 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})
|
|
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}); 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; 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: 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
|
|
@@ -18,7 +18,7 @@ Agents (${o.agents.length}):${m?`
|
|
|
18
18
|
${m}`:" none"}
|
|
19
19
|
Failures: ${o.quarantined} run-quarantined unresolved, ${o.resolved} resolved
|
|
20
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}
|
|
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}; 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?`
|
package/dist/handlers/search.js
CHANGED
|
@@ -11,5 +11,5 @@ import{TOOL_ANNOTATIONS as D}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 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
|
|
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: 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'] (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; 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
15
|
`)})}}];export{V as searchToolSpecs};
|
package/dist/handlers/session.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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
|
|
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 A}from"../render.js";import{computeStaleness as le}from"../intelligence.js";import{briefTag as me,checkTruthPaths as fe,truthFreshness as he}from"../truth-freshness.js";import{ValidationError as ye,asInt as _e,asString as F}from"../validate.js";import{sanitizeActorId as ge,getActor as je}from"./boundary.js";import{makeEstimator as be}from"../token-budget.js";import{refreshThread as Z}from"../thread-assemble.js";import{autoEnrollProject as Se,resolveProjectForPath as ee}from"../project-resolve.js";import{resolvePolicy as te,policyHash as se}from"../recall-policy.js";import{reviewSampleCounts as ve}from"../review-sample.js";function ne(e){return!e||!e.thread.trim()?null:`### \u{1F9F5} Where We Left Off _(the living thread: 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
|
|
5
|
-
|
|
6
|
-
`;for(const
|
|
7
|
-
`}}
|
|
4
|
+
`+e.thread.slice(-1800)}const we={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){const s=`### \u{1F4DA} Project Ground Truths _(treat as authoritative; do not infer or contradict)_
|
|
5
|
+
`,n=new Map;for(const c of e)n.has(c.category)||n.set(c.category,[]),n.get(c.category).push(c);const d=Date.now();let i=!1,o="";for(const[c,p]of n){o+=`**${we[c]??c}:**
|
|
6
|
+
`;for(const r of p){if(!t){o+=`- **${r.key}:** ${r.value}${r.rationale?` _(${r.rationale})_`:""}
|
|
7
|
+
`;continue}const l=le(r),a=he(r,d),b=a.stale||l!==null&&l>.7,h=fe(`${r.value} ${r.rationale??""}`).dead.length>0;(b||h)&&(i=!0),o+=`- **${r.key}** ${me(a,{stale:b,deadPath:h})}: ${r.value}${r.rationale?` _(${r.rationale})_`:""}
|
|
8
|
+
`}}return s+(i?"_STALE = past its TTL, path missing = a named path is gone: re-verify those before relying on them (`wyrm truth verify <id>`, or re-set the same value)._\n":"")+o}const re=`
|
|
8
9
|
SELECT * FROM quests
|
|
9
10
|
WHERE project_id = ? AND status = 'pending'
|
|
10
11
|
ORDER BY
|
|
@@ -12,15 +13,15 @@ import{basename as ue}from"path";import{createHash as pe}from"crypto";import{blo
|
|
|
12
13
|
id
|
|
13
14
|
LIMIT 5
|
|
14
15
|
`;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
|
-
`;for(const
|
|
16
|
-
`;return s}function D(e,t,s,
|
|
16
|
+
`;for(const n of e)s+=`- ${t[n.priority]??"\u2022"} #${n.id}: ${n.title}
|
|
17
|
+
`;return s}function D(e,t,s,n,d){if(s)return e.getProjectById(s);if(n)return t.prepare("SELECT * FROM projects WHERE LOWER(name) LIKE LOWER(?) LIMIT 1").get(`%${n}%`);if(d){const c=e.getProject(d);if(c)return c;const{id:p}=ee(t,d);return p!=null?e.getProjectById(p):void 0}const{id:i}=ee(t,process.cwd());if(i!=null)return e.getProjectById(i);const o=t.prepare(`
|
|
17
18
|
SELECT p.* FROM projects p
|
|
18
19
|
JOIN sessions s ON s.project_id = p.id
|
|
19
20
|
ORDER BY s.created_at DESC LIMIT 1
|
|
20
|
-
`).get();if(
|
|
21
|
-
${t.map(
|
|
21
|
+
`).get();if(o)return o;const f=Se(e);return f?e.getProjectById(f.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:
|
|
22
|
+
${t.map(n=>`- ${n.name} (ID: ${n.id})`).join(`
|
|
22
23
|
`)}`}],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}`)}
|
|
24
|
+
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}`)},$e=(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),n=e.sections.length>0?e.sections.map(d=>d.text).join(`
|
|
24
25
|
|
|
25
26
|
---
|
|
26
27
|
|
|
@@ -28,19 +29,20 @@ Session logged: #${e.session_id}`:"";return N(t.brand,`Wyrm primed for **${e.pro
|
|
|
28
29
|
|
|
29
30
|
`+s:s+`
|
|
30
31
|
|
|
31
|
-
_No memory stored yet -- start working and Wyrm will learn._`;return
|
|
32
|
+
_No memory stored yet -- start working and Wyrm will learn._`;return $e(e,t)+`
|
|
32
33
|
|
|
33
|
-
`+
|
|
34
|
-
${
|
|
35
|
-
**Date:** ${
|
|
36
|
-
**Objectives:** ${
|
|
37
|
-
${i}`:i),
|
|
38
|
-
${
|
|
39
|
-
${
|
|
40
|
-
${
|
|
41
|
-
${
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Provider '
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
`+n},W={summary:ae};async function H(e,t,s){try{const{logSavings:n}=await import("../statusline.js"),d=X(s.content[0]).length,i=Math.round(d/4),o=e.prepare("SELECT id FROM sessions WHERE project_id = ? ORDER BY created_at DESC LIMIT 1").get(t);n(e,"wyrm_session_prime","recovered_context",i,o?.id)}catch{}}const Ee={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"}},Te=48,xe=1200;async function ke(e,{store:t,raw:s,memory:n,truths:d,scaffolds:i,cache:o}){const{project_id:f,project_name:c,task:p,log_session:r}=e,l=ge(e.run_id);if(l===null)throw new ye("run_id",'must be <=64 printable-ASCII chars (no ";")');const a=F("role",e.role,{maxLen:200})??"",b=_e("token_budget",e.token_budget,{min:1})??xe,h=s(),$=te(h,l),x=$.mode==="full"&&$.mutedIds.length===0&&$.challengedTruthIds.length===0?a:`${a}::${se($)}`,R=()=>h.prepare("SELECT body_json FROM run_briefs WHERE run_id = ? AND role = ?").get(l,x),E=F("projectPath",e.projectPath),v=f||c||E?D(t,h,f,c,E):void 0;if((f||c||E)&&!v)return M(t);let w=R();if(w&&v){const u=JSON.parse(w.body_json);if(u.project_id!==v.id)return{content:[{type:"text",text:`Fleet brief for (run ${l}, role '${a}') is pinned to project '${u.project_name}' (ID: ${u.project_id}); refusing to serve it for requested project '${v.name}' (ID: ${v.id}). Use a distinct role for a second project, or prime without run_id.`}],isError:!0}}if(!w){const u=v??D(t,h,f,c,E);if(!u)return M(t);const _=[],I=ne(t.getContinuity(u.id));I&&_.push({kind:"continuity",text:I});const y=p??(a||void 0),m=d.getCurrent(u.id),g=n.buildContextBrief(u.id,y??"general",{maxItems:10,minConfidence:.3,runId:l,groundTruths:m}),Q=g.groundTruths??m;if(Q.length>0&&_.push({kind:"truths",text:oe(Q,!1).slice(0,1200)}),y){const T=i.findBest(y,u.id,.3);T&&_.push({kind:"scaffold",text:i.formatForContext(T,1500)})}g.text&&_.push({kind:"memory",text:g.text.slice(0,2e3)});const O=h.prepare(re).all(u.id);O.length>0&&_.push({kind:"quests",text:ce(O).slice(0,800)});const G=be(),q=[];let L=Te;for(const T of _){const Y=G.count(T.text)+2;if(L+Y<=b)q.push(T),L+=Y;else{const K=Ee[T.kind]??{path:T.kind,tool:"wyrm_search"},z=`[beyond token_budget] wyrm://project/${u.id}/${K.path} -- fetch via ${K.tool}`;q.push({kind:T.kind,text:z}),L+=G.count(z)+2}}let J=null;r&&(J=t.createSession(u.id,{objectives:p??"Session started via wyrm_session_prime",summary:""}).id);const V=await ie(),de={project_id:u.id,project_name:u.name,truth_count:d.getStats(u.id).current,quest_count:O.length,memory_count:n.getStats(u.id).total,session_id:J,sections:q,run_id:l,role:a,token_budget:b,tier_n:V.n,tier_label:V.label};h.prepare("INSERT OR IGNORE INTO run_briefs (run_id, role, body_json, token_budget) VALUES (?, ?, ?, ?)").run(l,x,JSON.stringify(de),b),w=R()}const S=JSON.parse(w.body_json),k=P(S,B,W);if(e.for_spawn===!0){const u=P(S,B,{...W,exemptStructured:!0}),_=X(u.content[0]),I=pe("sha256").update(_,"utf8").digest("hex"),y={project_id:S.project_id,project_name:S.project_name,session_id:S.session_id??null,sections:S.sections,for_spawn:!0,run_id:l,role:a,brief:_,brief_hash:I,brief_chars:_.length,cache_key:{table:"run_briefs",run_id:l,role:x}};await H(h,S.project_id,u);const m={content:[{type:"text",text:_}],structuredContent:y};return o.set(A("wyrm_session_prime",JSON.stringify(e)),m,3e4),m}return await H(h,S.project_id,k),o.set(A("wyrm_session_prime",JSON.stringify(e)),k,3e4),k}const He=[{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:n,objectives:d}=e;let i=t.getProject(n);i||(i=t.registerProject(ue(n),n));let o=t.getTodaySession(i.id);o?d&&(o=t.updateSession(o.id,{objectives:o.objectives?`${o.objectives}
|
|
35
|
+
${d}`:d})):o=t.createSession(i.id,{objectives:d||""}),t.archiveOldSessions(i.id,10),Z(s(),i.id);const f={session_id:o.id,project:i.name,date:o.date,objectives:o.objectives||null};return P(f,(c,p)=>N(p.brand,`Session ${c.session_id} for ${c.project}`)+`
|
|
36
|
+
**Date:** ${c.date}
|
|
37
|
+
**Objectives:** ${c.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:n})=>{const{projectPath:d,completed:i,issues:o,commits:f,notes:c}=e,p=t.getProject(d);if(!p)return{content:[{type:"text",text:"Project not found"}],isError:!0};let r=t.getTodaySession(p.id);r||(r=t.createSession(p.id,{}));const l={};i&&(l.completed=r.completed?`${r.completed}
|
|
38
|
+
${i}`:i),o&&(l.issues=r.issues?`${r.issues}
|
|
39
|
+
${o}`:o),f&&(l.commits=r.commits?`${r.commits}
|
|
40
|
+
${f}`:f),c&&(l.notes=r.notes?`${r.notes}
|
|
41
|
+
${c}`:c),r=t.updateSession(r.id,l);const a=[i,o,c].filter(Boolean).join(" \xB7 ");a&&t.updateContinuity(p.id,a,"session_update"),s()?.enqueue("session",r.id,p.id),Z(n(),p.id);const b={session_id:r.id,project:p.name,updated:Object.keys(l)};return P(b,(h,$)=>N($.brand,`Session updated for ${h.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:n,include_failures:d,max_truth_chars:i}=e,o=t.rehydrate(s,{include_artifacts:n,include_failures:d,max_truth_chars:i});return o?P({...o},f=>f.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:n,truths:d,scaffolds:i,cache:o})=>{const{project_id:f,project_name:c,task:p,log_session:r}=e;if(e.run_id!==void 0&&e.run_id!==null)return ke(e,{store:t,raw:s,memory:n,truths:d,scaffolds:i,cache:o});const l=F("projectPath",e.projectPath),a=D(t,s(),f,c,l);if(!a)return M(t);const b=je().run_id,h=te(s(),b),$=h.mode==="full"&&h.mutedIds.length===0&&h.challengedTruthIds.length===0,j=[],x=ne(t.getContinuity(a.id));x&&j.push({kind:"continuity",text:x});const R=d.getCurrent(a.id),E=n.buildContextBrief(a.id,p??"general",{maxItems:10,minConfidence:.3,runId:b,groundTruths:R}),v=E.groundTruths??R;if(v.length>0&&j.push({kind:"truths",text:oe(v,!0).slice(0,1200)}),p){const y=i.findBest(p,a.id,.3);y&&j.push({kind:"scaffold",text:i.formatForContext(y,1500)})}E.text&&j.push({kind:"memory",text:E.text.slice(0,2e3)});const w=s().prepare(re).all(a.id);w.length>0&&j.push({kind:"quests",text:ce(w).slice(0,800)});try{const y=s().prepare("SELECT COUNT(*) AS n FROM memory_artifacts WHERE project_id = ? AND needs_review = 1").get(a.id).n,m=ve(s(),a.id).unreviewed,g=[];y>0&&g.push(`${y} captured memor${y===1?"y awaits":"ies await"} review (wyrm_review). Queued memory is invisible to recall until approved.`),m>0&&g.push(`${m} auto-approved memor${m===1?"y was":"ies were"} sampled for a spot check (wyrm review sample). They stay in recall either way.`),g.length>0&&j.push({kind:"review_queue",text:`### \u{1F4E5} Review Queue
|
|
42
|
+
${g.join(`
|
|
43
|
+
`)}`})}catch{}try{const{resolveEmbeddingState:y}=await import("../providers/embedding-provider.js"),m=y();let g=-1;try{g=s().prepare("SELECT COUNT(*) AS n FROM vectors").get().n}catch{g=0}m.resolved==="none"?j.push({kind:"vector_health",text:`### [!] Semantic Recall is OFF
|
|
44
|
+
No embedding provider is active (${m.reason||"no provider"}) - recall is FTS5 keyword-only (~59.9% vs 72.2% hybrid recall@10). Fix: ${m.fix}`}):m.resolved==="local"?j.push({kind:"vector_health",text:`### [!] Semantic Recall is NON-SEMANTIC
|
|
45
|
+
Provider 'local' (hash-384) is a test-grade fallback, not a real embedder - recall quality is at the FTS floor. Fix: ${m.fix}`}):g===0&&j.push({kind:"vector_health",text:`### [!] Vector Index is EMPTY
|
|
46
|
+
Provider '${m.resolved}' (${m.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:y}=await import("../vault.js"),m=y();m.length>0&&j.push({kind:"vault",text:`### \u{1F510} Credential Vault
|
|
47
|
+
`+m.map(g=>`- ${g}`).join(`
|
|
48
|
+
`)})}catch{}let S=null;r&&(S=t.createSession(a.id,{objectives:p??"Session started via wyrm_session_prime",summary:""}).id);const k=await ie(),U={project_id:a.id,project_name:a.name,truth_count:d.getStats(a.id).current,quest_count:w.length,memory_count:n.getStats(a.id).total,session_id:S,sections:j,tier_n:k.n,tier_label:k.label},u=P(U,B,W);await H(s(),a.id,u);const _=A("wyrm_session_prime",JSON.stringify(e)),I=$?_:`${_}::${se(h)}`;return o.set(I,u,3e4),u}}];export{He as sessionToolSpecs};
|
package/dist/handlers/share.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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
|
|
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 a=g("kind",r.kind,m);if(a===void 0)throw new h("kind",`is required (one of: ${m.join(", ")})`);return{kind:a,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 (Claude via ANTHROPIC_API_KEY, NVIDIA NIM via NVIDIA_API_KEY, OpenAI via OPENAI_API_KEY) 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: 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,a)=>{const{db:c,subAgent:o}=a,{query:t,projectPath:e,max_context_chars:s,model_override:i}=r,u=e?c.getProject(e):null,n=await o.ask({query:t,project_id:u?.id??null,max_context_chars:s,model_override:i});return{content:[{type:"text",text:(n.degraded?`\u{F115D} Wyrm (${n.model}, ${n.context_chars} ctx chars, ${n.latency_ms}ms). DEGRADED MODE: ${n.error}
|
|
2
2
|
|
|
3
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
|
-
`)+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,
|
|
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,a)=>{const{federation:c}=a,{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 s=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 ${s}), and private groves never leave this machine. To share it, change the grove's lane first: \`wyrm grove policy ${s} cloud\` (your own cloud) or \`wyrm grove policy ${s} 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,a)=>{const{federation:c}=a,{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: 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,a)=>{const{federation:c,sync:o}=a,{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 s=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
|
-
${
|
|
7
|
+
${s}`}]}}},{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': full row payload to write"}},required:["conflict_id","mode"]},annotations:p.wyrm_sync_resolve,aliases:[],handler:async(r,a)=>{const{federation:c}=a,{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,a)=>{const{agentLoop:c,db:o,goals:t}=a,e=r;let s=e.goal_id,i=!1;if(!s){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;s=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(s,{max_steps:e.max_steps,model_override:e.model_override});if(u.length===0)return{content:[{type:"text",text:`Goal #${s} 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 #${s}${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}: ${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
8
|
`)}]}}}];export{w as shareToolSpecs};
|
package/dist/handlers/skill.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{TOOL_ANNOTATIONS as v}from"../tool-annotations.js";import{currentReadCacheKey as S}from"./boundary.js";import{cache as h}from"../performance.js";import{sanitizeFtsQuery as O}from"../security.js";import{deploySkill as j,validateAtomicSkill as C}from"../skill-author.js";const R=[{name:"wyrm_skill_create",description:"Author + deploy + register an ATOMIC skill in one clean step. Validates atomicity (one focused capability, discoverable 12\u2013280 char description, proper frontmatter), writes <skillsDir>/<slug>/SKILL.md, and registers it so it's instantly findable via wyrm_skill_search. Refuses to clobber an existing skill unless force=true. Use this to create skills as they're needed instead of leaving them un-deployed.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name (slugified to kebab-case)"},description:{type:"string",description:"One-line description, 12\u2013280 chars
|
|
1
|
+
import{TOOL_ANNOTATIONS as v}from"../tool-annotations.js";import{currentReadCacheKey as S}from"./boundary.js";import{cache as h}from"../performance.js";import{sanitizeFtsQuery as O}from"../security.js";import{deploySkill as j,validateAtomicSkill as C}from"../skill-author.js";const R=[{name:"wyrm_skill_create",description:"Author + deploy + register an ATOMIC skill in one clean step. Validates atomicity (one focused capability, discoverable 12\u2013280 char description, proper frontmatter), writes <skillsDir>/<slug>/SKILL.md, and registers it so it's instantly findable via wyrm_skill_search. Refuses to clobber an existing skill unless force=true. Use this to create skills as they're needed instead of leaving them un-deployed.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name (slugified to kebab-case)"},description:{type:"string",description:"One-line description, 12\u2013280 chars, used for discovery/recall ranking"},body:{type:"string",description:"The SKILL.md markdown body (the actual skill content; should be ONE focused capability)"},argumentHint:{type:"string",description:"Optional argument-hint frontmatter (for user-invocable skills)"},userInvocable:{type:"boolean",description:"Whether it's a user-invocable slash command"},category:{type:"string",description:"Optional category"},tags:{type:"array",items:{type:"string"},description:"Optional tags"},force:{type:"boolean",description:"Overwrite an existing skill of the same slug (default false)"},register:{type:"boolean",description:"Register in the skill registry for discovery (default true)"}},required:["name","description","body"]},annotations:v.wyrm_skill_create,aliases:[],handler:async(c,p)=>{const{db:d}=p,n=c||{},o=($=>{const x=$.filter(_=>!n[_]&&n[_]!==0&&n[_]!==!1);return x.length>0?{content:[{type:"text",text:`Missing required arguments: ${x.join(", ")}`}],isError:!0}:null})(["name","description","body"]);if(o)return o;const{name:t,description:s,body:g,argumentHint:m,userInvocable:a,category:y,tags:i,force:e,register:l}=c,k={name:t,description:s,body:g,argumentHint:m,userInvocable:a,category:y,tags:i},u=C(k);if(!u.ok)return{content:[{type:"text",text:`\u274C Skill rejected (not clean/atomic):
|
|
2
2
|
- ${u.errors.join(`
|
|
3
3
|
- `)}`}]};let f;try{f=j(k,{force:e})}catch($){return{content:[{type:"text",text:`\u274C Deploy failed: ${$ instanceof Error?$.message:String($)}`}]}}let b=!1;if(l!==!1)try{d.registerSkill(u.slug,s,f.path,y,"Ghost Protocol","1.0.0",(i||[]).join(",")),b=!0}catch{}const w=u.warnings.length?`
|
|
4
4
|
|
|
@@ -60,7 +60,7 @@ ${r?`Skill "${r}" is not registered (or has no graph).`:"No god-tier skills regi
|
|
|
60
60
|
|
|
61
61
|
`;if(t==="tree"||t==="both")for(const e of s)i+=y(e,0);if(t==="edges"||t==="both"){i+=`
|
|
62
62
|
**Edges (${g.length}):**
|
|
63
|
-
`;for(const e of g)i+=`- ${e.from}
|
|
63
|
+
`;for(const e of g)i+=`- ${e.from} [${e.kind}]\u2192 ${e.to}${e.kind==="governs"&&!e.resolved?" (unresolved)":""}
|
|
64
64
|
`}return i+=`
|
|
65
65
|
\`\`\`json
|
|
66
66
|
${JSON.stringify({tree:s,edges:g},null,2)}
|
package/dist/handlers/symbols.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{TOOL_ANNOTATIONS as m}from"../tool-annotations.js";const u=[{name:"wyrm_symbol_index",description:"Scan a project for function/class/type/interface definitions across TS/JS/Python/Rust/Go/PHP/Ruby and index them. Idempotent
|
|
1
|
+
import{TOOL_ANNOTATIONS as m}from"../tool-annotations.js";const u=[{name:"wyrm_symbol_index",description:"Scan a project for function/class/type/interface definitions across TS/JS/Python/Rust/Go/PHP/Ruby and index them. Idempotent: clears the project's symbols before re-indexing.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Project root to scan"}},required:["projectPath"]},annotations:m.wyrm_symbol_index,aliases:[],handler:async(s,r)=>{const{db:a,symbols:i}=r,{projectPath:e}=s,t=a.getProject(e);if(!t)return{content:[{type:"text",text:"Project not found"}],isError:!0};const o=i.indexProject(t.id,t.path);return{content:[{type:"text",text:`\u{F115D} Indexed ${o.symbols} symbol(s) across ${o.files} file(s) in ${t.name}.`}]}}},{name:"wyrm_symbol_search",description:"Look up a symbol across all (or one) project. Cursor's workspace-symbols stops at the repo boundary; this doesn't.",inputSchema:{type:"object",properties:{symbol:{type:"string",description:"Name to search for"},projectPath:{type:"string",description:"Scope to one project (optional)"},kind:{type:"string",enum:["function","class","type","interface","const","export"],description:"Filter by kind"},language:{type:"string",description:"Filter by language: ts/tsx/js/jsx/py/rs/go/php/rb"},exact:{type:"boolean",description:"Exact match (default: substring)"},limit:{type:"number",description:"Max results (default 50)"}},required:["symbol"]},annotations:m.wyrm_symbol_search,aliases:[],handler:async(s,r)=>{const{db:a,symbols:i}=r,{symbol:e,projectPath:t,kind:o,language:c,exact:l,limit:n}=s,y=t?a.getProject(t):null,d=i.search(e,{projectId:y?.id,kind:o,language:c,exact:l,limit:Math.min(Math.max(1,n??50),500)});if(d.length===0)return{content:[{type:"text",text:`\u{F115D} No symbols match "${e}".`}]};const b=d.map(p=>`\u2022 [${p.language}] ${p.kind} ${p.symbol} at ${p.file_path}:${p.line}
|
|
2
2
|
${p.signature??""}`).join(`
|
|
3
3
|
`);return{content:[{type:"text",text:`\u{F115D} ${d.length} match(es):
|
|
4
|
-
${b}`}]}}},{name:"wyrm_symbol_callers",description:"Best-effort caller graph
|
|
4
|
+
${b}`}]}}},{name:"wyrm_symbol_callers",description:"Best-effort caller graph: files in the project (or workspace) whose indexed signature mentions this symbol. Approximate; for a true caller graph use LSP.",inputSchema:{type:"object",properties:{symbol:{type:"string"},projectPath:{type:"string",description:"Scope to one project (optional)"}},required:["symbol"]},annotations:m.wyrm_symbol_callers,aliases:[],handler:async(s,r)=>{const{db:a,symbols:i}=r,{symbol:e,projectPath:t}=s,o=t?a.getProject(t):null,c=i.callers(e,o?.id);if(c.length===0)return{content:[{type:"text",text:`\u{F115D} No indexed callers for "${e}".`}]};const l=c.slice(0,50).map(n=>`\u2022 ${n.file_path}:${n.line}: ${n.signature?.slice(0,100)??""}`).join(`
|
|
5
5
|
`);return{content:[{type:"text",text:`\u{F115D} ${c.length} caller candidate(s) (showing first 50):
|
|
6
|
-
${l}`}]}}},{name:"wyrm_symbol_stats",description:"Symbol-index stats
|
|
7
|
-
Languages: ${c||"
|
|
8
|
-
Kinds: ${l||"
|
|
6
|
+
${l}`}]}}},{name:"wyrm_symbol_stats",description:"Symbol-index stats: total count, per-language and per-kind breakdown. Project-scoped if projectPath given, else workspace-wide.",inputSchema:{type:"object",properties:{projectPath:{type:"string",description:"Scope to one project (optional)"}}},annotations:m.wyrm_symbol_stats,aliases:[],handler:async(s,r)=>{const{db:a,symbols:i}=r,{projectPath:e}=s,t=e?a.getProject(e):null,o=i.stats(t?.id),c=Object.entries(o.by_language).map(([n,y])=>`${n}: ${y}`).join(", "),l=Object.entries(o.by_kind).map(([n,y])=>`${n}: ${y}`).join(", ");return{content:[{type:"text",text:`\u{F115D} Symbols indexed: ${o.total}
|
|
7
|
+
Languages: ${c||"none"}
|
|
8
|
+
Kinds: ${l||"none"}`}]}}}];export{u as symbolsToolSpecs};
|
package/dist/handlers/syncops.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{TOOL_ANNOTATIONS as I}from"../tool-annotations.js";import{logger as O}from"../logger.js";import{asInt as U}from"../validate.js";import{createCipheriv as H,createDecipheriv as q,pbkdf2Sync as C,randomBytes as j}from"crypto";import{chmodSync as J,copyFileSync as M,existsSync as w,readFileSync as k,unlinkSync as p,writeFileSync as N}from"fs";import{homedir as T}from"os";import{join as D}from"path";import{resolveUserPath as Y,userPathRoots as W,SecurityError as B}from"../security.js";const rt=[{name:"wyrm_prune",description:"Prune low-confidence, stale memory artifacts. Dry-run by default
|
|
1
|
+
import{TOOL_ANNOTATIONS as I}from"../tool-annotations.js";import{logger as O}from"../logger.js";import{asInt as U}from"../validate.js";import{createCipheriv as H,createDecipheriv as q,pbkdf2Sync as C,randomBytes as j}from"crypto";import{chmodSync as J,copyFileSync as M,existsSync as w,readFileSync as k,unlinkSync as p,writeFileSync as N}from"fs";import{homedir as T}from"os";import{join as D}from"path";import{resolveUserPath as Y,userPathRoots as W,SecurityError as B}from"../security.js";const rt=[{name:"wyrm_prune",description:"Prune low-confidence, stale memory artifacts. Dry-run by default; use dry_run:false with confirm_ids to actually delete. Never deletes artifacts in the review queue.",inputSchema:{type:"object",properties:{project_id:{type:"number",description:"Project ID (optional, all projects if omitted)"},min_confidence:{type:"number",description:"Prune artifacts below this confidence (default: 0.3)"},older_than_days:{type:"number",description:"Last accessed more than N days ago (default: 90)"},types:{type:"array",items:{type:"string"},description:"Filter by artifact_type (optional)"},dry_run:{type:"boolean",description:"If true (default), return candidates without deleting. Set false to delete."},confirm_ids:{type:"array",items:{type:"number"},description:"Required when dry_run:false; only IDs in this list will be deleted"}}},annotations:I.wyrm_prune,aliases:[],handler:async(u,h)=>{const{db:d}=h,R=u||{},{project_id:_,min_confidence:S=.3,older_than_days:s=90,types:o,dry_run:g=!0,confirm_ids:e}=u,t=d.getDatabase(),n=U("project_id",_),m=n!==void 0?"AND project_id = ?":"",l=n!==void 0?[n]:[],f=o&&o.length>0?`AND kind IN (${o.map(()=>"?").join(",")})`:"",x=o??[],c=t.prepare(`
|
|
2
2
|
SELECT id, project_id, kind, problem, confidence, last_accessed_at, access_count
|
|
3
3
|
FROM memory_artifacts
|
|
4
4
|
WHERE confidence < ?
|
|
@@ -9,4 +9,4 @@ import{TOOL_ANNOTATIONS as I}from"../tool-annotations.js";import{logger as O}fro
|
|
|
9
9
|
${f}
|
|
10
10
|
ORDER BY confidence ASC, last_accessed_at ASC
|
|
11
11
|
LIMIT 500
|
|
12
|
-
`).all(S,`-${s} days`,...l,...x);if(g)return{content:[{type:"text",text:JSON.stringify({dry_run:!0,count:c.length,candidates:c.map(r=>({id:r.id,kind:r.kind,problem:r.problem.slice(0,100),confidence:r.confidence,last_accessed_at:r.last_accessed_at}))})}]};if(!e||e.length===0)return{content:[{type:"text",text:"\u{F115D} **Prune**: dry_run:false requires confirm_ids
|
|
12
|
+
`).all(S,`-${s} days`,...l,...x);if(g)return{content:[{type:"text",text:JSON.stringify({dry_run:!0,count:c.length,candidates:c.map(r=>({id:r.id,kind:r.kind,problem:r.problem.slice(0,100),confidence:r.confidence,last_accessed_at:r.last_accessed_at}))})}]};if(!e||e.length===0)return{content:[{type:"text",text:"\u{F115D} **Prune**: dry_run:false requires confirm_ids; provide the IDs from a dry-run to confirm deletion."}],isError:!0};const b=new Set(c.map(r=>r.id)),a=e.filter(r=>b.has(r));if(a.length===0)return{content:[{type:"text",text:"\u{F115D} **Prune**: No matching IDs to delete (confirm_ids not in candidate set)."}]};const y=a.map(()=>"?").join(","),i=t.prepare(`DELETE FROM memory_artifacts WHERE id IN (${y}) AND needs_review = 0`).run(...a);return{content:[{type:"text",text:JSON.stringify({deleted:i.changes,ids:a})}]}}},{name:"wyrm_sync_export",description:"Export an AES-256-GCM encrypted snapshot of the Wyrm database for cross-device sync. Passphrase from WYRM_SYNC_PASSPHRASE env var.",inputSchema:{type:"object",properties:{output_path:{type:"string",description:"File path to write the encrypted snapshot"},description:{type:"string",description:"Optional description of this export"}},required:["output_path"]},annotations:I.wyrm_sync_export,aliases:[],handler:async(u,h)=>{const{db:d,server:R}=h,{output_path:_,description:S}=u;let s;try{s=Y(_,W(),"wyrm_sync_export.output_path")}catch(n){if(n instanceof B)return{content:[{type:"text",text:`\u{F115D} **Sync Export** refused: ${n.message}`}],isError:!0};throw n}const o=process.env.WYRM_SYNC_PASSPHRASE;if(!o)return{content:[{type:"text",text:"\u{F115D} **Sync Export**: Set WYRM_SYNC_PASSPHRASE env var to enable encrypted export."}],isError:!0};const g=d.getDatabasePath(),e=D(T(),".wyrm"),t=D(e,"wyrm_sync_export_temp.db");try{const n=d.getDatabase();w(t)&&p(t),n.prepare("VACUUM INTO ?").run(t);const m=k(t),l=j(32),f=j(16),x=C(o,l,6e5,32,"sha256"),c=H("aes-256-gcm",x,f),b=Buffer.concat([c.update(m),c.final()]),a=c.getAuthTag(),y=Buffer.from("WYRM"),i=Buffer.alloc(1);i.writeUInt8(1,0);const r=Buffer.concat([y,i,l,f,a,b]);N(s,r);try{J(s,384)}catch{}try{p(t)}catch{}const P=(r.length/(1024*1024)).toFixed(2);return{content:[{type:"text",text:JSON.stringify({success:!0,path:s,size_mb:parseFloat(P),exported_at:new Date().toISOString(),description:S??null})}]}}catch(n){try{w(t)&&p(t)}catch{}return O.error("Sync export failed",{error:n.message}),{content:[{type:"text",text:"\u{F115D} **Sync Export** failed. Check server logs for details."}],isError:!0}}}},{name:"wyrm_sync_import",description:"Import an encrypted Wyrm snapshot. Use restore_mode:'preview' to inspect without touching the DB; 'restore' to replace current DB (backs up first).",inputSchema:{type:"object",properties:{input_path:{type:"string",description:"Path to the encrypted snapshot file"},restore_mode:{type:"string",enum:["preview","restore"],description:"preview = show stats only; restore = replace DB (backs up first)"}},required:["input_path","restore_mode"]},annotations:I.wyrm_sync_import,aliases:[],handler:async(u,h)=>{const{db:d,server:R}=h,{input_path:_,restore_mode:S}=u;let s;try{s=Y(_,W(),"wyrm_sync_import.input_path")}catch(t){if(t instanceof B)return{content:[{type:"text",text:`\u{F115D} **Sync Import** refused: ${t.message}`}],isError:!0};throw t}const o=process.env.WYRM_SYNC_PASSPHRASE;if(!o)return{content:[{type:"text",text:"\u{F115D} **Sync Import**: Set WYRM_SYNC_PASSPHRASE env var to enable encrypted import."}],isError:!0};const g=D(T(),".wyrm"),e=D(g,"wyrm_sync_import_temp.db");try{const t=k(s);if(t.subarray(0,4).toString("ascii")!=="WYRM")return{content:[{type:"text",text:"\u{F115D} Invalid Wyrm snapshot file (bad magic bytes)."}],isError:!0};const m=t.readUInt8(4);if(m!==1)return{content:[{type:"text",text:`\u{F115D} Unsupported snapshot version: ${m}`}],isError:!0};const l=t.subarray(5,37),f=t.subarray(37,53),x=t.subarray(53,69),c=t.subarray(69),b=C(o,l,6e5,32,"sha256"),a=q("aes-256-gcm",b,f);a.setAuthTag(x);let y;try{y=Buffer.concat([a.update(c),a.final()])}catch{return{content:[{type:"text",text:"\u{F115D} Decryption failed: wrong passphrase or corrupted snapshot."}],isError:!0}}if(S==="preview"){w(e)&&p(e),N(e,y);let E={};try{const L=(await import("better-sqlite3")).default,A=new L(e,{readonly:!0}),$=["projects","sessions","ground_truths","memory_artifacts","quests"];for(const v of $)try{const F=A.prepare(`SELECT COUNT(*) as n FROM ${v}`).get();E[v]=F.n}catch{E[v]=0}A.close()}catch{}try{p(e)}catch{}return{content:[{type:"text",text:JSON.stringify({preview:!0,stats:E})}]}}const i=d.getDatabasePath(),r=new Date().toISOString().replace(/[:.]/g,"-"),P=`${i}.backup.${r}`;M(i,P),w(e)&&p(e),N(e,y),d.getDatabase().close(),M(e,i);try{p(e)}catch{}return{content:[{type:"text",text:JSON.stringify({success:!0,restored_from:s,backup_at:P})}]}}catch(t){try{w(e)&&p(e)}catch{}return O.error("Sync import failed",{error:t.message}),{content:[{type:"text",text:"\u{F115D} **Sync Import** failed. Check server logs for details."}],isError:!0}}}}];export{rt as syncopsToolSpecs};
|
package/dist/harvest.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{execFileSync as m}from"child_process";import{readFileSync as h,existsSync as g}from"fs";import{join as u}from"path";import{createHash as x}from"crypto";const
|
|
2
|
-
`).map(a=>a.trim()).filter(a=>a&&!a.startsWith("#")&&!a.startsWith("|")&&!a.startsWith("```")).slice(0,3).join(" ").replace(d,"").replace(/\s+/g," ");if(!r||f.length<24)continue;const l=`${r}: ${f}`.slice(0,600);if(n.push({kind:"lesson",text:l,source:`doc:${
|
|
3
|
-
`)){const t=s.trim();if(!t)continue;const
|
|
4
|
-
`).map(
|
|
5
|
-
`)){if(t>=25)break;const d=
|
|
1
|
+
import{execFileSync as m}from"child_process";import{readFileSync as h,existsSync as g}from"fs";import{join as u}from"path";import{createHash as x}from"crypto";import{runAsWriter as $}from"./handlers/boundary.js";const k=["README.md","CLAUDE.md","AGENTS.md","ARCHITECTURE.md"],y=40,p=o=>x("sha1").update(o).digest("hex").slice(0,8);function S(o){const n=[];for(const i of k){const e=u(o,i);if(!g(e))continue;let s;try{s=h(e,"utf-8")}catch{continue}for(const t of s.split(/\n(?=#{1,4}\s)/)){const c=t.match(/^#{1,4}\s+(.+)/);if(!c)continue;const d=/[\x00-\x1f\x7f]/g,r=c[1].replace(/[#*`]/g,"").replace(d,"").trim(),f=t.slice(c[0].length).split(`
|
|
2
|
+
`).map(a=>a.trim()).filter(a=>a&&!a.startsWith("#")&&!a.startsWith("|")&&!a.startsWith("```")).slice(0,3).join(" ").replace(d,"").replace(/\s+/g," ");if(!r||f.length<24)continue;const l=`${r}: ${f}`.slice(0,600);if(n.push({kind:"lesson",text:l,source:`doc:${i}`,sig:`doc:${i}:${p(l)}`,tags:["harvest","doc",i],confidence:.6}),n.length>=y)return n}}return n}const C=/^(merge|wip|typo|fixup|squash|amend|\.+|chore: lint|format)\b/i;function v(o,n=30){if(!g(u(o,".git")))return[];let i;try{i=m("git",["-C",o,"log",`-n${Math.max(1,Math.min(n,200))}`,"--no-merges","--pretty=%h%x09%s"],{encoding:"utf-8",timeout:5e3})}catch{return[]}const e=[];for(const s of i.split(`
|
|
3
|
+
`)){const t=s.trim();if(!t)continue;const c=t.indexOf(" ");if(c<0)continue;const d=t.slice(0,c),r=t.slice(c+1).trim();r.length<8||C.test(r)||e.push({kind:"pattern",text:r.slice(0,300),source:"git",sig:`git:${d}`,tags:["harvest","git",`sha:${d}`],confidence:.45})}return e}function b(o){const n=[],i=u(o,"package.json");if(g(i))try{const e=JSON.parse(h(i,"utf-8")),s=Object.keys({...e.dependencies,...e.devDependencies}).slice(0,16),t=Object.keys(e.scripts??{}).slice(0,10);if(s.length||t.length){const c=`Stack (package.json): ${s.join(", ")||"\u2014"}${t.length?` \xB7 scripts: ${t.join(", ")}`:""}`.slice(0,600);n.push({kind:"lesson",text:c,source:"code:package.json",sig:`code:pkg:${p(c)}`,tags:["harvest","code","stack"],confidence:.6})}}catch{}for(const e of["Cargo.toml","composer.json","requirements.txt","pyproject.toml","go.mod","wrangler.toml"]){const s=u(o,e);if(g(s))try{const t=h(s,"utf-8").split(`
|
|
4
|
+
`).map(c=>c.trim()).filter(Boolean).slice(0,6).join(" ").replace(/\s+/g," ").slice(0,400);t&&n.push({kind:"lesson",text:`Stack (${e}): ${t}`,source:`code:${e}`,sig:`code:${e}:${p(t)}`,tags:["harvest","code","stack"],confidence:.55})}catch{}}if(g(u(o,".git")))try{const e=m("git",["-C",o,"grep","-nIE","\\b(TODO|FIXME|HACK|XXX)\\b","--","*.ts","*.tsx","*.js","*.py","*.rs","*.go","*.php","*.rb"],{encoding:"utf-8",timeout:6e3,maxBuffer:4194304}),s=/[\x00-\x1f\x7f]/g;let t=0;for(const c of e.split(`
|
|
5
|
+
`)){if(t>=25)break;const d=c.trim();if(!d)continue;const r=d.match(/^([^:]+):(\d+):(.*)$/);if(!r)continue;const f=r[3].trim().replace(s,"").slice(0,200);if(f.length<8)continue;const l=`${r[1]}:${r[2]}: ${f}`;n.push({kind:"anti_pattern",text:l,source:"code:todo",sig:`code:todo:${r[1]}:${r[2]}:${p(f)}`,tags:["harvest","code","todo"],confidence:.4}),t++}}catch{}return n}function A(o,n,i={}){const e=S(n.path),s=v(n.path,i.gitLimit??30),t=i.includeCode?b(n.path):[],c=[...e,...s,...t];let d=0,r=0;const f=[],l=new Set;for(const a of c){if(l.has(a.sig)||o.existsBySig(n.id,a.sig)){r++,i.dryRun||o.onSkip?.(n.id,a);continue}l.add(a.sig),i.dryRun||$("harvest",()=>o.addCandidate(n.id,a)),d++,f.length<5&&f.push(`[${a.source}] ${a.text.slice(0,80)}`)}return{project:n.name,docFacts:e.length,commits:s.length,codeSignals:t.length,added:d,skipped:r,sample:f}}function F(o,n,i={}){const e=n.map(s=>A(o,s,i));return{reports:e,totalAdded:e.reduce((s,t)=>s+t.added,0),totalSkipped:e.reduce((s,t)=>s+t.skipped,0)}}export{b as harvestCodeSignals,S as harvestDocFacts,v as harvestGitCommits,A as harvestProject,F as harvestProjects};
|
package/dist/hours.js
CHANGED
|
@@ -9,5 +9,5 @@ class j{db;constructor(e){this.db=e}report(e){const r=e.default_session_hours??1
|
|
|
9
9
|
`).map(o=>` ${o}`).join(`
|
|
10
10
|
`)),e.business_contact&&s.push(` ${e.business_contact}`),s.push("")),s.push(`**To:** ${e.client_name}`),e.client_address&&s.push(e.client_address.split(`
|
|
11
11
|
`).map(o=>` ${o}`).join(`
|
|
12
|
-
`)),s.push(""),s.push(`**Period:** ${r.range.start} \u2192 ${r.range.end}`),s.push(`**Invoice Date:** ${new Date().toISOString().slice(0,10)}`),s.push(`**Rate:** ${a} ${c.toFixed(2)}/hour`),s.push(""),s.push("## Line Items"),s.push(""),s.push("| Date | Project | Hours | Summary |"),s.push("|------|---------|-------|---------|");for(const o of r.entries){const t=(o.summary??"").replace(/\|/g,"\\|").replace(/\n/g," ").slice(0,80),n=o.is_estimated?" *(est.)*":"";s.push(`| ${o.date} | ${o.project_name} | ${o.hours.toFixed(2)}${n} | ${t} |`)}s.push(""),s.push("## Totals"),s.push(""),s.push("| Project | Sessions | Hours |"),s.push("|---------|----------|-------|");for(const o of r.by_project)s.push(`| ${o.project_name} | ${o.session_count} | ${o.hours.toFixed(2)} |`);return s.push(""),s.push(`**Total Hours:** ${r.total_hours.toFixed(2)}`),s.push(`**Subtotal:** ${a} ${i.toFixed(2)}`),s.push(`**Total Due:** ${a} ${u.toFixed(2)}`),s.push(""),r.estimated_sessions>0&&(s.push(`> \u2139\uFE0F ${r.estimated_sessions} session(s) had no recorded work content
|
|
12
|
+
`)),s.push(""),s.push(`**Period:** ${r.range.start} \u2192 ${r.range.end}`),s.push(`**Invoice Date:** ${new Date().toISOString().slice(0,10)}`),s.push(`**Rate:** ${a} ${c.toFixed(2)}/hour`),s.push(""),s.push("## Line Items"),s.push(""),s.push("| Date | Project | Hours | Summary |"),s.push("|------|---------|-------|---------|");for(const o of r.entries){const t=(o.summary??"").replace(/\|/g,"\\|").replace(/\n/g," ").slice(0,80),n=o.is_estimated?" *(est.)*":"";s.push(`| ${o.date} | ${o.project_name} | ${o.hours.toFixed(2)}${n} | ${t} |`)}s.push(""),s.push("## Totals"),s.push(""),s.push("| Project | Sessions | Hours |"),s.push("|---------|----------|-------|");for(const o of r.by_project)s.push(`| ${o.project_name} | ${o.session_count} | ${o.hours.toFixed(2)} |`);return s.push(""),s.push(`**Total Hours:** ${r.total_hours.toFixed(2)}`),s.push(`**Subtotal:** ${a} ${i.toFixed(2)}`),s.push(`**Total Due:** ${a} ${u.toFixed(2)}`),s.push(""),r.estimated_sessions>0&&(s.push(`> \u2139\uFE0F ${r.estimated_sessions} session(s) had no recorded work content; hours estimated at ${e.default_session_hours??1}h default. Review marked rows.`),s.push("")),e.notes&&(s.push("## Notes"),s.push(""),s.push(e.notes),s.push("")),s.join(`
|
|
13
13
|
`)}}export{j as HourLedger};
|
package/dist/http-auth.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as L,readFileSync as N,writeFileSync as v,mkdirSync as K}from"fs";import{join as k}from"path";import{homedir as b}from"os";import{randomBytes as m,createHash as W,timingSafeEqual as E}from"crypto";import{WyrmLogger as D}from"./logger.js";import{DEFAULT_RATE_LIMIT as F,WindowCounter as x,budgetKey as B,failureLimit as _,isLoopbackAddress as U,normalizeRateLimit as S,requestLimit as X}from"./http-rate-limit.js";import{canonicalOrigin as R,configuredOrigins as z,isCrossSiteRequest as Y,ownOrigins as G}from"./write-guard.js";const p=k(b(),".wyrm"),w=k(p,"http-config.json"),J=new x,M=new x;let u=null;const l=new D;function A(e){return W("sha256").update(e).digest("hex")}function V(e,t){if(e.length!==t.length)return!1;let n=0;for(let o=0;o<e.length;o++)n|=e.charCodeAt(o)^t.charCodeAt(o);return n===0}function $(){if(L(p)||K(p,{recursive:!0,mode:448}),L(w))try{const n=JSON.parse(N(w,"utf-8"));return{apiKeyHash:n.apiKeyHash,allowedOrigins:z(n.allowedOrigins),rateLimit:S(n.rateLimit),requireAuth:n.requireAuth??!0,devMode:process.env.WYRM_DEV==="true"}}catch(n){l.error("Failed to load HTTP config, regenerating",{error:n.message})}const e=m(32).toString("hex");l.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:
|
|
3
|
+
`);const t={apiKeyHash:A(e),allowedOrigins:[],rateLimit:{...F},requireAuth:!0,devMode:process.env.WYRM_DEV==="true"};return f(t),t}function f(e){const{allowedOrigins:t,...n}=e,o=t.length>0?{...n,allowedOrigins:t}:n;v(w,JSON.stringify(o,null,2),{mode:384})}function a(){return u||(u=$()),u}function P(e){return{ownOrigins:G(e.socket.localPort,e.socket.localAddress),allowedOrigins:a().allowedOrigins}}function se(e){return T(e)!==null}function T(e){const t=a(),n=U(e.socket.remoteAddress??""),o=(e.headers.host??"").split(":")[0].toLowerCase(),r=o==="localhost"||o==="127.0.0.1"||o==="::1"||o==="[::1]";if(t.devMode&&n&&r)return{kind:"dev"};const i=e.headers["x-wyrm-nonce"];if(n&&r&&typeof i=="string"&&Z(i))return{kind:"ui"};if(!t.requireAuth)return l.warn("Authentication disabled - API is open"),{kind:"open"};const s=e.headers.authorization;if(!s||!s.startsWith("Bearer "))return null;const c=s.slice(7);if(!c||c.length<32)return null;const d=A(c);return V(d,t.apiKeyHash)?{kind:"token",tokenHash:d}:null}function H(e,t,n,o,r){t.writeHead(429,{...y(e),"X-RateLimit-Limit":String(n.limit),"X-RateLimit-Remaining":"0","X-RateLimit-Reset":String(n.resetAt),"Retry-After":String(Math.max(1,Math.ceil((n.resetAt-r)/1e3)))}),t.end(JSON.stringify({error:o})),l.warn(o,{ip:e.socket.remoteAddress,path:e.url})}function I(e,t,n,o,r){const i=a().rateLimit;if(!i.enabled)return!0;const s=e.socket.remoteAddress??"",c=J.hit(B(n,s)+r,X(i,s),i.windowMs,o);return c.allowed?!0:(H(e,t,c,"Rate limit exceeded",o),!1)}function j(e){const t=e.headers.origin,n=Array.isArray(t)?t[0]:t,o=n===void 0?null:R(n);if(o===null)return;const r=P(e);return[...r.ownOrigins,...r.allowedOrigins].some(i=>R(i)===o)?n:void 0}function y(e){const t=j(e);return{...t?{"Access-Control-Allow-Origin":t}:{},Vary:"Origin","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 Q=["/health","/auth/status","/ui"];function ae(e,t){if(e.method==="OPTIONS"){const g=y(e);return t.writeHead(204,g),t.end(),{error:!0}}const n=Date.now(),o=e.socket.remoteAddress??"",r=Y(e.headers,P(e))?"|cross-site":"",i=new URL(e.url||"/","http://localhost").pathname;if(Q.includes(i))return I(e,t,null,n,r)?{error:!1}:{error:!0};const s=a().rateLimit,c=`failed-auth:${o||"unknown"}${r}`,d=_(s,o),O=M.peek(c,d,s.windowMs,n);if(!O.allowed)return H(e,t,O,"Too many failed authentication attempts",n),{error:!0};const C=T(e);if(!C){M.hit(c,d,s.windowMs,n);const g=y(e);return t.writeHead(401,{...g,"WWW-Authenticate":'Bearer realm="Wyrm API"'}),t.end(JSON.stringify({error:"Unauthorized"})),l.warn("Authentication failed",{ip:e.socket.remoteAddress,path:e.url}),{error:!0}}return I(e,t,C,n,r)?{error:!1}:{error:!0}}function ce(){const e=m(32).toString("hex"),t=a();return t.apiKeyHash=A(e),f(t),u=t,l.info("API key regenerated"),e}function le(e){const t=a();t.allowedOrigins=e,f(t),u=t,l.info("CORS origins updated",{origins:e})}function ue(e,t){const n=a();n.rateLimit=S({...n.rateLimit,enabled:!0,requests:e,windowMs:t}),f(n),u=n,l.info("Rate limit updated",{requests:e,windowMs:t})}function de(e){const t=a();t.requireAuth=e,f(t),u=t,e||l.warn("Authentication disabled - API is now open")}function fe(){const e=a();e.devMode=!0}let h=null;function he(){return h===null&&(h=m(32).toString("hex")),h}function Z(e){if(h===null)return!1;const t=Buffer.from(e,"utf8"),n=Buffer.from(h,"utf8");return t.length!==n.length?!1:E(t,n)}function ge(){const e=a();return{requireAuth:e.requireAuth,devMode:e.devMode,rateLimit:e.rateLimit,allowedOrigins:e.allowedOrigins}}export{ae as authMiddleware,se as authenticate,T as authenticateRequest,fe as enableDevMode,ge as getAuthStatus,j as getCorsOrigin,y as getSecurityHeaders,he as getUiNonce,ce as regenerateApiKey,le as setAllowedOrigins,ue as setRateLimit,de as setRequireAuth,P as siteContextFor};
|