@a5c-ai/channels-adapter 5.1.1-staging.5ee7f5f98b91
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/LICENSE +21 -0
- package/README.md +661 -0
- package/dist/backend.d.ts +7 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +22 -0
- package/dist/backend.js.map +1 -0
- package/dist/backends/github.d.ts +16 -0
- package/dist/backends/github.d.ts.map +1 -0
- package/dist/backends/github.js +357 -0
- package/dist/backends/github.js.map +1 -0
- package/dist/backends/jira.d.ts +18 -0
- package/dist/backends/jira.d.ts.map +1 -0
- package/dist/backends/jira.js +256 -0
- package/dist/backends/jira.js.map +1 -0
- package/dist/backends/webhook.d.ts +25 -0
- package/dist/backends/webhook.d.ts.map +1 -0
- package/dist/backends/webhook.js +206 -0
- package/dist/backends/webhook.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +28 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +323 -0
- package/dist/config.js.map +1 -0
- package/dist/dedup.d.ts +26 -0
- package/dist/dedup.d.ts.map +1 -0
- package/dist/dedup.js +68 -0
- package/dist/dedup.js.map +1 -0
- package/dist/filter.d.ts +9 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +115 -0
- package/dist/filter.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/poller.d.ts +63 -0
- package/dist/poller.d.ts.map +1 -0
- package/dist/poller.js +195 -0
- package/dist/poller.js.map +1 -0
- package/dist/registry.d.ts +22 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +59 -0
- package/dist/registry.js.map +1 -0
- package/dist/relay.d.ts +58 -0
- package/dist/relay.d.ts.map +1 -0
- package/dist/relay.js +172 -0
- package/dist/relay.js.map +1 -0
- package/dist/runtime.d.ts +27 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +198 -0
- package/dist/runtime.js.map +1 -0
- package/dist/server.d.ts +64 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +217 -0
- package/dist/server.js.map +1 -0
- package/dist/spawner.d.ts +96 -0
- package/dist/spawner.d.ts.map +1 -0
- package/dist/spawner.js +368 -0
- package/dist/spawner.js.map +1 -0
- package/dist/state.d.ts +43 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +92 -0
- package/dist/state.js.map +1 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +78 -0
package/dist/spawner.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
// Event-triggered session spawner (SPEC §10 / DESIGN §7).
|
|
2
|
+
//
|
|
3
|
+
// A surviving event (post filter + dedup) can spawn a fresh agent session via
|
|
4
|
+
// `@a5c-ai/adapters` instead of — or in addition to — emitting into the current
|
|
5
|
+
// session. The spawned session is SELF-ASSOCIATED: it is re-launched with THIS
|
|
6
|
+
// MCP server over stdio (same config path) and handed the event context + the
|
|
7
|
+
// same `reply_to` token, so it can post back to the SAME origin by calling the
|
|
8
|
+
// `reply` tool.
|
|
9
|
+
//
|
|
10
|
+
// Two seams:
|
|
11
|
+
// - `buildSpawnRunOptions(source, event, { configPath, replySecret })` is a
|
|
12
|
+
// PURE function mapping the effective spawn config + event -> adapters
|
|
13
|
+
// `RunOptions`. No I/O, no client — the cheapest, most stable unit target.
|
|
14
|
+
// - `SessionSpawner` calls an INJECTED adapters-like client's `run(opts)` with
|
|
15
|
+
// bounded concurrency + error isolation. `@a5c-ai/adapters` is lazy-loaded
|
|
16
|
+
// only when no client is injected (so the suite stays fully offline and the
|
|
17
|
+
// dep is never imported in CI).
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
20
|
+
/**
|
|
21
|
+
* The canonical adapters agent id for Claude Code, as registered by
|
|
22
|
+
* `registerBuiltInAdapters(client)` in `@a5c-ai/adapters` (verified empirically:
|
|
23
|
+
* `client.adapters.get('claude')` resolves; `'claude-code'` does NOT). SPEC §10
|
|
24
|
+
* examples and configs write `claude-code` as a friendly alias — we normalize it
|
|
25
|
+
* to the canonical id so `client.run({ agent })` actually resolves the adapter.
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_AGENT = 'claude';
|
|
28
|
+
/**
|
|
29
|
+
* Map of friendly/catalog agent aliases -> the canonical adapter key the
|
|
30
|
+
* `@a5c-ai/adapters` registry resolves. `'claude-code'` is the documented friendly
|
|
31
|
+
* name (SPEC §10.3) but is an UNNORMALIZED catalog alias the registry does not
|
|
32
|
+
* resolve; the registered id is `'claude'`. A user may write either.
|
|
33
|
+
*/
|
|
34
|
+
const AGENT_ALIASES = { 'claude-code': 'claude' };
|
|
35
|
+
/**
|
|
36
|
+
* Normalize a configured agent id to the canonical adapter key, so a friendly
|
|
37
|
+
* alias (e.g. `claude-code`) resolves against the real adapters registry. An
|
|
38
|
+
* unknown/already-canonical id passes through unchanged.
|
|
39
|
+
*/
|
|
40
|
+
export function normalizeAgentId(agent) {
|
|
41
|
+
if (agent == null || agent === '')
|
|
42
|
+
return DEFAULT_AGENT;
|
|
43
|
+
return AGENT_ALIASES[agent] || agent;
|
|
44
|
+
}
|
|
45
|
+
/** Absolute path to THIS framework's cli.js, resolved from this module so it is
|
|
46
|
+
* correct regardless of the child's cwd (Windows-safe; no process.cwd reliance). */
|
|
47
|
+
function defaultCliPath() {
|
|
48
|
+
return fileURLToPath(new URL('./cli.js', import.meta.url));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Render a `promptTemplate` with literal `{{…}}` substitution. Supported
|
|
52
|
+
* placeholders: `{{content}}`, `{{reply_to}}`, `{{source_id}}`, `{{meta.KEY}}`.
|
|
53
|
+
* Substitution is a SINGLE pass with literal values, so untrusted event text in
|
|
54
|
+
* `content`/`meta` can never inject a *new* placeholder (an unknown `{{meta.X}}`
|
|
55
|
+
* or missing key expands to the empty string).
|
|
56
|
+
*/
|
|
57
|
+
function renderTemplate(template, { content, reply_to, source_id, meta }) {
|
|
58
|
+
return String(template).replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_m, key) => {
|
|
59
|
+
if (key === 'content')
|
|
60
|
+
return content == null ? '' : String(content);
|
|
61
|
+
if (key === 'reply_to')
|
|
62
|
+
return reply_to == null ? '' : String(reply_to);
|
|
63
|
+
if (key === 'source_id')
|
|
64
|
+
return source_id == null ? '' : String(source_id);
|
|
65
|
+
if (key.startsWith('meta.')) {
|
|
66
|
+
const metaKey = key.slice('meta.'.length);
|
|
67
|
+
const val = meta ? meta[metaKey] : undefined;
|
|
68
|
+
return val == null ? '' : String(val);
|
|
69
|
+
}
|
|
70
|
+
// Unknown placeholder -> empty string (boring, predictable).
|
|
71
|
+
return '';
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build the default spawned prompt (AC-23): the event content, the routing-
|
|
76
|
+
* relevant meta (rendered as `key: value` lines), the source id, the `reply_to`
|
|
77
|
+
* token verbatim, and an explicit instruction to call the `reply` tool with it.
|
|
78
|
+
*/
|
|
79
|
+
function buildDefaultPrompt(source, event) {
|
|
80
|
+
const meta = event.meta || {};
|
|
81
|
+
const reply_to = meta.reply_to;
|
|
82
|
+
const lines = [];
|
|
83
|
+
lines.push('A new channel event was triggered and needs your attention.');
|
|
84
|
+
lines.push('');
|
|
85
|
+
lines.push(`source_id: ${source.id}`);
|
|
86
|
+
// Routing-relevant meta first (whatever identifies origin for that backend),
|
|
87
|
+
// then the remaining identifier meta (kind/author/etc.), excluding reply_to
|
|
88
|
+
// (surfaced separately, verbatim, below).
|
|
89
|
+
const routingKeys = ['repo', 'issue_number', 'project', 'issue_key', 'kind', 'author'];
|
|
90
|
+
const seen = new Set();
|
|
91
|
+
for (const key of routingKeys) {
|
|
92
|
+
if (key in meta && meta[key] != null && meta[key] !== '') {
|
|
93
|
+
lines.push(`${key}: ${meta[key]}`);
|
|
94
|
+
seen.add(key);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
98
|
+
if (key === 'reply_to' || seen.has(key))
|
|
99
|
+
continue;
|
|
100
|
+
if (value == null || value === '')
|
|
101
|
+
continue;
|
|
102
|
+
lines.push(`${key}: ${value}`);
|
|
103
|
+
}
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push('Event content:');
|
|
106
|
+
lines.push(event.content == null ? '' : String(event.content));
|
|
107
|
+
lines.push('');
|
|
108
|
+
lines.push(`reply_to: ${reply_to == null ? '' : reply_to}`);
|
|
109
|
+
lines.push('');
|
|
110
|
+
lines.push('To respond to the origin of this event, call the `reply` tool with `reply_to` ' +
|
|
111
|
+
'set to the token above and your message as `text`. Echo the token exactly; ' +
|
|
112
|
+
'do not modify it.');
|
|
113
|
+
return lines.join('\n');
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* PURE mapping of (source, event, ctx) -> adapters `RunOptions` (SPEC §10.2).
|
|
117
|
+
* Reads the source's EFFECTIVE spawn config (`source.spawn`, already merged over
|
|
118
|
+
* the global defaults by config.js / the caller). Optional fields are OMITTED
|
|
119
|
+
* when unset (never emitted as `undefined`) so the adapter's own defaults apply.
|
|
120
|
+
*/
|
|
121
|
+
export function buildSpawnRunOptions(source, event, ctx = {}) {
|
|
122
|
+
const spawn = (source && source.spawn) || {};
|
|
123
|
+
const { configPath, replySecret, resolveCliPath } = ctx;
|
|
124
|
+
// --- prompt -------------------------------------------------------------
|
|
125
|
+
let prompt;
|
|
126
|
+
if (typeof spawn.promptTemplate === 'string') {
|
|
127
|
+
prompt = renderTemplate(spawn.promptTemplate, {
|
|
128
|
+
content: event.content,
|
|
129
|
+
reply_to: event.meta ? event.meta.reply_to : undefined,
|
|
130
|
+
source_id: source.id,
|
|
131
|
+
meta: event.meta || {}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
prompt = buildDefaultPrompt(source, event);
|
|
136
|
+
}
|
|
137
|
+
const opts = {
|
|
138
|
+
// Normalize the configured agent to the canonical adapters key so a friendly
|
|
139
|
+
// alias (`claude-code`) resolves against the real registry (`claude`). An
|
|
140
|
+
// unset agent falls back to the canonical default.
|
|
141
|
+
agent: normalizeAgentId(spawn.agent),
|
|
142
|
+
prompt
|
|
143
|
+
};
|
|
144
|
+
// --- mode mapping (exactly one of nonInteractive/interactive) -----------
|
|
145
|
+
const mode = spawn.mode || 'headless';
|
|
146
|
+
if (mode === 'interactive') {
|
|
147
|
+
opts.interactive = true;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
opts.nonInteractive = true;
|
|
151
|
+
}
|
|
152
|
+
// --- optional passthroughs (omitted when unset) -------------------------
|
|
153
|
+
if (spawn.model != null && spawn.model !== '')
|
|
154
|
+
opts.model = spawn.model;
|
|
155
|
+
if (spawn.systemPrompt != null && spawn.systemPrompt !== '') {
|
|
156
|
+
opts.systemPrompt = spawn.systemPrompt;
|
|
157
|
+
}
|
|
158
|
+
// approvalMode defaults to 'yolo' (autonomous reply).
|
|
159
|
+
opts.approvalMode = spawn.approvalMode || 'yolo';
|
|
160
|
+
// cwd resolved to an absolute path against the config's baseDir when set.
|
|
161
|
+
if (spawn.cwd != null && spawn.cwd !== '') {
|
|
162
|
+
const base = (source && source.baseDir) || process.cwd();
|
|
163
|
+
opts.cwd = isAbsolute(spawn.cwd) ? spawn.cwd : resolve(base, spawn.cwd);
|
|
164
|
+
}
|
|
165
|
+
if (spawn.env && typeof spawn.env === 'object') {
|
|
166
|
+
opts.env = { ...spawn.env };
|
|
167
|
+
}
|
|
168
|
+
// --- self-association mcpServers entry (AC-20) --------------------------
|
|
169
|
+
const cliPath = (typeof resolveCliPath === 'function' && resolveCliPath()) || defaultCliPath();
|
|
170
|
+
const selfMcp = {
|
|
171
|
+
name: spawn.selfMcpName || 'mcp-channels',
|
|
172
|
+
transport: 'stdio',
|
|
173
|
+
command: 'node',
|
|
174
|
+
args: [cliPath, configPath],
|
|
175
|
+
env: replySecret ? { MCP_CHANNELS_REPLY_SECRET: replySecret } : {}
|
|
176
|
+
};
|
|
177
|
+
opts.mcpServers = [selfMcp];
|
|
178
|
+
return opts;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The default lazy loader for the `@a5c-ai/adapters` client. Dynamic-imports the
|
|
182
|
+
* package, creates a client via the real `createClient()` factory, registers the
|
|
183
|
+
* built-in adapters ONTO that client via the real `registerBuiltInAdapters(client)`
|
|
184
|
+
* (so `agent:'claude'` resolves — the core `AdapterRegistry` starts empty), and
|
|
185
|
+
* returns it. NEVER imported at module load — only invoked when no client is
|
|
186
|
+
* injected (so tests that inject a fake stay fully offline).
|
|
187
|
+
*
|
|
188
|
+
* Bound HONESTLY to the in-repo `@a5c-ai/adapters` API: `createClient` and
|
|
189
|
+
* `registerBuiltInAdapters` are real exported symbols (re-exported from
|
|
190
|
+
* `@a5c-ai/comm-adapter` + `@a5c-ai/adapters-cli`), and the returned
|
|
191
|
+
* `AgentMuxClient.run(options)` is the real launch entry. The dynamic `import(...)`
|
|
192
|
+
* type is the package's own module type, so the factory/registration call sites are
|
|
193
|
+
* genuinely type-checked against the real signatures rather than cast through
|
|
194
|
+
* `unknown`. The structural `AdaptersClientLike` seam is preserved ONLY at the
|
|
195
|
+
* injection boundary (so the offline suite can supply a fake), and the real
|
|
196
|
+
* `AgentMuxClient` structurally satisfies it (it exposes `run(options): RunHandle`).
|
|
197
|
+
*/
|
|
198
|
+
/* c8 ignore start -- the real `@a5c-ai/adapters` launch path is environment-gated
|
|
199
|
+
(needs the claude CLI + auth) and out of scope for the offline suite (DESIGN §7.7);
|
|
200
|
+
tests always inject a fake client or loadClient. */
|
|
201
|
+
async function defaultLoadClient() {
|
|
202
|
+
const mod = await import('@a5c-ai/adapters');
|
|
203
|
+
const client = mod.createClient();
|
|
204
|
+
mod.registerBuiltInAdapters(client);
|
|
205
|
+
return client;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Launches sessions for surviving events via an injected adapters-like client.
|
|
209
|
+
* Bounded concurrency (a tiny promise-queue semaphore), error isolation (a
|
|
210
|
+
* throwing/rejecting `run` is caught + logged, never thrown to the caller), and
|
|
211
|
+
* an `onDispatch` hook so the poller records seen-on-dispatch exactly once.
|
|
212
|
+
*/
|
|
213
|
+
export class SessionSpawner {
|
|
214
|
+
client;
|
|
215
|
+
configPath;
|
|
216
|
+
replySecret;
|
|
217
|
+
maxConcurrent;
|
|
218
|
+
log;
|
|
219
|
+
resolveCliPath;
|
|
220
|
+
loadClient;
|
|
221
|
+
onDispatch;
|
|
222
|
+
_free;
|
|
223
|
+
_waiters;
|
|
224
|
+
_clientPromise;
|
|
225
|
+
constructor({ client, configPath, replySecret, maxConcurrent = 4, log, resolveCliPath, loadClient, onDispatch } = {}) {
|
|
226
|
+
this.client = client || null;
|
|
227
|
+
this.configPath = configPath;
|
|
228
|
+
this.replySecret = replySecret;
|
|
229
|
+
this.maxConcurrent = Math.max(1, Number(maxConcurrent) || 4);
|
|
230
|
+
this.log = typeof log === 'function' ? log : () => { };
|
|
231
|
+
this.resolveCliPath = resolveCliPath;
|
|
232
|
+
this.loadClient = typeof loadClient === 'function' ? loadClient : defaultLoadClient;
|
|
233
|
+
this.onDispatch = typeof onDispatch === 'function' ? onDispatch : null;
|
|
234
|
+
// Concurrency semaphore: count of free slots + a FIFO queue of waiters.
|
|
235
|
+
this._free = this.maxConcurrent;
|
|
236
|
+
this._waiters = [];
|
|
237
|
+
// Memoize a lazily-loaded client so we resolve the dep at most once.
|
|
238
|
+
this._clientPromise = null;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Resolve the adapters client: the injected one if present, else the lazily
|
|
242
|
+
* loaded one. A failure to obtain a client is surfaced as a CLEAR, actionable
|
|
243
|
+
* error (AC-25). Memoized so the dep is resolved at most once.
|
|
244
|
+
*/
|
|
245
|
+
async _getClient() {
|
|
246
|
+
if (this.client)
|
|
247
|
+
return this.client;
|
|
248
|
+
if (!this._clientPromise) {
|
|
249
|
+
this._clientPromise = (async () => {
|
|
250
|
+
let loaded;
|
|
251
|
+
try {
|
|
252
|
+
loaded = await this.loadClient();
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
throw new Error("Spawn is configured but no client was injected and '@a5c-ai/adapters' " +
|
|
256
|
+
`could not be loaded (${err?.message || err}); install it or inject a client.`);
|
|
257
|
+
}
|
|
258
|
+
if (!loaded || typeof loaded.run !== 'function') {
|
|
259
|
+
throw new Error("Spawn is configured but no usable adapters client is available; install " +
|
|
260
|
+
"'@a5c-ai/adapters' or inject a client with a run(options) method.");
|
|
261
|
+
}
|
|
262
|
+
return loaded;
|
|
263
|
+
})();
|
|
264
|
+
// Don't cache a rejected promise — let a later validate()/spawn retry.
|
|
265
|
+
this._clientPromise.catch(() => {
|
|
266
|
+
this._clientPromise = null;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return this._clientPromise;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Startup validation hook (AC-25): if no client is injected, eagerly attempt
|
|
273
|
+
* to obtain one so a missing dep fails FAST and LOUDLY at startup rather than
|
|
274
|
+
* silently at event time. Throws a clear error when no client can be obtained.
|
|
275
|
+
* A no-op when a client is injected (stays offline).
|
|
276
|
+
*/
|
|
277
|
+
async validate() {
|
|
278
|
+
if (this.client)
|
|
279
|
+
return;
|
|
280
|
+
await this._getClient();
|
|
281
|
+
}
|
|
282
|
+
/** Acquire a concurrency slot (awaits when the cap is reached). */
|
|
283
|
+
_acquire() {
|
|
284
|
+
if (this._free > 0) {
|
|
285
|
+
this._free -= 1;
|
|
286
|
+
return Promise.resolve();
|
|
287
|
+
}
|
|
288
|
+
return new Promise((res) => {
|
|
289
|
+
this._waiters.push(res);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/** Release a concurrency slot, handing it to the next waiter if any. */
|
|
293
|
+
_release() {
|
|
294
|
+
const next = this._waiters.shift();
|
|
295
|
+
if (next) {
|
|
296
|
+
next();
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
this._free += 1;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Spawn a session for one surviving event. Builds RunOptions, resolves the
|
|
304
|
+
* client, acquires a concurrency slot, calls `client.run(opts)`, and returns
|
|
305
|
+
* the launch outcome. NEVER throws to the caller (the poller): a launch error
|
|
306
|
+
* is caught + logged and converted to `{ ok:false, error }`. The event is
|
|
307
|
+
* recorded as dispatched EXACTLY ONCE (AC-24), regardless of launch outcome.
|
|
308
|
+
*
|
|
309
|
+
* The missing-client/dep error (AC-25) is the one case allowed to surface as a
|
|
310
|
+
* rejection (it is a misconfiguration, not a per-launch failure).
|
|
311
|
+
*/
|
|
312
|
+
async spawn(source, event) {
|
|
313
|
+
// Resolve the client FIRST. A missing client/dep is a hard configuration
|
|
314
|
+
// error (AC-25) and is allowed to surface. (When a client is injected this
|
|
315
|
+
// never throws.)
|
|
316
|
+
const client = await this._getClient();
|
|
317
|
+
// Record the event as dispatched exactly once, BEFORE the launch settles, so
|
|
318
|
+
// a launch failure does not roll back dedup (at-most-once stance, AC-24).
|
|
319
|
+
if (this.onDispatch) {
|
|
320
|
+
try {
|
|
321
|
+
this.onDispatch(source, event);
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
// an onDispatch hook failure must not affect the spawn outcome
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const opts = buildSpawnRunOptions(source, event, {
|
|
328
|
+
configPath: this.configPath,
|
|
329
|
+
replySecret: this.replySecret,
|
|
330
|
+
resolveCliPath: this.resolveCliPath
|
|
331
|
+
});
|
|
332
|
+
await this._acquire();
|
|
333
|
+
// `client.run` may throw SYNCHRONOUSLY (bad options) -> caught here -> the
|
|
334
|
+
// slot is released and the failure reported. This is a launch-start failure.
|
|
335
|
+
let handle;
|
|
336
|
+
try {
|
|
337
|
+
handle = client.run(opts);
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
this._release();
|
|
341
|
+
this.log(`spawner: spawn failed for source "${source?.id}": ${err?.message || err}`);
|
|
342
|
+
return { ok: false, error: err };
|
|
343
|
+
}
|
|
344
|
+
// The slot is bound to the HANDLE's lifetime: the cap is on in-flight launches
|
|
345
|
+
// (DESIGN §7.2), so the slot stays occupied while the handle is open, and is
|
|
346
|
+
// freed the moment it settles (success OR failure).
|
|
347
|
+
let rejected = false;
|
|
348
|
+
let launchError;
|
|
349
|
+
Promise.resolve(handle).then(() => {
|
|
350
|
+
this._release();
|
|
351
|
+
}, (err) => {
|
|
352
|
+
rejected = true;
|
|
353
|
+
launchError = err;
|
|
354
|
+
this._release();
|
|
355
|
+
});
|
|
356
|
+
// Give an IMMEDIATE rejection a chance to settle, so it is reported as
|
|
357
|
+
// `{ ok:false }` and logged (AC-24). A pending (long-lived) handle does not
|
|
358
|
+
// reject in this window and is reported as a started launch.
|
|
359
|
+
await Promise.resolve();
|
|
360
|
+
await Promise.resolve();
|
|
361
|
+
if (rejected) {
|
|
362
|
+
this.log(`spawner: spawn failed for source "${source?.id}": ${launchError?.message || launchError}`);
|
|
363
|
+
return { ok: false, error: launchError };
|
|
364
|
+
}
|
|
365
|
+
return { ok: true, handle };
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
//# sourceMappingURL=spawner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawner.js","sourceRoot":"","sources":["../src/spawner.ts"],"names":[],"mappings":"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"}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface SourceState {
|
|
2
|
+
cursor: unknown;
|
|
3
|
+
seen: string[];
|
|
4
|
+
}
|
|
5
|
+
export interface StateSetArgs {
|
|
6
|
+
cursor?: unknown;
|
|
7
|
+
seen?: string[];
|
|
8
|
+
keepSeen?: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface StateStoreLike {
|
|
11
|
+
get(id: string): SourceState;
|
|
12
|
+
set(id: string, state: StateSetArgs): void | Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* File-backed state store. One JSON file per source under `dir`.
|
|
16
|
+
*/
|
|
17
|
+
export declare class StateStore implements StateStoreLike {
|
|
18
|
+
dir: string;
|
|
19
|
+
maxSeenPerSource?: number;
|
|
20
|
+
constructor({ dir, maxSeenPerSource }?: {
|
|
21
|
+
dir: string;
|
|
22
|
+
maxSeenPerSource?: number;
|
|
23
|
+
});
|
|
24
|
+
_pathFor(id: string): string;
|
|
25
|
+
get(id: string): SourceState;
|
|
26
|
+
/**
|
|
27
|
+
* Atomically persist state for a source (temp file + rename). `state.keepSeen`
|
|
28
|
+
* (optional) lists boundary ids the FIFO prune must never drop (finding §3).
|
|
29
|
+
*/
|
|
30
|
+
set(id: string, state: StateSetArgs): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* In-memory state store with the same contract (pluggable for tests).
|
|
34
|
+
*/
|
|
35
|
+
export declare class MemoryStateStore implements StateStoreLike {
|
|
36
|
+
maxSeenPerSource?: number;
|
|
37
|
+
_map: Map<string, SourceState>;
|
|
38
|
+
constructor({ maxSeenPerSource }?: {
|
|
39
|
+
maxSeenPerSource?: number;
|
|
40
|
+
});
|
|
41
|
+
get(id: string): SourceState;
|
|
42
|
+
set(id: string, state: StateSetArgs): Promise<void>;
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,CAAC;IAC7B,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AASD;;GAEG;AACH,qBAAa,UAAW,YAAW,cAAc;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,MAAM,CAAC;gBAEd,EAAE,GAAG,EAAE,gBAAgB,EAAE,GAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAqD;IAOpI,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM;IAI5B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW;IAiB5B;;;OAGG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAU1D;AAED;;GAEG;AACH,qBAAa,gBAAiB,YAAW,cAAc;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;gBAEnB,EAAE,gBAAgB,EAAE,GAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAO;IAKpE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW;IAMtB,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAM1D"}
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// State persistence (SPEC §3, DESIGN §1).
|
|
2
|
+
//
|
|
3
|
+
// Each source has state `{ cursor, seen[] }`. The cursor narrows the next API
|
|
4
|
+
// query to "since last time"; the seen-set is the correctness backstop that
|
|
5
|
+
// guarantees at-most-once across overlapping windows. `seen` is FIFO-bounded by
|
|
6
|
+
// `maxSeenPerSource` so state files stay small.
|
|
7
|
+
//
|
|
8
|
+
// Two implementations:
|
|
9
|
+
// - StateStore: file-backed JSON, one file per source, written atomically
|
|
10
|
+
// (temp file + rename) so a crash never leaves a half-written state file.
|
|
11
|
+
// - MemoryStateStore: in-memory, for tests / ephemeral runs.
|
|
12
|
+
import { readFileSync, writeFileSync, renameSync, mkdirSync, existsSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { boundSeen } from './dedup.js';
|
|
15
|
+
const DEFAULT_STATE = () => ({ cursor: null, seen: [] });
|
|
16
|
+
/** Turn a source id into a filesystem-safe file name. */
|
|
17
|
+
function safeFileName(id) {
|
|
18
|
+
return `${String(id).replace(/[^A-Za-z0-9._-]/g, '_')}.json`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* File-backed state store. One JSON file per source under `dir`.
|
|
22
|
+
*/
|
|
23
|
+
export class StateStore {
|
|
24
|
+
dir;
|
|
25
|
+
maxSeenPerSource;
|
|
26
|
+
constructor({ dir, maxSeenPerSource } = {}) {
|
|
27
|
+
if (!dir)
|
|
28
|
+
throw new Error('StateStore requires a `dir`');
|
|
29
|
+
this.dir = dir;
|
|
30
|
+
this.maxSeenPerSource = maxSeenPerSource;
|
|
31
|
+
mkdirSync(this.dir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
_pathFor(id) {
|
|
34
|
+
return join(this.dir, safeFileName(id));
|
|
35
|
+
}
|
|
36
|
+
get(id) {
|
|
37
|
+
const file = this._pathFor(id);
|
|
38
|
+
if (!existsSync(file))
|
|
39
|
+
return DEFAULT_STATE();
|
|
40
|
+
try {
|
|
41
|
+
const raw = readFileSync(file, 'utf8');
|
|
42
|
+
const parsed = JSON.parse(raw);
|
|
43
|
+
return {
|
|
44
|
+
cursor: parsed?.cursor ?? null,
|
|
45
|
+
seen: Array.isArray(parsed?.seen) ? parsed.seen : []
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// A corrupt/unreadable state file degrades to empty state rather than
|
|
50
|
+
// crashing a poll; the cursor simply restarts.
|
|
51
|
+
return DEFAULT_STATE();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Atomically persist state for a source (temp file + rename). `state.keepSeen`
|
|
56
|
+
* (optional) lists boundary ids the FIFO prune must never drop (finding §3).
|
|
57
|
+
*/
|
|
58
|
+
async set(id, state) {
|
|
59
|
+
const next = {
|
|
60
|
+
cursor: state?.cursor ?? null,
|
|
61
|
+
seen: boundSeen(state?.seen, { max: this.maxSeenPerSource, keep: state?.keepSeen })
|
|
62
|
+
};
|
|
63
|
+
const file = this._pathFor(id);
|
|
64
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
65
|
+
writeFileSync(tmp, JSON.stringify(next), 'utf8');
|
|
66
|
+
renameSync(tmp, file);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* In-memory state store with the same contract (pluggable for tests).
|
|
71
|
+
*/
|
|
72
|
+
export class MemoryStateStore {
|
|
73
|
+
maxSeenPerSource;
|
|
74
|
+
_map;
|
|
75
|
+
constructor({ maxSeenPerSource } = {}) {
|
|
76
|
+
this.maxSeenPerSource = maxSeenPerSource;
|
|
77
|
+
this._map = new Map();
|
|
78
|
+
}
|
|
79
|
+
get(id) {
|
|
80
|
+
const v = this._map.get(id);
|
|
81
|
+
if (!v)
|
|
82
|
+
return DEFAULT_STATE();
|
|
83
|
+
return { cursor: v.cursor, seen: [...v.seen] };
|
|
84
|
+
}
|
|
85
|
+
async set(id, state) {
|
|
86
|
+
this._map.set(id, {
|
|
87
|
+
cursor: state?.cursor ?? null,
|
|
88
|
+
seen: boundSeen(state?.seen, { max: this.maxSeenPerSource, keep: state?.keepSeen })
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export interface ChannelEvent {
|
|
2
|
+
/** Stable dedup id for this event. */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Text Claude sees inside <channel>…</channel>. */
|
|
5
|
+
content: string;
|
|
6
|
+
/** Identifier-keyed channel attributes ([A-Za-z0-9_]+). */
|
|
7
|
+
meta: Record<string, string>;
|
|
8
|
+
/** Raw upstream object (so declarative dot-path filters can match). */
|
|
9
|
+
payload: Record<string, unknown>;
|
|
10
|
+
/** Origin coordinates a reply needs to reach back to. */
|
|
11
|
+
routing: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
export interface PollContext {
|
|
14
|
+
source: Record<string, unknown>;
|
|
15
|
+
state: {
|
|
16
|
+
cursor: unknown;
|
|
17
|
+
seen: string[];
|
|
18
|
+
};
|
|
19
|
+
http: (url: string | URL, opts?: Record<string, unknown>) => Promise<unknown>;
|
|
20
|
+
log: (...args: unknown[]) => void;
|
|
21
|
+
now: Date;
|
|
22
|
+
}
|
|
23
|
+
export interface PollResult {
|
|
24
|
+
events: ChannelEvent[];
|
|
25
|
+
state: {
|
|
26
|
+
cursor: unknown;
|
|
27
|
+
seen?: string[];
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface ReplyArgs {
|
|
31
|
+
routing: Record<string, unknown>;
|
|
32
|
+
text: string;
|
|
33
|
+
source: Record<string, unknown>;
|
|
34
|
+
http: (url: string | URL, opts?: Record<string, unknown>) => Promise<unknown>;
|
|
35
|
+
}
|
|
36
|
+
export interface ReplyResult {
|
|
37
|
+
ok: boolean;
|
|
38
|
+
ref?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface Backend {
|
|
41
|
+
type: string;
|
|
42
|
+
validateConfig?: (source: Record<string, unknown>) => string[];
|
|
43
|
+
init?: (source: Record<string, unknown>) => void | Promise<void>;
|
|
44
|
+
poll: (ctx: PollContext) => Promise<PollResult>;
|
|
45
|
+
reply: (a: ReplyArgs) => Promise<ReplyResult>;
|
|
46
|
+
}
|
|
47
|
+
export interface ReplyToken {
|
|
48
|
+
sourceId: string;
|
|
49
|
+
backendType: string;
|
|
50
|
+
routing: Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
/** Effective spawn config (global defaults merged with per-source overrides). */
|
|
53
|
+
export interface SpawnConfig {
|
|
54
|
+
agent?: string;
|
|
55
|
+
mode?: 'headless' | 'interactive';
|
|
56
|
+
approvalMode?: 'yolo' | 'prompt' | 'deny';
|
|
57
|
+
selfMcpName?: string;
|
|
58
|
+
maxConcurrent?: number;
|
|
59
|
+
model?: string;
|
|
60
|
+
cwd?: string;
|
|
61
|
+
systemPrompt?: string;
|
|
62
|
+
env?: Record<string, string>;
|
|
63
|
+
promptTemplate?: string;
|
|
64
|
+
}
|
|
65
|
+
/** Adapters McpServerConfig (the self-association entry). */
|
|
66
|
+
export interface McpServerConfig {
|
|
67
|
+
name: string;
|
|
68
|
+
transport: 'stdio';
|
|
69
|
+
command: string;
|
|
70
|
+
args?: string[];
|
|
71
|
+
env?: Record<string, string>;
|
|
72
|
+
url?: string;
|
|
73
|
+
headers?: Record<string, string>;
|
|
74
|
+
}
|
|
75
|
+
/** Adapters RunOptions produced by `buildSpawnRunOptions` (SPEC §10.2). The
|
|
76
|
+
* package's OWN structural type — deliberately NOT imported from the adapters
|
|
77
|
+
* SDK so `buildSpawnRunOptions` stays a pure mapping duck-typed by `client.run()`. */
|
|
78
|
+
export interface RunOptions {
|
|
79
|
+
agent: string;
|
|
80
|
+
prompt: string;
|
|
81
|
+
mcpServers: McpServerConfig[];
|
|
82
|
+
nonInteractive?: boolean;
|
|
83
|
+
interactive?: boolean;
|
|
84
|
+
cwd?: string;
|
|
85
|
+
model?: string;
|
|
86
|
+
approvalMode?: 'yolo' | 'prompt' | 'deny';
|
|
87
|
+
systemPrompt?: string;
|
|
88
|
+
env?: Record<string, string>;
|
|
89
|
+
}
|
|
90
|
+
/** The leaf-clause operators the filter engine understands (SPEC §4). */
|
|
91
|
+
export type FilterOp = 'eq' | 'ne' | 'in' | 'nin' | 'includes' | 'contains' | 'regex' | 'exists' | 'gt' | 'gte' | 'lt' | 'lte';
|
|
92
|
+
/** A leaf clause: a dot-path `field`, an `op`, a comparison `value`. */
|
|
93
|
+
export interface FilterLeaf {
|
|
94
|
+
field?: string;
|
|
95
|
+
op?: FilterOp;
|
|
96
|
+
value?: unknown;
|
|
97
|
+
ignoreCase?: boolean;
|
|
98
|
+
}
|
|
99
|
+
/** A combinator clause: `all` (AND), `any` (OR), or `not` (negation). */
|
|
100
|
+
export type FilterCombinator = {
|
|
101
|
+
all?: FilterSpec[];
|
|
102
|
+
} | {
|
|
103
|
+
any?: FilterSpec[];
|
|
104
|
+
} | {
|
|
105
|
+
not?: FilterSpec;
|
|
106
|
+
};
|
|
107
|
+
/** A filter spec is either a leaf clause or a combinator (recursively). */
|
|
108
|
+
export type FilterSpec = FilterLeaf | FilterCombinator;
|
|
109
|
+
/** A normalized source after `loadConfig`: defaults applied + spawn merged. */
|
|
110
|
+
export interface NormalizedSource {
|
|
111
|
+
id: unknown;
|
|
112
|
+
backend: unknown;
|
|
113
|
+
pollIntervalSeconds: number;
|
|
114
|
+
auth: Record<string, unknown>;
|
|
115
|
+
config: Record<string, unknown>;
|
|
116
|
+
filter: unknown;
|
|
117
|
+
routing: Record<string, unknown>;
|
|
118
|
+
onEvent: 'emit' | 'spawn' | 'both';
|
|
119
|
+
spawn: SpawnConfig;
|
|
120
|
+
baseDir: string;
|
|
121
|
+
}
|
|
122
|
+
/** The validated channels config returned by `loadConfig`. */
|
|
123
|
+
export interface ChannelsConfig {
|
|
124
|
+
server: {
|
|
125
|
+
name?: unknown;
|
|
126
|
+
instructions?: unknown;
|
|
127
|
+
permissionRelay: boolean;
|
|
128
|
+
replySecret?: string;
|
|
129
|
+
};
|
|
130
|
+
state: {
|
|
131
|
+
dir?: unknown;
|
|
132
|
+
maxSeenPerSource: number;
|
|
133
|
+
};
|
|
134
|
+
defaults: {
|
|
135
|
+
pollIntervalSeconds: number;
|
|
136
|
+
};
|
|
137
|
+
spawn: SpawnConfig;
|
|
138
|
+
sources: NormalizedSource[];
|
|
139
|
+
baseDir: string;
|
|
140
|
+
}
|