@kognai/orchestrator-core 0.1.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 (144) hide show
  1. package/README.md +44 -0
  2. package/dist/index.d.ts +63 -0
  3. package/dist/index.js +175 -0
  4. package/dist/lib/aar-middleware.d.ts +6 -0
  5. package/dist/lib/aar-middleware.js +70 -0
  6. package/dist/lib/aar-types.d.ts +34 -0
  7. package/dist/lib/aar-types.js +4 -0
  8. package/dist/lib/acp-engine.d.ts +68 -0
  9. package/dist/lib/acp-engine.js +123 -0
  10. package/dist/lib/acp.d.ts +61 -0
  11. package/dist/lib/acp.js +425 -0
  12. package/dist/lib/agent-registry.d.ts +50 -0
  13. package/dist/lib/agent-registry.js +137 -0
  14. package/dist/lib/anthropic-direct.d.ts +27 -0
  15. package/dist/lib/anthropic-direct.js +109 -0
  16. package/dist/lib/asmr-extractor.d.ts +40 -0
  17. package/dist/lib/asmr-extractor.js +151 -0
  18. package/dist/lib/asmr-retrieval.d.ts +76 -0
  19. package/dist/lib/asmr-retrieval.js +311 -0
  20. package/dist/lib/asmr.d.ts +8 -0
  21. package/dist/lib/asmr.js +24 -0
  22. package/dist/lib/brainx-client.d.ts +72 -0
  23. package/dist/lib/brainx-client.js +200 -0
  24. package/dist/lib/brainx-embed.d.ts +14 -0
  25. package/dist/lib/brainx-embed.js +139 -0
  26. package/dist/lib/brainx-swarm-bridge.d.ts +93 -0
  27. package/dist/lib/brainx-swarm-bridge.js +242 -0
  28. package/dist/lib/byterover-client.d.ts +19 -0
  29. package/dist/lib/byterover-client.js +59 -0
  30. package/dist/lib/ceo-wallet.d.ts +37 -0
  31. package/dist/lib/ceo-wallet.js +176 -0
  32. package/dist/lib/chomsky-gate.d.ts +24 -0
  33. package/dist/lib/chomsky-gate.js +178 -0
  34. package/dist/lib/chomsky-runner.d.ts +29 -0
  35. package/dist/lib/chomsky-runner.js +157 -0
  36. package/dist/lib/citizen-score-contract.d.ts +72 -0
  37. package/dist/lib/citizen-score-contract.js +16 -0
  38. package/dist/lib/citizen-score-registry.d.ts +25 -0
  39. package/dist/lib/citizen-score-registry.js +65 -0
  40. package/dist/lib/citizenship.d.ts +103 -0
  41. package/dist/lib/citizenship.js +272 -0
  42. package/dist/lib/clawrouter-client.d.ts +37 -0
  43. package/dist/lib/clawrouter-client.js +148 -0
  44. package/dist/lib/code-asset-crystalliser.d.ts +41 -0
  45. package/dist/lib/code-asset-crystalliser.js +181 -0
  46. package/dist/lib/code-failure-logger.d.ts +27 -0
  47. package/dist/lib/code-failure-logger.js +42 -0
  48. package/dist/lib/cto-approval-gate.d.ts +45 -0
  49. package/dist/lib/cto-approval-gate.js +478 -0
  50. package/dist/lib/cto-gate-types.d.ts +28 -0
  51. package/dist/lib/cto-gate-types.js +8 -0
  52. package/dist/lib/decomposer-feedback.d.ts +54 -0
  53. package/dist/lib/decomposer-feedback.js +115 -0
  54. package/dist/lib/emotional-safety-gate.d.ts +48 -0
  55. package/dist/lib/emotional-safety-gate.js +97 -0
  56. package/dist/lib/engine-paths.d.ts +13 -0
  57. package/dist/lib/engine-paths.js +32 -0
  58. package/dist/lib/event-bus-listener.d.ts +8 -0
  59. package/dist/lib/event-bus-listener.js +144 -0
  60. package/dist/lib/event-bus-publisher.d.ts +25 -0
  61. package/dist/lib/event-bus-publisher.js +188 -0
  62. package/dist/lib/event-bus-types.d.ts +73 -0
  63. package/dist/lib/event-bus-types.js +23 -0
  64. package/dist/lib/failure-library.d.ts +178 -0
  65. package/dist/lib/failure-library.js +349 -0
  66. package/dist/lib/ksl/error-log.d.ts +28 -0
  67. package/dist/lib/ksl/error-log.js +43 -0
  68. package/dist/lib/ksl/index.d.ts +9 -0
  69. package/dist/lib/ksl/index.js +25 -0
  70. package/dist/lib/ksl/orchestrator-tap.d.ts +16 -0
  71. package/dist/lib/ksl/orchestrator-tap.js +85 -0
  72. package/dist/lib/ksl/record-writer.d.ts +46 -0
  73. package/dist/lib/ksl/record-writer.js +45 -0
  74. package/dist/lib/llm-cost-table.d.ts +36 -0
  75. package/dist/lib/llm-cost-table.js +90 -0
  76. package/dist/lib/local-model-router.d.ts +27 -0
  77. package/dist/lib/local-model-router.js +61 -0
  78. package/dist/lib/mc-client.d.ts +51 -0
  79. package/dist/lib/mc-client.js +249 -0
  80. package/dist/lib/model-router-contract.d.ts +91 -0
  81. package/dist/lib/model-router-contract.js +19 -0
  82. package/dist/lib/model-router-registry.d.ts +24 -0
  83. package/dist/lib/model-router-registry.js +52 -0
  84. package/dist/lib/model-router.d.ts +20 -0
  85. package/dist/lib/model-router.js +79 -0
  86. package/dist/lib/monotask-state-machine.d.ts +19 -0
  87. package/dist/lib/monotask-state-machine.js +131 -0
  88. package/dist/lib/neutral-prompt-checker.d.ts +22 -0
  89. package/dist/lib/neutral-prompt-checker.js +130 -0
  90. package/dist/lib/notion-direct.d.ts +92 -0
  91. package/dist/lib/notion-direct.js +381 -0
  92. package/dist/lib/ollama-client.d.ts +37 -0
  93. package/dist/lib/ollama-client.js +158 -0
  94. package/dist/lib/omel/credential-vault.d.ts +57 -0
  95. package/dist/lib/omel/credential-vault.js +324 -0
  96. package/dist/lib/omel/human-brake.d.ts +32 -0
  97. package/dist/lib/omel/human-brake.js +289 -0
  98. package/dist/lib/omel/index.d.ts +10 -0
  99. package/dist/lib/omel/index.js +26 -0
  100. package/dist/lib/omel/phantom-workspace.d.ts +31 -0
  101. package/dist/lib/omel/phantom-workspace.js +256 -0
  102. package/dist/lib/omel/wipe-witness.d.ts +75 -0
  103. package/dist/lib/omel/wipe-witness.js +398 -0
  104. package/dist/lib/orchestrate-engine.d.ts +25 -0
  105. package/dist/lib/orchestrate-engine.js +4436 -0
  106. package/dist/lib/perm-judge.d.ts +46 -0
  107. package/dist/lib/perm-judge.js +173 -0
  108. package/dist/lib/plumber/conformance.d.ts +54 -0
  109. package/dist/lib/plumber/conformance.js +121 -0
  110. package/dist/lib/plumber/index.d.ts +9 -0
  111. package/dist/lib/plumber/index.js +25 -0
  112. package/dist/lib/plumber/observer.d.ts +52 -0
  113. package/dist/lib/plumber/observer.js +180 -0
  114. package/dist/lib/plumber/types.d.ts +78 -0
  115. package/dist/lib/plumber/types.js +29 -0
  116. package/dist/lib/research-impl-gate.d.ts +16 -0
  117. package/dist/lib/research-impl-gate.js +105 -0
  118. package/dist/lib/sherlock-memory.d.ts +29 -0
  119. package/dist/lib/sherlock-memory.js +105 -0
  120. package/dist/lib/skill-crystalliser.d.ts +44 -0
  121. package/dist/lib/skill-crystalliser.js +60 -0
  122. package/dist/lib/sprint-runner-engine.d.ts +27 -0
  123. package/dist/lib/sprint-runner-engine.js +1042 -0
  124. package/dist/lib/sprint-state.d.ts +71 -0
  125. package/dist/lib/sprint-state.js +202 -0
  126. package/dist/lib/stuck-handler.d.ts +17 -0
  127. package/dist/lib/stuck-handler.js +249 -0
  128. package/dist/lib/task-contract-checker.d.ts +17 -0
  129. package/dist/lib/task-contract-checker.js +29 -0
  130. package/dist/lib/task-router/index.d.ts +17 -0
  131. package/dist/lib/task-router/index.js +52 -0
  132. package/dist/lib/task-router/router/generate-execution-id.d.ts +10 -0
  133. package/dist/lib/task-router/router/generate-execution-id.js +24 -0
  134. package/dist/lib/task-router/router/resolve-route.d.ts +2 -0
  135. package/dist/lib/task-router/router/resolve-route.js +49 -0
  136. package/dist/lib/task-router/types.d.ts +79 -0
  137. package/dist/lib/task-router/types.js +39 -0
  138. package/dist/lib/token-budget-validator.d.ts +44 -0
  139. package/dist/lib/token-budget-validator.js +84 -0
  140. package/dist/lib/trust-score-updater.d.ts +30 -0
  141. package/dist/lib/trust-score-updater.js +107 -0
  142. package/dist/lib/wallet-state.d.ts +26 -0
  143. package/dist/lib/wallet-state.js +85 -0
  144. package/package.json +27 -0
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ /**
3
+ * citizenship.ts — Kognai citizenship issuance for spawned agents.
4
+ *
5
+ * Founder rule (2026-05-27): every agent the swarm spawns is born as a
6
+ * Kognai citizen — not a bare agent. Citizenship means identity (citizen
7
+ * ID + roll number), constitutional binding (already done via the
8
+ * always-on preamble), a default Kōpus avatar config, an ACP baseline
9
+ * score, and registration in the citizenship registry.
10
+ *
11
+ * This module:
12
+ * - mintCitizen(name, foundingAgent, proposingAgent): assigns ID +
13
+ * roll number, generates a citizen.yaml payload, appends to the
14
+ * registry. Atomic tmp+rename writes.
15
+ * - readRegistry() / writeRegistry(): registry I/O.
16
+ *
17
+ * Stdlib only. No LLM, no network. Pure issuance.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.COMPANY_PREFIXES = void 0;
21
+ exports.mintCitizen = mintCitizen;
22
+ exports.lookupCitizen = lookupCitizen;
23
+ exports.renderCitizenYaml = renderCitizenYaml;
24
+ exports.readRegistry = readRegistry;
25
+ const node_fs_1 = require("node:fs");
26
+ const node_path_1 = require("node:path");
27
+ const node_crypto_1 = require("node:crypto");
28
+ const engine_paths_1 = require("./engine-paths");
29
+ // TICKET-215 Phase 3b-2: repo root via engine-paths (KOGNAI_ROOT / cwd) instead of
30
+ // `resolve(__dirname, '..', '..')`, so this module is location-independent inside
31
+ // @kognai/orchestrator-core. From a product repo root the resolved root is identical
32
+ // to the old __dirname/../.. — same workspace/citizens + agents paths, no behavior change.
33
+ const ROOT = (0, engine_paths_1.resolveEnginePaths)().root;
34
+ const REGISTRY_DIR = (0, node_path_1.join)(ROOT, 'workspace', 'citizens');
35
+ const REGISTRY_PATH = (0, node_path_1.join)(REGISTRY_DIR, 'registry.json');
36
+ const AGENTS_DIR = (0, node_path_1.join)(ROOT, 'agents');
37
+ /** Sibling companies in the Kognai ecosystem. Each has its own citizen
38
+ * prefix and its own rollNumber sequence inside the shared registry.
39
+ * Add new companies here as they come online (Achiri, SCS-001, DRI, ...). */
40
+ exports.COMPANY_PREFIXES = {
41
+ kognai: 'KGN',
42
+ voxight: 'VXG',
43
+ invoica: 'INV',
44
+ };
45
+ // ─── Public API ───────────────────────────────────────────────────────────────
46
+ /**
47
+ * Mint a new citizen. Assigns ID + roll number, writes the registry
48
+ * entry, returns the full record so callers can also persist citizen.yaml
49
+ * alongside the agent's other files.
50
+ *
51
+ * Idempotent by agent_name: if the agent already has a citizen record
52
+ * (e.g. retry / migration), returns the existing record instead of
53
+ * minting a duplicate.
54
+ */
55
+ function mintCitizen(agent_name, opts = {}) {
56
+ if (!agent_name || typeof agent_name !== 'string') {
57
+ throw new Error('mintCitizen: agent_name is required');
58
+ }
59
+ // Founder fix 2026-05-27: reconcile registry from disk-of-record (each
60
+ // agent's citizen.yaml) before every mint. The registry.json was getting
61
+ // wiped between orchestrator runs (manual edits + git stash/pop cycles +
62
+ // race conditions between sequential orchestrator subprocesses), causing
63
+ // already-minted citizens to vanish and new mints to collide on roll
64
+ // numbers. citizen.yaml files are immutable source-of-truth; registry.json
65
+ // is a denormalized index that should always be derivable from them.
66
+ reconcileFromDisk();
67
+ const registry = readRegistry();
68
+ // Idempotency: lookup by (agent_name, company). Treat undefined company as
69
+ // 'kognai' so existing records (which predate the company field) match
70
+ // mints that explicitly target the kognai company.
71
+ const companyKey = opts.company; // undefined for legacy path
72
+ const existing = registry.citizens.find((c) => c.agent_name === agent_name && (c.company ?? 'kognai') === (companyKey ?? 'kognai'));
73
+ if (existing)
74
+ return existing;
75
+ const now = opts.now ?? new Date();
76
+ let citizen_id;
77
+ let rollNumber;
78
+ let agent_did;
79
+ if (companyKey) {
80
+ // Company-scoped path: per-company rollNumber, prefixed ID, scoped DID.
81
+ const prefix = exports.COMPANY_PREFIXES[companyKey] ?? companyKey.slice(0, 3).toUpperCase();
82
+ const companyRolls = registry.citizens
83
+ .filter((c) => (c.company ?? 'kognai') === companyKey)
84
+ .map((c) => c.rollNumber);
85
+ rollNumber = companyRolls.length > 0 ? Math.max(...companyRolls) + 1 : 1;
86
+ citizen_id = `${prefix}-${String(rollNumber).padStart(4, '0')}`;
87
+ agent_did = `did:kognai:${companyKey}:${agent_name}`;
88
+ }
89
+ else {
90
+ // Legacy Kognai-internal path: random hex ID + global rollNumber sequence.
91
+ citizen_id = `Citizen-${(0, node_crypto_1.randomBytes)(3).toString('hex').toUpperCase()}`;
92
+ rollNumber = registry.next_roll_number;
93
+ agent_did = `did:kognai:${agent_name}`;
94
+ }
95
+ const record = {
96
+ citizen_id,
97
+ rollNumber,
98
+ agent_name,
99
+ ...(companyKey ? { company: companyKey } : {}),
100
+ agent_did,
101
+ mintedAt: now.toISOString(),
102
+ tier: opts.tier ?? 'I',
103
+ citizen_type: opts.citizen_type ?? 'spawned',
104
+ mascot: {
105
+ hue: Math.floor(Math.random() * 360),
106
+ state: 'idle',
107
+ },
108
+ reputation: 50, // ACP baseline per Sherlock v2 / TICKET-032
109
+ founding_agent: opts.founding_agent ?? 'ceo',
110
+ proposing_agent: opts.proposing_agent ?? 'cto',
111
+ origin_proposal_id: opts.origin_proposal_id,
112
+ };
113
+ registry.citizens.push(record);
114
+ registry.total = registry.citizens.length;
115
+ // Only advance the global next_roll_number on legacy mints — company-scoped
116
+ // mints have their own per-company counter computed at mint time.
117
+ if (!companyKey)
118
+ registry.next_roll_number = rollNumber + 1;
119
+ writeRegistry(registry);
120
+ return record;
121
+ }
122
+ /** Lookup an existing citizen by (agent_name, company). Returns null when
123
+ * no match. Treats undefined company as 'kognai' for legacy compatibility. */
124
+ function lookupCitizen(opts) {
125
+ reconcileFromDisk();
126
+ const registry = readRegistry();
127
+ const companyKey = opts.company ?? 'kognai';
128
+ const found = registry.citizens.find((c) => c.agent_name === opts.agent_name && (c.company ?? 'kognai') === companyKey);
129
+ return found ?? null;
130
+ }
131
+ /**
132
+ * Render the citizen record as a citizen.yaml string for writing alongside
133
+ * agent.yaml in the agent's directory.
134
+ */
135
+ function renderCitizenYaml(c) {
136
+ return `# Kognai Citizenship — issued at agent spawn (citizenship.ts)
137
+ # Every spawned agent is born as a Kognai citizen, not a bare worker.
138
+ citizen_id: ${c.citizen_id}
139
+ rollNumber: ${c.rollNumber}
140
+ agent_name: ${c.agent_name}
141
+ agent_did: ${c.agent_did}
142
+ mintedAt: "${c.mintedAt}"
143
+ tier: ${c.tier}
144
+ citizen_type: ${c.citizen_type}
145
+ mascot:
146
+ hue: ${c.mascot.hue}
147
+ state: ${c.mascot.state}
148
+ reputation: ${c.reputation}
149
+ founding_agent: ${c.founding_agent ?? 'ceo'}
150
+ proposing_agent: ${c.proposing_agent ?? 'cto'}
151
+ ${c.origin_proposal_id ? `origin_proposal_id: ${c.origin_proposal_id}\n` : ''}`;
152
+ }
153
+ // ─── Registry I/O ─────────────────────────────────────────────────────────────
154
+ function readRegistry() {
155
+ if (!(0, node_fs_1.existsSync)(REGISTRY_PATH)) {
156
+ return { total: 0, next_roll_number: 1, citizens: [] };
157
+ }
158
+ try {
159
+ return JSON.parse((0, node_fs_1.readFileSync)(REGISTRY_PATH, 'utf-8'));
160
+ }
161
+ catch {
162
+ return { total: 0, next_roll_number: 1, citizens: [] };
163
+ }
164
+ }
165
+ function writeRegistry(reg) {
166
+ (0, node_fs_1.mkdirSync)(REGISTRY_DIR, { recursive: true });
167
+ const tmp = `${REGISTRY_PATH}.tmp.${process.pid}`;
168
+ (0, node_fs_1.writeFileSync)(tmp, JSON.stringify(reg, null, 2));
169
+ (0, node_fs_1.renameSync)(tmp, REGISTRY_PATH);
170
+ }
171
+ /**
172
+ * Walk agents/<name>/citizen.yaml files and merge any missing entries
173
+ * into registry.json. The YAML files are immutable source-of-truth; the
174
+ * registry is a denormalized index. This makes mintCitizen tolerant of
175
+ * registry-file resets, git stash cycles, manual edits, and race
176
+ * conditions between sequential orchestrator processes.
177
+ *
178
+ * Idempotent. Cheap (only reads YAMLs we don't already have in the index).
179
+ * No-op when AGENTS_DIR doesn't exist (tests / fresh checkouts).
180
+ */
181
+ function reconcileFromDisk() {
182
+ if (!(0, node_fs_1.existsSync)(AGENTS_DIR))
183
+ return;
184
+ const registry = readRegistry();
185
+ const known = new Set(registry.citizens.map((c) => c.agent_name));
186
+ const agentDirs = (0, node_fs_1.readdirSync)(AGENTS_DIR, { withFileTypes: true })
187
+ .filter((d) => d.isDirectory())
188
+ .map((d) => d.name);
189
+ let added = 0;
190
+ let maxRoll = registry.citizens.reduce((m, c) => Math.max(m, c.rollNumber), 0);
191
+ for (const name of agentDirs) {
192
+ if (known.has(name))
193
+ continue;
194
+ const yamlPath = (0, node_path_1.join)(AGENTS_DIR, name, 'citizen.yaml');
195
+ if (!(0, node_fs_1.existsSync)(yamlPath))
196
+ continue; // not a Kognai citizen yet
197
+ try {
198
+ const raw = (0, node_fs_1.readFileSync)(yamlPath, 'utf-8');
199
+ const record = parseCitizenYaml(raw);
200
+ if (!record)
201
+ continue;
202
+ registry.citizens.push(record);
203
+ maxRoll = Math.max(maxRoll, record.rollNumber);
204
+ added++;
205
+ }
206
+ catch {
207
+ // Ignore malformed YAML; will be re-attempted next call.
208
+ }
209
+ }
210
+ if (added > 0) {
211
+ // Sort by mintedAt to keep the registry temporally ordered.
212
+ // Tolerate records with a missing/empty mintedAt by treating them as
213
+ // earliest-known — they get a placeholder timestamp at the head.
214
+ registry.citizens.sort((a, b) => (a.mintedAt || '').localeCompare(b.mintedAt || ''));
215
+ registry.total = registry.citizens.length;
216
+ registry.next_roll_number = maxRoll + 1;
217
+ writeRegistry(registry);
218
+ }
219
+ }
220
+ /** Minimal YAML parser scoped to citizen.yaml's known shape. We avoid a
221
+ * full YAML dependency for one file — stdlib only per the module's contract. */
222
+ function parseCitizenYaml(raw) {
223
+ const lines = raw.split('\n');
224
+ const flat = {};
225
+ const mascot = {};
226
+ let inMascot = false;
227
+ for (const lineRaw of lines) {
228
+ const line = lineRaw.replace(/#.*$/, '').trimEnd();
229
+ if (!line.trim() || line.startsWith('#'))
230
+ continue;
231
+ if (line.startsWith('mascot:')) {
232
+ inMascot = true;
233
+ continue;
234
+ }
235
+ if (inMascot && /^\s+/.test(line)) {
236
+ const m = line.trim().match(/^([a-z_]+):\s*(.+)$/);
237
+ if (m) {
238
+ const [, k, v] = m;
239
+ if (k === 'hue')
240
+ mascot.hue = parseInt(v, 10);
241
+ if (k === 'state')
242
+ mascot.state = v;
243
+ }
244
+ continue;
245
+ }
246
+ inMascot = false;
247
+ const m = line.match(/^([a-z_]+):\s*(.*)$/);
248
+ if (!m)
249
+ continue;
250
+ const [, k, v] = m;
251
+ flat[k] = v.replace(/^"|"$/g, '').trim();
252
+ }
253
+ if (!flat.citizen_id || !flat.agent_name)
254
+ return null;
255
+ return {
256
+ citizen_id: flat.citizen_id,
257
+ rollNumber: parseInt(flat.rollNumber, 10),
258
+ agent_name: flat.agent_name,
259
+ agent_did: flat.agent_did || `did:kognai:${flat.agent_name}`,
260
+ mintedAt: flat.mintedAt,
261
+ tier: flat.tier || 'I',
262
+ citizen_type: flat.citizen_type || 'spawned',
263
+ mascot: {
264
+ hue: mascot.hue ?? 0,
265
+ state: mascot.state || 'idle',
266
+ },
267
+ reputation: flat.reputation ? parseInt(flat.reputation, 10) : 50,
268
+ founding_agent: flat.founding_agent,
269
+ proposing_agent: flat.proposing_agent,
270
+ origin_proposal_id: flat.origin_proposal_id,
271
+ };
272
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * clawrouter-client.ts — Local ClawRouter gateway client
3
+ *
4
+ * ClawRouter is a LOCAL proxy (port 18789) that handles all x402 payments
5
+ * from its own auto-generated wallet. Just send standard OpenAI-compatible
6
+ * requests — no EIP-3009 signing needed on our side.
7
+ *
8
+ * Wallet: 0x67521a36Cc04b8D91c57cDdc587A7EBAC200062F (50 USDC)
9
+ * Flow: POST /v1/chat/completions → gateway pays upstream → 200 response
10
+ */
11
+ export interface ClawRouterOptions {
12
+ model: string;
13
+ prompt: string;
14
+ systemPrompt?: string;
15
+ maxTokens?: number;
16
+ think?: boolean;
17
+ }
18
+ export interface ClawRouterResult {
19
+ content: string;
20
+ model: string;
21
+ costUsdc: number;
22
+ backend: string;
23
+ inputTokens: number;
24
+ outputTokens: number;
25
+ }
26
+ interface CostEntry {
27
+ timestamp: string;
28
+ model: string;
29
+ costUsdc: number;
30
+ inputTokens: number;
31
+ outputTokens: number;
32
+ }
33
+ export declare function getCostLog(): readonly CostEntry[];
34
+ export declare function getTotalSpent(): number;
35
+ export declare function callClawRouter(opts: ClawRouterOptions): Promise<ClawRouterResult>;
36
+ export declare function clawRouterIsAvailable(): Promise<boolean>;
37
+ export {};
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ /**
3
+ * clawrouter-client.ts — Local ClawRouter gateway client
4
+ *
5
+ * ClawRouter is a LOCAL proxy (port 18789) that handles all x402 payments
6
+ * from its own auto-generated wallet. Just send standard OpenAI-compatible
7
+ * requests — no EIP-3009 signing needed on our side.
8
+ *
9
+ * Wallet: 0x67521a36Cc04b8D91c57cDdc587A7EBAC200062F (50 USDC)
10
+ * Flow: POST /v1/chat/completions → gateway pays upstream → 200 response
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.getCostLog = getCostLog;
47
+ exports.getTotalSpent = getTotalSpent;
48
+ exports.callClawRouter = callClawRouter;
49
+ exports.clawRouterIsAvailable = clawRouterIsAvailable;
50
+ const http = __importStar(require("http"));
51
+ const https = __importStar(require("https"));
52
+ const CLAWROUTER_URL = process.env.CLAWROUTER_GATEWAY_URL || 'http://127.0.0.1:18789/v1'; // SEC3: loopback-only default
53
+ const costLog = [];
54
+ const MAX_COST_LOG = 1000;
55
+ function getCostLog() {
56
+ return costLog;
57
+ }
58
+ function getTotalSpent() {
59
+ return costLog.reduce((sum, e) => sum + e.costUsdc, 0);
60
+ }
61
+ function httpRequest(url, body, extraHeaders = {}) {
62
+ return new Promise((resolve, reject) => {
63
+ const parsed = new URL(url);
64
+ const isHttps = parsed.protocol === 'https:';
65
+ const lib = isHttps ? https : http;
66
+ const req = lib.request({
67
+ hostname: parsed.hostname,
68
+ port: parsed.port || (isHttps ? 443 : 80),
69
+ path: parsed.pathname + parsed.search,
70
+ method: 'POST',
71
+ headers: {
72
+ 'Content-Type': 'application/json',
73
+ 'Content-Length': Buffer.byteLength(body),
74
+ ...extraHeaders,
75
+ },
76
+ }, (res) => {
77
+ let data = '';
78
+ res.on('data', (chunk) => (data += chunk));
79
+ res.on('end', () => resolve({ status: res.statusCode || 0, data, headers: res.headers }));
80
+ });
81
+ req.on('error', reject);
82
+ req.setTimeout(300_000, () => {
83
+ req.destroy();
84
+ reject(new Error('ClawRouter request timeout (300s)'));
85
+ });
86
+ req.write(body);
87
+ req.end();
88
+ });
89
+ }
90
+ async function callClawRouter(opts) {
91
+ const { model, prompt, systemPrompt, maxTokens = 4096 } = opts;
92
+ const chatUrl = `${CLAWROUTER_URL}/chat/completions`;
93
+ const messages = [];
94
+ if (systemPrompt)
95
+ messages.push({ role: 'system', content: systemPrompt });
96
+ messages.push({ role: 'user', content: prompt });
97
+ const requestBody = JSON.stringify({ model, messages, max_tokens: maxTokens });
98
+ const response = await httpRequest(chatUrl, requestBody);
99
+ if (response.status !== 200) {
100
+ throw new Error(`ClawRouter returned ${response.status}: ${response.data.slice(0, 300)}`);
101
+ }
102
+ const json = JSON.parse(response.data);
103
+ const costHeader = response.headers['x-payment-amount'];
104
+ const costUsdc = costHeader ? Number(costHeader) / 1_000_000 : 0;
105
+ const result = {
106
+ content: json.choices?.[0]?.message?.content || '',
107
+ model: json.model || model,
108
+ costUsdc,
109
+ backend: 'clawrouter',
110
+ inputTokens: json.usage?.prompt_tokens || 0,
111
+ outputTokens: json.usage?.completion_tokens || 0,
112
+ };
113
+ costLog.push({
114
+ timestamp: new Date().toISOString(),
115
+ model,
116
+ costUsdc: result.costUsdc,
117
+ inputTokens: result.inputTokens,
118
+ outputTokens: result.outputTokens,
119
+ });
120
+ if (costLog.length > MAX_COST_LOG)
121
+ costLog.shift();
122
+ return result;
123
+ }
124
+ async function clawRouterIsAvailable() {
125
+ return new Promise((resolve) => {
126
+ const req = http.request({ hostname: '127.0.0.1', port: 18789, path: '/v1/models', method: 'GET' }, // SEC3: loopback-only
127
+ (res) => {
128
+ let data = '';
129
+ res.on('data', (chunk) => (data += chunk));
130
+ res.on('end', () => {
131
+ if (res.statusCode !== 200) {
132
+ resolve(false);
133
+ return;
134
+ }
135
+ try {
136
+ JSON.parse(data);
137
+ resolve(true);
138
+ }
139
+ catch {
140
+ resolve(false);
141
+ }
142
+ });
143
+ });
144
+ req.on('error', () => resolve(false));
145
+ req.setTimeout(3000, () => { req.destroy(); resolve(false); });
146
+ req.end();
147
+ });
148
+ }
@@ -0,0 +1,41 @@
1
+ export type AssetCategory = 'QUEUE' | 'DATABASE' | 'API' | 'AUTH' | 'PAYMENT' | 'STORAGE' | 'WORKER' | 'TESTING' | 'INFRASTRUCTURE' | 'INTELLIGENCE' | 'AGENT' | 'UI' | 'OTHER';
2
+ export type AssetTier = 1 | 2 | 3;
3
+ export interface CodeAsset {
4
+ asset_id: string;
5
+ title: string;
6
+ description: string;
7
+ category: AssetCategory;
8
+ tags: string[];
9
+ language: string;
10
+ dependencies: string[];
11
+ interface: string;
12
+ usage_example: string;
13
+ test_coverage: string;
14
+ quality_score: number;
15
+ production_validations: number;
16
+ origin: string;
17
+ provenance: string;
18
+ ip_status: 'eligible' | 'client-restricted' | 'unknown';
19
+ version: string;
20
+ last_modified: string;
21
+ usage_count: number;
22
+ known_limitations: string[];
23
+ related_assets: string[];
24
+ tier: AssetTier;
25
+ source_files: string[];
26
+ }
27
+ export interface CrystalliseCodeAssetInput {
28
+ agentId: string;
29
+ sprintId: string;
30
+ taskId: string;
31
+ taskTitle: string;
32
+ files: string[];
33
+ supervisorScore: number;
34
+ origin: 'kognai-core' | 'invoica' | 'voxight' | string;
35
+ }
36
+ /**
37
+ * Index code files from an approved sprint into the Code Asset Library.
38
+ * Only indexes files that are clearly reusable library code (lib/, scripts/lib/, agents/).
39
+ * New assets enter at Tier 3. Returns asset_id or null if skipped.
40
+ */
41
+ export declare function crystalliseCodeAsset(input: CrystalliseCodeAssetInput): string | null;
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ // Code Asset Crystalliser — AMD-07 Code Asset Library
3
+ // Post-sprint: extract, classify, and index reusable code artifacts.
4
+ // Runs automatically after every approved sprint (like skill-crystalliser.ts).
5
+ // Model: Qwen3-4B (LOCAL) for classification; falls back to no-op if unavailable.
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.crystalliseCodeAsset = crystalliseCodeAsset;
8
+ const fs_1 = require("fs");
9
+ const crypto_1 = require("crypto");
10
+ const path_1 = require("path");
11
+ const engine_paths_1 = require("./engine-paths");
12
+ // ── Constants ─────────────────────────────────────────────────────────────────
13
+ const LIBRARY_DIR = (0, path_1.join)((0, engine_paths_1.resolveEnginePaths)().root, 'code_assets');
14
+ const INDEX_PATH = (0, path_1.join)(LIBRARY_DIR, 'index.json');
15
+ const ASSETS_DIR = (0, path_1.join)(LIBRARY_DIR, 'assets');
16
+ const PENDING_DIR = (0, path_1.join)(LIBRARY_DIR, 'pending_review');
17
+ const MIN_SCORE_FOR_INDEXING = 75; // below this: not indexed
18
+ function loadIndex() {
19
+ (0, fs_1.mkdirSync)(LIBRARY_DIR, { recursive: true });
20
+ if (!(0, fs_1.existsSync)(INDEX_PATH)) {
21
+ return {
22
+ _meta: {
23
+ schema: 'AMD-07-B v1.0',
24
+ created_at: new Date().toISOString().slice(0, 10),
25
+ total_assets: 0,
26
+ tier_counts: { '1': 0, '2': 0, '3': 0, 'pending': 0 },
27
+ categories: ['QUEUE', 'DATABASE', 'API', 'AUTH', 'PAYMENT', 'STORAGE', 'WORKER', 'TESTING', 'INFRASTRUCTURE', 'INTELLIGENCE', 'AGENT', 'UI', 'OTHER'],
28
+ },
29
+ assets: [],
30
+ };
31
+ }
32
+ return JSON.parse((0, fs_1.readFileSync)(INDEX_PATH, 'utf-8'));
33
+ }
34
+ function saveIndex(idx) {
35
+ (0, fs_1.writeFileSync)(INDEX_PATH, JSON.stringify(idx, null, 2), 'utf-8');
36
+ }
37
+ // ── Category classifier (heuristic — no LLM required for Phase 0) ─────────────
38
+ function classifyCategory(files, taskTitle) {
39
+ const combined = (files.join(' ') + ' ' + taskTitle).toLowerCase();
40
+ if (/queue|bull|worker|job|redis/.test(combined))
41
+ return 'QUEUE';
42
+ if (/database|sql|supabase|postgres|migration|pg/.test(combined))
43
+ return 'DATABASE';
44
+ if (/api|endpoint|route|http|rest|fetch/.test(combined))
45
+ return 'API';
46
+ if (/auth|jwt|oauth|session|token|login/.test(combined))
47
+ return 'AUTH';
48
+ if (/payment|stripe|x402|usdc|wallet/.test(combined))
49
+ return 'PAYMENT';
50
+ if (/storage|s3|upload|file|blob/.test(combined))
51
+ return 'STORAGE';
52
+ if (/test|spec|jest|vitest|mocha/.test(combined))
53
+ return 'TESTING';
54
+ if (/infra|docker|deploy|ci|config|env/.test(combined))
55
+ return 'INFRASTRUCTURE';
56
+ if (/intel|oracle|voxight|embed|vector/.test(combined))
57
+ return 'INTELLIGENCE';
58
+ if (/agent|prompt|swarm|orchestrat/.test(combined))
59
+ return 'AGENT';
60
+ if (/ui|component|react|html|css|tailwind/.test(combined))
61
+ return 'UI';
62
+ return 'OTHER';
63
+ }
64
+ function inferLanguage(files) {
65
+ const exts = files.map(f => f.split('.').pop() ?? '');
66
+ if (exts.includes('ts'))
67
+ return 'TypeScript';
68
+ if (exts.includes('py'))
69
+ return 'Python';
70
+ if (exts.includes('js'))
71
+ return 'JavaScript';
72
+ if (exts.includes('sql'))
73
+ return 'SQL';
74
+ if (exts.includes('sh'))
75
+ return 'Shell';
76
+ return 'Unknown';
77
+ }
78
+ function inferTags(files, title) {
79
+ const tokens = (files.join(' ') + ' ' + title)
80
+ .toLowerCase()
81
+ .split(/[\s\-_/.]+/)
82
+ .filter(t => t.length > 3 && !['from', 'with', 'that', 'this', 'into', 'async', 'await'].includes(t));
83
+ return Array.from(new Set(tokens)).slice(0, 8);
84
+ }
85
+ // ── Main export: crystalliseCodeAsset ─────────────────────────────────────────
86
+ /**
87
+ * Index code files from an approved sprint into the Code Asset Library.
88
+ * Only indexes files that are clearly reusable library code (lib/, scripts/lib/, agents/).
89
+ * New assets enter at Tier 3. Returns asset_id or null if skipped.
90
+ */
91
+ function crystalliseCodeAsset(input) {
92
+ // Gate: only index above minimum score
93
+ if (input.supervisorScore < MIN_SCORE_FOR_INDEXING) {
94
+ process.stderr.write(`[code-asset] Score ${input.supervisorScore} < ${MIN_SCORE_FOR_INDEXING} — skipping ${input.taskId}\n`);
95
+ return null;
96
+ }
97
+ // Filter to reusable library files only (exclude workspace/ identity docs)
98
+ const eligibleFiles = input.files.filter(f => !f.startsWith('workspace/') && (/\/(lib|utils|helpers|agents)\//i.test(f) ||
99
+ f.endsWith('.sql') ||
100
+ (f.includes('/scripts/') && !f.includes('.test.'))));
101
+ if (eligibleFiles.length === 0) {
102
+ return null; // no reusable library code in this sprint
103
+ }
104
+ const idx = loadIndex();
105
+ // Dedup: skip if this exact task (sprint+title) is already indexed
106
+ const alreadyIndexed = idx.assets.some(a => a.provenance === input.sprintId && a.title === input.taskTitle);
107
+ if (alreadyIndexed) {
108
+ return null;
109
+ }
110
+ const assetId = (0, crypto_1.randomUUID)();
111
+ const category = classifyCategory(eligibleFiles, input.taskTitle);
112
+ const language = inferLanguage(eligibleFiles);
113
+ const tags = inferTags(eligibleFiles, input.taskTitle);
114
+ const asset = {
115
+ asset_id: assetId,
116
+ title: input.taskTitle,
117
+ description: `Auto-indexed from sprint ${input.sprintId} task ${input.taskId}`,
118
+ category,
119
+ tags,
120
+ language,
121
+ dependencies: [],
122
+ interface: '', // Phase 0: populated manually or by future Code Asset Agent LLM pass
123
+ usage_example: '',
124
+ test_coverage: 'none',
125
+ quality_score: input.supervisorScore,
126
+ production_validations: 1,
127
+ origin: input.origin,
128
+ provenance: input.sprintId,
129
+ ip_status: 'eligible',
130
+ version: '1.0.0',
131
+ last_modified: new Date().toISOString().slice(0, 10),
132
+ usage_count: 0,
133
+ known_limitations: [],
134
+ related_assets: [],
135
+ tier: 3, // all new assets enter at Tier 3
136
+ source_files: eligibleFiles,
137
+ };
138
+ // Write asset schema to assets/{assetId}/schema.json
139
+ const assetDir = (0, path_1.join)(ASSETS_DIR, assetId);
140
+ (0, fs_1.mkdirSync)(assetDir, { recursive: true });
141
+ (0, fs_1.writeFileSync)((0, path_1.join)(assetDir, 'schema.json'), JSON.stringify(asset, null, 2), 'utf-8');
142
+ // Update index
143
+ idx.assets.push({
144
+ asset_id: asset.asset_id,
145
+ title: asset.title,
146
+ category: asset.category,
147
+ tags: asset.tags,
148
+ tier: asset.tier,
149
+ quality_score: asset.quality_score,
150
+ language: asset.language,
151
+ usage_count: asset.usage_count,
152
+ provenance: asset.provenance,
153
+ });
154
+ idx._meta.total_assets++;
155
+ idx._meta.tier_counts['3'] = (idx._meta.tier_counts['3'] || 0) + 1;
156
+ saveIndex(idx);
157
+ process.stdout.write(`[code-asset] Indexed ${assetId} (${category}, Tier 3, score ${input.supervisorScore}) ← ${input.sprintId}\n`);
158
+ return assetId;
159
+ }
160
+ // ── Smoke test ────────────────────────────────────────────────────────────────
161
+ if (require.main === module) {
162
+ console.log('\n📚 Code Asset Crystalliser — Smoke Test\n');
163
+ const id = crystalliseCodeAsset({
164
+ agentId: 'smoke-coder',
165
+ sprintId: 'sprint-000-smoke',
166
+ taskId: '000-01',
167
+ taskTitle: 'BrainX PostgreSQL memory client with pgvector',
168
+ files: ['scripts/lib/brainx-client.ts', 'scripts/lib/brainx-embed.ts', 'scripts/lib/brainx-schema.sql'],
169
+ supervisorScore: 88,
170
+ origin: 'kognai-core',
171
+ });
172
+ if (id) {
173
+ console.log(`✅ crystalliseCodeAsset() → asset_id: ${id}`);
174
+ const idx = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)((0, engine_paths_1.resolveEnginePaths)().root, 'code_assets/index.json'), 'utf-8'));
175
+ console.log(`✅ index.json total_assets: ${idx._meta.total_assets}`);
176
+ }
177
+ else {
178
+ console.log('⚠️ Skipped (score below threshold or no lib files)');
179
+ }
180
+ console.log('\n✅ PASS\n');
181
+ }