agentix-cli 0.69.4 → 0.70.0

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 (69) hide show
  1. package/dist/{agent-IDAWXA3K.js → agent-V223TG6H.js} +2 -2
  2. package/dist/{board-dashboard-MAP6A2N7.js → board-dashboard-HR3PQPFW.js} +2 -2
  3. package/dist/{board-dashboard-MAP6A2N7.js.map → board-dashboard-HR3PQPFW.js.map} +1 -1
  4. package/dist/{chunk-CKBAVCGU.js → chunk-3IY2YWXP.js} +2 -2
  5. package/dist/{chunk-J7HA7ZTU.js → chunk-3RC4LO2G.js} +2 -2
  6. package/dist/{chunk-M6GPRMCQ.js → chunk-6QDOUOKD.js} +2 -2
  7. package/dist/{chunk-64P4QHSZ.js → chunk-CECHWYJ2.js} +1 -1
  8. package/dist/{chunk-64P4QHSZ.js.map → chunk-CECHWYJ2.js.map} +1 -1
  9. package/dist/{chunk-K63IOY6A.js → chunk-FBDIJEKL.js} +4 -4
  10. package/dist/chunk-FBDIJEKL.js.map +1 -0
  11. package/dist/{chunk-OHRTBD7U.js → chunk-LHLNARRW.js} +9 -9
  12. package/dist/chunk-ML6CNRBK.js +105 -0
  13. package/dist/chunk-ML6CNRBK.js.map +1 -0
  14. package/dist/{chunk-5ONI54E6.js → chunk-PHDS2HIN.js} +118 -118
  15. package/dist/chunk-PHDS2HIN.js.map +1 -0
  16. package/dist/{chunk-2UYV24UD.js → chunk-SN5N2FPG.js} +2 -2
  17. package/dist/chunk-TLDQWTSW.js +49 -0
  18. package/dist/chunk-TLDQWTSW.js.map +1 -0
  19. package/dist/chunk-TTVVXBCM.js +2 -0
  20. package/dist/chunk-TTVVXBCM.js.map +1 -0
  21. package/dist/{chunk-IU6L4V3A.js → chunk-W7SEPJFH.js} +2 -2
  22. package/dist/chunk-WLMQ5JMX.js +6 -0
  23. package/dist/chunk-WLMQ5JMX.js.map +1 -0
  24. package/dist/{chunk-DQQ3BCJW.js → chunk-YK7GFEGO.js} +2 -2
  25. package/dist/cli.js +1 -1
  26. package/dist/cli.js.map +1 -1
  27. package/dist/graph-FJ53FDWK.js +2 -0
  28. package/dist/index.d.ts +20 -4
  29. package/dist/index.js +1 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/migrate-v2-ZOIMGITH.js +4 -0
  32. package/dist/{migrate-v2-KOMPDC6U.js.map → migrate-v2-ZOIMGITH.js.map} +1 -1
  33. package/dist/program-UTELWTXC.js +2 -0
  34. package/dist/promote-B5AOKPQR.js +2 -0
  35. package/dist/query-QLJNQUUP.js +2 -0
  36. package/dist/registry-CTZFLXGB.js +2 -0
  37. package/dist/types-NZOBHQGS.js +2 -0
  38. package/dist/wiki-X76JUHXT.js +2 -0
  39. package/dist/wiki-X76JUHXT.js.map +1 -0
  40. package/package.json +1 -1
  41. package/dist/chunk-4DRNEOOW.js +0 -2
  42. package/dist/chunk-4DRNEOOW.js.map +0 -1
  43. package/dist/chunk-535MLOSB.js +0 -109
  44. package/dist/chunk-535MLOSB.js.map +0 -1
  45. package/dist/chunk-5ONI54E6.js.map +0 -1
  46. package/dist/chunk-K63IOY6A.js.map +0 -1
  47. package/dist/chunk-V2TLFYKH.js +0 -45
  48. package/dist/chunk-V2TLFYKH.js.map +0 -1
  49. package/dist/migrate-v2-KOMPDC6U.js +0 -4
  50. package/dist/program-T275Y2V6.js +0 -2
  51. package/dist/promote-VZAUTV6E.js +0 -2
  52. package/dist/query-7QQZPMXM.js +0 -2
  53. package/dist/registry-WQ65MQEY.js +0 -2
  54. package/dist/types-S6NHM5C3.js +0 -2
  55. package/dist/wiki-J4MMCONT.js +0 -2
  56. /package/dist/{agent-IDAWXA3K.js.map → agent-V223TG6H.js.map} +0 -0
  57. /package/dist/{chunk-CKBAVCGU.js.map → chunk-3IY2YWXP.js.map} +0 -0
  58. /package/dist/{chunk-J7HA7ZTU.js.map → chunk-3RC4LO2G.js.map} +0 -0
  59. /package/dist/{chunk-M6GPRMCQ.js.map → chunk-6QDOUOKD.js.map} +0 -0
  60. /package/dist/{chunk-OHRTBD7U.js.map → chunk-LHLNARRW.js.map} +0 -0
  61. /package/dist/{chunk-2UYV24UD.js.map → chunk-SN5N2FPG.js.map} +0 -0
  62. /package/dist/{chunk-IU6L4V3A.js.map → chunk-W7SEPJFH.js.map} +0 -0
  63. /package/dist/{chunk-DQQ3BCJW.js.map → chunk-YK7GFEGO.js.map} +0 -0
  64. /package/dist/{program-T275Y2V6.js.map → graph-FJ53FDWK.js.map} +0 -0
  65. /package/dist/{promote-VZAUTV6E.js.map → program-UTELWTXC.js.map} +0 -0
  66. /package/dist/{query-7QQZPMXM.js.map → promote-B5AOKPQR.js.map} +0 -0
  67. /package/dist/{registry-WQ65MQEY.js.map → query-QLJNQUUP.js.map} +0 -0
  68. /package/dist/{types-S6NHM5C3.js.map → registry-CTZFLXGB.js.map} +0 -0
  69. /package/dist/{wiki-J4MMCONT.js.map → types-NZOBHQGS.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/graph/starter-schema.ts","../src/graph/store.ts","../src/graph/classifier.ts","../src/decisions/seats/intent-path.ts"],"sourcesContent":["import type { GraphSchema } from \"./types\"\n\n/**\n * Ships with the install. When .agentx/graph/schema.json is missing,\n * the store seeds this so a fresh daemon has something to classify\n * against.\n *\n * v2 — Phase 1 of the classifier-retire plan: verb-level intent\n * taxonomy. The classifier now answers ONLY \"what kind of work is\n * this\" — it does not embed client/project/agent in the leaf because\n * those already live on the event metadata (project, subject,\n * agentId). One copy of \"review.merge-request\" instead of one per\n * client.\n *\n * Two levels:\n * category — broad work area (code/ops/support/admin/knowledge/social/system)\n * verb — specific intent (review.merge-request, deploy.staging, etc.)\n *\n * The category is closed-set; the verb is open-set so the operator\n * can add new ones via `agentx graph review` over time. Most paths\n * will reuse the seeded verbs below.\n *\n * Why so few levels: every level is information the classifier\n * actually has to decide. The previous v1 schema (scope > location >\n * org > unit > activity) re-encoded data already on the event —\n * forcing a 5-LLM-question prompt to recover what we already knew.\n */\nexport const STARTER_SCHEMA: GraphSchema = {\n version: 2,\n levels: [\n {\n id: \"category\",\n description: \"Top-level kind of work. Closed set — pick the closest fit.\",\n axes: [\n {\n name: \"kind\",\n type: \"enum\",\n values: [\n \"code\", // anything code-related: review, fix, implement, refactor\n \"ops\", // infra: deploy, monitor, rollback, scale, incident\n \"support\", // human-facing: answer question, triage, route\n \"admin\", // governance: config change, scheduling, token, role\n \"knowledge\", // wiki, docs, research, summarize\n \"social\", // marketing, comms, brief, content\n \"system\", // internal infrastructure (rare; mostly classifier itself)\n ],\n description: \"Which category this work belongs in.\",\n },\n ],\n },\n {\n id: \"verb\",\n description: \"Specific intent — verb-level only, NOT client/project-bound.\",\n axes: [\n {\n name: \"name\",\n type: \"free\",\n description:\n \"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, \" +\n \"investigate.error, deploy.staging, deploy.production, rollback.release, \" +\n \"spec.feature, triage.issue, document.feature, chat.greeting, \" +\n \"chat.support-request, schedule.add, config.change, ingest.contact, \" +\n \"transcribe.audio, brief.daily, summarize.thread, plan.work.\",\n },\n ],\n },\n ],\n leafInput: {\n name: \"input\",\n type: \"enum\",\n values: [\n \"telegram\",\n \"whatsapp\",\n \"slack\",\n \"discord\",\n \"gitlab-mr\",\n \"gitlab-issue\",\n \"github-issue\",\n \"github-pr\",\n \"cron\",\n \"webhook\",\n \"mesh\",\n \"http\",\n ],\n description: \"Where the message entered the system.\",\n },\n leafOutput: {\n name: \"output\",\n type: \"free\",\n description:\n \"One short sentence describing what the agent is expected to produce. \" +\n \"Generic — refers to the verb's outcome, not the specific client/project \" +\n \"(those are on the event metadata already).\",\n },\n}\n\n/** Pre-seeded verbs shipped with v2. The classifier picks from these\n * first; new verbs go through the review queue before they're\n * committed (so the taxonomy doesn't drift). Adding to this list is\n * cheap — the seed runs once when nodes.json is empty. */\nexport const STARTER_VERB_NODES: Array<{\n category: \"code\" | \"ops\" | \"support\" | \"admin\" | \"knowledge\" | \"social\" | \"system\"\n verb: string\n description?: string\n}> = [\n // code\n { category: \"code\", verb: \"review.merge-request\", description: \"Review a GitLab MR or GitHub PR\" },\n { category: \"code\", verb: \"review.code-change\", description: \"General code review (snippet, diff)\" },\n { category: \"code\", verb: \"fix.bug\", description: \"Fix a reported bug\" },\n { category: \"code\", verb: \"implement.feature\", description: \"Build a new feature\" },\n { category: \"code\", verb: \"investigate.error\", description: \"Investigate a failing test, exception, pipeline\" },\n { category: \"code\", verb: \"refactor.code\", description: \"Restructure existing code without behaviour change\" },\n { category: \"code\", verb: \"spec.feature\", description: \"Write or update a feature spec / RFC\" },\n { category: \"code\", verb: \"triage.issue\", description: \"Classify, label, or assign an issue\" },\n { category: \"code\", verb: \"answer.code-question\", description: \"Answer a code-related question\" },\n\n // ops\n { category: \"ops\", verb: \"deploy.staging\", description: \"Deploy to a staging/preview environment\" },\n { category: \"ops\", verb: \"deploy.production\", description: \"Deploy to production\" },\n { category: \"ops\", verb: \"rollback.release\", description: \"Roll back a release\" },\n { category: \"ops\", verb: \"investigate.incident\", description: \"Investigate a production incident\" },\n { category: \"ops\", verb: \"monitor.system\", description: \"Check system status / metrics\" },\n { category: \"ops\", verb: \"rotate.credential\", description: \"Rotate a token, key, certificate\" },\n { category: \"ops\", verb: \"audit.security\", description: \"Review for security issues\" },\n\n // support\n { category: \"support\", verb: \"chat.greeting\", description: \"A casual hello, no actionable request\" },\n { category: \"support\", verb: \"chat.support-request\", description: \"End-user / customer asking for help\" },\n { category: \"support\", verb: \"chat.casual\", description: \"Informal back-and-forth chat\" },\n { category: \"support\", verb: \"answer.question\", description: \"Answer an open-ended question\" },\n { category: \"support\", verb: \"route.request\", description: \"Forward a request to the right agent\" },\n\n // admin\n { category: \"admin\", verb: \"config.change\", description: \"Change agentx.json / environment / config\" },\n { category: \"admin\", verb: \"schedule.add\", description: \"Add a new cron / scheduled task\" },\n { category: \"admin\", verb: \"schedule.remove\", description: \"Remove or disable a schedule\" },\n { category: \"admin\", verb: \"token.create\", description: \"Mint a scoped API token\" },\n { category: \"admin\", verb: \"agent.add\", description: \"Configure a new agent\" },\n { category: \"admin\", verb: \"channel.add\", description: \"Wire up a new channel\" },\n\n // knowledge\n { category: \"knowledge\", verb: \"document.feature\", description: \"Write or update documentation\" },\n { category: \"knowledge\", verb: \"summarize.thread\", description: \"Summarize a long conversation/thread\" },\n { category: \"knowledge\", verb: \"research.topic\", description: \"Investigate / gather info on a topic\" },\n { category: \"knowledge\", verb: \"wiki.absorb\", description: \"Roll raw entries into typed wiki articles\" },\n\n // social\n { category: \"social\", verb: \"brief.daily\", description: \"Compose a daily brief / digest\" },\n { category: \"social\", verb: \"draft.post\", description: \"Draft a marketing post / announcement\" },\n { category: \"social\", verb: \"report.weekly\", description: \"Compose a weekly status report\" },\n\n // system (the classifier itself; rare to expose)\n { category: \"system\", verb: \"classify.intent\", description: \"Internal: graph-classifier sub-call\" },\n]\n\n","import {\n readFileSync,\n writeFileSync,\n appendFileSync,\n existsSync,\n mkdirSync,\n renameSync,\n statSync,\n} from \"fs\"\nimport { resolve, dirname } from \"path\"\nimport { createHash } from \"crypto\"\nimport {\n graphSchemaSchema,\n nodesFileSchema,\n indexFileSchema,\n fingerprintEntrySchema,\n classificationSchema,\n type GraphSchema,\n type GraphNode,\n type NodesFile,\n type IndexFile,\n type Classification,\n type FingerprintEntry,\n} from \"./types\"\nimport { STARTER_SCHEMA, STARTER_VERB_NODES } from \"./starter-schema\"\n\n// --- Intent Knowledge Graph filesystem store ---\n//\n// Mirrors the .agentx/wiki pattern: plain JSON on disk, one directory, no DB.\n// Not thread-safe across processes — writes go through safeWrite (tmp + rename).\n\nexport interface GraphStoreOptions {\n baseDir?: string\n log?: (...args: unknown[]) => void\n}\n\nexport class GraphStore {\n readonly baseDir: string\n private log: (...args: unknown[]) => void\n /** Parsed nodes/index files, keyed by path. The classifier reads both\n * several times per message and they grow without bound, so re-reading\n * and re-validating them each time blocked the daemon for seconds. */\n private parsed = new Map<string, { mtimeMs: number; size: number; data: unknown }>()\n\n constructor(opts: GraphStoreOptions = {}) {\n this.baseDir = opts.baseDir ?? resolve(process.cwd(), \".agentx/graph\")\n this.log = opts.log ?? console.error.bind(console, \"[graph]\")\n mkdirSync(this.baseDir, { recursive: true })\n }\n\n // --- Paths ---\n\n schemaPath(): string { return resolve(this.baseDir, \"schema.json\") }\n nodesPath(): string { return resolve(this.baseDir, \"nodes.json\") }\n indexPath(): string { return resolve(this.baseDir, \"index.json\") }\n classificationsPath(): string { return resolve(this.baseDir, \"classifications.jsonl\") }\n\n // --- Schema ---\n\n /** Load the graph schema, seeding STARTER_SCHEMA if the file is missing.\n * Phase 1 of the classifier-retire plan ships a v2 verb-level schema;\n * installs with the v1 hierarchical schema (scope > org > unit >\n * activity) get auto-migrated on next load: the v1 file is renamed\n * to schema.v1.bak.json, the v2 starter is written, and the\n * per-event nodes.json + classifications.jsonl are left alone for\n * historical reading. New events classify under v2 from then on. */\n loadSchema(): GraphSchema {\n const p = this.schemaPath()\n if (!existsSync(p)) {\n this.saveSchema(STARTER_SCHEMA)\n this.log(\"seeded starter schema at\", p)\n return STARTER_SCHEMA\n }\n const raw = readFileSync(p, \"utf-8\")\n const parsed = graphSchemaSchema.safeParse(JSON.parse(raw))\n if (!parsed.success) {\n throw new Error(`Invalid ${p}: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n if ((parsed.data.version ?? 1) < (STARTER_SCHEMA.version ?? 2)) {\n const backup = p.replace(/\\.json$/, `.v${parsed.data.version ?? 1}.bak.json`)\n try {\n writeFileSync(backup, raw)\n this.log(`migrated graph schema v${parsed.data.version ?? 1} -> v${STARTER_SCHEMA.version}; old saved at ${backup}`)\n } catch (e: any) {\n this.log(`could not back up old schema: ${e?.message ?? e}`)\n }\n this.saveSchema(STARTER_SCHEMA)\n // Same treatment for nodes.json — the v1 nodes were parented under\n // levels that don't exist in v2 and would fail validation. Archive\n // them and re-seed.\n const np = this.nodesPath()\n if (existsSync(np)) {\n const nbackup = np.replace(/\\.json$/, `.v1.bak.json`)\n try {\n writeFileSync(nbackup, readFileSync(np, \"utf-8\"))\n this.log(`archived v1 nodes.json -> ${nbackup}`)\n } catch (e: any) {\n this.log(`could not back up old nodes: ${e?.message ?? e}`)\n }\n try {\n const seeded = seedNodesFile()\n this.saveNodes(seeded)\n } catch (e: any) {\n this.log(`could not seed v2 nodes: ${e?.message ?? e}`)\n }\n }\n return STARTER_SCHEMA\n }\n return parsed.data\n }\n\n saveSchema(schema: GraphSchema): void {\n const parsed = graphSchemaSchema.safeParse(schema)\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid schema: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n safeWriteJson(this.schemaPath(), parsed.data)\n }\n\n // --- Nodes ---\n\n loadNodes(): NodesFile {\n const p = this.nodesPath()\n if (!existsSync(p)) {\n // Seed the verb-level taxonomy on first load. Lets the classifier\n // pick from a stable set of common verbs out of the box, instead of\n // inventing slightly-different variants for every event.\n const seeded = seedNodesFile()\n this.saveNodes(seeded)\n return seeded\n }\n // A copy, so callers can edit it before saveNodes without touching the cache.\n return structuredClone(this.readParsed(p, (raw) => {\n const parsed = nodesFileSchema.safeParse(JSON.parse(raw))\n if (!parsed.success) {\n throw new Error(`Invalid ${p}: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n return parsed.data\n }))\n }\n\n saveNodes(file: NodesFile): void {\n const parsed = nodesFileSchema.safeParse(file)\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid nodes file: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n this.writeParsed(this.nodesPath(), parsed.data)\n }\n\n /** The parsed file, re-read only when its mtime or size changes, so a\n * write from another process (CLI, dashboard) is still picked up. */\n private readParsed<T>(p: string, parse: (raw: string) => T): T {\n const { mtimeMs, size } = statSync(p)\n const hit = this.parsed.get(p)\n if (hit && hit.mtimeMs === mtimeMs && hit.size === size) return hit.data as T\n const data = parse(readFileSync(p, \"utf-8\"))\n this.parsed.set(p, { mtimeMs, size, data })\n return data\n }\n\n /** Write the file and keep the parsed copy, so the writer never re-reads\n * what it just wrote. `data` must not be mutated afterwards. */\n private writeParsed(p: string, data: unknown): void {\n this.parsed.delete(p)\n safeWriteJson(p, data)\n const { mtimeMs, size } = statSync(p)\n this.parsed.set(p, { mtimeMs, size, data })\n }\n\n /** Validate a node against the schema AND uniqueness within the nodes file. */\n validateNode(node: GraphNode, schema: GraphSchema, all: GraphNode[]): void {\n const level = schema.levels.find((l) => l.id === node.level)\n if (!level) throw new Error(`Unknown level: \"${node.level}\"`)\n\n const missing = level.axes\n .filter((a) => !a.optional)\n .filter((a) => !(a.name in node.axes))\n if (missing.length) {\n throw new Error(\n `Node ${node.id} (${node.level}) missing required axes: ${missing.map((a) => a.name).join(\", \")}`,\n )\n }\n\n for (const a of level.axes) {\n const v = node.axes[a.name]\n if (v == null) continue\n if (a.type === \"enum\" && a.values && !a.values.includes(v)) {\n throw new Error(`Node ${node.id}: axis ${a.name}=\"${v}\" not in enum ${JSON.stringify(a.values)}`)\n }\n if (a.type === \"ref\") {\n if (!all.some((n) => n.id === v)) {\n throw new Error(`Node ${node.id}: axis ${a.name}=\"${v}\" refers to unknown node`)\n }\n if (a.refLevel) {\n const target = all.find((n) => n.id === v)!\n if (target.level !== a.refLevel) {\n throw new Error(\n `Node ${node.id}: axis ${a.name} expected level \"${a.refLevel}\" but got \"${target.level}\"`,\n )\n }\n }\n }\n }\n\n const levelIdx = schema.levels.findIndex((l) => l.id === node.level)\n if (levelIdx === 0) {\n if (node.parentId !== null) {\n throw new Error(`Root-level node ${node.id} must have parentId: null`)\n }\n } else {\n if (!node.parentId) {\n throw new Error(`Non-root node ${node.id} must have a parentId`)\n }\n const parent = all.find((n) => n.id === node.parentId)\n if (!parent) throw new Error(`Node ${node.id}: parent \"${node.parentId}\" not found`)\n const parentLevelIdx = schema.levels.findIndex((l) => l.id === parent.level)\n // Parent may be at any higher level — intermediate levels can be skipped\n // for orgs that don't use them (e.g. remote-first: no location node).\n if (parentLevelIdx < 0 || parentLevelIdx >= levelIdx) {\n throw new Error(\n `Node ${node.id} at level \"${node.level}\" has parent at level \"${parent.level}\" — parent must be at a higher level`,\n )\n }\n }\n }\n\n /**\n * Commit a full classification path: for each path element that doesn't\n * yet exist, infer its schema level by matching the proposed axes against\n * the level's required axes (first match ≥ cursor wins — this handles\n * paths that skip levels, like business → acme where location is\n * absent). Refreshes the node list between adds so callers in a loop\n * don't hit \"Node id already exists\" on the second iteration.\n */\n commitNodesAlongPath(\n path: string[],\n proposedAxes: Record<string, Record<string, string>>,\n schema: GraphSchema,\n createdBy?: string,\n ): { added: GraphNode[]; skipped: string[] } {\n const added: GraphNode[] = []\n const skipped: string[] = []\n const now = new Date().toISOString()\n\n for (let i = 0; i < path.length; i++) {\n const id = path[i]\n // Load fresh each iteration — cheap O(N) file read, avoids stale snapshots.\n const current = this.loadNodes().nodes\n const existing = current.find((n) => n.id === id)\n if (existing) {\n skipped.push(id)\n continue\n }\n\n // Cursor: how deep the schema-levels we've already filled up to.\n let cursor = 0\n // Scan backwards through path[0..i-1] to find the deepest already-committed\n // or just-added node and resume after its level.\n for (let j = i - 1; j >= 0; j--) {\n const priorId = path[j]\n const priorNode = current.find((n) => n.id === priorId) || added.find((n) => n.id === priorId)\n if (priorNode) {\n cursor = schema.levels.findIndex((l) => l.id === priorNode.level) + 1\n break\n }\n }\n\n const axes = proposedAxes[id] ?? {}\n // Find the first level at or after the cursor whose required axes are\n // satisfied by proposedAxes[id]. \"Required\" here = listed in level.axes;\n // we accept a partial match (axes present in proposal, even if some\n // schema axes are missing — validateNode will catch truly broken ones).\n let chosenLevel: string | null = null\n for (let j = cursor; j < schema.levels.length; j++) {\n const level = schema.levels[j]\n const missing = level.axes\n .filter((a) => !a.optional)\n .filter((a) => !(a.name in axes))\n if (missing.length === 0) {\n chosenLevel = level.id\n cursor = j + 1\n break\n }\n }\n // Fallback: if no level's axes fully match, try the NEXT level after\n // the cursor anyway. Better to commit at the likely level and let\n // validateNode reject on write than to silently drop the whole path.\n if (!chosenLevel && cursor < schema.levels.length) {\n chosenLevel = schema.levels[cursor].id\n cursor++\n }\n if (!chosenLevel) {\n throw new Error(`Can't place node ${id} — no schema level left (cursor=${cursor}, levels=${schema.levels.length})`)\n }\n\n // Parent is the DEEPEST already-placed node at a level < chosenLevel.\n let parentId: string | null = null\n const chosenIdx = schema.levels.findIndex((l) => l.id === chosenLevel)\n for (let j = i - 1; j >= 0; j--) {\n const priorId = path[j]\n const prior = current.find((n) => n.id === priorId) || added.find((n) => n.id === priorId)\n if (!prior) continue\n const priorIdx = schema.levels.findIndex((l) => l.id === prior.level)\n if (priorIdx >= 0 && priorIdx < chosenIdx) { parentId = priorId; break }\n }\n\n const node: GraphNode = {\n id, level: chosenLevel, parentId, axes,\n createdAt: now,\n createdBy,\n }\n this.addNode(node) // throws if schema validation fails\n added.push(node)\n }\n\n return { added, skipped }\n }\n\n /** Append a node. Caller is responsible for id uniqueness; we check and throw. */\n addNode(node: GraphNode): GraphNode {\n const schema = this.loadSchema()\n const file = this.loadNodes()\n if (file.nodes.some((n) => n.id === node.id)) {\n throw new Error(`Node id already exists: ${node.id}`)\n }\n this.validateNode(node, schema, [...file.nodes, node])\n file.nodes.push(node)\n this.saveNodes(file)\n return node\n }\n\n // --- Classifications (append-only log) ---\n\n appendClassification(c: Classification): boolean {\n const parsed = classificationSchema.safeParse(c)\n if (!parsed.success) {\n // Before the fix this threw and took down the whole task. Now we drop\n // the bad classification and log enough detail (the offending path +\n // axes keys) for the operator to diagnose the prompt. Returning false\n // lets callers decide whether to retry with a cleaner proposal.\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`).join(\" · \")\n this.log(\n `skipped invalid classification — ${issues} ·`,\n `path=${JSON.stringify(c.path)}`,\n `axes-keys=${JSON.stringify(Object.keys(c.proposedAxes || {}))}`,\n )\n return false\n }\n const line = JSON.stringify(parsed.data) + \"\\n\"\n const p = this.classificationsPath()\n if (!existsSync(p)) {\n mkdirSync(dirname(p), { recursive: true })\n }\n appendFileSync(p, line)\n return true\n }\n\n /**\n * All pending classifications, newest first. Collapses the append-only\n * ledger by msgHash — the most recent entry per message wins (so if an\n * entry was updated via updateClassificationStatus, we see the current\n * state, not the original).\n */\n listPendingClassifications(limit = 200): Classification[] {\n return this.listByStatus(\"pending\", limit)\n }\n\n /** Every classification, newest-first, collapsed one row per message.\n * The human-label queue needs the whole population rather than one\n * status, because the rows worth checking are the auto-approved ones. */\n listAllClassifications(): Classification[] {\n return this.readAllCollapsed()\n }\n\n /** Same contract as listPendingClassifications but filtered to any status. */\n listByStatus(status: Classification[\"status\"], limit = 200): Classification[] {\n const all = this.readAllCollapsed()\n return all.filter((c) => c.status === status).slice(0, limit)\n }\n\n /**\n * Read every classification, collapse by msgHash keeping the newest entry\n * per message. Used by listByStatus + the review loop to see current state.\n * Returns newest-first order.\n */\n private readAllCollapsed(): Classification[] {\n const p = this.classificationsPath()\n if (!existsSync(p)) return []\n const raw = readFileSync(p, \"utf-8\")\n const lines = raw.split(\"\\n\").filter(Boolean)\n const byHash = new Map<string, Classification>()\n for (const line of lines) {\n try {\n const parsed = classificationSchema.safeParse(JSON.parse(line))\n if (!parsed.success) continue\n // Later entries overwrite earlier ones for the same msgHash.\n byHash.set(parsed.data.msgHash, parsed.data)\n } catch {\n // skip malformed\n }\n }\n // Newest-first — sort by ts desc.\n return Array.from(byHash.values()).sort((a, b) => (a.ts < b.ts ? 1 : -1))\n }\n\n /**\n * Append a status-update entry for an existing classification. The ledger\n * stays append-only; readers collapse by msgHash. Returns the updated\n * classification, or null if no prior entry for msgHash exists.\n */\n updateClassificationStatus(\n msgHash: string,\n update: {\n status: Classification[\"status\"]\n reviewer?: string\n reviewReason?: string\n },\n ): Classification | null {\n const all = this.readAllCollapsed()\n const prior = all.find((c) => c.msgHash === msgHash)\n if (!prior) return null\n const next: Classification = {\n ...prior,\n status: update.status,\n ts: new Date().toISOString(),\n }\n // Store reviewer + reason in the preview field so readers can see who\n // flipped what without needing a separate review-log file. Non-breaking:\n // preview is already optional text meant for UI display.\n if (update.reviewer || update.reviewReason) {\n const suffix = ` · ${update.reviewer ? `reviewer=${update.reviewer}` : \"\"}${update.reviewReason ? ` · ${update.reviewReason}` : \"\"}`\n next.preview = ((prior.preview || \"\") + suffix).slice(0, 500)\n }\n this.appendClassification(next)\n return next\n }\n\n /** Read the last N classifications, newest first. Cheap enough for the admin UI. */\n readRecentClassifications(limit = 50): Classification[] {\n const p = this.classificationsPath()\n if (!existsSync(p)) return []\n const raw = readFileSync(p, \"utf-8\")\n const lines = raw.split(\"\\n\").filter(Boolean)\n const out: Classification[] = []\n for (let i = lines.length - 1; i >= 0 && out.length < limit; i--) {\n try {\n const parsed = classificationSchema.safeParse(JSON.parse(lines[i]))\n if (parsed.success) out.push(parsed.data)\n } catch {\n // skip malformed line — append-only log; don't fail reads on one bad line\n }\n }\n return out\n }\n\n /**\n * The path the most recent classification in a conversation was filed\n * under, or undefined. Used by tools running inside that conversation\n * (`agentx_wiki_query`) to bias retrieval toward articles on the same\n * branch. Bounded by age so a stale label from a day-old thread does not\n * steer an unrelated question.\n */\n latestPathForChat(agentId: string, channel: string, chatId: string, maxAgeMs = 6 * 60 * 60 * 1000): string[] | undefined {\n const since = Date.now() - maxAgeMs\n for (const c of this.readRecentClassifications(300)) {\n if (c.agentId !== agentId || c.channel !== channel || c.chatId !== chatId) continue\n if (Date.parse(c.ts) < since) return undefined\n return c.path.length ? c.path.slice() : undefined\n }\n return undefined\n }\n\n // --- Fingerprint index ---\n\n /**\n * Hash that represents \"this kind of message\" for snap-to-path caching.\n *\n * Keyed on the normalized wording plus the channel. The sender used to be\n * part of the key too, and the exact text was: measured over the last\n * 2,000 classifications, 89% missed, so nearly every task paid an LLM\n * call for a taxonomy label. Identifiers, numbers, links and mentions\n * are what differ between two messages that mean the same thing, so\n * they are folded away (see `normalizeForFingerprint`).\n */\n fingerprint(msg: {\n text: string\n channel?: string\n sender?: string\n }): string {\n const norm = normalizeForFingerprint(msg.text)\n const payload = [norm, msg.channel ?? \"\"].join(\"\\u0001\")\n return createHash(\"sha256\").update(payload).digest(\"hex\").slice(0, 32)\n }\n\n loadIndex(): IndexFile {\n return structuredClone(this.index())\n }\n\n saveIndex(file: IndexFile): void {\n const parsed = indexFileSchema.safeParse(file)\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid index: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n this.writeParsed(this.indexPath(), parsed.data)\n }\n\n /** The cached index. Read-only: copy before handing it to a caller. */\n private index(): IndexFile {\n const p = this.indexPath()\n if (!existsSync(p)) return { version: 1, entries: {} }\n return this.readParsed(p, (raw) => {\n const parsed = indexFileSchema.safeParse(JSON.parse(raw))\n if (!parsed.success) {\n throw new Error(`Invalid ${p}: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n return parsed.data\n })\n }\n\n getFingerprint(fp: string): FingerprintEntry | undefined {\n const entry = this.index().entries[fp]\n return entry && structuredClone(entry)\n }\n\n setFingerprint(fp: string, entry: Omit<FingerprintEntry, \"fingerprint\" | \"updatedAt\">): void {\n // Validate only the new entry: the rest of the index was validated when\n // it was read, and re-checking thousands of entries per message is the\n // cost this cache exists to avoid.\n const parsed = fingerprintEntrySchema.safeParse({\n ...entry,\n fingerprint: fp,\n updatedAt: new Date().toISOString(),\n })\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid index entry: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n const current = this.index()\n this.writeParsed(this.indexPath(), { ...current, entries: { ...current.entries, [fp]: parsed.data } })\n }\n\n /** Update the axes on an existing node. Schema-validates the result. */\n updateNodeAxes(id: string, axes: Record<string, string>): GraphNode {\n const schema = this.loadSchema()\n const file = this.loadNodes()\n const node = file.nodes.find((n) => n.id === id)\n if (!node) throw new Error(`Node not found: ${id}`)\n const updated: GraphNode = { ...node, axes }\n this.validateNode(updated, schema, file.nodes.map((n) => (n.id === id ? updated : n)))\n file.nodes = file.nodes.map((n) => (n.id === id ? updated : n))\n this.saveNodes(file)\n return updated\n }\n\n /** Remove a node. Refuses to delete if any other node still references it\n * (child via parentId, or axis ref). */\n deleteNode(id: string): void {\n const file = this.loadNodes()\n const child = file.nodes.find((n) => n.parentId === id)\n if (child) {\n throw new Error(`Cannot delete \"${id}\" — child node \"${child.id}\" still points to it`)\n }\n const refHolder = file.nodes.find((n) =>\n Object.values(n.axes).some((v) => v === id),\n )\n if (refHolder) {\n throw new Error(`Cannot delete \"${id}\" — node \"${refHolder.id}\" references it via an axis`)\n }\n file.nodes = file.nodes.filter((n) => n.id !== id)\n this.saveNodes(file)\n }\n\n /** Approve a pending classification. Commits any new nodes along its path,\n * stamps the fingerprint index so future similar messages hit the cache,\n * and appends an audit entry. Idempotent — no-op if already cached. */\n approveClassification(c: Classification): void {\n if (this.getFingerprint(c.msgHash)) return // already approved\n // Commit any path nodes the LLM proposed but hadn't been confirmed yet.\n const schema = this.loadSchema()\n const known = new Set(this.loadNodes().nodes.map((n) => n.id))\n for (let i = 0; i < c.path.length; i++) {\n const id = c.path[i]\n if (known.has(id)) continue\n const level = schema.levels[i]?.id\n if (!level) break\n const axes = c.proposedAxes?.[id] ?? {}\n const parentId = i === 0 ? null : c.path[i - 1]\n this.addNode({\n id,\n level,\n parentId,\n axes,\n createdAt: new Date().toISOString(),\n createdBy: c.agentId,\n })\n known.add(id)\n }\n this.setFingerprint(c.msgHash, { path: c.path, leaf: c.leaf })\n this.appendClassification({ ...c, status: \"approved\", ts: new Date().toISOString() })\n }\n\n /** Record rejection — no nodes committed, no fingerprint set. */\n rejectClassification(c: Classification): void {\n this.appendClassification({ ...c, status: \"rejected\", ts: new Date().toISOString() })\n }\n}\n\n/** Atomic write: tmp file + rename. Avoids half-written JSON on crashes. */\nfunction safeWriteJson(path: string, data: unknown): void {\n mkdirSync(dirname(path), { recursive: true })\n const tmp = `${path}.tmp-${process.pid}`\n writeFileSync(tmp, JSON.stringify(data, null, 2))\n renameSync(tmp, path)\n}\n\n/** Build the seed nodes.json — one category node per enum value, plus\n * one verb node per STARTER_VERB_NODES entry parented under its\n * category. Idempotent shape: same input always produces the same\n * file, so re-seeding is safe. */\nfunction seedNodesFile(): NodesFile {\n const now = new Date().toISOString()\n const nodes: NodesFile[\"nodes\"] = []\n const seenCats = new Set<string>()\n for (const v of STARTER_VERB_NODES) {\n if (!seenCats.has(v.category)) {\n nodes.push({\n id: v.category,\n level: \"category\",\n parentId: null,\n axes: { kind: v.category },\n createdAt: now,\n createdBy: \"starter\",\n })\n seenCats.add(v.category)\n }\n }\n for (const v of STARTER_VERB_NODES) {\n nodes.push({\n id: v.verb,\n level: \"verb\",\n parentId: v.category,\n axes: { name: v.verb, ...(v.description ? { description: v.description } : {}) },\n createdAt: now,\n createdBy: \"starter\",\n })\n }\n return { version: 1, nodes }\n}\n\n/**\n * Fold a message down to the words that carry its intent. Links, mentions,\n * issue or MR references, hashes and numbers vary between two requests of\n * the same kind, so they collapse to placeholders; punctuation and case go.\n * Long webhook bodies are cut so a note on the same thread hashes alike.\n */\nexport function normalizeForFingerprint(text: string): string {\n return text\n .toLowerCase()\n .replace(/https?:\\/\\/\\S+/g, \" url \")\n .replace(/[@#][\\w.\\/-]+/g, \" ref \")\n .replace(/\\b[0-9a-f]{7,}\\b/g, \" id \")\n .replace(/\\d+/g, \" num \")\n .replace(/[^\\p{L}\\p{N}\\s]+/gu, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n .slice(0, 400)\n}\n","import { createHash } from \"crypto\"\nimport { extractJson } from \"@/utils/extract-json\"\nimport { GraphStore } from \"./store\"\nimport {\n type GraphSchema,\n type GraphNode,\n type Classification,\n} from \"./types\"\nimport { createProvider, type ProviderName, type AgentProvider } from \"@/agent/providers\"\nimport { getSeatMode } from \"@/decisions/seat\"\nimport { INTENT_PATH_SEAT, proposePathViaSeat } from \"@/decisions/seats/intent-path\"\n\n/** A path for a cache miss, with where it came from. */\ninterface Proposal {\n path: string[]\n proposedAxes: Record<string, Record<string, string>>\n confidence?: number\n leaf: { input?: string; output?: string }\n source: \"llm\" | \"seat\"\n}\n\n// --- Intent Knowledge Graph classifier ---\n//\n// For every incoming message, returns a path through the taxonomy:\n// 1. fingerprint → cache hit → reuse path (0 LLM calls, source=cache)\n// 2. miss → LLM draftAgent proposes { path, axes } → validated → pending\n// 3. autoApproveConfidence met → committed + cached\n//\n// Pending paths are still usable immediately (caller tags artifacts with\n// them); approval flips the flag and populates the fingerprint index so\n// the next similar message hits the cache.\n\nexport interface ClassifyInput {\n text: string\n channel?: string\n sender?: string\n /** Conversation id; stored on the classification so `agentx_wiki_query`\n * can look up the path of the request it is serving. */\n chatId?: string\n /** Agent that will RECEIVE the message after classification. When this\n * equals `draftAgent`, classification is skipped to prevent a deadlock —\n * the classifier's sub-task would otherwise queue behind the in-progress\n * main task on the same agent. */\n agentId?: string\n}\n\nexport interface ClassifyResult {\n /** Node ids from root to leaf. Shorter-than-full paths are allowed. */\n path: string[]\n /** Stable hash of the path — used as the `graph:<pathId>` wiki tag. */\n pathId: string\n /** Human-readable \"A › B › C\" path for logs + UI. */\n pathLabel: string\n /** Axis values asserted by the classifier. Keyed by node id. */\n axes: Record<string, Record<string, string>>\n leaf: { input?: string; output?: string }\n source: \"cache\" | \"llm\" | \"seat\"\n status: \"pending\" | \"approved\"\n confidence?: number\n}\n\nexport type ApprovalStructurePolicy = \"strict\" | \"extend-leaves\" | \"any\"\n\nexport interface ClassifierOptions {\n store: GraphStore\n /** Base URL of the daemon — used to POST /task for LLM proposals. */\n daemonUrl: string\n /** Optional bearer token for the daemon. */\n token?: string\n /** Which agent makes the proposal. Required if caller ever expects a\n * non-cached classification (cache hits work without an agent). */\n draftAgent?: string\n /** Structural approval policy — see config.ts for semantics. Default\n * \"extend-leaves\" (auto-approve pure reuse + single-leaf additions). */\n autoApproveStructure?: ApprovalStructurePolicy\n /** Minimum confidence (0..1) to commit without human approval. 1.0 = never.\n * OR'd with the structural policy. */\n autoApproveConfidence?: number\n /** Provider used for the in-process classifier call. Defaults to\n * \"claude-code\", which auto-detects auth via `loadAuthConfig()` —\n * Claude Max OAuth (CLI subprocess), API key, etc. NEVER opens a\n * paid path beyond what the operator has already configured. */\n classifierProvider?: ProviderName\n /** Model id passed to the provider. For claude-code OAuth this is\n * mapped through `CLI_MODEL_ALIASES` (sonnet/opus/haiku). Cheap\n * default — classification is metadata, not work. */\n classifierModel?: string\n log?: (...args: unknown[]) => void\n}\n\nexport class Classifier {\n private store: GraphStore\n private daemonUrl: string\n private token?: string\n private draftAgent?: string\n private autoApprove: number\n private autoApproveStructure: ApprovalStructurePolicy\n private classifierModel: string\n private classifierProviderName: ProviderName\n private providerInstance?: AgentProvider\n private log: (...args: unknown[]) => void\n\n constructor(opts: ClassifierOptions) {\n this.store = opts.store\n this.daemonUrl = opts.daemonUrl.replace(/\\/+$/, \"\")\n this.token = opts.token\n this.draftAgent = opts.draftAgent\n this.autoApprove = opts.autoApproveConfidence ?? 1.0\n this.autoApproveStructure = opts.autoApproveStructure ?? \"extend-leaves\"\n this.classifierModel = opts.classifierModel ?? \"claude-haiku-4-5-20251001\"\n this.classifierProviderName = opts.classifierProvider ?? \"claude-code\"\n this.log = opts.log ?? console.error.bind(console, \"[classifier]\")\n }\n\n /** Lazily build the provider instance. Lazy because constructing\n * ClaudeCodeProvider hits auth-store; deferring it keeps the\n * classifier importable in tests / dry-run paths that never call\n * classify(). */\n private getProvider(): AgentProvider {\n if (!this.providerInstance) {\n this.providerInstance = createProvider(this.classifierProviderName)\n }\n return this.providerInstance\n }\n\n async classify(input: ClassifyInput): Promise<ClassifyResult | null> {\n const fp = this.store.fingerprint({\n text: input.text,\n channel: input.channel,\n sender: input.sender,\n })\n\n // 1. Cache hit — instant return, zero LLM. Logged to classifications.jsonl\n // so the cache-hit rate is measurable from the persisted record alone\n // (otherwise hits leave no trace and ROI of the classifier is invisible).\n const cached = this.store.getFingerprint(fp)\n if (cached) {\n this.store.appendClassification({\n ts: new Date().toISOString(),\n msgHash: fp,\n agentId: input.agentId,\n channel: input.channel,\n sender: input.sender,\n chatId: input.chatId,\n path: cached.path,\n proposedAxes: {},\n leaf: cached.leaf,\n source: \"cache\",\n status: \"approved\",\n preview: input.text.slice(0, 200),\n })\n return {\n path: cached.path,\n pathId: hashPath(cached.path),\n pathLabel: pathLabel(cached.path, this.store.loadNodes().nodes),\n axes: {},\n leaf: cached.leaf,\n source: \"cache\",\n status: \"approved\",\n }\n }\n\n // 2. Cache miss — need an LLM proposal. Phase 2 of classifier-retire\n // replaced the /task → graph-agent dispatch with a direct Anthropic\n // Messages call, so we no longer need a draftAgent and the\n // self-dispatch deadlock guard (`agentId === draftAgent`) is moot.\n // `graph.enabled` is the only gate now.\n const schema = this.store.loadSchema()\n const nodes = this.store.loadNodes().nodes\n const proposal = await this.propose(input, schema, nodes)\n if (!proposal) return null\n\n // 3. Validate + persist as pending. Any NEW node the LLM proposed is\n // only committed if the whole classification auto-approves below.\n const { path, proposedAxes, confidence, leaf } = proposal\n if (path.length === 0) return null\n\n const classification: Classification = {\n ts: new Date().toISOString(),\n msgHash: fp,\n agentId: input.agentId,\n channel: input.channel,\n sender: input.sender,\n chatId: input.chatId,\n path,\n proposedAxes,\n leaf,\n source: proposal.source,\n status: \"pending\",\n confidence,\n preview: input.text.slice(0, 200),\n }\n\n // --- Auto-approval policy (see config.ts for full semantics) ---\n //\n // Two conditions, OR'd. Either approves the classification:\n // 1. Structure policy — what KIND of change the proposed path makes:\n // strict: nothing auto — always pending\n // extend-leaves: auto if the path either (a) reuses only existing\n // nodes, or (b) adds exactly one new node at the\n // DEEPEST level (a new leaf). Structural changes\n // (new mid-path or root node) still queue.\n // any: auto regardless of structure.\n // 2. Confidence — LLM-self-reported confidence >= autoApproveConfidence.\n //\n // Pending classifications still persist to the log, but don't commit nodes\n // and don't populate the fingerprint cache (so similar messages re-query).\n const existingIds = new Set(nodes.map((n) => n.id))\n const newNodeIndices = path\n .map((id, idx) => (existingIds.has(id) ? -1 : idx))\n .filter((idx) => idx >= 0)\n\n let structureOk = false\n if (this.autoApproveStructure === \"any\") {\n structureOk = true\n } else if (this.autoApproveStructure === \"extend-leaves\") {\n const isReuseOnly = newNodeIndices.length === 0\n const isSingleLeafAddition =\n newNodeIndices.length === 1 && newNodeIndices[0] === path.length - 1\n structureOk = isReuseOnly || isSingleLeafAddition\n }\n const confidenceOk = confidence !== undefined && confidence >= this.autoApprove\n\n let status: \"pending\" | \"approved\" = \"pending\"\n if (structureOk || confidenceOk) {\n // Commit any new nodes; if ANY fail schema validation (missing axes,\n // bad ref, etc.) fall back to pending for the whole classification\n // rather than half-committing the graph and bubbling the error up to\n // the task handler.\n try {\n this.commitNewNodes(path, proposedAxes, schema, nodes, input.sender)\n this.store.setFingerprint(fp, { path, leaf })\n status = \"approved\"\n } catch (e: any) {\n this.log(\n `auto-approve failed — staying pending ·`,\n `path=${JSON.stringify(path)}`,\n `reason=${(e?.message || e).toString().slice(0, 200)}`,\n )\n }\n }\n classification.status = status\n this.store.appendClassification(classification)\n\n return {\n path,\n pathId: hashPath(path),\n pathLabel: pathLabel(path, this.store.loadNodes().nodes),\n axes: proposedAxes,\n leaf,\n source: proposal.source,\n status,\n confidence,\n }\n }\n\n /**\n * Where a cache miss gets its path from, by the intent-path seat's mode:\n *\n * off the LLM, as before\n * shadow the LLM decides; the seat runs afterwards with the LLM's path\n * as incumbent, so agreement is measured before it can act\n * active the seat decides when both stages clear their thresholds;\n * the LLM only answers what the seat could not\n *\n * The seat reuses existing nodes only, so a seat proposal never adds to\n * the taxonomy; the LLM keeps that role in every mode.\n */\n private async propose(\n input: ClassifyInput,\n schema: GraphSchema,\n nodes: GraphNode[],\n ): Promise<Proposal | null> {\n const mode = getSeatMode(INTENT_PATH_SEAT)\n const seatInput = { message: input.text, channel: input.channel, sender: input.sender, agent: input.agentId }\n\n if (mode === \"active\") {\n const viaSeat = await proposePathViaSeat(seatInput, nodes).catch((e) => {\n this.log(\"intent-path seat failed:\", e?.message || e)\n return null\n })\n if (viaSeat?.confident) {\n return { path: viaSeat.path, proposedAxes: {}, leaf: {}, confidence: viaSeat.confidence, source: \"seat\" }\n }\n }\n\n const viaLlm = await this.proposePath(input, schema, nodes).catch((e) => {\n this.log(\"LLM proposal failed:\", e?.message || e)\n return null\n })\n if (mode === \"shadow\" && viaLlm) {\n void proposePathViaSeat(seatInput, nodes, { incumbent: viaLlm.path }).catch(() => {})\n }\n return viaLlm ? { ...viaLlm, source: \"llm\" } : null\n }\n\n /** Direct call to Anthropic to propose a path. Pure I/O; no side effects.\n * Bypasses the mesh / dispatch / ledger pipeline so a classification\n * doesn't surface as a sibling task in the activity graph. */\n private async proposePath(\n input: ClassifyInput,\n schema: GraphSchema,\n nodes: GraphNode[],\n ): Promise<{\n path: string[]\n proposedAxes: Record<string, Record<string, string>>\n confidence?: number\n leaf: { input?: string; output?: string }\n } | null> {\n const nodesForPrompt = nodes.map((n) => ({\n id: n.id,\n level: n.level,\n parentId: n.parentId,\n axes: n.axes,\n }))\n const userPrompt = [\n `You are the intent classifier for AgentX. You answer ONE question:`,\n `\"what kind of work does this message describe?\" — verb-level only.`,\n ``,\n `IMPORTANT — what NOT to encode in the path:`,\n `- Client / company / project / team names. Those are already on the event`,\n ` metadata (project, channel, agentId). Embedding them in the path causes`,\n ` duplicate nodes (one per client) for the same verb.`,\n `- The specific subject (issue number, file name, person name). Same reason.`,\n `- The agent's name or role. Pick the verb the *requester* intended.`,\n ``,\n `SCHEMA (fixed levels — pick one node per level):`,\n \"```json\",\n JSON.stringify(schema, null, 2),\n \"```\",\n ``,\n `EXISTING NODES (prefer reusing these; only invent a new node when no`,\n `existing one fits):`,\n \"```json\",\n JSON.stringify(nodesForPrompt, null, 2),\n \"```\",\n ``,\n `MESSAGE:`,\n \"```\",\n input.text.slice(0, 2000),\n \"```\",\n `channel: ${input.channel ?? \"?\"}`,\n `sender: ${input.sender ?? \"?\"}`,\n ``,\n `TASK:`,\n `1. Classify into 'category' (closed enum): code, ops, support, admin,`,\n ` knowledge, social, system. Pick the closest fit.`,\n `2. Pick or propose a 'verb' node. Verb ids are dot-namespaced lower-kebab,`,\n ` e.g. \"review.merge-request\", \"deploy.staging\", \"investigate.error\",`,\n ` \"chat.greeting\", \"fix.bug\". Reuse an existing verb when the message is`,\n ` the same kind of work as something already classified, even when the`,\n ` client / project / subject differs.`,\n ``,\n `Examples (right vs wrong):`,\n `- \"Please review MR #957 on globex/system\" → [\"code\", \"review.merge-request\"]`,\n ` NOT [\"business\", \"acme\", \"globex-v2\", \"review-mr-957-system\"]`,\n `- \"Deploy initech-v2 to staging please\" → [\"ops\", \"deploy.staging\"]`,\n ` NOT [\"business\", \"acme\", \"initech-v2\", \"deploy-initech-v2-to-staging\"]`,\n `- \"Hello Atlas\" → [\"support\", \"chat.greeting\"]`,\n ``,\n `NODE ID RULES (strict — invalid ids get dropped):`,\n `- lowercase-kebab + dots only: [a-z0-9][a-z0-9._-]*`,\n `- no spaces, no uppercase, no Arabic/other non-Latin`,\n `- verb ids should use a \"category.specific\" or \"verb.modifier\" shape`,\n ``,\n `Return ONE JSON object on one line, no prose, no fences:`,\n ` { \"path\": string[], \"proposedAxes\": { [nodeId]: { [axisName]: string } }, \"leaf\": { \"input\"?: string, \"output\"?: string }, \"confidence\": number }`,\n `confidence in [0,1]. Prefer low confidence over guessing.`,\n ].join(\"\\n\")\n\n // Phase 2 of classifier-retire: in-process provider call.\n // Replaces the `/task → graph-agent → mesh` dispatch path that\n // generated a sibling ledger event per classification (60–70% of\n // dispatches in the activity graph). Reuses whatever auth the\n // operator already configured (Claude Max OAuth via CLI subprocess,\n // Anthropic API key, etc.) — never opens a paid path the operator\n // didn't already opt into. The provider returns a plain JSON-text\n // GenerationResult, so we parse the same way as before.\n const text = await this.callProvider(userPrompt)\n const parsed = extractJson(text)\n if (!parsed || !Array.isArray(parsed.path)) return null\n\n // LLM often returns human-readable labels (\"Business\", \"Sales Manager\").\n // The node-id schema requires a lowercase slug, so normalize here and\n // remember the original → slug mapping so proposedAxes stays attached.\n // We also drop any element that can't be slugified into a valid id (e.g.\n // a path entry of pure Arabic script) — better to classify with a shorter\n // path than to have the store reject the whole classification.\n const NODE_ID_RE = /^[a-z0-9][a-z0-9._-]*$/\n const slugMap = new Map<string, string>()\n const path: string[] = parsed.path\n .filter((s: unknown): s is string => typeof s === \"string\" && s.length > 0)\n .map((s: string) => {\n const trimmed = s.trim()\n const slugged = slugifyNodeId(trimmed)\n if (trimmed !== slugged) slugMap.set(trimmed, slugged)\n return slugged\n })\n .filter((s: string) => NODE_ID_RE.test(s))\n if (path.length === 0) return null\n\n const proposedAxes: Record<string, Record<string, string>> = {}\n if (parsed.proposedAxes && typeof parsed.proposedAxes === \"object\") {\n for (const [rawNodeId, axes] of Object.entries(parsed.proposedAxes as Record<string, any>)) {\n if (!axes || typeof axes !== \"object\") continue\n const nodeId = slugMap.get(rawNodeId) ?? slugifyNodeId(rawNodeId)\n if (!nodeId) continue\n const clean: Record<string, string> = {}\n for (const [k, v] of Object.entries(axes)) {\n if (typeof v === \"string\") clean[k] = v\n }\n // If the LLM didn't volunteer a human-readable name but we slugified\n // one away, preserve the original as a `name` axis so the UI can\n // render \"Sales Manager\" not \"sales-manager\".\n if (!clean.name && rawNodeId !== nodeId) clean.name = rawNodeId.trim()\n proposedAxes[nodeId] = clean\n }\n }\n\n const leaf: { input?: string; output?: string } = {}\n if (parsed.leaf && typeof parsed.leaf === \"object\") {\n if (typeof parsed.leaf.input === \"string\") leaf.input = parsed.leaf.input\n if (typeof parsed.leaf.output === \"string\") leaf.output = parsed.leaf.output\n }\n\n const confidence = typeof parsed.confidence === \"number\"\n ? Math.max(0, Math.min(1, parsed.confidence))\n : undefined\n\n return { path, proposedAxes, confidence, leaf }\n }\n\n /** Create any nodes along `path` that don't exist yet. Delegates to the\n * store so level inference (for paths that skip levels) + fresh reloads\n * between adds are shared with the review path. */\n private commitNewNodes(\n path: string[],\n proposedAxes: Record<string, Record<string, string>>,\n schema: GraphSchema,\n _existing: GraphNode[],\n createdBy?: string,\n ): void {\n this.store.commitNodesAlongPath(path, proposedAxes, schema, createdBy)\n }\n\n /** Run the prompt through whichever AgentProvider the operator already\n * configured (Claude Max subscription via CLI, Anthropic API key, …).\n * Hard rule: NEVER open a paid path the operator didn't opt into —\n * this is just a thin wrapper that reuses existing auth. 30s timeout\n * is enough for Haiku/CLI cold-start; any longer and the classifier\n * is stalling the caller. */\n private async callProvider(userPrompt: string): Promise<string> {\n const provider = this.getProvider()\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), 30_000)\n try {\n const res = await provider.generate(\n [\n {\n role: \"system\",\n content:\n \"You are a taxonomy classifier. Respond with a single JSON object on one line. No prose, no code fences.\",\n },\n { role: \"user\", content: userPrompt },\n ],\n { model: this.classifierModel, maxTokens: 800, abortSignal: ac.signal, bare: true },\n )\n return res.content || \"\"\n } finally {\n clearTimeout(timer)\n }\n }\n}\n\n/** Normalize an LLM-proposed node label to a valid node id. Matches the\n * schema's `^[a-z0-9][a-z0-9._-]*$` — lowercase, alnum/./-/_, leading alnum.\n * Dots are preserved for verb ids like \"review.merge-request\". */\nfunction slugifyNodeId(raw: string): string {\n const s = raw\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, \"-\")\n .replace(/^[-_]+|[-_]+$/g, \"\")\n .replace(/-{2,}/g, \"-\")\n if (!s) return \"\"\n // Ensure leading char is alnum (schema rejects leading _ or -).\n return /^[a-z0-9]/.test(s) ? s : `n-${s}`.replace(/[-_]+$/, \"\")\n}\n\n// --- path helpers used by both classifier + wiki retrieval ---\n\n/** Deterministic hash of a path, used as the `graph:<pathId>` wiki tag. */\nexport function hashPath(path: string[]): string {\n return createHash(\"sha1\")\n .update(path.join(\"\\u0001\"))\n .digest(\"hex\")\n .slice(0, 16)\n}\n\n/** \"Business › Acme › DevOps › Review MR\" — for the UI + context render. */\nexport function pathLabel(path: string[], nodes: GraphNode[]): string {\n return path\n .map((id) => {\n const n = nodes.find((x) => x.id === id)\n if (!n) return id\n // Prefer a `name` axis when the schema has one; fall back to id.\n const name = n.axes?.name || n.axes?.what || id\n return name\n })\n .join(\" › \")\n}\n\n/**\n * Depth of the deepest common ancestor between two paths, normalized to [0,1].\n * Used to score wiki articles: exact match = 1, shared grandparent = 0.5, none = 0.\n */\nexport function ancestryScore(a: string[], b: string[]): number {\n if (a.length === 0 || b.length === 0) return 0\n let shared = 0\n for (let i = 0; i < Math.min(a.length, b.length); i++) {\n if (a[i] === b[i]) shared++\n else break\n }\n if (shared === 0) return 0\n const depth = Math.max(a.length, b.length)\n return shared / depth\n}\n\n","import { choice } from \"../questions\"\nimport { askSeat, getSeatMode } from \"../seat\"\nimport type { SeatMode } from \"../store\"\nimport type { ChoiceAnswer, StateValue } from \"../types\"\nimport type { GraphNode } from \"@/graph/types\"\n\n// Which intent-graph path a message belongs to.\n//\n// The intent graph is a closed set: seven categories, a few hundred verbs,\n// and in the last few thousand classifications the LLM classifier never\n// proposed a node that did not already exist. Picking one label out of a\n// known list is what a decision seat does in under a second for a fraction\n// of a cent; asking a chat model to write the label out cost a fresh CLI\n// process per task, twenty seconds of wall time on average.\n//\n// Two stages because Jev caps a Choice at 255 options and the largest\n// category holds about a hundred verbs: first the category, then the verb\n// within it. Each stage carries the LLM's answer as the incumbent while the\n// seat runs in shadow, so `agentx decisions stats` reports agreement before\n// the seat is allowed to decide.\n//\n// Fail-open, as every seat: a missing or unconfident answer means \"no\n// proposal\", and the classifier falls back to the LLM it uses today. The\n// seat can only ever reuse existing nodes; growing the taxonomy stays the\n// LLM's job.\n\nexport const INTENT_PATH_SEAT = \"intent-path\"\n\n/** Below these the seat's answer is not acted on. A Choice always returns\n * an argmax, so the probability mass behind it is the only signal that\n * the pick is a match rather than the least bad option. */\nexport const INTENT_PATH_MIN_CATEGORY_P = 0.5\nexport const INTENT_PATH_MIN_VERB_P = 0.3\n\nexport interface IntentPathInput {\n message: string\n channel?: string\n sender?: string\n agent?: string\n}\n\nexport interface IntentPathProposal {\n /** `[category]` or `[category, verb]`, existing node ids only. */\n path: string[]\n /** min over the stages' argmax probability. */\n confidence: number\n /** Both stages cleared their thresholds; the caller may act on `path`. */\n confident: boolean\n mode: SeatMode\n}\n\nexport function intentPathState(input: IntentPathInput): StateValue {\n return {\n message: clip(input.message, 2000),\n channel: input.channel ?? null,\n sender: input.sender ?? null,\n agent: input.agent ?? null,\n }\n}\n\n/** One Choice over the given nodes. The option text is the node's own\n * description when it has one, so the seat reads what a reviewer reads. */\nexport function nodeChoice(nodes: GraphNode[], instructions: string) {\n const criteria: Record<string, string | null> = {}\n for (const n of nodes) criteria[n.id] = describe(n)\n return choice(criteria, instructions)\n}\n\nexport function categoryQuestions(categories: GraphNode[]) {\n return {\n category: nodeChoice(\n categories,\n \"Which category of work does this message ask for or report on? Pick the one a reviewer would file it under.\",\n ),\n }\n}\n\nexport function verbQuestions(verbs: GraphNode[]) {\n return {\n verb: nodeChoice(\n verbs,\n \"Within that category, which specific action does the message ask for or report on? Prefer the most specific verb that fits; a casual or ambiguous message belongs to the most general one.\",\n ),\n }\n}\n\nexport interface ProposeViaSeatOptions {\n /** The LLM's path for the same message, recorded as the incumbent so the\n * shadow report can measure agreement. */\n incumbent?: string[]\n}\n\n/**\n * Two-stage classification through the seat. Returns null when the seat is\n * off, unreachable or the taxonomy is too small to ask; otherwise a proposal\n * whose `confident` flag says whether the thresholds were met.\n */\nexport async function proposePathViaSeat(\n input: IntentPathInput,\n nodes: GraphNode[],\n opts: ProposeViaSeatOptions = {},\n): Promise<IntentPathProposal | null> {\n if (getSeatMode(INTENT_PATH_SEAT) === \"off\") return null\n const categories = nodes.filter((n) => n.parentId === null)\n if (categories.length < 2) return null\n\n const state = intentPathState(input)\n const features = { agent: input.agent ?? \"unknown\", channel: input.channel ?? \"unknown\" }\n const links = input.agent ? [{ kind: \"agent\", id: input.agent }] : undefined\n\n const stage1 = await askSeat(INTENT_PATH_SEAT, state, categoryQuestions(categories), {\n incumbent: opts.incumbent?.[0] ? { category: opts.incumbent[0] } : undefined,\n features: { ...features, stage: \"category\" },\n links,\n })\n if (!stage1) return null\n const cat = stage1.answers.category as ChoiceAnswer\n let confidence = cat.pMax\n let confident = cat.pMax >= INTENT_PATH_MIN_CATEGORY_P\n const path = [cat.choice]\n\n const verbs = nodes.filter((n) => n.parentId === cat.choice)\n if (verbs.length >= 2) {\n const sameCategory = opts.incumbent?.[0] === cat.choice\n const stage2 = await askSeat(INTENT_PATH_SEAT, { ...(state as Record<string, StateValue>), category: cat.choice }, verbQuestions(verbs), {\n // The LLM's verb is only a fair incumbent when it lives in the same\n // category; otherwise it is not among the options at all.\n incumbent: sameCategory && opts.incumbent?.[1] ? { verb: opts.incumbent[1] } : undefined,\n features: { ...features, stage: \"verb\", category: cat.choice },\n links,\n })\n if (!stage2) return null\n const verb = stage2.answers.verb as ChoiceAnswer\n confidence = Math.min(confidence, verb.pMax)\n confident = confident && verb.pMax >= INTENT_PATH_MIN_VERB_P\n path.push(verb.choice)\n } else if (verbs.length === 1) {\n path.push(verbs[0].id)\n }\n\n return { path, confidence, confident, mode: stage1.mode }\n}\n\nfunction describe(n: GraphNode): string | null {\n const d = n.axes?.description?.trim() || undefined\n const rawName = n.axes?.name?.trim()\n // A name that merely repeats the id adds nothing the label does not say.\n const name = rawName && rawName !== n.id ? rawName : undefined\n if (d && name) return `${name}: ${d}`\n return d ?? name ?? null\n}\n\nfunction clip(s: string, max: number): string {\n return s.length > max ? `${s.slice(0, max)}…` : s\n}\n"],"mappings":"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","names":["STARTER_SCHEMA","STARTER_VERB_NODES","readFileSync","writeFileSync","appendFileSync","existsSync","mkdirSync","renameSync","statSync","resolve","dirname","createHash","GraphStore","opts","resolve","mkdirSync","p","existsSync","STARTER_SCHEMA","raw","readFileSync","parsed","graphSchemaSchema","i","backup","writeFileSync","e","np","nbackup","seeded","seedNodesFile","schema","safeWriteJson","nodesFileSchema","file","parse","mtimeMs","size","statSync","hit","data","node","all","level","l","missing","a","v","n","target","levelIdx","parent","parentLevelIdx","path","proposedAxes","createdBy","added","skipped","now","id","current","cursor","j","priorId","priorNode","axes","chosenLevel","parentId","chosenIdx","prior","priorIdx","c","classificationSchema","issues","line","dirname","appendFileSync","limit","status","lines","byHash","b","msgHash","update","next","suffix","out","agentId","channel","chatId","maxAgeMs","since","msg","payload","normalizeForFingerprint","createHash","indexFileSchema","fp","entry","fingerprintEntrySchema","updated","child","refHolder","known","tmp","renameSync","nodes","seenCats","STARTER_VERB_NODES","text","init_extract_json","init_providers","init_seat","createHash","init_questions","init_seat","INTENT_PATH_SEAT","INTENT_PATH_MIN_CATEGORY_P","INTENT_PATH_MIN_VERB_P","intentPathState","input","clip","nodeChoice","nodes","instructions","criteria","n","describe","choice","categoryQuestions","categories","verbQuestions","verbs","proposePathViaSeat","opts","getSeatMode","state","features","links","stage1","askSeat","cat","confidence","confident","path","sameCategory","stage2","verb","d","rawName","name","s","max","Classifier","opts","createProvider","input","fp","cached","hashPath","pathLabel","schema","nodes","proposal","path","proposedAxes","confidence","leaf","classification","existingIds","n","newNodeIndices","id","idx","structureOk","isReuseOnly","isSingleLeafAddition","confidenceOk","status","e","mode","getSeatMode","INTENT_PATH_SEAT","seatInput","viaSeat","proposePathViaSeat","viaLlm","nodesForPrompt","userPrompt","text","parsed","extractJson","NODE_ID_RE","slugMap","s","trimmed","slugged","slugifyNodeId","rawNodeId","axes","nodeId","clean","k","v","_existing","createdBy","provider","ac","timer","raw","createHash","x","ancestryScore","b","shared","i","depth"]}
