negotium 0.1.38 → 0.1.39

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 (49) hide show
  1. package/README.md +19 -0
  2. package/dist/agent-helpers.js +16227 -6264
  3. package/dist/agent-helpers.js.map +82 -35
  4. package/dist/browser-runtime.js +1600 -0
  5. package/dist/browser-runtime.js.map +28 -0
  6. package/dist/hosted-agent.js +2 -2
  7. package/dist/hosted-agent.js.map +2 -2
  8. package/dist/main.js +5041 -4533
  9. package/dist/main.js.map +22 -22
  10. package/dist/mcp-factories.js +15141 -301
  11. package/dist/mcp-factories.js.map +100 -7
  12. package/dist/prompts.js +117 -49
  13. package/dist/prompts.js.map +3 -3
  14. package/dist/query-runtime.js +309 -1
  15. package/dist/query-runtime.js.map +9 -4
  16. package/dist/runtime/src/agents/archiver.ts +407 -296
  17. package/dist/runtime/src/agents/mcp-tools/ask-user.ts +440 -274
  18. package/dist/runtime/src/agents/mcp-tools/self-config.ts +139 -65
  19. package/dist/runtime/src/agents/mcp-tools/spawn-subagent.ts +1037 -816
  20. package/dist/runtime/src/agents/public-helpers.ts +82 -0
  21. package/dist/runtime/src/agents/self-config-core.ts +574 -278
  22. package/dist/runtime/src/agents/topic-cleanup.ts +167 -129
  23. package/dist/runtime/src/index.ts +4 -1
  24. package/dist/runtime/src/mcp/factories/index.ts +9 -0
  25. package/dist/runtime/src/mcp/session-comm/topic-catalog.ts +132 -0
  26. package/dist/runtime/src/mcp/session-comm/topics.ts +23 -57
  27. package/dist/runtime/src/platform/playwright/manager.ts +323 -50
  28. package/dist/runtime/src/platform/playwright/public-runtime.ts +45 -0
  29. package/dist/runtime/src/prompts/builders.ts +192 -51
  30. package/dist/runtime/src/query/public-runtime.ts +8 -0
  31. package/dist/runtime/src/query/state.ts +69 -33
  32. package/dist/runtime/src/version.ts +1 -1
  33. package/dist/types/packages/core/src/agents/api-topic-agent-switch.d.ts +38 -0
  34. package/dist/types/packages/core/src/agents/archiver.d.ts +52 -16
  35. package/dist/types/packages/core/src/agents/mcp-tools/ask-user.d.ts +75 -4
  36. package/dist/types/packages/core/src/agents/mcp-tools/self-config.d.ts +17 -0
  37. package/dist/types/packages/core/src/agents/mcp-tools/spawn-subagent.d.ts +112 -25
  38. package/dist/types/packages/core/src/agents/public-helpers.d.ts +7 -0
  39. package/dist/types/packages/core/src/agents/self-config-core.d.ts +185 -0
  40. package/dist/types/packages/core/src/agents/topic-cleanup.d.ts +30 -24
  41. package/dist/types/packages/core/src/mcp/factories/index.d.ts +1 -0
  42. package/dist/types/packages/core/src/mcp/session-comm/topic-catalog.d.ts +46 -0
  43. package/dist/types/packages/core/src/platform/playwright/manager.d.ts +56 -2
  44. package/dist/types/packages/core/src/platform/playwright/public-runtime.d.ts +4 -0
  45. package/dist/types/packages/core/src/prompts/builders.d.ts +29 -3
  46. package/dist/types/packages/core/src/query/public-runtime.d.ts +1 -0
  47. package/dist/types/packages/core/src/query/state.d.ts +13 -0
  48. package/dist/types/packages/core/src/version.d.ts +1 -1
  49. package/package.json +6 -1
@@ -175,6 +175,44 @@ export interface SessionSystemPromptOpts {
175
175
  fileDeliveryTools?: boolean;
176
176
  }
177
177
 
