@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,381 @@
1
+ "use strict";
2
+ /**
3
+ * notion-direct.ts — Zero-dep Notion API client for senior-coder.
4
+ *
5
+ * Bypasses both OpenClaw and the @notionhq/client npm package (which would be a
6
+ * new dep). Uses raw HTTPS to api.notion.com.
7
+ *
8
+ * Public surface — only what the bot actually needs:
9
+ * - upsertSprint(sprint) → push a sprint to Notion Sprints DB (create or update by Sprint ID)
10
+ * - updateSprintStatus(id, ...) → patch a sprint's status/outcome after completion
11
+ * - createBlocker(blocker) → push a blocker to Notion Blockers DB
12
+ * - archiveDailyBrief(date,...) → create a sub-page under the Daily Briefs parent
13
+ * - queryNewSprintsInNotion() → pull sprints with Status=pending that don't exist locally yet
14
+ *
15
+ * Fail-open: every function returns {ok: false, reason} on error rather than
16
+ * throwing, so the bot pipeline doesn't break when Notion is down or the
17
+ * integration loses access. Callers log the failure and continue.
18
+ *
19
+ * Created: 2026-04-29.
20
+ */
21
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ var desc = Object.getOwnPropertyDescriptor(m, k);
24
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
25
+ desc = { enumerable: true, get: function() { return m[k]; } };
26
+ }
27
+ Object.defineProperty(o, k2, desc);
28
+ }) : (function(o, m, k, k2) {
29
+ if (k2 === undefined) k2 = k;
30
+ o[k2] = m[k];
31
+ }));
32
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
33
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
34
+ }) : function(o, v) {
35
+ o["default"] = v;
36
+ });
37
+ var __importStar = (this && this.__importStar) || (function () {
38
+ var ownKeys = function(o) {
39
+ ownKeys = Object.getOwnPropertyNames || function (o) {
40
+ var ar = [];
41
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
42
+ return ar;
43
+ };
44
+ return ownKeys(o);
45
+ };
46
+ return function (mod) {
47
+ if (mod && mod.__esModule) return mod;
48
+ var result = {};
49
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
50
+ __setModuleDefault(result, mod);
51
+ return result;
52
+ };
53
+ })();
54
+ Object.defineProperty(exports, "__esModule", { value: true });
55
+ exports.upsertSprint = upsertSprint;
56
+ exports.updateSprintStatus = updateSprintStatus;
57
+ exports.createBlocker = createBlocker;
58
+ exports.archiveDailyBrief = archiveDailyBrief;
59
+ exports.queryPendingSprintsFromNotion = queryPendingSprintsFromNotion;
60
+ exports.getAllNotionSprintStatuses = getAllNotionSprintStatuses;
61
+ exports.notionHealthCheck = notionHealthCheck;
62
+ const https = __importStar(require("https"));
63
+ const NOTION_API_KEY = process.env.NOTION_API_KEY || '';
64
+ const NOTION_VERSION = '2022-06-28';
65
+ const SPRINTS_DB_ID = process.env.NOTION_SPRINTS_DB_ID || '';
66
+ const BLOCKERS_DB_ID = process.env.NOTION_BLOCKERS_DB_ID || '';
67
+ const DAILY_BRIEFS_PARENT_ID = process.env.NOTION_DAILY_BRIEFS_PAGE_ID || '';
68
+ const TIMEOUT_MS = 15_000;
69
+ // ── HTTP helper ──────────────────────────────────────────────────────────────
70
+ function notionRequest(method, path, body) {
71
+ return new Promise((resolve, reject) => {
72
+ const data = body ? JSON.stringify(body) : '';
73
+ const headers = {
74
+ 'Authorization': `Bearer ${NOTION_API_KEY}`,
75
+ 'Notion-Version': NOTION_VERSION,
76
+ 'Content-Type': 'application/json',
77
+ };
78
+ if (data)
79
+ headers['Content-Length'] = Buffer.byteLength(data);
80
+ const req = https.request({
81
+ hostname: 'api.notion.com',
82
+ path: `/v1${path}`,
83
+ method,
84
+ headers,
85
+ timeout: TIMEOUT_MS,
86
+ }, (res) => {
87
+ let chunks = '';
88
+ res.on('data', (c) => (chunks += c));
89
+ res.on('end', () => {
90
+ try {
91
+ const parsed = JSON.parse(chunks);
92
+ if (parsed.object === 'error') {
93
+ reject(new Error(`Notion ${parsed.code}: ${parsed.message}`));
94
+ return;
95
+ }
96
+ resolve(parsed);
97
+ }
98
+ catch (e) {
99
+ reject(new Error(`Notion parse failed (HTTP ${res.statusCode}): ${chunks.slice(0, 300)}`));
100
+ }
101
+ });
102
+ });
103
+ req.on('error', reject);
104
+ req.on('timeout', () => { req.destroy(); reject(new Error(`Notion timeout after ${TIMEOUT_MS}ms`)); });
105
+ if (data)
106
+ req.write(data);
107
+ req.end();
108
+ });
109
+ }
110
+ function envGuard() {
111
+ if (!NOTION_API_KEY)
112
+ return { ok: false, reason: 'NOTION_API_KEY not set' };
113
+ return { ok: true };
114
+ }
115
+ // ── Property builders (Notion's verbose nested format) ───────────────────────
116
+ function rt(text) {
117
+ return { rich_text: [{ type: 'text', text: { content: (text || '').slice(0, 2000) } }] };
118
+ }
119
+ function title(text) {
120
+ return { title: [{ type: 'text', text: { content: (text || '').slice(0, 2000) } }] };
121
+ }
122
+ function sel(name) {
123
+ return name ? { select: { name } } : { select: null };
124
+ }
125
+ function num(n) {
126
+ return { number: n ?? null };
127
+ }
128
+ function url(u) {
129
+ return u ? { url: u } : { url: null };
130
+ }
131
+ function dateProp(iso) {
132
+ return iso ? { date: { start: iso } } : { date: null };
133
+ }
134
+ function relation(pageIds) {
135
+ return { relation: (pageIds || []).map(id => ({ id })) };
136
+ }
137
+ // ── Sprints DB ──────────────────────────────────────────────────────────────
138
+ /**
139
+ * Find a sprint row by its Sprint ID title. Returns the page object or null.
140
+ */
141
+ async function findSprintPage(sprintId) {
142
+ if (!SPRINTS_DB_ID)
143
+ return null;
144
+ try {
145
+ const r = await notionRequest('POST', `/databases/${SPRINTS_DB_ID}/query`, {
146
+ filter: { property: 'Sprint ID', title: { equals: sprintId } },
147
+ page_size: 1,
148
+ });
149
+ return r.results[0] || null;
150
+ }
151
+ catch (err) {
152
+ console.warn(`[notion] findSprintPage(${sprintId}) failed: ${err.message}`);
153
+ return null;
154
+ }
155
+ }
156
+ /** Build the properties payload for a SprintRow. */
157
+ function sprintRowProps(s) {
158
+ const props = {
159
+ 'Sprint ID': title(s.sprint_id),
160
+ 'Title': rt(s.title),
161
+ 'Status': sel(s.status),
162
+ };
163
+ if (s.priority)
164
+ props['Priority'] = sel(s.priority);
165
+ if (s.tasks_summary !== undefined)
166
+ props['Tasks Summary'] = rt(s.tasks_summary);
167
+ if (s.authored_by)
168
+ props['Authored By'] = sel(s.authored_by);
169
+ if (s.sprint_file_url !== undefined)
170
+ props['Sprint File'] = url(s.sprint_file_url);
171
+ if (s.outcome !== undefined)
172
+ props['Outcome'] = rt(s.outcome);
173
+ return props;
174
+ }
175
+ /**
176
+ * Upsert a sprint row in Notion. Creates if not exists (by Sprint ID), updates if it does.
177
+ * Returns the page_id of the row in Notion (useful for setting Blocker.Sprint relation).
178
+ */
179
+ async function upsertSprint(s) {
180
+ const guard = envGuard();
181
+ if (!guard.ok)
182
+ return guard;
183
+ if (!SPRINTS_DB_ID)
184
+ return { ok: false, reason: 'NOTION_SPRINTS_DB_ID not set' };
185
+ try {
186
+ const existing = await findSprintPage(s.sprint_id);
187
+ if (existing) {
188
+ await notionRequest('PATCH', `/pages/${existing.id}`, { properties: sprintRowProps(s) });
189
+ return { ok: true, data: { page_id: existing.id } };
190
+ }
191
+ const created = await notionRequest('POST', '/pages', {
192
+ parent: { database_id: SPRINTS_DB_ID },
193
+ properties: sprintRowProps(s),
194
+ });
195
+ return { ok: true, data: { page_id: created.id } };
196
+ }
197
+ catch (err) {
198
+ return { ok: false, reason: err.message };
199
+ }
200
+ }
201
+ /**
202
+ * Lighter-weight update: only patches status (+ optional outcome). Use after sprint completion.
203
+ */
204
+ async function updateSprintStatus(sprintId, status, outcome, tasksSummary) {
205
+ const guard = envGuard();
206
+ if (!guard.ok)
207
+ return guard;
208
+ try {
209
+ const existing = await findSprintPage(sprintId);
210
+ if (!existing)
211
+ return { ok: false, reason: `sprint ${sprintId} not in Notion` };
212
+ const props = { 'Status': sel(status) };
213
+ if (outcome !== undefined)
214
+ props['Outcome'] = rt(outcome);
215
+ if (tasksSummary !== undefined)
216
+ props['Tasks Summary'] = rt(tasksSummary);
217
+ await notionRequest('PATCH', `/pages/${existing.id}`, { properties: props });
218
+ return { ok: true };
219
+ }
220
+ catch (err) {
221
+ return { ok: false, reason: err.message };
222
+ }
223
+ }
224
+ // ── Blockers DB ─────────────────────────────────────────────────────────────
225
+ async function createBlocker(b) {
226
+ const guard = envGuard();
227
+ if (!guard.ok)
228
+ return guard;
229
+ if (!BLOCKERS_DB_ID)
230
+ return { ok: false, reason: 'NOTION_BLOCKERS_DB_ID not set' };
231
+ const props = {
232
+ 'Title': title(b.title),
233
+ 'Detected At': dateProp(b.detected_at_iso),
234
+ 'Pick Count 24h': num(b.pick_count_24h),
235
+ 'Status': sel(b.status),
236
+ };
237
+ if (b.sprint_page_id)
238
+ props['Sprint'] = relation([b.sprint_page_id]);
239
+ if (b.diagnosis !== undefined)
240
+ props['Diagnosis'] = rt(b.diagnosis);
241
+ if (b.resolution !== undefined)
242
+ props['Resolution'] = rt(b.resolution);
243
+ try {
244
+ const created = await notionRequest('POST', '/pages', {
245
+ parent: { database_id: BLOCKERS_DB_ID },
246
+ properties: props,
247
+ });
248
+ return { ok: true, data: { page_id: created.id } };
249
+ }
250
+ catch (err) {
251
+ return { ok: false, reason: err.message };
252
+ }
253
+ }
254
+ // ── Daily Briefs ────────────────────────────────────────────────────────────
255
+ /**
256
+ * Create a sub-page under the Daily Briefs parent, titled by date, with the
257
+ * brief text as content. Notion pages support markdown-ish content via blocks;
258
+ * we use a single paragraph with the brief text (wrapped in code-block-style).
259
+ */
260
+ async function archiveDailyBrief(dateIso, briefText) {
261
+ const guard = envGuard();
262
+ if (!guard.ok)
263
+ return guard;
264
+ if (!DAILY_BRIEFS_PARENT_ID)
265
+ return { ok: false, reason: 'NOTION_DAILY_BRIEFS_PAGE_ID not set' };
266
+ // Notion paragraph blocks have a 2000-char limit per text segment.
267
+ // Split the brief into chunks and create one paragraph block per chunk.
268
+ const CHUNK = 1900;
269
+ const chunks = [];
270
+ for (let i = 0; i < briefText.length; i += CHUNK)
271
+ chunks.push(briefText.slice(i, i + CHUNK));
272
+ const children = chunks.map(text => ({
273
+ object: 'block',
274
+ type: 'paragraph',
275
+ paragraph: { rich_text: [{ type: 'text', text: { content: text } }] },
276
+ }));
277
+ try {
278
+ const created = await notionRequest('POST', '/pages', {
279
+ parent: { page_id: DAILY_BRIEFS_PARENT_ID },
280
+ properties: { title: { title: [{ type: 'text', text: { content: `Brief — ${dateIso}` } }] } },
281
+ icon: { type: 'emoji', emoji: '🌅' },
282
+ children,
283
+ });
284
+ return { ok: true, data: { page_id: created.id } };
285
+ }
286
+ catch (err) {
287
+ return { ok: false, reason: err.message };
288
+ }
289
+ }
290
+ /**
291
+ * Fetch all sprint rows in Notion with Status=pending. Caller compares against
292
+ * local `workspace/sprints/` to find which ones to materialize as JSON files.
293
+ */
294
+ async function queryPendingSprintsFromNotion() {
295
+ const guard = envGuard();
296
+ if (!guard.ok)
297
+ return guard;
298
+ if (!SPRINTS_DB_ID)
299
+ return { ok: false, reason: 'NOTION_SPRINTS_DB_ID not set' };
300
+ try {
301
+ const r = await notionRequest('POST', `/databases/${SPRINTS_DB_ID}/query`, {
302
+ filter: { property: 'Status', select: { equals: 'pending' } },
303
+ page_size: 100,
304
+ });
305
+ const out = r.results.map((row) => {
306
+ const p = row.properties || {};
307
+ const titleArr = p['Sprint ID']?.title || [];
308
+ const titleText = p['Title']?.rich_text || [];
309
+ return {
310
+ page_id: row.id,
311
+ sprint_id: titleArr[0]?.plain_text || '',
312
+ title: titleText.map((t) => t.plain_text).join(''),
313
+ status: p['Status']?.select?.name || '',
314
+ priority: p['Priority']?.select?.name,
315
+ tasks_summary: (p['Tasks Summary']?.rich_text || []).map((t) => t.plain_text).join(''),
316
+ authored_by: p['Authored By']?.select?.name,
317
+ };
318
+ });
319
+ return { ok: true, data: out };
320
+ }
321
+ catch (err) {
322
+ return { ok: false, reason: err.message };
323
+ }
324
+ }
325
+ // ── Source-of-truth lookup (sprint-runner uses this before picking a local file) ─
326
+ /**
327
+ * Fetch ALL sprints in the Notion Sprints DB, return a Map of sprint_id → status.
328
+ * Used by sprint-runner.ts so Notion overrides local file state — protects against
329
+ * other Claude sessions reverting sprint files locally.
330
+ * Fail-safe: returns {ok: false} on Notion error so caller falls back to local-only.
331
+ */
332
+ async function getAllNotionSprintStatuses() {
333
+ const guard = envGuard();
334
+ if (!guard.ok)
335
+ return guard;
336
+ if (!SPRINTS_DB_ID)
337
+ return { ok: false, reason: 'NOTION_SPRINTS_DB_ID not set' };
338
+ try {
339
+ const r = await notionRequest('POST', `/databases/${SPRINTS_DB_ID}/query`, { page_size: 100 });
340
+ const map = new Map();
341
+ for (const row of r.results) {
342
+ const titleArr = row.properties?.['Sprint ID']?.title || [];
343
+ const sprintId = titleArr[0]?.plain_text || '';
344
+ const status = row.properties?.['Status']?.select?.name || '';
345
+ if (sprintId && status)
346
+ map.set(sprintId, status);
347
+ }
348
+ if (r.has_more)
349
+ console.warn('[notion] >100 sprint rows; pagination not implemented');
350
+ return { ok: true, data: map };
351
+ }
352
+ catch (err) {
353
+ return { ok: false, reason: err.message };
354
+ }
355
+ }
356
+ // ── Health check ────────────────────────────────────────────────────────────
357
+ async function notionHealthCheck() {
358
+ const guard = envGuard();
359
+ if (!guard.ok)
360
+ return guard;
361
+ try {
362
+ const me = await notionRequest('GET', '/users/me');
363
+ const checks = await Promise.all([
364
+ notionRequest('GET', `/databases/${SPRINTS_DB_ID}`).then(() => true).catch(() => false),
365
+ notionRequest('GET', `/databases/${BLOCKERS_DB_ID}`).then(() => true).catch(() => false),
366
+ notionRequest('GET', `/blocks/${DAILY_BRIEFS_PARENT_ID}`).then(() => true).catch(() => false),
367
+ ]);
368
+ return {
369
+ ok: true,
370
+ data: {
371
+ bot_name: me.name,
372
+ sprints_db: checks[0],
373
+ blockers_db: checks[1],
374
+ briefs_page: checks[2],
375
+ },
376
+ };
377
+ }
378
+ catch (err) {
379
+ return { ok: false, reason: err.message };
380
+ }
381
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * ollama-client.ts — TypeScript client for local Ollama inference
3
+ *
4
+ * Calls local models on the Mac Mini vault via the Ollama REST API.
5
+ * Ollama is always available at localhost:11434 when running.
6
+ * No API key needed — pure local, $0 cost.
7
+ */
8
+ export interface OllamaOptions {
9
+ model: string;
10
+ prompt: string;
11
+ systemPrompt?: string;
12
+ maxTokens?: number;
13
+ temperature?: number;
14
+ }
15
+ export interface OllamaResult {
16
+ content: string;
17
+ model: string;
18
+ totalDuration: number;
19
+ evalCount: number;
20
+ promptEvalCount: number;
21
+ }
22
+ /**
23
+ * Call a local model via Ollama. Returns the response content.
24
+ */
25
+ export declare function callOllama(opts: OllamaOptions): Promise<OllamaResult>;
26
+ /**
27
+ * Check if Ollama is running and accessible.
28
+ */
29
+ export declare function ollamaIsAvailable(): Promise<boolean>;
30
+ /**
31
+ * Check if a specific model is currently loaded in Ollama memory.
32
+ */
33
+ export declare function ollamaModelLoaded(model: string): Promise<boolean>;
34
+ /**
35
+ * List all models available in Ollama.
36
+ */
37
+ export declare function ollamaListModels(): Promise<string[]>;
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ /**
3
+ * ollama-client.ts — TypeScript client for local Ollama inference
4
+ *
5
+ * Calls local models on the Mac Mini vault via the Ollama REST API.
6
+ * Ollama is always available at localhost:11434 when running.
7
+ * No API key needed — pure local, $0 cost.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.callOllama = callOllama;
44
+ exports.ollamaIsAvailable = ollamaIsAvailable;
45
+ exports.ollamaModelLoaded = ollamaModelLoaded;
46
+ exports.ollamaListModels = ollamaListModels;
47
+ const http = __importStar(require("http"));
48
+ const OLLAMA_BASE = (() => { const h = process.env.OLLAMA_HOST || 'http://localhost:11434'; return h.startsWith('http') ? h : `http://${h}`; })();
49
+ const OLLAMA_TIMEOUT_MS = 600_000; // 10 min — local models can be slow
50
+ function httpPost(path, body) {
51
+ return new Promise((resolve, reject) => {
52
+ const parsed = new URL(OLLAMA_BASE);
53
+ const req = http.request({
54
+ hostname: parsed.hostname,
55
+ port: parseInt(parsed.port || '11434', 10),
56
+ path,
57
+ method: 'POST',
58
+ headers: {
59
+ 'Content-Type': 'application/json',
60
+ 'Content-Length': Buffer.byteLength(body),
61
+ },
62
+ }, (res) => {
63
+ let data = '';
64
+ res.on('data', (chunk) => (data += chunk));
65
+ res.on('end', () => resolve({ status: res.statusCode || 0, data }));
66
+ });
67
+ req.on('error', reject);
68
+ req.setTimeout(OLLAMA_TIMEOUT_MS, () => {
69
+ req.destroy();
70
+ reject(new Error(`Ollama timeout after ${OLLAMA_TIMEOUT_MS / 1000}s — model: ${body.slice(0, 60)}`));
71
+ });
72
+ req.write(body);
73
+ req.end();
74
+ });
75
+ }
76
+ function httpGet(path) {
77
+ return new Promise((resolve, reject) => {
78
+ const parsed = new URL(OLLAMA_BASE);
79
+ const req = http.request({
80
+ hostname: parsed.hostname,
81
+ port: parseInt(parsed.port || '11434', 10),
82
+ path,
83
+ method: 'GET',
84
+ }, (res) => {
85
+ let data = '';
86
+ res.on('data', (chunk) => (data += chunk));
87
+ res.on('end', () => resolve({ status: res.statusCode || 0, data }));
88
+ });
89
+ req.on('error', reject);
90
+ req.setTimeout(5000, () => { req.destroy(); resolve({ status: 0, data: '' }); });
91
+ req.end();
92
+ });
93
+ }
94
+ /**
95
+ * Call a local model via Ollama. Returns the response content.
96
+ */
97
+ async function callOllama(opts) {
98
+ const { model, prompt, systemPrompt, maxTokens = 4096, temperature = 0.1 } = opts;
99
+ const requestBody = JSON.stringify({
100
+ model,
101
+ prompt,
102
+ system: systemPrompt,
103
+ stream: false,
104
+ options: {
105
+ num_predict: maxTokens,
106
+ temperature,
107
+ },
108
+ });
109
+ const response = await httpPost('/api/generate', requestBody);
110
+ if (response.status !== 200) {
111
+ throw new Error(`Ollama returned ${response.status}: ${response.data.slice(0, 300)}`);
112
+ }
113
+ const json = JSON.parse(response.data);
114
+ return {
115
+ content: json.response || '',
116
+ model: json.model || model,
117
+ totalDuration: json.total_duration || 0,
118
+ evalCount: json.eval_count || 0,
119
+ promptEvalCount: json.prompt_eval_count || 0,
120
+ };
121
+ }
122
+ /**
123
+ * Check if Ollama is running and accessible.
124
+ */
125
+ async function ollamaIsAvailable() {
126
+ const res = await httpGet('/api/tags').catch(() => ({ status: 0, data: '' }));
127
+ return res.status === 200;
128
+ }
129
+ /**
130
+ * Check if a specific model is currently loaded in Ollama memory.
131
+ */
132
+ async function ollamaModelLoaded(model) {
133
+ const res = await httpGet('/api/ps').catch(() => ({ status: 0, data: '' }));
134
+ if (res.status !== 200)
135
+ return false;
136
+ try {
137
+ const json = JSON.parse(res.data);
138
+ return (json.models || []).some((m) => m.name === model || m.model === model);
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ }
144
+ /**
145
+ * List all models available in Ollama.
146
+ */
147
+ async function ollamaListModels() {
148
+ const res = await httpGet('/api/tags').catch(() => ({ status: 0, data: '' }));
149
+ if (res.status !== 200)
150
+ return [];
151
+ try {
152
+ const json = JSON.parse(res.data);
153
+ return (json.models || []).map((m) => m.name || m.model);
154
+ }
155
+ catch {
156
+ return [];
157
+ }
158
+ }
@@ -0,0 +1,57 @@
1
+ export declare class CredentialVault {
2
+ private rateCounters;
3
+ private checkRateLimit;
4
+ private checkRotation;
5
+ /**
6
+ * Read a secret from process.env.
7
+ * The value is returned to the caller but NEVER written to logs.
8
+ */
9
+ getSecret(key: string, agentId?: string): string;
10
+ /**
11
+ * Return the NAMES of all env variables matching known secret patterns.
12
+ * Values are NEVER returned.
13
+ */
14
+ listSecrets(agentId?: string): string[];
15
+ /**
16
+ * Check if a secret is present (non-empty).
17
+ */
18
+ hasSecret(key: string, agentId?: string): boolean;
19
+ /**
20
+ * Mask a secret value for safe inclusion in logs or Telegram messages.
21
+ * Format: first 4 chars + "****...****" + last 4 chars.
22
+ */
23
+ maskForLog(value: string): string;
24
+ /**
25
+ * Passive leak scanner: check if any known secret value appears verbatim
26
+ * in the given log line. Returns the leaked key names (never the values).
27
+ */
28
+ scanForLeaks(line: string): string[];
29
+ /**
30
+ * Scan last N lines of a log file for secret leaks.
31
+ * Returns list of (key, line_number) tuples where leaks were detected.
32
+ * Intended for use by CTO gate pre-check and orchestrator run startup.
33
+ */
34
+ scanLogFile(logFilePath: string, lastNLines?: number): Array<{
35
+ key: string;
36
+ lineIndex: number;
37
+ }>;
38
+ /**
39
+ * Startup required-secrets validation.
40
+ * Logs + throws if any required key is missing.
41
+ * Call once at orchestrator startup.
42
+ */
43
+ validateRequired(requiredKeys?: string[]): void;
44
+ /**
45
+ * CTO approval gate audit report.
46
+ * Returns a summary of vault state: which secrets are present, rotation status.
47
+ * Values are NEVER included.
48
+ */
49
+ audit(): {
50
+ total_secrets: number;
51
+ present: string[];
52
+ missing: string[];
53
+ rotation_warnings: string[];
54
+ generated_at: string;
55
+ };
56
+ }
57
+ export declare const credentialVault: CredentialVault;