negotium 0.5.12 → 0.5.15

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 (38) hide show
  1. package/dist/agent-helpers.js +100 -37
  2. package/dist/agent-helpers.js.map +10 -9
  3. package/dist/hosted-agent.js +2 -2
  4. package/dist/hosted-agent.js.map +2 -2
  5. package/dist/main.js +553 -317
  6. package/dist/main.js.map +16 -14
  7. package/dist/mcp-factories.js +102 -39
  8. package/dist/mcp-factories.js.map +10 -9
  9. package/dist/outbox.js +103 -21
  10. package/dist/outbox.js.map +5 -4
  11. package/dist/registry.js +2 -2
  12. package/dist/registry.js.map +2 -2
  13. package/dist/runtime/src/bus.ts +21 -2
  14. package/dist/runtime/src/index.ts +1 -0
  15. package/dist/runtime/src/outbox/coalescing-runner.ts +157 -0
  16. package/dist/runtime/src/outbox/index.ts +6 -0
  17. package/dist/runtime/src/runtime/inbox.ts +98 -20
  18. package/dist/runtime/src/runtime/turn-event-stream.ts +5 -0
  19. package/dist/runtime/src/runtime/turn-runner.ts +61 -3
  20. package/dist/runtime/src/storage/api-messages.ts +11 -2
  21. package/dist/runtime/src/storage/session-inbox-signal.ts +76 -0
  22. package/dist/runtime/src/storage/session-inbox.ts +5 -1
  23. package/dist/runtime/src/types/api.ts +11 -1
  24. package/dist/runtime/src/version.ts +1 -1
  25. package/dist/runtime-helpers.js +53 -10
  26. package/dist/runtime-helpers.js.map +6 -5
  27. package/dist/storage.js +9 -4
  28. package/dist/storage.js.map +3 -3
  29. package/dist/types/packages/core/src/bus.d.ts +4 -0
  30. package/dist/types/packages/core/src/outbox/coalescing-runner.d.ts +68 -0
  31. package/dist/types/packages/core/src/outbox/index.d.ts +1 -0
  32. package/dist/types/packages/core/src/runtime/turn-event-stream.d.ts +5 -1
  33. package/dist/types/packages/core/src/runtime/turn-runner.d.ts +4 -0
  34. package/dist/types/packages/core/src/storage/api-messages.d.ts +1 -0
  35. package/dist/types/packages/core/src/storage/session-inbox-signal.d.ts +35 -0
  36. package/dist/types/packages/core/src/types/api.d.ts +10 -1
  37. package/dist/types/packages/core/src/version.d.ts +1 -1
  38. package/package.json +1 -1
@@ -2443,7 +2443,7 @@ var init_claude_registry = __esm(() => {
2443
2443
  });
2444
2444
 
2445
2445
  // ../../packages/core/src/version.ts
2446
- var NEGOTIUM_VERSION = "0.5.12";
2446
+ var NEGOTIUM_VERSION = "0.5.15";
2447
2447
 
2448
2448
  // ../../packages/core/src/agents/codex-native-multi-agent.ts
2449
2449
  import { spawn as spawn2 } from "child_process";
@@ -6413,12 +6413,21 @@ class SqliteRuntimeBus {
6413
6413
  queryId,
6414
6414
  usage,
6415
6415
  agent: meta?.agent,
6416
- model: meta?.model
6416
+ model: meta?.model,
6417
+ budgetReason: meta?.budgetReason
6417
6418
  });
6418
6419
  }
6419
6420
  broadcastError(topicId, queryId, error) {
6420
6421
  this.broadcastAiStatus(topicId, { kind: "ai_error", queryId, error });
6421
6422
  }
6423
+ broadcastTopicNotice(topicId, severity, label) {
6424
+ this.broadcastAiStatus(topicId, {
6425
+ kind: "topic_notice",
6426
+ severity,
6427
+ label,
6428
+ ts: new Date().toISOString()
6429
+ });
6430
+ }
6422
6431
  broadcastAborted(topicId, queryId, reason) {
6423
6432
  this.broadcastAiStatus(topicId, { kind: "ai_aborted", queryId, reason });
6424
6433
  }
