@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 +235 -1
- package/dist/index.js +626 -16
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
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 };
|