178
+ export type SessionPromptKind = "topic" | "channel" | "manager";
179
+ export type PromptSectionSlot =
180
+ | "after-runtime-tools"
181
+ | "after-shared-tasks"
182
+ | "before-session-communication"
183
+ | "after-session-communication"
184
+ | "before-topic-configuration"
185
+ | "after-topic-configuration"
186
+ | "after-system-prompt";
187
+
188
+ export interface PromptSectionContext extends SessionSystemPromptOpts {
189
+ sessionKind: SessionPromptKind;
190
+ }
191
+
192
+ export interface PromptExtraSection {
193
+ id: string;
194
+ slot: PromptSectionSlot;
195
+ order?: number;
196
+ render(context: PromptSectionContext): string | null | undefined;
197
+ }
198
+
199
+ export interface PromptTemplateRequest {
200
+ kind: "topic-system" | "channel-system" | "manager-system" | "visual-design";
201
+ filename: string;
202
+ fallback: string;
203
+ }
204
+
205
+ export interface PromptBuilderHost {
206
+ readonly loadTemplate?: (request: PromptTemplateRequest) => string | null | undefined;
207
+ readonly extraSections?: readonly PromptExtraSection[];
208
+ }
209
+
210
+ export interface PromptBuilders {
211
+ buildTopicSystemPrompt(opts: SessionSystemPromptOpts): string;
212
+ buildChannelSystemPrompt(opts: SessionSystemPromptOpts): string;
213
+ buildManagerSystemPrompt(opts: SessionSystemPromptOpts): string;
214
+ }
215
+
178
216
  interface RuntimeToolSectionOpts {
179
217
  agentKind: AgentKind;
180
218
  canSpawnSubagents?: boolean;
@@ -187,7 +225,15 @@ interface RuntimeToolSectionOpts {
187
225
  subagentReportMode?: SubagentReportMode;
188
226
  }
189
227
 
190
- function buildRuntimeToolSection(opts: RuntimeToolSectionOpts): string {
228
+ interface RuntimeToolSectionExtensions {
229
+ render(slot: PromptSectionSlot): string[];
230
+ visualDesignGuide: string;
231
+ }
232
+
233
+ function buildRuntimeToolSection(
234
+ opts: RuntimeToolSectionOpts,
235
+ extensions: RuntimeToolSectionExtensions,
236
+ ): string {
191
237
  const {
192
238
  agentKind,
193
239
  canSpawnSubagents = false,
@@ -282,13 +328,16 @@ function buildRuntimeToolSection(opts: RuntimeToolSectionOpts): string {
282
328
  'Do not use provider built-in "AskUserQuestion"; it is disabled or unsupported in this headless chat runtime. Use the runtime ask_user_question tool instead.',
283
329
  ]),
284
330
  scheduleSelfToolLine,
285
- ...(visualTools ? ["", visualDesignGuide()] : []),
331
+ ...(visualTools && extensions.visualDesignGuide ? ["", extensions.visualDesignGuide] : []),
332
+ ...extensions.render("after-runtime-tools"),
286
333
  "",
287
334
  "## Shared Tasks",
288
335
  taskToolLine,
289
336
  "Use this shared task store for plans, task progress, and checklist updates. It is visible across claude/codex/maestro turns and drives the live task panel.",
290
337
  nativeTaskPolicyLine,
338
+ ...extensions.render("after-shared-tasks"),
291
339
  ...fileDeliverySection,
340
+ ...extensions.render("before-session-communication"),
292
341
  "",
293
342
  "## Session Communication",
294
343
  ...(subagentParentTitle
@@ -313,6 +362,7 @@ function buildRuntimeToolSection(opts: RuntimeToolSectionOpts): string {
313
362
  : []),
314
363
  "Do not use session communication to make another topic perform destructive changes without the user's clear intent.",
315
364
  ...spawnSubagentSection,
365
+ ...extensions.render("after-session-communication"),
316
366
  ];
317
367
 
318
368
  const modelCatalog = SELECTABLE_MODELS.map(
@@ -343,69 +393,160 @@ function buildRuntimeToolSection(opts: RuntimeToolSectionOpts): string {
343
393
  if (agentKind !== "claude") {
344
394
  return [
345
395
  ...shared,
396
+ ...extensions.render("before-topic-configuration"),
346
397
  ...topicConfig,
398
+ ...extensions.render("after-topic-configuration"),
347
399
  "",
348
400
  "## Runtime Tool Limits",
349
401
  "If file delivery or topic configuration tools are not present in your available tools for this session, do not claim you used them. Tell the user this session does not expose that in-chat tool action.",
350
402
  ].join("\n");
351
403
  }
352
404
 
353
- return [...shared, ...topicConfig].join("\n");
405
+ return [
406
+ ...shared,
407
+ ...extensions.render("before-topic-configuration"),
408
+ ...topicConfig,
409
+ ...extensions.render("after-topic-configuration"),
410
+ ].join("\n");
354
411
  }
355
412
 
356
- export function buildTopicSystemPrompt(opts: SessionSystemPromptOpts): string {
357
- const uploadsDir = `${opts.workspaceCwd}/attachments`;
358
- let prompt =
359
- replaceVars(topicSystemPromptTemplate(), {
360
- AI_LABEL: opts.aiLabel,
361
- TOPIC_TITLE: opts.topicTitle,
362
- WORKSPACE_CWD: opts.workspaceCwd,
363
- UPLOADS_DIR: uploadsDir,
364
- }) +
365
- buildRuntimeToolSection({
366
- agentKind: opts.agentKind,
367
- canSpawnSubagents: opts.canSpawnSubagents,
368
- canStageSubagents: opts.canStageSubagents,
369
- visualTools: opts.visualTools,
370
- fileDeliveryTools: opts.fileDeliveryTools,
371
- currentModel: opts.currentModel,
372
- currentEffort: opts.currentEffort,
373
- subagentParentTitle: opts.subagentParentTitle,
374
- subagentReportMode: opts.subagentReportMode,
375
- });
376
- if (opts.description?.trim()) {
377
- prompt += `\n\n## Topic-Specific Instructions\n${opts.description.trim()}`;
413
+ export function createPromptBuilders(host: PromptBuilderHost = {}): PromptBuilders {
414
+ const loadTemplate = host.loadTemplate;
415
+ const sections = (host.extraSections ?? []).map((section): PromptExtraSection => {
416
+ const render = section.render;
417
+ const snapshot: PromptExtraSection = {
418
+ id: section.id,
419
+ slot: section.slot,
420
+ ...(section.order === undefined ? {} : { order: section.order }),
421
+ render(context) {
422
+ return render.call(snapshot, context);
423
+ },
424
+ };
425
+ return Object.freeze(snapshot);
426
+ });
427
+ const ids = new Set<string>();
428
+ for (const section of sections) {
429
+ if (!section.id.trim()) throw new Error("prompt extra section id is required");
430
+ if (ids.has(section.id)) throw new Error(`duplicate prompt extra section id: ${section.id}`);
431
+ ids.add(section.id);
378
432
  }
379
- return prompt;
380
- }
433
+ sections.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.id.localeCompare(b.id));
434
+
435
+ const templateCache = new Map<PromptTemplateRequest["kind"], string>();
436
+ const template = (request: PromptTemplateRequest): string => {
437
+ const cached = templateCache.get(request.kind);
438
+ if (cached !== undefined) return cached;
439
+ const loaded = loadTemplate?.(request);
440
+ const value =
441
+ loaded ??
442
+ (request.kind === "topic-system"
443
+ ? topicSystemPromptTemplate()
444
+ : request.kind === "channel-system"
445
+ ? channelSystemPromptTemplate()
446
+ : request.kind === "manager-system"
447
+ ? managerSystemPromptTemplate()
448
+ : visualDesignGuide());
449
+ templateCache.set(request.kind, value);
450
+ return value;
451
+ };
381
452
 
382
- export function buildChannelSystemPrompt(opts: SessionSystemPromptOpts): string {
383
- const uploadsDir = `${opts.workspaceCwd}/attachments`;
384
- return (
385
- replaceVars(channelSystemPromptTemplate(), {
386
- AI_LABEL: opts.aiLabel,
387
- TOPIC_TITLE: opts.topicTitle,
388
- WORKSPACE_CWD: opts.workspaceCwd,
389
- UPLOADS_DIR: uploadsDir,
390
- }) +
391
- buildRuntimeToolSection({
392
- agentKind: opts.agentKind,
393
- canSpawnSubagents: false,
394
- canStageSubagents: false,
395
- visualTools: opts.visualTools,
396
- fileDeliveryTools: opts.fileDeliveryTools,
397
- currentModel: opts.currentModel,
398
- currentEffort: opts.currentEffort,
399
- subagentParentTitle: opts.subagentParentTitle,
400
- subagentReportMode: opts.subagentReportMode,
401
- })
402
- );
403
- }
453
+ const build = (
454
+ sessionKind: SessionPromptKind,
455
+ opts: SessionSystemPromptOpts,
456
+ sessionTemplate: string,
457
+ ): string => {
458
+ const context = Object.freeze({ ...opts, sessionKind });
459
+ const render = (slot: PromptSectionSlot): string[] =>
460
+ sections
461
+ .filter((section) => section.slot === slot)
462
+ .map((section) => section.render(context)?.trim())
463
+ .filter((section): section is string => Boolean(section))
464
+ .flatMap((section) => ["", section]);
465
+ const uploadsDir = `${opts.workspaceCwd}/attachments`;
466
+ let prompt =
467
+ replaceVars(sessionTemplate, {
468
+ AI_LABEL: opts.aiLabel,
469
+ TOPIC_TITLE: opts.topicTitle,
470
+ WORKSPACE_CWD: opts.workspaceCwd,
471
+ UPLOADS_DIR: uploadsDir,
472
+ }) +
473
+ buildRuntimeToolSection(
474
+ {
475
+ agentKind: opts.agentKind,
476
+ canSpawnSubagents: sessionKind === "channel" ? false : opts.canSpawnSubagents,
477
+ canStageSubagents: sessionKind === "channel" ? false : opts.canStageSubagents,
478
+ visualTools: opts.visualTools,
479
+ fileDeliveryTools: opts.fileDeliveryTools,
480
+ currentModel: opts.currentModel,
481
+ currentEffort: opts.currentEffort,
482
+ subagentParentTitle: opts.subagentParentTitle,
483
+ subagentReportMode: opts.subagentReportMode,
484
+ },
485
+ {
486
+ render,
487
+ visualDesignGuide: template({
488
+ kind: "visual-design",
489
+ filename: "visual-design.md",
490
+ fallback: "",
491
+ }),
492
+ },
493
+ );
494
+ if (sessionKind !== "channel" && opts.description?.trim()) {
495
+ prompt += `\n\n## Topic-Specific Instructions\n${opts.description.trim()}`;
496
+ }
497
+ if (sessionKind === "manager") {
498
+ prompt += `\n\n${template({
499
+ kind: "manager-system",
500
+ filename: "manager-system.md",
501
+ fallback: FALLBACK_MANAGER_SYSTEM_PROMPT_TEMPLATE,
502
+ })}`;
503
+ }
504
+ return `${prompt}${render("after-system-prompt").join("\n")}`;
505
+ };
404
506
 
405
- export function buildManagerSystemPrompt(opts: SessionSystemPromptOpts): string {
406
- return `${buildTopicSystemPrompt(opts)}\n\n${managerSystemPromptTemplate()}`;
507
+ return Object.freeze({
508
+ buildTopicSystemPrompt(opts: SessionSystemPromptOpts) {
509
+ return build(
510
+ "topic",
511
+ opts,
512
+ template({
513
+ kind: "topic-system",
514
+ filename: "topic-system.md",
515
+ fallback: FALLBACK_TOPIC_SYSTEM_PROMPT_TEMPLATE,
516
+ }),
517
+ );
518
+ },
519
+ buildChannelSystemPrompt(opts: SessionSystemPromptOpts) {
520
+ return build(
521
+ "channel",
522
+ opts,
523
+ template({
524
+ kind: "channel-system",
525
+ filename: "channel-system.md",
526
+ fallback: FALLBACK_CHANNEL_SYSTEM_PROMPT_TEMPLATE,
527
+ }),
528
+ );
529
+ },
530
+ buildManagerSystemPrompt(opts: SessionSystemPromptOpts) {
531
+ return build(
532
+ "manager",
533
+ opts,
534
+ template({
535
+ kind: "topic-system",
536
+ filename: "topic-system.md",
537
+ fallback: FALLBACK_TOPIC_SYSTEM_PROMPT_TEMPLATE,
538
+ }),
539
+ );
540
+ },
541
+ });
407
542
  }
408
543
 
544
+ const defaultPromptBuilders = createPromptBuilders();
545
+
546
+ export const buildTopicSystemPrompt = defaultPromptBuilders.buildTopicSystemPrompt;
547
+ export const buildChannelSystemPrompt = defaultPromptBuilders.buildChannelSystemPrompt;
548
+ export const buildManagerSystemPrompt = defaultPromptBuilders.buildManagerSystemPrompt;
549
+
409
550
  export function buildMemoryPromptSection(opts: {
410
551
  briefFile: string;
411
552
  wikiDir: string;
@@ -17,6 +17,14 @@ export {
17
17
  type RoomQueryRegistryHost,
18
18
  type RuntimeTurnLeaseLike,
19
19
  } from "./room-query-registry";
20
+ export {
21
+ clearQueryState,
22
+ createQueryStateStore,
23
+ type QueryStateStore,
24
+ type QueryStateStoreLogger,
25
+ type QueryStateStoreOptions,
26
+ writeQueryState,
27
+ } from "./state";
20
28
  export {
21
29
  AbortReason,
22
30
  type HandleAgentQueryOutcome,
@@ -7,38 +7,86 @@ import type { QueryState } from "#types";
7
7
 
8
8
  type QueryStateUserId = number | string;
9
9
 
10
- function queryStateDirPath(userId: QueryStateUserId): string {
11
- return join(USERS_LOG_DIR, String(userId), "active-queries");
10
+ export interface QueryStateStoreLogger {
11
+ warn(context: Record<string, unknown>, message: string): void;
12
12
  }
13
13
 
14
- function queryStateFile(userId: QueryStateUserId, topicId: string): string {
15
- return join(queryStateDirPath(userId), `${sanitizeId(topicId)}.json`);
14
+ export interface QueryStateStoreOptions {
15
+ usersLogDir: string;
16
+ logger?: QueryStateStoreLogger;
17
+ sanitizeTopicId?: (topicId: string) => string;
16
18
  }
17
19
 
18
- function legacyQueryStateFile(
19
- userId: QueryStateUserId,
20
- topicName: string | undefined,
21
- ): string | null {
22
- if (!topicName || topicName === "." || topicName === ".." || basename(topicName) !== topicName) {
23
- return null;
24
- }
25
- return join(queryStateDirPath(userId), `${topicName}.json`);
20
+ export interface QueryStateStore {
21
+ write(userId: QueryStateUserId, topicId: string, topicName: string, task?: string): void;
22
+ clear(userId: QueryStateUserId, topicId: string, legacyTopicName?: string): void;
26
23
  }
27
24
 
25
+ export function createQueryStateStore(options: QueryStateStoreOptions): QueryStateStore {
26
+ const sanitize = options.sanitizeTopicId ?? sanitizeId;
27
+ const queryStateDirPath = (userId: QueryStateUserId): string =>
28
+ join(options.usersLogDir, String(userId), "active-queries");
29
+ const queryStateFile = (userId: QueryStateUserId, topicId: string): string =>
30
+ join(queryStateDirPath(userId), `${sanitize(topicId)}.json`);
31
+ const legacyQueryStateFile = (
32
+ userId: QueryStateUserId,
33
+ topicName: string | undefined,
34
+ ): string | null => {
35
+ if (
36
+ !topicName ||
37
+ topicName === "." ||
38
+ topicName === ".." ||
39
+ basename(topicName) !== topicName
40
+ ) {
41
+ return null;
42
+ }
43
+ return join(queryStateDirPath(userId), `${topicName}.json`);
44
+ };
45
+
46
+ return {
47
+ write(userId, topicId, topicName, task) {
48
+ const dir = queryStateDirPath(userId);
49
+ mkdirSync(dir, { recursive: true });
50
+ const state: QueryState = { topicId, topicName, since: new Date().toISOString() };
51
+ if (task) state.task = [...task.replace(/\n+/g, " ").trim()].slice(0, 100).join("");
52
+ const target = queryStateFile(userId, topicId);
53
+ const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
54
+ writeFileSync(tmp, JSON.stringify(state));
55
+ renameSync(tmp, target);
56
+ },
57
+ clear(userId, topicId, legacyTopicName) {
58
+ const paths = [
59
+ queryStateFile(userId, topicId),
60
+ legacyQueryStateFile(userId, legacyTopicName),
61
+ ].filter((path): path is string => Boolean(path));
62
+ for (const path of new Set(paths)) {
63
+ try {
64
+ unlinkSync(path);
65
+ } catch (error) {
66
+ if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") {
67
+ options.logger?.warn(
68
+ { err: error, userId, topicId, path },
69
+ "Failed to clear query state file",
70
+ );
71
+ }
72
+ }
73
+ }
74
+ },
75
+ };
76
+ }
77
+
78
+ const defaultQueryStateStore = createQueryStateStore({
79
+ usersLogDir: USERS_LOG_DIR,
80
+ logger,
81
+ });
82
+
28
83
  export function writeQueryState(
29
84
  userId: QueryStateUserId,
30
85
  topicId: string,
31
86
  topicName: string,
32
87
  task?: string,
33
88
  ) {
34
- const dir = queryStateDirPath(userId);
35
- mkdirSync(dir, { recursive: true });
36
- const state: QueryState = { topicId, topicName, since: new Date().toISOString() };
37
- if (task) state.task = [...task.replace(/\n+/g, " ").trim()].slice(0, 100).join("");
38
- const target = queryStateFile(userId, topicId);
39
- const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
40
- writeFileSync(tmp, JSON.stringify(state));
41
- renameSync(tmp, target);
89
+ defaultQueryStateStore.write(userId, topicId, topicName, task);
42
90
  }
43
91
 
44
92
  export function clearQueryState(
@@ -46,17 +94,5 @@ export function clearQueryState(
46
94
  topicId: string,
47
95
  legacyTopicName?: string,
48
96
  ) {
49
- const paths = [
50
- queryStateFile(userId, topicId),
51
- legacyQueryStateFile(userId, legacyTopicName),
52
- ].filter((path): path is string => Boolean(path));
53
- for (const path of new Set(paths)) {
54
- try {
55
- unlinkSync(path);
56
- } catch (e) {
57
- if ((e as NodeJS.ErrnoException)?.code !== "ENOENT") {
58
- logger.warn({ err: e, userId, topicId, path }, "Failed to clear query state file");
59
- }
60
- }
61
- }
97
+ defaultQueryStateStore.clear(userId, topicId, legacyTopicName);
62
98
  }
@@ -1 +1 @@
1
- export const NEGOTIUM_VERSION = "0.1.38";
1
+ export const NEGOTIUM_VERSION = "0.1.39";
@@ -0,0 +1,38 @@
1
+ import { type TopicConfig } from "../storage/api-topic-config";
2
+ import type { AgentKind, EffortLevel } from "../types";
3
+ export type ApiTopicSwitchOutcome = {
4
+ kind: "fresh";
5
+ agent: AgentKind;
6
+ reason: "no-history" | "bridge-failed";
7
+ } | {
8
+ kind: "bridged";
9
+ agent: AgentKind;
10
+ bridgedSessionId: string;
11
+ rolloutPath: string;
12
+ };
13
+ export type ApiTopicSwitchResult = {
14
+ ok: true;
15
+ outcome: ApiTopicSwitchOutcome;
16
+ } | {
17
+ ok: false;
18
+ error: string;
19
+ };
20
+ export interface SwitchApiTopicAgentOptions {
21
+ topicId: string;
22
+ topicTitle: string;
23
+ userId: string;
24
+ fromAgent?: AgentKind;
25
+ agent: AgentKind;
26
+ cwd: string;
27
+ config: TopicConfig;
28
+ /** Topic base values used when config has no explicit override. */
29
+ defaultModel?: string;
30
+ defaultEffort?: EffortLevel;
31
+ reason: string;
32
+ }
33
+ /**
34
+ * API-topic equivalent of `switchTopicAgent`: all web/MCP agent changes must
35
+ * pass through this path so the synthetic SDK rollout, unified-log manifest,
36
+ * and durable topic session stay in sync.
37
+ */
38
+ export declare function switchApiTopicAgent(opts: SwitchApiTopicAgentOptions): ApiTopicSwitchResult;
@@ -1,6 +1,6 @@
1
- import type { AgentKind } from "../types";
2
- import type { BackgroundSessionDto } from "../types/api";
3
- export declare function listActiveMemoryArchiverSessions(userId: string): BackgroundSessionDto[];
1
+ import { type AgentDef } from "../prompts/builders";
2
+ import type { AgentKind, AgentQueryOptions, UnifiedEvent } from "../types";
3
+ import type { BackgroundSessionDto, MessageDto } from "../types/api";
4
4
  export interface RunArchiverTurnParams {
5
5
  /** Stringified user id (route layer hands us strings). */
6
6
  userId: string;
@@ -23,17 +23,53 @@ export interface RunArchiverTurnParams {
23
23
  /** Called once when an accepted background turn settles. */
24
24
  onSettled?: (success: boolean) => void;
25
25
  }
26
- /**
27
- * Fire a background wiki-archiver turn for a deleted topic or active idle
28
- * snapshot.
29
- *
30
- * Reads the forensic archive file produced by `archiveTopicMessages` and
31
- * distils it into the **shared** wiki (summaries / articles / topic brief /
32
- * indexes / skills) via the wiki MCP, which the turn launches in topic-id mode
33
- * so its writes land in the same root normal forum turns query.
34
- *
35
- * Fire-and-forget: the async generator is drained on a detached promise so the
36
- * caller's HTTP response returns immediately. Every failure is best-effort and
37
- * logged only — a broken archiver must never block or fail a topic deletion.
38
- */
26
+ export interface ArchiverStorageHost {
27
+ getWikiDir(): string;
28
+ fileExists(path: string): boolean;
29
+ listDirectory(path: string): string[];
30
+ readTextFile(path: string): string;
31
+ fileSize(path: string): number;
32
+ fileModifiedAt(path: string): number;
33
+ getGeneralTopicId(userId: string): string;
34
+ getTopicBrief(topicId: string): {
35
+ briefMd: string;
36
+ } | null;
37
+ setTopicBrief(topicId: string, fields: {
38
+ briefMd: string;
39
+ latestSummaryMd?: string;
40
+ summaryDate?: string;
41
+ }): void;
42
+ }
43
+ export interface ArchiverMessagingHost {
44
+ appendMessage(message: MessageDto): void;
45
+ broadcastMessage(topicId: string, message: MessageDto): void;
46
+ }
47
+ export interface ArchiverConfigHost {
48
+ readonly workspaceDir: string;
49
+ readonly completedSessionRetentionMs: number;
50
+ loadAgentDefinition(): AgentDef;
51
+ sanitizeTopicName(topicTitle: string): string;
52
+ createId(): string;
53
+ now(): Date;
54
+ schedule(callback: () => void, delayMs: number): unknown;
55
+ cancelScheduled(handle: unknown): void;
56
+ unrefScheduled?(handle: unknown): void;
57
+ info(context: Record<string, unknown>, message: string): void;
58
+ warn(context: Record<string, unknown>, message: string): void;
59
+ }
60
+ export interface ArchiverAgentRuntimeHost {
61
+ run(options: AgentQueryOptions): AsyncIterable<UnifiedEvent>;
62
+ }
63
+ export interface ArchiverHost {
64
+ storage: ArchiverStorageHost;
65
+ messaging: ArchiverMessagingHost;
66
+ config: ArchiverConfigHost;
67
+ agentRuntime: ArchiverAgentRuntimeHost;
68
+ }
69
+ export interface ArchiverRuntime {
70
+ runArchiverTurn(params: RunArchiverTurnParams): boolean;
71
+ listActiveMemoryArchiverSessions(userId: string): BackgroundSessionDto[];
72
+ }
73
+ export declare function createArchiverRuntime(host: ArchiverHost): ArchiverRuntime;
39
74
  export declare function runArchiverTurn(params: RunArchiverTurnParams): boolean;
75
+ export declare function listActiveMemoryArchiverSessions(userId: string): BackgroundSessionDto[];
@@ -1,6 +1,8 @@
1
1
  import { type SharedMcpTool } from "./common";
2
+ import { type AskUserGateCardUpdate, type ClaimAskUserGateResult, type PrepareAskUserGateResult } from "../../storage/ask-user-gates";
3
+ import { type RuntimeProcessLease, type RuntimeProcessLeaseHandle } from "../../storage/runtime-process-leases";
2
4
  import type { AgentKind } from "../../types";
3
- import type { MessageDto } from "../../types/api";
5
+ import type { MessageDto, TopicDto } from "../../types/api";
4
6
  export type AskUserChoice = {
5
7
  label: string;
6
8
  description?: string;
@@ -20,8 +22,70 @@ export type AnswerAskUserQuestionResult = {
20
22
  ok: false;
21
23
  error: string;
22
24
  };
23
- export declare function startAskUserQuestionGateOwner(): void;
24
- export declare function stopAskUserQuestionGateOwner(): void;
25
+ export interface AskUserRuntimeHost {
26
+ messaging: {
27
+ persistence: {
28
+ getMessage(topicId: string, messageId: string): MessageDto | null | undefined;
29
+ appendMessage(message: MessageDto): void;
30
+ };
31
+ publication: {
32
+ publishMessage(topicId: string, message: MessageDto): void;
33
+ publishCardUpdate(update: AskUserGateCardUpdate): void;
34
+ };
35
+ };
36
+ topics: {
37
+ getTopic(topicId: string): Pick<TopicDto, "participants"> | null | undefined;
38
+ getConfig(topicId: string): {
39
+ model?: string;
40
+ } | undefined;
41
+ };
42
+ durability: {
43
+ gates: {
44
+ prepare(args: {
45
+ gateId: string;
46
+ topicId: string;
47
+ queryId?: string;
48
+ idempotencyKey: string;
49
+ bodyHash: string;
50
+ messageId: string;
51
+ ownerId: string;
52
+ now: string;
53
+ }): PrepareAskUserGateResult;
54
+ claimAndSelect(args: {
55
+ topicId: string;
56
+ messageId: string;
57
+ label: string;
58
+ userId: string;
59
+ ownerId: string;
60
+ source: string;
61
+ now: string;
62
+ }): ClaimAskUserGateResult;
63
+ cancel(topicId: string, messageId: string, ownerId: string, now: string): AskUserGateCardUpdate | null;
64
+ quarantine(gateId: string, ownerId: string, now: string): AskUserGateCardUpdate | null;
65
+ quarantineForeign(liveOwnerIds: ReadonlySet<string>, now: string): AskUserGateCardUpdate[];
66
+ };
67
+ processLeases: {
68
+ acquire(role: string, ownerId: string): RuntimeProcessLeaseHandle | null;
69
+ list(rolePrefix: string): RuntimeProcessLease[];
70
+ isAlive(lease: RuntimeProcessLease): boolean;
71
+ removeDead(rolePrefix: string): void;
72
+ };
73
+ };
74
+ runtime: {
75
+ ownerId: string;
76
+ createId(): string;
77
+ now(): string;
78
+ };
79
+ }
80
+ export interface AskUserRuntime {
81
+ startGateOwner(): void;
82
+ stopGateOwner(): void;
83
+ reconcilePendingGates(): number;
84
+ hasPendingQuestion(topicId: string, messageId: string): boolean;
85
+ answerPendingQuestion(topicId: string, messageId: string, label: string, userId: string): AnswerAskUserQuestionResult;
86
+ cancelPendingQuestions(topicId: string, queryId: string): void;
87
+ createToolDefinition(ctx: AskUserToolContext): SharedMcpTool;
88
+ }
25
89
  export declare function normalizeAskUserChoices(value: unknown): AskUserChoice[];
26
90
  export declare function normalizeAskUserQuestionInput(input: {
27
91
  question?: unknown;
@@ -32,8 +96,15 @@ export declare function normalizeAskUserQuestionInput(input: {
32
96
  } | {
33
97
  error: string;
34
98
  };
35
- export declare function hasPendingAskUserQuestion(topicId: string, messageId: string): boolean;
99
+ /**
100
+ * Create an isolated ask-user lifecycle over host-provided messaging, topic,
101
+ * durability, and runtime services.
102
+ */
103
+ export declare function createAskUserRuntime(host: AskUserRuntimeHost): AskUserRuntime;
104
+ export declare function startAskUserQuestionGateOwner(): void;
105
+ export declare function stopAskUserQuestionGateOwner(): void;
36
106
  export declare function reconcilePendingAskUserQuestionGates(): number;
107
+ export declare function hasPendingAskUserQuestion(topicId: string, messageId: string): boolean;
37
108
  /**
38
109
  * Host-neutral answer path for runtime ask cards. Web, terminal, Telegram, and
39
110
  * future adapters should call this rather than reimplementing pending-map and
@@ -0,0 +1,17 @@
1
+ import { type SharedMcpTool } from "./common";
2
+ import { SELF_CONFIG_MCP_KEY, type SelfConfigContext, type SelfConfigCore, type SelfConfigHost, type SelfConfigProductConfig } from "../self-config-core";
3
+ export type { SelfConfigContext };
4
+ export { SELF_CONFIG_MCP_KEY };
5
+ export declare const SELF_CONFIG_DERIVED_TOPIC_LIMIT = 5;
6
+ export interface SelfConfigRuntime {
7
+ readonly core: SelfConfigCore;
8
+ readonly mcpKey: string;
9
+ createToolDefinitions(ctx: SelfConfigContext | (() => SelfConfigContext)): SharedMcpTool[];
10
+ }
11
+ export interface SelfConfigRuntimeOptions {
12
+ host: SelfConfigHost;
13
+ product?: Partial<SelfConfigProductConfig>;
14
+ }
15
+ export declare function createSelfConfigRuntime(options: SelfConfigRuntimeOptions): SelfConfigRuntime;
16
+ export declare function createSelfConfigToolDefinitionsForCore(core: SelfConfigCore, ctx: SelfConfigContext | (() => SelfConfigContext)): SharedMcpTool[];
17
+ export declare function createSelfConfigToolDefinitions(ctx: SelfConfigContext | (() => SelfConfigContext)): SharedMcpTool[];