@@ -7112,6 +7121,7 @@ function initializeApiMessagesSchema() {
7112
7121
  reactions TEXT,
7113
7122
  kind TEXT,
7114
7123
  ask_user_question TEXT,
7124
+ tell_card TEXT,
7115
7125
  mentions TEXT,
7116
7126
  thread_root_id TEXT,
7117
7127
  created_at TEXT NOT NULL
@@ -7141,6 +7151,9 @@ function initializeApiMessagesSchema() {
7141
7151
  try {
7142
7152
  db.exec("ALTER TABLE api_messages ADD COLUMN ask_user_question TEXT");
7143
7153
  } catch {}
7154
+ try {
7155
+ db.exec("ALTER TABLE api_messages ADD COLUMN tell_card TEXT");
7156
+ } catch {}
7144
7157
  try {
7145
7158
  db.exec("ALTER TABLE api_messages ADD COLUMN mentions TEXT");
7146
7159
  } catch {}
@@ -7184,6 +7197,7 @@ function rowToDto(r) {
7184
7197
  reactions: r.reactions ? JSON.parse(r.reactions) : undefined,
7185
7198
  kind: r.kind ?? undefined,
7186
7199
  askUserQuestion: r.ask_user_question ? JSON.parse(r.ask_user_question) : undefined,
7200
+ tellCard: r.tell_card ? JSON.parse(r.tell_card) : undefined,
7187
7201
  subagentCard: r.subagent_card ? JSON.parse(r.subagent_card) : undefined,
7188
7202
  mentions: r.mentions ? JSON.parse(r.mentions) : undefined,
7189
7203
  threadRootId: r.thread_root_id ?? undefined,
@@ -7206,9 +7220,9 @@ function appendApiMessage(msg, options = {}) {
7206
7220
  let inserted = false;
7207
7221
  db.transaction(() => {
7208
7222
  const result = db.query(`INSERT INTO api_messages
7209
- (id, topic_id, parent_id, author_id, source_adapter, source_node, source_message_id, text, query_id, agent_type, model, attachments, usage, deleted, edited_at, reactions, kind, ask_user_question, subagent_card, mentions, thread_root_id, created_at)
7210
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7211
- ON CONFLICT(id) DO NOTHING`).run(msg.id, msg.topicId, msg.parentId ?? null, msg.authorId, msg.sourceAdapter ?? null, msg.sourceNode ?? null, msg.sourceMessageId ?? null, msg.text, msg.queryId ?? null, msg.agentType ?? null, msg.model ?? null, msg.attachments ? JSON.stringify(msg.attachments) : null, msg.usage ? JSON.stringify(msg.usage) : null, msg.deleted ? 1 : 0, msg.editedAt ?? null, msg.reactions?.length ? JSON.stringify(msg.reactions) : null, msg.kind ?? null, msg.askUserQuestion ? JSON.stringify(msg.askUserQuestion) : null, msg.subagentCard ? JSON.stringify(msg.subagentCard) : null, msg.mentions?.length ? JSON.stringify(msg.mentions) : null, msg.threadRootId ?? null, msg.createdAt);
7223
+ (id, topic_id, parent_id, author_id, source_adapter, source_node, source_message_id, text, query_id, agent_type, model, attachments, usage, deleted, edited_at, reactions, kind, ask_user_question, tell_card, subagent_card, mentions, thread_root_id, created_at)
7224
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7225
+ ON CONFLICT(id) DO NOTHING`).run(msg.id, msg.topicId, msg.parentId ?? null, msg.authorId, msg.sourceAdapter ?? null, msg.sourceNode ?? null, msg.sourceMessageId ?? null, msg.text, msg.queryId ?? null, msg.agentType ?? null, msg.model ?? null, msg.attachments ? JSON.stringify(msg.attachments) : null, msg.usage ? JSON.stringify(msg.usage) : null, msg.deleted ? 1 : 0, msg.editedAt ?? null, msg.reactions?.length ? JSON.stringify(msg.reactions) : null, msg.kind ?? null, msg.askUserQuestion ? JSON.stringify(msg.askUserQuestion) : null, msg.tellCard ? JSON.stringify(msg.tellCard) : null, msg.subagentCard ? JSON.stringify(msg.subagentCard) : null, msg.mentions?.length ? JSON.stringify(msg.mentions) : null, msg.threadRootId ?? null, msg.createdAt);
7212
7226
  inserted = Number(result.changes ?? 0) > 0;
7213
7227
  if (inserted && updateTopicLastMessageAt && !msg.deleted) {
7214
7228
  db.query(`UPDATE api_topics
@@ -13308,12 +13322,23 @@ var init_session_inbox_path = __esm(() => {
13308
13322
  init_config();
13309
13323
  });
13310
13324
 
13325
+ // ../../packages/core/src/storage/session-inbox-signal.ts
13326
+ import { join as join23 } from "path";
13327
+ var SESSION_INBOX_WAKE_FILE, listeners;
13328
+ var init_session_inbox_signal = __esm(() => {
13329
+ init_config();
13330
+ init_logger();
13331
+ SESSION_INBOX_WAKE_FILE = join23(SESSION_INBOX_DIR, ".wake");
13332
+ listeners = new Set;
13333
+ });
13334
+
13311
13335
  // ../../packages/core/src/storage/session-inbox.ts
13312
13336
  function deleteSessionInboxForTopic(topicId) {
13313
13337
  return Number(db.run("DELETE FROM session_inbox WHERE topic_id = ?", [topicId]).changes ?? 0);
13314
13338
  }
13315
13339
  var init_session_inbox = __esm(async () => {
13316
13340
  await init_forum_db();
13341
+ init_session_inbox_signal();
13317
13342
  await init_storage_host();
13318
13343
  registerStorageSchemaInitializer((database) => {
13319
13344
  database.exec(`
@@ -13339,13 +13364,13 @@ var init_session_inbox = __esm(async () => {
13339
13364
 
13340
13365
  // ../../packages/core/src/query/session-inbox-cleanup.ts
13341
13366
  import { unlinkSync as unlinkSync14 } from "fs";
13342
- import { basename as basename3, join as join23 } from "path";
13367
+ import { basename as basename3, join as join24 } from "path";
13343
13368
  function cleanupSessionInboxFiles(userId, topicId, legacyTopicTitle) {
13344
13369
  const live = sessionInboxPath(userId, topicId);
13345
13370
  const scheduled = scheduledSessionInboxPath(userId, topicId);
13346
13371
  const candidates = new Set([live, `${live}.processing`, scheduled, `${scheduled}.processing`]);
13347
13372
  if (legacyTopicTitle && legacyTopicTitle !== "." && legacyTopicTitle !== ".." && basename3(legacyTopicTitle) === legacyTopicTitle) {
13348
- const legacyBase = join23(SESSION_INBOX_DIR, userId, legacyTopicTitle);
13373
+ const legacyBase = join24(SESSION_INBOX_DIR, userId, legacyTopicTitle);
13349
13374
  for (const suffix of [".jsonl", ".jsonl.processing", ".schedule", ".schedule.processing"]) {
13350
13375
  candidates.add(`${legacyBase}${suffix}`);
13351
13376
  }
@@ -13372,16 +13397,16 @@ var init_session_inbox_cleanup = __esm(async () => {
13372
13397
 
13373
13398
  // ../../packages/core/src/query/state.ts
13374
13399
  import { mkdirSync as mkdirSync14, renameSync as renameSync7, unlinkSync as unlinkSync15, writeFileSync as writeFileSync12 } from "fs";
13375
- import { basename as basename4, join as join24 } from "path";
13400
+ import { basename as basename4, join as join25 } from "path";
13376
13401
  function createQueryStateStore(options) {
13377
13402
  const sanitize = options.sanitizeTopicId ?? sanitizeId;
13378
- const queryStateDirPath = (userId) => join24(options.usersLogDir, String(userId), "active-queries");
13379
- const queryStateFile = (userId, topicId) => join24(queryStateDirPath(userId), `${sanitize(topicId)}.json`);
13403
+ const queryStateDirPath = (userId) => join25(options.usersLogDir, String(userId), "active-queries");
13404
+ const queryStateFile = (userId, topicId) => join25(queryStateDirPath(userId), `${sanitize(topicId)}.json`);
13380
13405
  const legacyQueryStateFile = (userId, topicName) => {
13381
13406
  if (!topicName || topicName === "." || topicName === ".." || basename4(topicName) !== topicName) {
13382
13407
  return null;
13383
13408
  }
13384
- return join24(queryStateDirPath(userId), `${topicName}.json`);
13409
+ return join25(queryStateDirPath(userId), `${topicName}.json`);
13385
13410
  };
13386
13411
  return {
13387
13412
  write(userId, topicId, topicName, task) {
@@ -13542,29 +13567,29 @@ import {
13542
13567
  unlinkSync as unlinkSync16,
13543
13568
  writeFileSync as writeFileSync13
13544
13569
  } from "fs";
13545
- import { dirname as dirname14, join as join25 } from "path";
13570
+ import { dirname as dirname14, join as join26 } from "path";
13546
13571
  function pendingAskDir(userId) {
13547
13572
  const rawUserId = String(userId);
13548
13573
  const safeUserId = /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash6("sha256").update(rawUserId).digest("hex")}`;
13549
- return join25(resolveStorageSessionAsksDir(), safeUserId);
13574
+ return join26(resolveStorageSessionAsksDir(), safeUserId);
13550
13575
  }
13551
13576
  function encodeAskKey(key) {
13552
13577
  return JSON.stringify([key.from, key.to]);
13553
13578
  }
13554
13579
  function pendingAskPath(key) {
13555
13580
  const digest = createHash6("sha256").update(encodeAskKey(key)).digest("hex");
13556
- return join25(pendingAskDir(key.userId), `${ASK_FILENAME_PREFIX}${digest}.pending`);
13581
+ return join26(pendingAskDir(key.userId), `${ASK_FILENAME_PREFIX}${digest}.pending`);
13557
13582
  }
13558
13583
  function v2PendingAskPath(key) {
13559
13584
  const encoded = Buffer.from(encodeAskKey(key), "utf8").toString("base64url");
13560
- return join25(pendingAskDir(key.userId), `${V2_ASK_FILENAME_PREFIX}${encoded}.pending`);
13585
+ return join26(pendingAskDir(key.userId), `${V2_ASK_FILENAME_PREFIX}${encoded}.pending`);
13561
13586
  }
13562
13587
  function legacyPendingAskPath(key) {
13563
13588
  if (key.from.includes("/") || key.from.includes("\\") || key.to.includes("/") || key.to.includes("\\") || key.from.includes("\x00") || key.to.includes("\x00")) {
13564
13589
  return null;
13565
13590
  }
13566
13591
  const dir = pendingAskDir(key.userId);
13567
- const candidate = join25(dir, `${key.from}___${key.to}.pending`);
13592
+ const candidate = join26(dir, `${key.from}___${key.to}.pending`);
13568
13593
  return dirname14(candidate) === dir ? candidate : null;
13569
13594
  }
13570
13595
  function parsePendingAskFilename(fileName) {
@@ -13805,7 +13830,7 @@ function listPendingAsksForCaller(args) {
13805
13830
  const parsed = isV3 ? { from: args.from, to: "" } : parsePendingAskFilename(fileName);
13806
13831
  if (!parsed)
13807
13832
  continue;
13808
- const path = join25(dir, fileName);
13833
+ const path = join26(dir, fileName);
13809
13834
  const record = readPendingAskFile(path, {
13810
13835
  userId: args.userId,
13811
13836
  from: parsed.from,
@@ -13847,7 +13872,7 @@ function deletePendingAsksForTopic(args) {
13847
13872
  }
13848
13873
  let deleted = 0;
13849
13874
  for (const fileName of files) {
13850
- const path = join25(dir, fileName);
13875
+ const path = join26(dir, fileName);
13851
13876
  const parsed = parsePendingAskFilename(fileName);
13852
13877
  const record = readPendingAskFile(path, {
13853
13878
  userId: args.userId,
@@ -14247,7 +14272,7 @@ body{font-family:system-ui,-apple-system,"Segoe UI",sans-serif;background:var(--
14247
14272
  // ../../packages/core/src/storage/token-stats.ts
14248
14273
  import { createHash as createHash7 } from "crypto";
14249
14274
  import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "fs";
14250
- import { join as join26 } from "path";
14275
+ import { join as join27 } from "path";
14251
14276
  function tokenStatsFileId(userId) {
14252
14277
  const rawUserId = String(userId);
14253
14278
  return /^[A-Za-z0-9][A-Za-z0-9_.@-]{0,255}$/.test(rawUserId) && !rawUserId.includes("..") ? rawUserId : `sha256-${createHash7("sha256").update(rawUserId).digest("hex")}`;
@@ -14256,7 +14281,7 @@ function queriesPath(userId) {
14256
14281
  const fileId = tokenStatsFileId(userId);
14257
14282
  const logDir = resolveStorageLogDir();
14258
14283
  mkdirSync16(logDir, { recursive: true });
14259
- return join26(logDir, `token-queries-${fileId}.jsonl`);
14284
+ return join27(logDir, `token-queries-${fileId}.jsonl`);
14260
14285
  }
14261
14286
  function estimateUsageCost(agent, model, usage) {
14262
14287
  const prices = TOKEN_PRICES[`${agent}:${model}`];
@@ -14600,7 +14625,7 @@ var init_lifecycle = __esm(async () => {
14600
14625
  // ../../packages/core/src/runtime/attachments.ts
14601
14626
  import { randomUUID as randomUUID14 } from "crypto";
14602
14627
  import { copyFileSync as copyFileSync2, mkdirSync as mkdirSync17, writeFileSync as writeFileSync15 } from "fs";
14603
- import { basename as basename5, join as join27 } from "path";
14628
+ import { basename as basename5, join as join28 } from "path";
14604
14629
  function workspaceCwdFor(topicId) {
14605
14630
  return resolveTopicWorkspaceDir(topicId);
14606
14631
  }
@@ -14613,7 +14638,7 @@ function materializePromptAttachments(topicId, queryId, attachmentIds) {
14613
14638
  if (!attachmentIds?.length)
14614
14639
  return [];
14615
14640
  const out = [];
14616
- const destDir = join27(workspaceCwdFor(topicId), "attachments", queryId);
14641
+ const destDir = join28(workspaceCwdFor(topicId), "attachments", queryId);
14617
14642
  for (const rawId of attachmentIds) {
14618
14643
  if (typeof rawId !== "string")
14619
14644
  continue;
@@ -14630,7 +14655,7 @@ function materializePromptAttachments(topicId, queryId, attachmentIds) {
14630
14655
  mkdirSync17(destDir, { recursive: true });
14631
14656
  const index = String(out.length + 1).padStart(2, "0");
14632
14657
  const safeName = safeAttachmentFilename(attachment.filename, fileId);
14633
- const destPath = join27(destDir, `${index}-${fileId.slice(0, 8)}-${safeName}`);
14658
+ const destPath = join28(destDir, `${index}-${fileId.slice(0, 8)}-${safeName}`);
14634
14659
  copyFileSync2(sourcePath, destPath);
14635
14660
  out.push({
14636
14661
  id: attachment.id,
@@ -14660,10 +14685,10 @@ function promptWithAttachments(prompt, attachments) {
14660
14685
  return composeAttachmentPrompt(prompt, attachments.map(({ filename, path }) => attachmentPromptLine(filename, path)));
14661
14686
  }
14662
14687
  function ingestAttachment(args) {
14663
- const destDir = join27(UPLOADS_DIR, args.topicId);
14688
+ const destDir = join28(UPLOADS_DIR, args.topicId);
14664
14689
  mkdirSync17(destDir, { recursive: true });
14665
14690
  const safeName = safeAttachmentFilename(args.filename, "upload");
14666
- const destPath = join27(destDir, `${Date.now()}-${randomUUID14().slice(0, 8)}-${safeName}`);
14691
+ const destPath = join28(destDir, `${Date.now()}-${randomUUID14().slice(0, 8)}-${safeName}`);
14667
14692
  if (args.sourcePath !== undefined) {
14668
14693
  copyFileSync2(args.sourcePath, destPath);
14669
14694
  } else if (args.bytes !== undefined) {
@@ -15755,6 +15780,10 @@ ${JSON.stringify(event.input ?? {})}`);
15755
15780
  logger.warn({ topicId, queryId, agentType, model, silent, error: event.content }, "ai: provider returned error");
15756
15781
  terminalEmitted = true;
15757
15782
  errorOccurred = true;
15783
+ if (event.code === "budget_exceeded") {
15784
+ outcome = { kind: "budget-capped", reason: "cost", usage: event.usage };
15785
+ return outcome;
15786
+ }
15758
15787
  if (retryableSessionExpired && isSessionExpiredError(event.content)) {
15759
15788
  outcome = { kind: "session-expired", error: event.content };
15760
15789
  return outcome;
@@ -15958,9 +15987,9 @@ var init_turn_session = __esm(async () => {
15958
15987
 
15959
15988
  // ../../packages/core/src/storage/app-settings.ts
15960
15989
  import { existsSync as existsSync19, mkdirSync as mkdirSync18, readFileSync as readFileSync17, writeFileSync as writeFileSync16 } from "fs";
15961
- import { dirname as dirname15, join as join28 } from "path";
15990
+ import { dirname as dirname15, join as join29 } from "path";
15962
15991
  function settingsFile() {
15963
- return join28(resolveStorageDataDir(), "otium-settings.json");
15992
+ return join29(resolveStorageDataDir(), "otium-settings.json");
15964
15993
  }
15965
15994
  function getGlobalAiName() {
15966
15995
  const path = settingsFile();
@@ -16055,7 +16084,7 @@ __export(exports_turn_runner, {
16055
16084
  });
16056
16085
  import { randomUUID as randomUUID16 } from "crypto";
16057
16086
  import { existsSync as existsSync20, mkdirSync as mkdirSync19, readdirSync as readdirSync5, statSync as statSync10 } from "fs";
16058
- import { join as join29 } from "path";
16087
+ import { join as join30 } from "path";
16059
16088
  function withDefaultPlaywright(configuredMcp, isManager) {
16060
16089
  if (isManager)
16061
16090
  return configuredMcp;
@@ -16095,13 +16124,20 @@ function notifyPlaywrightUnavailable(topicId) {
16095
16124
  return;
16096
16125
  playwrightUnavailableNoticeAt.set(topicId, now);
16097
16126
  appendSystemMessage(topicId, "Playwright browser tools are unavailable this turn. Browser automation was removed from the tool catalog; retry shortly if browser interaction is required.");
16127
+ WsHub.get().broadcastTopicNotice(topicId, "info", "Playwright \uC0AC\uC6A9 \uBD88\uAC00");
16098
16128
  }
16099
- function appendAskReplyMessage(topicId, text2, agentType) {
16129
+ function appendAskReplyMessage(topicId, text2, sourceLabel, body, kind, agentType) {
16100
16130
  const message = {
16101
16131
  id: randomUUID16(),
16102
16132
  topicId,
16103
16133
  authorId: "ai",
16104
16134
  text: text2,
16135
+ kind: "tell",
16136
+ tellCard: {
16137
+ fromLabel: sourceLabel,
16138
+ label: `${kind === "error" ? "Error" : "Reply"} from ${sourceLabel}`,
16139
+ message: body
16140
+ },
16105
16141
  ...agentType ? { agentType } : {},
16106
16142
  createdAt: new Date().toISOString()
16107
16143
  };
@@ -16110,7 +16146,7 @@ function appendAskReplyMessage(topicId, text2, agentType) {
16110
16146
  return message;
16111
16147
  }
16112
16148
  function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, matchesTitle, preferExact = true) {
16113
- const preferred = join29(directory, preferredFilename);
16149
+ const preferred = join30(directory, preferredFilename);
16114
16150
  if (preferExact && existsSync20(preferred))
16115
16151
  return preferred;
16116
16152
  let newestLegacyId = null;
@@ -16121,7 +16157,7 @@ function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, ma
16121
16157
  const legacyIdMatch = !titleMatch && matchesLegacyId(filename);
16122
16158
  if (!titleMatch && !legacyIdMatch)
16123
16159
  continue;
16124
- const path = join29(directory, filename);
16160
+ const path = join30(directory, filename);
16125
16161
  const mtimeMs = statSync10(path).mtimeMs;
16126
16162
  if (titleMatch) {
16127
16163
  if (!newestTitle || mtimeMs > newestTitle.mtimeMs) {
@@ -16135,9 +16171,9 @@ function resolveWikiMirrorPath(directory, preferredFilename, matchesLegacyId, ma
16135
16171
  return newestTitle?.path ?? newestLegacyId?.path ?? preferred;
16136
16172
  }
16137
16173
  function resolveWikiMemoryMirror(wikiDir, topicId, topicTitle) {
16138
- const briefFile = resolveWikiMirrorPath(join29(wikiDir, "topic"), `${wikiBriefStorageKey(topicTitle, topicId)}.md`, (filename) => isTopicBriefFile(filename, topicId), (filename) => isTopicBriefFile(filename, topicId, topicTitle));
16174
+ const briefFile = resolveWikiMirrorPath(join30(wikiDir, "topic"), `${wikiBriefStorageKey(topicTitle, topicId)}.md`, (filename) => isTopicBriefFile(filename, topicId), (filename) => isTopicBriefFile(filename, topicId, topicTitle));
16139
16175
  const hasBriefFile = existsSync20(briefFile) && statSync10(briefFile).isFile();
16140
- const latestSummaryCandidate = resolveWikiMirrorPath(join29(wikiDir, "summaries"), `__missing__-${wikiSummaryFilename("0000-00-00", topicTitle, topicId)}`, (filename) => isTopicSummaryFile(filename, topicId), (filename) => isTopicSummaryFile(filename, topicId, topicTitle), false);
16176
+ const latestSummaryCandidate = resolveWikiMirrorPath(join30(wikiDir, "summaries"), `__missing__-${wikiSummaryFilename("0000-00-00", topicTitle, topicId)}`, (filename) => isTopicSummaryFile(filename, topicId), (filename) => isTopicSummaryFile(filename, topicId, topicTitle), false);
16141
16177
  const latestSummaryFile = existsSync20(latestSummaryCandidate) && statSync10(latestSummaryCandidate).isFile() ? latestSummaryCandidate : null;
16142
16178
  return { briefFile, hasBriefFile, latestSummaryFile };
16143
16179
  }
@@ -16244,7 +16280,7 @@ ${body}`;
16244
16280
  await markPendingAskFile(pending, "reply_ready");
16245
16281
  if (!callerTopic?.agent) {
16246
16282
  try {
16247
- appendAskReplyMessage(pending.callerTopicId, prompt);
16283
+ appendAskReplyMessage(pending.callerTopicId, prompt, sourceLabel, body, kind);
16248
16284
  await clearPendingAskFile(pending);
16249
16285
  return true;
16250
16286
  } catch (err2) {
@@ -16274,7 +16310,7 @@ ${body}`;
16274
16310
  }
16275
16311
  });
16276
16312
  if (queued) {
16277
- appendAskReplyMessage(pending.callerTopicId, prompt, callerTopic.agent);
16313
+ appendAskReplyMessage(pending.callerTopicId, prompt, sourceLabel, body, kind, callerTopic.agent);
16278
16314
  await markPendingAskFile(pending, "queued_for_caller");
16279
16315
  return true;
16280
16316
  }
@@ -16283,7 +16319,7 @@ ${body}`;
16283
16319
  return true;
16284
16320
  }
16285
16321
  logger.warn({ requestId: pending.requestId, callerTopicId: pending.callerTopicId, source: sourceLabel }, "sessions: ask callback could not enter caller batch; appending direct fallback");
16286
- appendAskReplyMessage(pending.callerTopicId, prompt, callerTopic.agent);
16322
+ appendAskReplyMessage(pending.callerTopicId, prompt, sourceLabel, body, kind, callerTopic.agent);
16287
16323
  await clearPendingAskFile(pending);
16288
16324
  return true;
16289
16325
  }
@@ -17151,12 +17187,36 @@ function startAiTurn(params) {
17151
17187
  return;
17152
17188
  }
17153
17189
  }
17190
+ if (outcome.kind === "budget-capped") {
17191
+ const error = "The job reached its cost limit";
17192
+ if (!silent) {
17193
+ appendSystemMessage(topicId, "\uC5EC\uAE30\uAE4C\uC9C0 \uC644\uB8CC\uD588\uC5B4\uC694. \uBE44\uC6A9 \uC608\uC0B0 \uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD574 \uC791\uC5C5\uC744 \uBA48\uCDC4\uC2B5\uB2C8\uB2E4.");
17194
+ WsHub.get().broadcastDone(topicId, queryId, outcome.usage ? {
17195
+ input: outcome.usage.inputTokens,
17196
+ output: outcome.usage.outputTokens,
17197
+ cachedInput: outcome.usage.cacheReadInputTokens,
17198
+ context: outcome.usage.contextTokens,
17199
+ contextWindow: outcome.usage.contextWindow
17200
+ } : undefined, { agent: agentKind, model: resolvedModel, budgetReason: outcome.reason });
17201
+ WsHub.get().broadcastTopicNotice(topicId, "warning", "\uC608\uC0B0 \uD55C\uB3C4 \uB3C4\uB2EC");
17202
+ } else {
17203
+ await deliverAskError(queryId, topic.title, error);
17204
+ }
17205
+ await settleSubagentFailure(queryId, error);
17206
+ try {
17207
+ onSettled?.({ queryId, kind: "error", error });
17208
+ } catch (err2) {
17209
+ logger.warn({ err: err2, topicId, queryId }, "ai: turn settlement hook failed");
17210
+ }
17211
+ return;
17212
+ }
17154
17213
  if (outcome.kind === "provider-error") {
17155
17214
  if (!silent) {
17156
17215
  appendSystemMessage(topicId, `${classifyAgentError(outcome.error, agentKind)}
17157
17216
 
17158
17217
  \uB2E4\uB978 \uB4F1\uB85D\uB41C \uBAA8\uB378\uC744 \uC4F0\uB824\uBA74 /model <model>\uB85C \uBC14\uAFBC \uB4A4 \uB2E4\uC2DC \uBCF4\uB0B4\uC138\uC694.`);
17159
17218
  WsHub.get().broadcastError(topicId, queryId, outcome.error);
17219
+ WsHub.get().broadcastTopicNotice(topicId, "error", "\uC751\uB2F5 \uC2E4\uD328");
17160
17220
  }
17161
17221
  await deliverAskError(queryId, topic.title, outcome.error);
17162
17222
  await settleSubagentFailure(queryId, classifyAgentError(outcome.error, agentKind));
@@ -17184,6 +17244,7 @@ function startAiTurn(params) {
17184
17244
  logger.warn({ err: err2, topicId, queryId, agent: agentKind, model: resolvedModel, silent }, "ai: background stream task failed");
17185
17245
  if (!silent) {
17186
17246
  WsHub.get().broadcastError(topicId, queryId, error);
17247
+ WsHub.get().broadcastTopicNotice(topicId, "error", "\uC751\uB2F5 \uC2E4\uD328");
17187
17248
  }
17188
17249
  await settleSubagentFailure(queryId, classifyAgentError(error, agentKind));
17189
17250
  try {
@@ -17238,6 +17299,8 @@ function triggerTopicAiTurn(topicId, userId, prompt, agentType, opts) {
17238
17299
  text: prompt,
17239
17300
  agentType: execution.agent,
17240
17301
  model: execution.model,
17302
+ kind: opts?.injectKind,
17303
+ tellCard: opts?.injectTellCard,
17241
17304
  createdAt: now
17242
17305
  };
17243
17306
  try {
@@ -19219,4 +19282,4 @@ export {
19219
19282
  DEFAULT_SELF_CONFIG_PRODUCT
19220
19283
  };
19221
19284
 
19222
- //# debugId=751F37A04AE5257A64756E2164756E21
19285
+ //# debugId=693942027C41748764756E2164756E21