@noir-ai/daemon 1.0.0-beta.1 → 1.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -2,11 +2,96 @@ import { EmbedderConfig, ContextEngine, EmbedFn } from '@noir-ai/context';
2
2
  export { ContextStatus } from '@noir-ai/context';
3
3
  import { ProjectId, ProjectInfo } from '@noir-ai/core';
4
4
  import { Store } from '@noir-ai/store';
5
+ import { IntegrationDeclaration } from '@noir-ai/skills';
5
6
  import { MemoryConfig, MemoryEngine } from '@noir-ai/memory';
6
7
  import { ResolvedModelConfig } from '@noir-ai/model';
7
8
  import { McpServer } from '@modelcontextprotocol/server';
8
9
  import { WorkflowEngine, Phase, WorkflowState, Mode, GateResult } from '@noir-ai/workflow';
9
10
 
11
+ /** Op-shaped error thrown when input violates the contract (bad id, missing
12
+ * required field, unknown op). The handler maps it to `{ok:false, reason:'invalid-op'}`. */
13
+ declare class InvalidOp extends Error {
14
+ constructor(message: string);
15
+ }
16
+ type ClickUpOp = 'status' | 'subtask' | 'comment' | 'batch';
17
+ /** Workspace binding the proxy resolves listId/teamId from (config > payload). */
18
+ interface ClickUpBinding {
19
+ teamId?: string;
20
+ listId?: string;
21
+ }
22
+ /** A constructed HTTP request against an allowlisted endpoint. The auth header
23
+ * is ABSENT here — it is added at execute time only; the dry-run preview shows
24
+ * a redacted placeholder so the token never leaves the execute path. */
25
+ interface BuiltRequest {
26
+ method: 'GET' | 'POST' | 'PUT';
27
+ /** Full allowlisted URL (constructed from op + payload + binding). */
28
+ url: string;
29
+ /** JSON body (already stringified-safe as an object). */
30
+ body?: Record<string, unknown>;
31
+ /** Stable descriptor for the audit `target` field (e.g. `task/abc123`). */
32
+ target: string;
33
+ }
34
+ /** A single preview/result row. */
35
+ interface RequestRow {
36
+ method: string;
37
+ url: string;
38
+ body?: Record<string, unknown>;
39
+ /** Redacted auth header for the preview (always `pk_***`). */
40
+ auth: 'pk_***';
41
+ target: string;
42
+ }
43
+ /** A normalized task for the batch op (H2 markdown + the `tasks[]` array both
44
+ * reduce to this). All fields are JSON DATA in the POST body — adversary text
45
+ * in a task title becomes the `name` string, never an executable instruction. */
46
+ interface NormalizedTask {
47
+ name: string;
48
+ description?: string;
49
+ tags?: string[];
50
+ assignees?: number[];
51
+ status?: string;
52
+ }
53
+ /** Parse H2-per-task markdown into normalized tasks. Each `## <title>` opens a
54
+ * task; body lines until the next `## ` are the description, EXCEPT lines that
55
+ * match `- key: value` which are folded into structured fields (assignees/tags).
56
+ * Tolerant: a missing leading H2 still yields the body as one task (named from
57
+ * the first non-empty line) so a slightly-malformed template still previews. */
58
+ declare function parseH2Tasks(markdown: string): NormalizedTask[];
59
+ /** Build ALL requests an op renders, WITHOUT touching the network. Used by both
60
+ * the dry-run preview (no auth) and the execute path (auth added). Throws
61
+ * `InvalidOp` on a contract violation (bad id, missing field, unknown op). */
62
+ declare function buildRequests(opRaw: string, payload: Record<string, unknown>, binding: ClickUpBinding): {
63
+ op: ClickUpOp;
64
+ requests: BuiltRequest[];
65
+ };
66
+ /** Render a dry-run preview row set. NO auth header, NO network — the token
67
+ * never enters this path. */
68
+ declare function previewRows(requests: BuiltRequest[]): RequestRow[];
69
+ /** Minimal fetch shape the proxy needs. Bound to global `fetch` in production;
70
+ * a spy in tests (cassette). */
71
+ type FetchLike = (url: string, init: RequestInit) => Promise<Response>;
72
+ interface ExecResult {
73
+ target: string;
74
+ method: string;
75
+ url: string;
76
+ httpStatus: number;
77
+ success: boolean;
78
+ /** ClickUp's parsed JSON body (or `{}` when absent). NEVER contains the token
79
+ * (ClickUp never echoes it). */
80
+ response: Record<string, unknown>;
81
+ /** Present when a 429 backoff fired on this request. */
82
+ rateLimitedWaitMs?: number;
83
+ /** For `subtask` with a status follow-up: the new subtask id resolved from the
84
+ * create response so the host can reference it. */
85
+ newTaskId?: string;
86
+ /** Error message on a non-2xx / network failure (NEVER includes the token). */
87
+ error?: string;
88
+ }
89
+ /** Execute an op's requests against ClickUp. The `subtask` op is special: its
90
+ * optional status follow-up needs the create response's `id`, so the two
91
+ * requests run sequentially with the id threaded through. `batch` runs with a
92
+ * concurrency cap of 4 + per-request 429 backoff. */
93
+ declare function executeOp(op: ClickUpOp, requests: BuiltRequest[], token: string, fetchImpl: FetchLike): Promise<ExecResult[]>;
94
+
10
95
  /**
11
96
  * Build the daemon's {@link ContextEngine} from its already-open store handle
12
97
  * + the project `root` + `projectId` + a resolved {@link EmbedderConfig}.
@@ -64,6 +149,141 @@ interface RunningDaemon {
64
149
  }
65
150
  declare function startHttpServer(opts: StartHttpOptions): Promise<RunningDaemon>;
66
151
 
152
+ /**
153
+ * Per-integration binding resolved at the seam: the shipped declaration (source
154
+ * of truth for `auth.tokenEnv` + `runtime`) merged with the user's
155
+ * `integrations.<name>` config overlay (source of truth for `teamId`/`listId`/
156
+ * `spaceId` + a local `auth.tokenEnv` override / `runtime` downgrade). The
157
+ * declaration is authoritative for `tokenEnv` unless the config explicitly
158
+ * overrides it (a workspace that renamed the env var).
159
+ */
160
+ interface IntegrationBinding {
161
+ /** Full declaration name, e.g. `'noir-clickup'`. */
162
+ name: string;
163
+ /** Short name — declaration name with the `noir-` namespace stripped
164
+ * (`'clickup'`). The `noir.clickup_write` tool segment + the documented
165
+ * `integrations.clickup.*` config key both use this form. */
166
+ shortName: string;
167
+ declaration: IntegrationDeclaration;
168
+ /** Resolved token env-var name (config override > declaration). */
169
+ tokenEnv: string;
170
+ /** Effective runtime tier: the user's `integrations.<name>.runtime` overlay
171
+ * wins when set, else the declaration's `runtime`. The daemon gates write-tool
172
+ * registration on THIS (not the declaration's) so a local downgrade to `none`
173
+ * actually takes the write tool off — the tier model: `none` = skill-side
174
+ * reads only, `integrations_auth` still resolves the token. */
175
+ effectiveRuntime: IntegrationDeclaration['runtime'];
176
+ teamId?: string;
177
+ listId?: string;
178
+ spaceId?: string;
179
+ }
180
+ /**
181
+ * The per-serve-lifecycle integration handle. Built once (mirrors the store +
182
+ * workflow + memory engines) and reused across every MCP request. Carries the
183
+ * discovered declarations merged with the user config so the tools never re-read
184
+ * the filesystem per call.
185
+ */
186
+ interface IntegrationService {
187
+ /** Project root — the anchor for `.noir/audit/` writes. */
188
+ root: string;
189
+ /** Bindings keyed by BOTH the full name (`noir-clickup`) and the short name
190
+ * (`clickup`) so a tool/config using either form resolves the same binding. */
191
+ bindings: Map<string, IntegrationBinding>;
192
+ }
193
+ /** Strip the `noir-` namespace prefix from a declaration name. The short form
194
+ * is what the `noir.<short>_write` tool segment + the `integrations.<short>`
195
+ * config key use; the declaration itself always carries the full `noir-` name. */
196
+ declare function shortNameOf(name: string): string;
197
+ /**
198
+ * Build the integration service for one serve lifecycle. Discovery is
199
+ * best-effort: a missing/unreadable skills pack ⇒ an empty service (the tools
200
+ * degrade gracefully — `integrations_auth` still resolves a caller `envVar`;
201
+ * `noir.clickup_write` is simply not registered). Never throws.
202
+ *
203
+ * @param root project root (`.noir/audit/` anchor)
204
+ * @param configIntegrations the parsed `integrations` block from `.noir/config.yml`
205
+ * (`{ [name]: { auth?, runtime, teamId?, listId?, spaceId? } }`). Keys may use
206
+ * either the full `noir-clickup` or the short `clickup` form.
207
+ */
208
+ declare function buildIntegrationService(root: string, configIntegrations?: Record<string, {
209
+ auth?: {
210
+ tokenEnv?: string;
211
+ };
212
+ runtime?: 'none' | 'gated-write-proxy' | 'mcp-stdio' | 'external-mcp';
213
+ teamId?: string;
214
+ listId?: string;
215
+ spaceId?: string;
216
+ }>): IntegrationService;
217
+ /** Look up a binding by either the full or short name. */
218
+ declare function findBinding(svc: IntegrationService, name: string): IntegrationBinding | undefined;
219
+ /** Result of a token resolution attempt. */
220
+ type TokenResolution = {
221
+ ok: true;
222
+ token: string;
223
+ envVar: string;
224
+ } | {
225
+ ok: false;
226
+ reason: 'no-token';
227
+ envVar: string;
228
+ } | {
229
+ ok: false;
230
+ reason: 'unknown-integration';
231
+ integration: string;
232
+ };
233
+ /**
234
+ * Resolve an integration token VALUE from `process.env` at CALL TIME (never at
235
+ * load time; never cached beyond the call). The token is returned to the calling
236
+ * agent in the tool result (trusted host) + travels in the outbound
237
+ * `Authorization` header — and NOWHERE else (no stderr, no audit body).
238
+ *
239
+ * Two call shapes:
240
+ * - `{ integration: 'noir-clickup' }` → look up `tokenEnv` from the discovered
241
+ * declaration (config override honored), then read `process.env[tokenEnv]`.
242
+ * - `{ envVar: 'CLICKUP_API_TOKEN' }` → read `process.env[envVar]` directly.
243
+ */
244
+ declare function resolveToken(svc: IntegrationService, opts: {
245
+ integration?: string;
246
+ envVar?: string;
247
+ }, env?: NodeJS.ProcessEnv): TokenResolution;
248
+ /** One executed-write audit record (X-T3, X-OQ2). Append-only JSONL — one line
249
+ * per executed write. Dry-runs are NOT audited (no write happened). */
250
+ interface IntegrationAuditEntry {
251
+ /** Fixed discriminator so a future `.noir/audit/` reader can filter mixed
252
+ * gate (`kind:'gate'`-ish) + integration rows. */
253
+ kind: 'integration';
254
+ /** Declaration name, e.g. `'noir-clickup'`. */
255
+ integration: string;
256
+ /** Op verb (normalized short form): `status` | `subtask` | `comment` | `batch`. */
257
+ op: string;
258
+ /** Stable target descriptor (e.g. `'task/abc123'`, `'list/90125/task'`). */
259
+ target: string;
260
+ /** HTTP method on the allowlisted endpoint. */
261
+ method: string;
262
+ /** HTTP status returned by ClickUp (last attempt after 429 backoff). */
263
+ httpStatus: number;
264
+ /** True iff `httpStatus` is 2xx. */
265
+ success: boolean;
266
+ /** Epoch-millis when the write was attempted. */
267
+ timestamp: number;
268
+ /** Present when a 429 backoff fired (the spec calls for recording the wait). */
269
+ rateLimitedWaitMs?: number;
270
+ /** Error message when `success === false` (NEVER includes the token). */
271
+ error?: string;
272
+ }
273
+ /**
274
+ * Append one integration audit record to `.noir/audit/integration-<short>.jsonl`
275
+ * (the SAME `.noir/audit/` dir as the S4 gate export — X-OQ2 resolved: REUSE).
276
+ * Append-only JSONL so concurrent/sequential writes don't clobber each other and
277
+ * the file grows linearly with executed writes. Creates the dir if missing.
278
+ *
279
+ * Propagates filesystem errors to the caller — the gated proxy's handler is the
280
+ * best-effort boundary: it catches, sets the result envelope's `audited:false`,
281
+ * and continues (the ClickUp write already happened; an audit failure does NOT
282
+ * roll it back). Keeping the throw honest makes `audited:false` reachable instead
283
+ * of a perpetually-true flag.
284
+ */
285
+ declare function writeIntegrationAudit(root: string, integrationName: string, entry: IntegrationAuditEntry): void;
286
+
67
287
  interface DaemonRecord {
68
288
  pid: number;
69
289
  port: number;
@@ -248,6 +468,20 @@ interface ServerContext {
248
468
  * the flag + the engine agree by construction.
249
469
  */
250
470
  memoryConsolidation?: boolean;
471
+ /**
472
+ * Optional integration service (slice X — X-T3). Built once per serve
473
+ * lifecycle from the discovered `integration.json` declarations
474
+ * (@noir-ai/skills `discoverIntegrations`) merged with the user's
475
+ * `integrations:<name>` config block. When present, the `integrations_auth`
476
+ * tool is ALWAYS registered (resolves a token by env var at call time — kills
477
+ * the non-interactive-shell gotcha), and `noir.clickup_write` is registered
478
+ * when a `noir-clickup` declaration with `runtime:'gated-write-proxy'` is
479
+ * discovered. Both tools degrade gracefully (`no-token`/`no-config`) when the
480
+ * binding/env is absent — never a crash. Token values NEVER enter stderr or
481
+ * audit bodies (only the tool RESULT to the trusted host + the outbound
482
+ * `Authorization` header).
483
+ */
484
+ integrations?: IntegrationService;
251
485
  }
252
486
  /** JSON returned by the `store_status` tool. */
253
487
  interface StoreStatus {
@@ -321,4 +555,4 @@ declare function openStoreForDaemon(projectId: ProjectId, root: string): Promise
321
555
  */
322
556
  declare function buildWorkflowEngine(store: Store, root: string, projectId: ProjectId): WorkflowEngine;
323
557
 
324
- export { type DaemonRecord, type DaemonStore, type EnsureResult, type HostStatus, type RunningDaemon, type ServerContext, type StartHttpOptions, type StatusContext, type StoreStatus, type Transport, type WorkflowStatus, buildContextEngine, buildMemoryEngine, buildStatus, buildWorkflowEngine, clearDaemonRecord, createNoirServer, daemonJsonPath, ensureDaemonRunning, noirHome, openStoreForDaemon, pidAlive, readDaemonRecord, resolveConsolidationCapability, resolveMemoryConsolidation, startHttpServer, startStdioServer, writeDaemonRecord };
558
+ export { type DaemonRecord, type DaemonStore, type EnsureResult, type HostStatus, type IntegrationAuditEntry, type IntegrationBinding, type IntegrationService, InvalidOp, type RunningDaemon, type ServerContext, type StartHttpOptions, type StatusContext, type StoreStatus, type Transport, type WorkflowStatus, buildContextEngine, buildIntegrationService, buildMemoryEngine, buildRequests, buildStatus, buildWorkflowEngine, clearDaemonRecord, createNoirServer, daemonJsonPath, ensureDaemonRunning, executeOp, findBinding, noirHome, openStoreForDaemon, parseH2Tasks, pidAlive, previewRows, readDaemonRecord, resolveConsolidationCapability, resolveMemoryConsolidation, resolveToken, shortNameOf, startHttpServer, startStdioServer, writeDaemonRecord, writeIntegrationAudit };