@@ -1,4 +1,4 @@
1
- import{b as F}from"./chunk-DP7RTNDM.js";import{q as _,r as W}from"./chunk-2AXFYDWF.js";import{b as Y,c as I,d as O}from"./chunk-AQASR6KR.js";import{e as E}from"./chunk-535MLOSB.js";import{b as L}from"./chunk-64P4QHSZ.js";import{createHash as ee}from"crypto";import{existsSync as H,readdirSync as te,readFileSync as re,writeFileSync as ne}from"fs";import{resolve as P}from"path";function be(n,r,s,t,o,i,c=""){return Z(r,s,t,o,i,c)}function Z(n,r,s,t,o,i=""){let c=r?`
1
+ import{b as Y,c as I,d as O}from"./chunk-AQASR6KR.js";import{b as F}from"./chunk-DP7RTNDM.js";import{q as _,r as W}from"./chunk-2AXFYDWF.js";import{a as E}from"./chunk-ML6CNRBK.js";import{b as L}from"./chunk-CECHWYJ2.js";import{createHash as ee}from"crypto";import{existsSync as H,readdirSync as te,readFileSync as re,writeFileSync as ne}from"fs";import{resolve as P}from"path";function be(n,r,s,t,o,i,c=""){return Z(r,s,t,o,i,c)}function Z(n,r,s,t,o,i=""){let c=r?`
2
2
  ## Worldview
3
3
 
4
4
  ${r}
@@ -240,4 +240,4 @@ ${g}`}var x="memory-promoter",oe=["project","reference","feedback"],K="_memory-p
240
240
  Re-emit the FULL corrected JSON object \u2014 same structure, fixed issues, valid JSON only.`:n,y=await t(`${a}/task`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({agent:r.viaAgent,message:h,freshSession:s===null,context:{channel:"api",chatId:r.chatId??"memory-promote"}}),signal:i.signal});if(!y.ok){let f=await y.text().catch(()=>"");throw new Error(`agent /task ${y.status}: ${f.slice(0,500)}`)}let w=await y.json();if(w.error)throw new Error(`agent reported error: ${w.error}`);let b=String(w.content??"").trim();if(!b)throw new Error("agent returned empty content");return b}if(!r.model)throw new Error("callPromotionLlm: viaAgent or model is required");let p=process.env.ANTHROPIC_API_KEY;if(!p)throw new Error("ANTHROPIC_API_KEY not set; use --via <agentId> instead");let u=[{role:"user",content:n}];s&&(u.push({role:"assistant",content:"(previous attempt; will retry with corrections)"}),u.push({role:"user",content:`Your previous response failed validation: ${s}
241
241
  Re-emit the FULL corrected JSON object.`}));let g=await t(me,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":p,"anthropic-version":ge},body:JSON.stringify({model:r.model,max_tokens:8e3,messages:u}),signal:i.signal});if(!g.ok){let a=await g.text().catch(()=>"");throw new Error(`anthropic API ${g.status}: ${a.slice(0,500)}`)}let l=await g.json(),e=(Array.isArray(l.content)?l.content:[]).filter(a=>a?.type==="text").map(a=>a.text).join(`
242
242
  `).trim();if(!e)throw new Error("anthropic API returned no text content");return e}finally{clearTimeout(c)}}async function Se(n={}){let r=n.wikiDir??P(process.cwd(),".agentx","wiki"),s=n.memoryRoot??P(process.cwd(),".agentx"),t=n.log??(()=>{}),o=n.now??Date.now(),i={candidates:[],clusters:[],proposed:[],skipped:[],gaps:[],warnings:[],errors:[],dryRun:!n.commit},c=new E(r,(...m)=>t(m.map(String).join(" "))),p=c.rebuildIndex(),u=z(r),g=[...ce(s),...n.extraCandidates??[]];if(i.candidates=de(g,p,u,{types:n.types,sinceMs:n.sinceMs,agentFilter:n.agentFilter,now:o,max:n.max}),i.candidates.length===0)return t("no unpromoted memories in window"),i;if(i.clusters=le(i.candidates),i.dryRun)return i;let l=p.articles.map(m=>({title:m.title,path:m.path,type:m.type})),d=c.getWorldview()??"",e=n.budgetTokens??ue,a=i.clusters,h=D(x,a,l,d);for(;R(h)>e&&a.length>1;)a=a.slice(0,-1),h=D(x,a,l,d);if(R(h)>e)return i.errors.push(`judge prompt (~${R(h)} tokens) exceeds the ${e}-token budget even with one cluster`),i;a.length<i.clusters.length&&i.warnings.push(`token budget: judged ${a.length} of ${i.clusters.length} clusters; the rest wait for the next run`);let y=a.flatMap(m=>m.candidates),w=new Set(y.map(m=>m.stamp)),b=n.chatId??`memory-promote-${new Date(o).toISOString().slice(0,10)}`,f=B(await G(h,{...n,chatId:b}),w);if("error"in f&&(t(`parse failed (${f.error}) \u2014 retrying with feedback`),f=B(await G(h,{...n,chatId:b},f.error),w)),"error"in f)return i.errors.push(`LLM response unusable after retry: ${f.error}`),i;i.warnings.push(...f.warnings),i.gaps=f.gaps;let v=new Date(o).toISOString(),k=[],T=new Map(y.map(m=>[m.stamp,m]));for(let m of f.articles){let A=c.readArticle(m.path);if(A&&!c.canWrite(A.meta,x)){i.errors.push(`write denied for "${m.path}" (owned by ${A.meta.owner??"?"})`);continue}let M=fe(m.promotedFrom,T),S={id:Y(o,m.path),createdAt:v,status:"pending",article:m,evidence:M,...A?{replaces:{lastUpdated:A.meta.lastUpdated,contentHash:V(A.content)}}:{}};I(r,S),i.proposed.push({id:S.id,path:m.path,title:m.title,type:m.type,stamps:m.promotedFrom,agents:M.agents,occurrences:M.occurrences});for(let X of m.promotedFrom)k.push({stamp:X,decision:"proposed",article:m.path,proposal:S.id,at:v})}for(let m of f.skipped)k.push({stamp:m.stamp,decision:"skipped",reason:m.reason,at:v}),i.skipped.push(m);return k.length&&C(r,k),i}var ue=6e4;function R(n){return Math.ceil(n.length/4)}function V(n){return ee("sha256").update(n).digest("hex").slice(0,16)}var he=600;function ye(n){return n.startsWith("review:")?"review":n.startsWith(j)?"failure":"memory"}function fe(n,r){let s=[];for(let t of n){let o=r.get(t);o&&s.push({stamp:t,kind:ye(t),agentId:o.agentId,type:o.memory.type,name:o.memory.name,description:o.memory.description,...o.occurrences?{occurrences:o.occurrences}:{},...o.sessions?.length?{sessions:o.sessions}:{},...o.tasks?.length?{tasks:o.tasks}:{},...o.failure?{failure:o.failure}:{},...o.memory.author?{author:o.memory.author}:{},...o.memory.taskId?{taskId:o.memory.taskId}:{},updatedAt:o.memory.updatedAt,excerpt:o.memory.body.slice(0,he)})}return{agents:[...new Set(s.map(t=>t.agentId))].sort(),occurrences:Math.max(1,...s.map(t=>t.occurrences??1)),sources:s}}function Ee(n,r,s={}){let t=O(n,r);if(!t)return{ok:!1,error:`no proposal "${r}"`};if(t.status!=="pending")return{ok:!1,error:`proposal "${r}" is already ${t.status}`};let o=s.log??(()=>{}),i=new E(n,(...a)=>o(a.map(String).join(" "))),{article:c}=t,p=i.readArticle(c.path);if(!s.force){if(p&&!t.replaces)return{ok:!1,error:`"${c.path}" was created after this proposal; review it, then approve with --force`};if(p&&t.replaces&&V(p.content)!==t.replaces.contentHash)return{ok:!1,error:`"${c.path}" changed since this proposal; review it, then approve with --force`}}let u=s.now??Date.now(),g=new Date(u).toISOString().slice(0,10);if(!i.writeArticle(c.path,{title:c.title,type:L(c.type)?c.type:void 0,related:c.related,tags:c.tags,owner:x,access:"public",created:p?.meta.created??g,lastUpdated:g,sources:ae(p?.meta.sources,c.promotedFrom)},c.content,x))return{ok:!1,error:`write denied for "${c.path}" (owned by ${p?.meta.owner??"?"})`};let d=new Date(u).toISOString();C(n,c.promotedFrom.map(a=>({stamp:a,decision:"promoted",article:c.path,proposal:r,at:d}))),i.rebuildIndex();let e={...t,status:"approved",decidedBy:s.by??"operator",decidedAt:d};return I(n,e),{ok:!0,proposal:e}}function Oe(n,r,s={}){let t=O(n,r);if(!t)return{ok:!1,error:`no proposal "${r}"`};if(t.status!=="pending")return{ok:!1,error:`proposal "${r}" is already ${t.status}`};let o=new Date(s.now??Date.now()).toISOString();C(n,t.article.promotedFrom.map(c=>({stamp:c,decision:"rejected",article:t.article.path,proposal:r,reason:s.reason,at:o})));let i={...t,status:"rejected",decidedBy:s.by??"operator",decidedAt:o,...s.reason?{reason:s.reason}:{}};return I(n,i),{ok:!0,proposal:i}}export{be as a,x as b,oe as c,K as d,q as e,ie as f,$ as g,ae as h,ce as i,z as j,C as k,le as l,B as m,de as n,G as o,Se as p,R as q,ye as r,fe as s,Ee as t,Oe as u};
243
- //# sourceMappingURL=chunk-DQQ3BCJW.js.map
243
+ //# sourceMappingURL=chunk-YK7GFEGO.js.map
package/dist/cli.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{a as n}from"./chunk-OHRTBD7U.js";import"./chunk-OVX6NRMD.js";import"./chunk-KLHG5ZQH.js";import"./chunk-M6GPRMCQ.js";import"./chunk-Z3IP66SL.js";import"./chunk-C4CDVE2E.js";import"./chunk-RKPAVWXF.js";import"./chunk-GV7S5BAX.js";import"./chunk-GSX4NK2N.js";import"./chunk-SZDACK6O.js";import"./chunk-5ONI54E6.js";import"./chunk-CKBAVCGU.js";import"./chunk-BYJ3A55Y.js";import"./chunk-NMFXY3QU.js";import"./chunk-YBF2QVZ7.js";import"./chunk-LQRBGQIR.js";import"./chunk-ZVP6GB4G.js";import"./chunk-DQQ3BCJW.js";import"./chunk-2D3RYIDU.js";import"./chunk-EKJG63EJ.js";import"./chunk-K63IOY6A.js";import"./chunk-IU6L4V3A.js";import"./chunk-N6EWZGIX.js";import"./chunk-WP6KLXTP.js";import"./chunk-DP7RTNDM.js";import"./chunk-J7HA7ZTU.js";import"./chunk-SFQUP3BP.js";import"./chunk-FSYA4GNU.js";import"./chunk-2AXFYDWF.js";import"./chunk-AQASR6KR.js";import"./chunk-76Z4VHDX.js";import"./chunk-W4YR2RO2.js";import"./chunk-AFRHKA7B.js";import"./chunk-OPUPVGYP.js";import"./chunk-YL223TEU.js";import"./chunk-KA2UIZXG.js";import"./chunk-GQ76J72O.js";import"./chunk-ILEC3IAV.js";import"./chunk-J3SOEFRE.js";import"./chunk-SIU6STB2.js";import"./chunk-X5C5W3E4.js";import"./chunk-P2RYUODK.js";import"./chunk-DWADWDMU.js";import"./chunk-23A5K5UW.js";import"./chunk-QD6IICK7.js";import"./chunk-VWTBHLMX.js";import"./chunk-2UYV24UD.js";import"./chunk-535MLOSB.js";import"./chunk-4DRNEOOW.js";import"./chunk-64P4QHSZ.js";import"./chunk-V2TLFYKH.js";import"./chunk-TFVUOC2C.js";import"./chunk-2GIDVZ4H.js";import"./chunk-BBIWAJRX.js";import"./chunk-FX3D7YON.js";import"./chunk-72SZI2UW.js";import"./chunk-FCPTN6NF.js";import"./chunk-QOYAX2FT.js";import"./chunk-5MUJZODV.js";import"./chunk-7I7IEX4S.js";import"./chunk-JHCI6XCJ.js";import"./chunk-DSYKYZMT.js";import"./chunk-DTX4I5LQ.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";var r=["SIGINT","SIGTERM"];function o(t=process){for(let i of r)t.on(i,()=>{t.listenerCount(i)<=1&&t.exit(0)})}o();async function s(){let t=await n();if(process.argv.slice(2).length===0){t.outputHelp();return}t.parse()}s();
2
+ import{a as n}from"./chunk-LHLNARRW.js";import"./chunk-OVX6NRMD.js";import"./chunk-KLHG5ZQH.js";import"./chunk-6QDOUOKD.js";import"./chunk-Z3IP66SL.js";import"./chunk-C4CDVE2E.js";import"./chunk-RKPAVWXF.js";import"./chunk-SZDACK6O.js";import"./chunk-GV7S5BAX.js";import"./chunk-GSX4NK2N.js";import"./chunk-PHDS2HIN.js";import"./chunk-3IY2YWXP.js";import"./chunk-YBF2QVZ7.js";import"./chunk-BYJ3A55Y.js";import"./chunk-NMFXY3QU.js";import"./chunk-LQRBGQIR.js";import"./chunk-ZVP6GB4G.js";import"./chunk-YK7GFEGO.js";import"./chunk-AQASR6KR.js";import"./chunk-2D3RYIDU.js";import"./chunk-EKJG63EJ.js";import"./chunk-FBDIJEKL.js";import"./chunk-W7SEPJFH.js";import"./chunk-WP6KLXTP.js";import"./chunk-N6EWZGIX.js";import"./chunk-DP7RTNDM.js";import"./chunk-3RC4LO2G.js";import"./chunk-SFQUP3BP.js";import"./chunk-FSYA4GNU.js";import"./chunk-2AXFYDWF.js";import"./chunk-76Z4VHDX.js";import"./chunk-W4YR2RO2.js";import"./chunk-AFRHKA7B.js";import"./chunk-OPUPVGYP.js";import"./chunk-YL223TEU.js";import"./chunk-VWTBHLMX.js";import"./chunk-SN5N2FPG.js";import"./chunk-ML6CNRBK.js";import"./chunk-KA2UIZXG.js";import"./chunk-GQ76J72O.js";import"./chunk-ILEC3IAV.js";import"./chunk-J3SOEFRE.js";import"./chunk-SIU6STB2.js";import"./chunk-X5C5W3E4.js";import"./chunk-P2RYUODK.js";import"./chunk-DWADWDMU.js";import"./chunk-23A5K5UW.js";import"./chunk-QD6IICK7.js";import"./chunk-CECHWYJ2.js";import"./chunk-TLDQWTSW.js";import"./chunk-TFVUOC2C.js";import"./chunk-2GIDVZ4H.js";import"./chunk-QOYAX2FT.js";import"./chunk-WLMQ5JMX.js";import"./chunk-TTVVXBCM.js";import"./chunk-BBIWAJRX.js";import"./chunk-FX3D7YON.js";import"./chunk-72SZI2UW.js";import"./chunk-FCPTN6NF.js";import"./chunk-5MUJZODV.js";import"./chunk-7I7IEX4S.js";import"./chunk-JHCI6XCJ.js";import"./chunk-DSYKYZMT.js";import"./chunk-DTX4I5LQ.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";var r=["SIGINT","SIGTERM"];function o(t=process){for(let i of r)t.on(i,()=>{t.listenerCount(i)<=1&&t.exit(0)})}o();async function s(){let t=await n();if(process.argv.slice(2).length===0){t.outputHelp();return}t.parse()}s();
3
3
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/signal-exit.ts","../src/cli.ts"],"sourcesContent":["// --- Ctrl-C / SIGTERM for CLI commands ---\n//\n// Short commands should exit at once when stopped. Long-running ones (the\n// daemon above all) install their own handler to finish in-flight work first.\n// The CLI's quick exit used to be registered unconditionally, before the\n// daemon's, and Node runs listeners in registration order — so every stop\n// exited the process before the daemon's drain began and killed in-flight\n// agent tasks mid-turn (#103). Now the quick exit only fires when nobody\n// else handles the signal.\n\ntype SignalTarget = Pick<NodeJS.Process, \"on\" | \"listenerCount\" | \"exit\">\n\nexport const CLI_EXIT_SIGNALS = [\"SIGINT\", \"SIGTERM\"] as const\n\nexport function installCliSignalExit(proc: SignalTarget = process): void {\n for (const sig of CLI_EXIT_SIGNALS) {\n proc.on(sig, () => {\n // This listener is always one of them; anything more means a command\n // is handling the stop itself.\n if (proc.listenerCount(sig) <= 1) proc.exit(0)\n })\n }\n}\n","#!/usr/bin/env node\nimport { buildProgram } from \"@/program\"\nimport { installCliSignalExit } from \"@/utils/signal-exit\"\n\ninstallCliSignalExit()\n\nasync function main() {\n const program = await buildProgram()\n\n const args = process.argv.slice(2)\n if (args.length === 0) {\n program.outputHelp()\n return\n }\n\n program.parse()\n}\n\nmain()\n"],"mappings":";wyDAYO,IAAMA,EAAmB,CAAC,SAAU,SAAS,EAE7C,SAASC,EAAqBC,EAAqB,QAAe,CACvE,QAAWC,KAAOH,EAChBE,EAAK,GAAGC,EAAK,IAAM,CAGbD,EAAK,cAAcC,CAAG,GAAK,GAAGD,EAAK,KAAK,CAAC,CAC/C,CAAC,CAEL,CClBAE,EAAqB,EAErB,eAAeC,GAAO,CACpB,IAAMC,EAAU,MAAMC,EAAa,EAGnC,GADa,QAAQ,KAAK,MAAM,CAAC,EACxB,SAAW,EAAG,CACrBD,EAAQ,WAAW,EACnB,OAGFA,EAAQ,MAAM,CAChB,CAEAD,EAAK","names":["CLI_EXIT_SIGNALS","installCliSignalExit","proc","sig","installCliSignalExit","main","program","buildProgram"]}
1
+ {"version":3,"sources":["../src/utils/signal-exit.ts","../src/cli.ts"],"sourcesContent":["// --- Ctrl-C / SIGTERM for CLI commands ---\n//\n// Short commands should exit at once when stopped. Long-running ones (the\n// daemon above all) install their own handler to finish in-flight work first.\n// The CLI's quick exit used to be registered unconditionally, before the\n// daemon's, and Node runs listeners in registration order — so every stop\n// exited the process before the daemon's drain began and killed in-flight\n// agent tasks mid-turn (#103). Now the quick exit only fires when nobody\n// else handles the signal.\n\ntype SignalTarget = Pick<NodeJS.Process, \"on\" | \"listenerCount\" | \"exit\">\n\nexport const CLI_EXIT_SIGNALS = [\"SIGINT\", \"SIGTERM\"] as const\n\nexport function installCliSignalExit(proc: SignalTarget = process): void {\n for (const sig of CLI_EXIT_SIGNALS) {\n proc.on(sig, () => {\n // This listener is always one of them; anything more means a command\n // is handling the stop itself.\n if (proc.listenerCount(sig) <= 1) proc.exit(0)\n })\n }\n}\n","#!/usr/bin/env node\nimport { buildProgram } from \"@/program\"\nimport { installCliSignalExit } from \"@/utils/signal-exit\"\n\ninstallCliSignalExit()\n\nasync function main() {\n const program = await buildProgram()\n\n const args = process.argv.slice(2)\n if (args.length === 0) {\n program.outputHelp()\n return\n }\n\n program.parse()\n}\n\nmain()\n"],"mappings":";o0DAYO,IAAMA,EAAmB,CAAC,SAAU,SAAS,EAE7C,SAASC,EAAqBC,EAAqB,QAAe,CACvE,QAAWC,KAAOH,EAChBE,EAAK,GAAGC,EAAK,IAAM,CAGbD,EAAK,cAAcC,CAAG,GAAK,GAAGD,EAAK,KAAK,CAAC,CAC/C,CAAC,CAEL,CClBAE,EAAqB,EAErB,eAAeC,GAAO,CACpB,IAAMC,EAAU,MAAMC,EAAa,EAGnC,GADa,QAAQ,KAAK,MAAM,CAAC,EACxB,SAAW,EAAG,CACrBD,EAAQ,WAAW,EACnB,OAGFA,EAAQ,MAAM,CAChB,CAEAD,EAAK","names":["CLI_EXIT_SIGNALS","installCliSignalExit","proc","sig","installCliSignalExit","main","program","buildProgram"]}
@@ -0,0 +1,2 @@
1
+ import{a as m,b as n,c as o,d as p,e as q,f as r}from"./chunk-WLMQ5JMX.js";import{a,b,c,d,e,f,g,h,i,j,k,l}from"./chunk-TTVVXBCM.js";import"./chunk-BBIWAJRX.js";import"./chunk-FX3D7YON.js";import"./chunk-72SZI2UW.js";import"./chunk-FCPTN6NF.js";import"./chunk-5MUJZODV.js";import"./chunk-7I7IEX4S.js";import"./chunk-DTX4I5LQ.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";export{o as Classifier,n as GraphStore,m as STARTER_SCHEMA,r as ancestryScore,b as axisDefSchema,a as axisTypeSchema,i as classificationSchema,g as classificationSourceSchema,h as classificationStatusSchema,j as fingerprintEntrySchema,f as graphNodeSchema,d as graphSchemaSchema,p as hashPath,l as indexFileSchema,c as levelDefSchema,e as nodeIdSchema,k as nodesFileSchema,q as pathLabel};
2
+ //# sourceMappingURL=graph-FJ53FDWK.js.map
package/dist/index.d.ts CHANGED
@@ -9160,6 +9160,8 @@ interface WikiIndex {
9160
9160
  backlinks: number;
9161
9161
  sources?: string[];
9162
9162
  lastUpdated?: string;
9163
+ /** Intent-graph path the article's sources were classified under. */
9164
+ graphPath?: string[];
9163
9165
  }>;
9164
9166
  lastRebuilt: string;
9165
9167
  }
@@ -9705,7 +9707,8 @@ declare const graphNodeSchema: z.ZodObject<{
9705
9707
  createdBy?: string | undefined;
9706
9708
  }>;
9707
9709
  type GraphNode = z.infer<typeof graphNodeSchema>;
9708
- declare const classificationSourceSchema: z.ZodEnum<["llm", "user", "cache"]>;
9710
+ /** "seat": the intent-path decision seat picked an existing path. */
9711
+ declare const classificationSourceSchema: z.ZodEnum<["llm", "user", "cache", "seat"]>;
9709
9712
  type ClassificationSource = z.infer<typeof classificationSourceSchema>;
9710
9713
  declare const classificationStatusSchema: z.ZodEnum<["pending", "approved", "rejected"]>;
9711
9714
  type ClassificationStatus = z.infer<typeof classificationStatusSchema>;
@@ -9715,6 +9718,9 @@ declare const classificationSchema: z.ZodObject<{
9715
9718
  agentId: z.ZodOptional<z.ZodString>;
9716
9719
  channel: z.ZodOptional<z.ZodString>;
9717
9720
  sender: z.ZodOptional<z.ZodString>;
9721
+ /** The conversation the message belongs to. Lets a tool running inside
9722
+ * that conversation find the path its request was filed under. */
9723
+ chatId: z.ZodOptional<z.ZodString>;
9718
9724
  /** Node ids from root to leaf, one per level. Shorter-than-full paths are
9719
9725
  * allowed if the classifier was only confident down to a certain depth. */
9720
9726
  path: z.ZodArray<z.ZodString, "many">;
@@ -9732,7 +9738,7 @@ declare const classificationSchema: z.ZodObject<{
9732
9738
  output?: string | undefined;
9733
9739
  input?: string | undefined;
9734
9740
  }>>;
9735
- source: z.ZodEnum<["llm", "user", "cache"]>;
9741
+ source: z.ZodEnum<["llm", "user", "cache", "seat"]>;
9736
9742
  status: z.ZodEnum<["pending", "approved", "rejected"]>;
9737
9743
  confidence: z.ZodOptional<z.ZodNumber>;
9738
9744
  /** Redacted first ~200 chars of the message, for the approval UI. */
@@ -9740,7 +9746,7 @@ declare const classificationSchema: z.ZodObject<{
9740
9746
  }, "strip", z.ZodTypeAny, {
9741
9747
  path: string[];
9742
9748
  status: "pending" | "approved" | "rejected";
9743
- source: "user" | "llm" | "cache";
9749
+ source: "user" | "seat" | "llm" | "cache";
9744
9750
  ts: string;
9745
9751
  msgHash: string;
9746
9752
  proposedAxes: Record<string, Record<string, string>>;
@@ -9750,17 +9756,19 @@ declare const classificationSchema: z.ZodObject<{
9750
9756
  };
9751
9757
  channel?: string | undefined;
9752
9758
  preview?: string | undefined;
9759
+ chatId?: string | undefined;
9753
9760
  agentId?: string | undefined;
9754
9761
  confidence?: number | undefined;
9755
9762
  sender?: string | undefined;
9756
9763
  }, {
9757
9764
  path: string[];
9758
9765
  status: "pending" | "approved" | "rejected";
9759
- source: "user" | "llm" | "cache";
9766
+ source: "user" | "seat" | "llm" | "cache";
9760
9767
  ts: string;
9761
9768
  msgHash: string;
9762
9769
  channel?: string | undefined;
9763
9770
  preview?: string | undefined;
9771
+ chatId?: string | undefined;
9764
9772
  agentId?: string | undefined;
9765
9773
  confidence?: number | undefined;
9766
9774
  sender?: string | undefined;
@@ -10023,6 +10031,14 @@ declare class GraphStore {
10023
10031
  }): Classification | null;
10024
10032
  /** Read the last N classifications, newest first. Cheap enough for the admin UI. */
10025
10033
  readRecentClassifications(limit?: number): Classification[];
10034
+ /**
10035
+ * The path the most recent classification in a conversation was filed
10036
+ * under, or undefined. Used by tools running inside that conversation
10037
+ * (`agentx_wiki_query`) to bias retrieval toward articles on the same
10038
+ * branch. Bounded by age so a stale label from a day-old thread does not
10039
+ * steer an unrelated question.
10040
+ */
10041
+ latestPathForChat(agentId: string, channel: string, chatId: string, maxAgeMs?: number): string[] | undefined;
10026
10042
  /**
10027
10043
  * Hash that represents "this kind of message" for snap-to-path caching.
10028
10044
  *
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import{a as L}from"./chunk-C245D4BZ.js";import{a as St,b as kt,c as vt,d as xt,da as Tt,e as $t,ea as Mt,f as Ct,g as Pt,h as bt}from"./chunk-5ONI54E6.js";import"./chunk-CKBAVCGU.js";import"./chunk-BYJ3A55Y.js";import"./chunk-NMFXY3QU.js";import"./chunk-YBF2QVZ7.js";import"./chunk-LQRBGQIR.js";import"./chunk-ZVP6GB4G.js";import"./chunk-DQQ3BCJW.js";import"./chunk-2D3RYIDU.js";import"./chunk-EKJG63EJ.js";import{S as yt,l as lt,m as ct,n as dt,o as mt,p as pt}from"./chunk-K63IOY6A.js";import"./chunk-IU6L4V3A.js";import"./chunk-N6EWZGIX.js";import"./chunk-WP6KLXTP.js";import"./chunk-DP7RTNDM.js";import{A as D,B as z,C as V,a as ye,b as we,c as Se,d as Ae,e as Ie,f as Oe,g as Ne,h as He,i as je,j as Ue,k as _e,l as I,m as q,n as Be,o as qe,p as Ye,q as O,r as ze,s as Ve,t as Xe,u as x,v as Y,w as Qe,x as Ze,y as et,z as wt}from"./chunk-J7HA7ZTU.js";import{a as B,b as M,c as F}from"./chunk-SFQUP3BP.js";import"./chunk-FSYA4GNU.js";import"./chunk-2AXFYDWF.js";import"./chunk-AQASR6KR.js";import"./chunk-76Z4VHDX.js";import"./chunk-W4YR2RO2.js";import"./chunk-AFRHKA7B.js";import{s as ut}from"./chunk-OPUPVGYP.js";import"./chunk-YL223TEU.js";import"./chunk-KA2UIZXG.js";import"./chunk-GQ76J72O.js";import"./chunk-ILEC3IAV.js";import"./chunk-J3SOEFRE.js";import"./chunk-SIU6STB2.js";import"./chunk-X5C5W3E4.js";import"./chunk-P2RYUODK.js";import"./chunk-DWADWDMU.js";import"./chunk-23A5K5UW.js";import"./chunk-QD6IICK7.js";import"./chunk-VWTBHLMX.js";import"./chunk-2UYV24UD.js";import{a as gt,b as ft,e as ht}from"./chunk-535MLOSB.js";import"./chunk-4DRNEOOW.js";import"./chunk-64P4QHSZ.js";import"./chunk-V2TLFYKH.js";import"./chunk-TFVUOC2C.js";import"./chunk-2GIDVZ4H.js";import{j as it,k as rt,l as at}from"./chunk-BBIWAJRX.js";import"./chunk-FX3D7YON.js";import"./chunk-72SZI2UW.js";import"./chunk-FCPTN6NF.js";import"./chunk-QOYAX2FT.js";import{a as ke,b as ve,c as xe,d as $e,e as Ce,f as Pe,g as be,h as Te,i as Me,j as Re,l as Ee,m as Ge,o as T,p as W}from"./chunk-5MUJZODV.js";import{a as m,b as G}from"./chunk-7I7IEX4S.js";import{b as Fe,c as Le,d as A,e as De}from"./chunk-JHCI6XCJ.js";import{a as Ke,b as Je,e as We}from"./chunk-DSYKYZMT.js";import{u as tt,w as st,x as ot,z as nt}from"./chunk-DTX4I5LQ.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";W();G();import{execa as Rt}from"execa";import{existsSync as se,promises as X}from"fs";import S from"path";import ie from"node-fetch";async function Et(o,e){let t=o.split("/");if(t.length<2)throw new Error(`Invalid skill package ID: ${o}. Expected format: owner/repo`);let s=t[0],i=t[1],n=t[2];try{let a=n?`${s}/${i}/${n}`:`${s}/${i}`;await Rt("npx",["-y","skills","add",a],{cwd:e,stdio:"pipe"}),m.success(`Installed skill package: ${o}`);let r=S.resolve(e,".skills",`${s}_${i}`);if(se(r))return await ne(r,o);let d=S.resolve(e,".skills");return se(d)?await ne(d,o):[]}catch{return m.info(`Fetching skills directly from GitHub: ${s}/${i}`),await Gt(s,i,n,e)}}async function Gt(o,e,t,s){let i=[];try{let n=`https://api.github.com/repos/${o}/${e}/git/trees/main?recursive=1`,a=await ie(n);if(!a.ok)throw new Error(`GitHub API error: ${a.status}`);let d=(await a.json()).tree.filter(p=>p.type==="blob"&&p.path.endsWith("SKILL.md"));if(t){let p=d.find(f=>f.path.includes(`/${t}/`)||f.path===`${t}/SKILL.md`);if(p){let f=await oe(o,e,p.path,s);f&&i.push(f)}}else for(let p of d){let f=await oe(o,e,p.path,s);f&&i.push(f)}}catch(n){m.error(`Failed to fetch skills from GitHub: ${n.message}`)}return i}async function oe(o,e,t,s){try{let i=`https://raw.githubusercontent.com/${o}/${e}/main/${t}`,n=await ie(i);if(!n.ok)return null;let a=await n.text(),r=A(a);if(r){r.source="remote",r.packageId=`${o}/${e}`;let d=S.dirname(t),p=S.resolve(s,".skills",`${o}_${e}`,d);await X.mkdir(p,{recursive:!0}),await X.writeFile(S.resolve(p,"SKILL.md"),a),r.path=S.resolve(p,"SKILL.md")}return r}catch{return null}}async function ne(o,e){let t=[],{default:s}=await import("fast-glob"),i=await s.glob("**/SKILL.md",{cwd:o,deep:5});for(let n of i)try{let a=S.resolve(o,n),r=await X.readFile(a,"utf8"),d=A(r);d&&(d.source="remote",d.path=a,d.packageId=e,t.push(d))}catch{}return t}async function At(o,e,t,s,i){let n=["---",`name: ${o}`,`description: ${e}`];if(s?.length){n.push("tags:");for(let a of s)n.push(` - ${a}`)}if(i?.length){n.push("globs:");for(let a of i)n.push(` - ${a}`)}return n.push("---"),`${n.join(`
1
+ import{a as L}from"./chunk-C245D4BZ.js";import{a as St,b as kt,c as vt,d as xt,da as Tt,e as $t,ea as Mt,f as Ct,g as Pt,h as bt}from"./chunk-PHDS2HIN.js";import"./chunk-3IY2YWXP.js";import"./chunk-YBF2QVZ7.js";import"./chunk-BYJ3A55Y.js";import"./chunk-NMFXY3QU.js";import"./chunk-LQRBGQIR.js";import"./chunk-ZVP6GB4G.js";import"./chunk-YK7GFEGO.js";import"./chunk-AQASR6KR.js";import"./chunk-2D3RYIDU.js";import"./chunk-EKJG63EJ.js";import{S as yt,l as lt,m as ct,n as dt,o as mt,p as pt}from"./chunk-FBDIJEKL.js";import"./chunk-W7SEPJFH.js";import"./chunk-WP6KLXTP.js";import"./chunk-N6EWZGIX.js";import"./chunk-DP7RTNDM.js";import{A as D,B as z,C as V,a as ye,b as we,c as Se,d as Ae,e as Ie,f as Oe,g as Ne,h as He,i as je,j as Ue,k as _e,l as I,m as q,n as Be,o as qe,p as Ye,q as O,r as ze,s as Ve,t as Xe,u as x,v as Y,w as Qe,x as Ze,y as et,z as wt}from"./chunk-3RC4LO2G.js";import{a as B,b as M,c as F}from"./chunk-SFQUP3BP.js";import"./chunk-FSYA4GNU.js";import"./chunk-2AXFYDWF.js";import"./chunk-76Z4VHDX.js";import"./chunk-W4YR2RO2.js";import"./chunk-AFRHKA7B.js";import{s as ut}from"./chunk-OPUPVGYP.js";import"./chunk-YL223TEU.js";import"./chunk-VWTBHLMX.js";import"./chunk-SN5N2FPG.js";import{a as ht}from"./chunk-ML6CNRBK.js";import"./chunk-KA2UIZXG.js";import"./chunk-GQ76J72O.js";import"./chunk-ILEC3IAV.js";import"./chunk-J3SOEFRE.js";import"./chunk-SIU6STB2.js";import"./chunk-X5C5W3E4.js";import"./chunk-P2RYUODK.js";import"./chunk-DWADWDMU.js";import"./chunk-23A5K5UW.js";import"./chunk-QD6IICK7.js";import"./chunk-CECHWYJ2.js";import"./chunk-TLDQWTSW.js";import"./chunk-TFVUOC2C.js";import"./chunk-2GIDVZ4H.js";import"./chunk-QOYAX2FT.js";import{a as gt,b as ft}from"./chunk-WLMQ5JMX.js";import"./chunk-TTVVXBCM.js";import{j as it,k as rt,l as at}from"./chunk-BBIWAJRX.js";import"./chunk-FX3D7YON.js";import"./chunk-72SZI2UW.js";import"./chunk-FCPTN6NF.js";import{a as ke,b as ve,c as xe,d as $e,e as Ce,f as Pe,g as be,h as Te,i as Me,j as Re,l as Ee,m as Ge,o as T,p as W}from"./chunk-5MUJZODV.js";import{a as m,b as G}from"./chunk-7I7IEX4S.js";import{b as Fe,c as Le,d as A,e as De}from"./chunk-JHCI6XCJ.js";import{a as Ke,b as Je,e as We}from"./chunk-DSYKYZMT.js";import{u as tt,w as st,x as ot,z as nt}from"./chunk-DTX4I5LQ.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";W();G();import{execa as Rt}from"execa";import{existsSync as se,promises as X}from"fs";import S from"path";import ie from"node-fetch";async function Et(o,e){let t=o.split("/");if(t.length<2)throw new Error(`Invalid skill package ID: ${o}. Expected format: owner/repo`);let s=t[0],i=t[1],n=t[2];try{let a=n?`${s}/${i}/${n}`:`${s}/${i}`;await Rt("npx",["-y","skills","add",a],{cwd:e,stdio:"pipe"}),m.success(`Installed skill package: ${o}`);let r=S.resolve(e,".skills",`${s}_${i}`);if(se(r))return await ne(r,o);let d=S.resolve(e,".skills");return se(d)?await ne(d,o):[]}catch{return m.info(`Fetching skills directly from GitHub: ${s}/${i}`),await Gt(s,i,n,e)}}async function Gt(o,e,t,s){let i=[];try{let n=`https://api.github.com/repos/${o}/${e}/git/trees/main?recursive=1`,a=await ie(n);if(!a.ok)throw new Error(`GitHub API error: ${a.status}`);let d=(await a.json()).tree.filter(p=>p.type==="blob"&&p.path.endsWith("SKILL.md"));if(t){let p=d.find(f=>f.path.includes(`/${t}/`)||f.path===`${t}/SKILL.md`);if(p){let f=await oe(o,e,p.path,s);f&&i.push(f)}}else for(let p of d){let f=await oe(o,e,p.path,s);f&&i.push(f)}}catch(n){m.error(`Failed to fetch skills from GitHub: ${n.message}`)}return i}async function oe(o,e,t,s){try{let i=`https://raw.githubusercontent.com/${o}/${e}/main/${t}`,n=await ie(i);if(!n.ok)return null;let a=await n.text(),r=A(a);if(r){r.source="remote",r.packageId=`${o}/${e}`;let d=S.dirname(t),p=S.resolve(s,".skills",`${o}_${e}`,d);await X.mkdir(p,{recursive:!0}),await X.writeFile(S.resolve(p,"SKILL.md"),a),r.path=S.resolve(p,"SKILL.md")}return r}catch{return null}}async function ne(o,e){let t=[],{default:s}=await import("fast-glob"),i=await s.glob("**/SKILL.md",{cwd:o,deep:5});for(let n of i)try{let a=S.resolve(o,n),r=await X.readFile(a,"utf8"),d=A(r);d&&(d.source="remote",d.path=a,d.packageId=e,t.push(d))}catch{}return t}async function At(o,e,t,s,i){let n=["---",`name: ${o}`,`description: ${e}`];if(s?.length){n.push("tags:");for(let a of s)n.push(` - ${a}`)}if(i?.length){n.push("globs:");for(let a of i)n.push(` - ${a}`)}return n.push("---"),`${n.join(`
2
2
  `)}
3
3
 
4
4
  ${t}`}async function It(o){let{loadLocalSkills:e}=await import("./loader-YFVSSYSL.js");return e(o)}async function Ot(o,e,t,s,i){let a=[{role:"system",content:`You are a skill author for the Agent Skills ecosystem (skills.sh).