@sanity-labs/nuum 0.2.2 → 0.3.0

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.js CHANGED
@@ -4245,7 +4245,7 @@ var require_conditions = __commonJS((exports, module) => {
4245
4245
  isNotNull: () => isNotNull,
4246
4246
  isNull: () => isNull2,
4247
4247
  like: () => like2,
4248
- lt: () => lt,
4248
+ lt: () => lt2,
4249
4249
  lte: () => lte2,
4250
4250
  ne: () => ne,
4251
4251
  not: () => not,
@@ -4310,7 +4310,7 @@ var require_conditions = __commonJS((exports, module) => {
4310
4310
  var gte2 = (left, right) => {
4311
4311
  return import_sql4.sql`${left} >= ${bindIfParam2(right, left)}`;
4312
4312
  };
4313
- var lt = (left, right) => {
4313
+ var lt2 = (left, right) => {
4314
4314
  return import_sql4.sql`${left} < ${bindIfParam2(right, left)}`;
4315
4315
  };
4316
4316
  var lte2 = (left, right) => {
@@ -17401,7 +17401,10 @@ __export(exports_schema, {
17401
17401
  sessionConfig: () => sessionConfig,
17402
17402
  presentState: () => presentState,
17403
17403
  ltmEntries: () => ltmEntries,
17404
- backgroundReports: () => backgroundReports
17404
+ backgroundTasks: () => backgroundTasks,
17405
+ backgroundTaskQueue: () => backgroundTaskQueue,
17406
+ backgroundReports: () => backgroundReports,
17407
+ alarms: () => alarms
17405
17408
  });
17406
17409
 
17407
17410
  // node_modules/drizzle-orm/entity.js
@@ -18575,6 +18578,9 @@ function and(...unfilteredConditions) {
18575
18578
  var gte = (left, right) => {
18576
18579
  return sql`${left} >= ${bindIfParam(right, left)}`;
18577
18580
  };
18581
+ var lt = (left, right) => {
18582
+ return sql`${left} < ${bindIfParam(right, left)}`;
18583
+ };
18578
18584
  var lte = (left, right) => {
18579
18585
  return sql`${left} <= ${bindIfParam(right, left)}`;
18580
18586
  };
@@ -18704,6 +18710,34 @@ var backgroundReports = sqliteTable("background_reports", {
18704
18710
  }, (table) => [
18705
18711
  index("idx_background_reports_unsurfaced").on(table.surfacedAt)
18706
18712
  ]);
18713
+ var backgroundTasks = sqliteTable("background_tasks", {
18714
+ id: text("id").primaryKey(),
18715
+ type: text("type").notNull(),
18716
+ description: text("description").notNull(),
18717
+ status: text("status").notNull(),
18718
+ createdAt: text("created_at").notNull(),
18719
+ completedAt: text("completed_at"),
18720
+ result: text("result"),
18721
+ error: text("error")
18722
+ }, (table) => [
18723
+ index("idx_background_tasks_status").on(table.status)
18724
+ ]);
18725
+ var backgroundTaskQueue = sqliteTable("background_task_queue", {
18726
+ id: text("id").primaryKey(),
18727
+ taskId: text("task_id").notNull(),
18728
+ createdAt: text("created_at").notNull(),
18729
+ content: text("content").notNull()
18730
+ }, (table) => [
18731
+ index("idx_background_task_queue_created").on(table.createdAt)
18732
+ ]);
18733
+ var alarms = sqliteTable("alarms", {
18734
+ id: text("id").primaryKey(),
18735
+ firesAt: text("fires_at").notNull(),
18736
+ note: text("note").notNull(),
18737
+ fired: integer("fired").notNull().default(0)
18738
+ }, (table) => [
18739
+ index("idx_alarms_fires_at").on(table.firesAt)
18740
+ ]);
18707
18741
 
18708
18742
  // src/storage/db.ts
18709
18743
  var isBun = typeof globalThis.Bun !== "undefined";
@@ -23468,6 +23502,9 @@ var Identifier;
23468
23502
  entry: "ent",
23469
23503
  worker: "wrk",
23470
23504
  report: "rpt",
23505
+ bgtask: "bgt",
23506
+ queue: "que",
23507
+ alarm: "alm",
23471
23508
  session: "ses",
23472
23509
  toolcall: "tcl"
23473
23510
  };
@@ -23612,6 +23649,159 @@ function createBackgroundStorage(db) {
23612
23649
  };
23613
23650
  }
23614
23651
 
23652
+ // src/storage/tasks.ts
23653
+ function createTasksStorage(db) {
23654
+ return {
23655
+ async createTask(input) {
23656
+ const id = Identifier.ascending("bgtask");
23657
+ const now = new Date().toISOString();
23658
+ await db.insert(backgroundTasks).values({
23659
+ id,
23660
+ type: input.type,
23661
+ description: input.description,
23662
+ status: "running",
23663
+ createdAt: now
23664
+ });
23665
+ return id;
23666
+ },
23667
+ async getTask(id) {
23668
+ const rows = await db.select().from(backgroundTasks).where(eq(backgroundTasks.id, id)).limit(1);
23669
+ if (rows.length === 0)
23670
+ return null;
23671
+ const row = rows[0];
23672
+ return {
23673
+ id: row.id,
23674
+ type: row.type,
23675
+ description: row.description,
23676
+ status: row.status,
23677
+ createdAt: row.createdAt,
23678
+ completedAt: row.completedAt,
23679
+ result: row.result ? JSON.parse(row.result) : null,
23680
+ error: row.error
23681
+ };
23682
+ },
23683
+ async listTasks(options) {
23684
+ const limit = options?.limit ?? 50;
23685
+ let query = db.select().from(backgroundTasks);
23686
+ if (options?.status) {
23687
+ query = query.where(eq(backgroundTasks.status, options.status));
23688
+ }
23689
+ const rows = await query.orderBy(backgroundTasks.createdAt).limit(limit);
23690
+ return rows.map((row) => ({
23691
+ id: row.id,
23692
+ type: row.type,
23693
+ description: row.description,
23694
+ status: row.status,
23695
+ createdAt: row.createdAt,
23696
+ completedAt: row.completedAt,
23697
+ result: row.result ? JSON.parse(row.result) : null,
23698
+ error: row.error
23699
+ }));
23700
+ },
23701
+ async completeTask(id, result) {
23702
+ const now = new Date().toISOString();
23703
+ await db.update(backgroundTasks).set({
23704
+ status: "completed",
23705
+ completedAt: now,
23706
+ result: JSON.stringify(result)
23707
+ }).where(eq(backgroundTasks.id, id));
23708
+ },
23709
+ async failTask(id, error) {
23710
+ const now = new Date().toISOString();
23711
+ await db.update(backgroundTasks).set({
23712
+ status: "failed",
23713
+ completedAt: now,
23714
+ error
23715
+ }).where(eq(backgroundTasks.id, id));
23716
+ },
23717
+ async recoverKilledTasks() {
23718
+ const runningTasks = await db.select().from(backgroundTasks).where(eq(backgroundTasks.status, "running"));
23719
+ if (runningTasks.length > 0) {
23720
+ const now = new Date().toISOString();
23721
+ await db.update(backgroundTasks).set({
23722
+ status: "killed",
23723
+ completedAt: now
23724
+ }).where(eq(backgroundTasks.status, "running"));
23725
+ }
23726
+ return runningTasks.map((row) => ({
23727
+ id: row.id,
23728
+ type: row.type,
23729
+ description: row.description,
23730
+ status: "killed",
23731
+ createdAt: row.createdAt,
23732
+ completedAt: row.completedAt,
23733
+ result: null,
23734
+ error: null
23735
+ }));
23736
+ },
23737
+ async queueResult(taskId, content) {
23738
+ const id = Identifier.ascending("bgtask");
23739
+ const now = new Date().toISOString();
23740
+ await db.insert(backgroundTaskQueue).values({
23741
+ id,
23742
+ taskId,
23743
+ createdAt: now,
23744
+ content
23745
+ });
23746
+ },
23747
+ async drainQueue() {
23748
+ const rows = await db.select().from(backgroundTaskQueue).orderBy(backgroundTaskQueue.createdAt);
23749
+ if (rows.length === 0)
23750
+ return [];
23751
+ const ids = rows.map((r) => r.id);
23752
+ for (const id of ids) {
23753
+ await db.delete(backgroundTaskQueue).where(eq(backgroundTaskQueue.id, id));
23754
+ }
23755
+ return rows.map((row) => ({
23756
+ id: row.id,
23757
+ taskId: row.taskId,
23758
+ createdAt: row.createdAt,
23759
+ content: row.content
23760
+ }));
23761
+ },
23762
+ async hasQueuedResults() {
23763
+ const rows = await db.select({ id: backgroundTaskQueue.id }).from(backgroundTaskQueue).limit(1);
23764
+ return rows.length > 0;
23765
+ },
23766
+ async createAlarm(input) {
23767
+ const id = Identifier.ascending("bgtask");
23768
+ await db.insert(alarms).values({
23769
+ id,
23770
+ firesAt: input.firesAt,
23771
+ note: input.note,
23772
+ fired: 0
23773
+ });
23774
+ return id;
23775
+ },
23776
+ async getDueAlarms() {
23777
+ const now = new Date().toISOString();
23778
+ const rows = await db.select().from(alarms).where(and(eq(alarms.fired, 0), lt(alarms.firesAt, now))).orderBy(alarms.firesAt);
23779
+ return rows.map((row) => ({
23780
+ id: row.id,
23781
+ firesAt: row.firesAt,
23782
+ note: row.note,
23783
+ fired: row.fired === 1
23784
+ }));
23785
+ },
23786
+ async markAlarmFired(id) {
23787
+ await db.update(alarms).set({ fired: 1 }).where(eq(alarms.id, id));
23788
+ },
23789
+ async listAlarms(options) {
23790
+ let query = db.select().from(alarms);
23791
+ if (!options?.includeFired) {
23792
+ query = query.where(eq(alarms.fired, 0));
23793
+ }
23794
+ const rows = await query.orderBy(alarms.firesAt);
23795
+ return rows.map((row) => ({
23796
+ id: row.id,
23797
+ firesAt: row.firesAt,
23798
+ note: row.note,
23799
+ fired: row.fired === 1
23800
+ }));
23801
+ }
23802
+ };
23803
+ }
23804
+
23615
23805
  // src/storage/index.ts
23616
23806
  function createStorage(dbPath, options) {
23617
23807
  const { mkdirSync } = __require("fs");
@@ -23631,6 +23821,7 @@ function createStorageFromDb(db) {
23631
23821
  workers: createWorkerStorage(db),
23632
23822
  session: createSessionStorage(db),
23633
23823
  background: createBackgroundStorage(db),
23824
+ tasks: createTasksStorage(db),
23634
23825
  _db: db
23635
23826
  };
23636
23827
  }
@@ -45105,6 +45296,488 @@ var LTMTools = {
45105
45296
  ...LTMReadOnlyTools,
45106
45297
  ...LTMWriteTools
45107
45298
  };
45299
+ // src/tool/list-tasks.ts
45300
+ function formatElapsed(startTime) {
45301
+ const start = new Date(startTime).getTime();
45302
+ const now2 = Date.now();
45303
+ const elapsed = Math.floor((now2 - start) / 1000);
45304
+ if (elapsed < 60)
45305
+ return `${elapsed}s`;
45306
+ if (elapsed < 3600)
45307
+ return `${Math.floor(elapsed / 60)}m ${elapsed % 60}s`;
45308
+ const hours = Math.floor(elapsed / 3600);
45309
+ const mins = Math.floor(elapsed % 3600 / 60);
45310
+ return `${hours}h ${mins}m`;
45311
+ }
45312
+ function formatTimeUntil(firesAt) {
45313
+ const fires = new Date(firesAt).getTime();
45314
+ const now2 = Date.now();
45315
+ const remaining = Math.floor((fires - now2) / 1000);
45316
+ if (remaining <= 0)
45317
+ return "due now";
45318
+ if (remaining < 60)
45319
+ return `in ${remaining}s`;
45320
+ if (remaining < 3600)
45321
+ return `in ${Math.floor(remaining / 60)}m`;
45322
+ const hours = Math.floor(remaining / 3600);
45323
+ const mins = Math.floor(remaining % 3600 / 60);
45324
+ return `in ${hours}h ${mins}m`;
45325
+ }
45326
+ function formatTask(task) {
45327
+ const elapsed = formatElapsed(task.createdAt);
45328
+ const statusIcon = {
45329
+ running: "\uD83D\uDD04",
45330
+ completed: "\u2705",
45331
+ failed: "\u274C",
45332
+ killed: "\uD83D\uDC80"
45333
+ }[task.status];
45334
+ let line = `${statusIcon} #${task.id.slice(-8)} ${task.type}: "${task.description}" (${task.status}, ${elapsed})`;
45335
+ if (task.error) {
45336
+ line += `
45337
+ Error: ${task.error}`;
45338
+ }
45339
+ return line;
45340
+ }
45341
+ function formatAlarm(alarm) {
45342
+ const timeUntil = formatTimeUntil(alarm.firesAt);
45343
+ return `\u23F0 #${alarm.id.slice(-8)}: "${alarm.note}" (${timeUntil})`;
45344
+ }
45345
+ var DESCRIPTION5 = `List background tasks and alarms.
45346
+
45347
+ Shows:
45348
+ - Running background tasks (research, reflect)
45349
+ - Scheduled alarms (notes to self)
45350
+ - Recent completed/failed tasks (if includeCompleted=true)
45351
+
45352
+ Use this to see what's in progress or scheduled.`;
45353
+ var parameters6 = exports_external.object({
45354
+ includeCompleted: exports_external.boolean().optional().describe("Include completed/failed tasks (default: false, only shows running)")
45355
+ });
45356
+ var ListTasksTool = Tool.define("list_tasks", {
45357
+ description: DESCRIPTION5,
45358
+ parameters: parameters6,
45359
+ async execute({ includeCompleted }, ctx) {
45360
+ const tasksStorage = ctx.extra?.tasks;
45361
+ if (!tasksStorage) {
45362
+ return {
45363
+ output: "Error: Tasks storage not available",
45364
+ title: "List tasks failed",
45365
+ metadata: {
45366
+ runningCount: 0,
45367
+ alarmCount: 0,
45368
+ completedCount: 0
45369
+ }
45370
+ };
45371
+ }
45372
+ const allTasks = await tasksStorage.listTasks({ limit: 20 });
45373
+ const runningTasks = allTasks.filter((t) => t.status === "running");
45374
+ const completedTasks = allTasks.filter((t) => t.status !== "running");
45375
+ const alarms2 = await tasksStorage.listAlarms();
45376
+ const lines = [];
45377
+ if (runningTasks.length > 0) {
45378
+ lines.push("**Running Tasks:**");
45379
+ for (const task of runningTasks) {
45380
+ lines.push(formatTask(task));
45381
+ }
45382
+ }
45383
+ if (alarms2.length > 0) {
45384
+ if (lines.length > 0)
45385
+ lines.push("");
45386
+ lines.push("**Scheduled Alarms:**");
45387
+ for (const alarm of alarms2) {
45388
+ lines.push(formatAlarm(alarm));
45389
+ }
45390
+ }
45391
+ if (includeCompleted && completedTasks.length > 0) {
45392
+ if (lines.length > 0)
45393
+ lines.push("");
45394
+ lines.push("**Recent Completed/Failed:**");
45395
+ for (const task of completedTasks.slice(0, 10)) {
45396
+ lines.push(formatTask(task));
45397
+ }
45398
+ }
45399
+ if (lines.length === 0) {
45400
+ return {
45401
+ output: "No background tasks or alarms.",
45402
+ title: "No tasks",
45403
+ metadata: {
45404
+ runningCount: 0,
45405
+ alarmCount: 0,
45406
+ completedCount: 0
45407
+ }
45408
+ };
45409
+ }
45410
+ return {
45411
+ output: lines.join(`
45412
+ `),
45413
+ title: `${runningTasks.length} running, ${alarms2.length} alarms`,
45414
+ metadata: {
45415
+ runningCount: runningTasks.length,
45416
+ alarmCount: alarms2.length,
45417
+ completedCount: completedTasks.length
45418
+ }
45419
+ };
45420
+ }
45421
+ });
45422
+ // src/tool/set-alarm.ts
45423
+ function parseDelay(delay2) {
45424
+ const match = delay2.match(/^(\d+(?:\.\d+)?)\s*(s|sec|second|seconds|m|min|minute|minutes|h|hr|hour|hours)$/i);
45425
+ if (!match)
45426
+ return null;
45427
+ const value = parseFloat(match[1]);
45428
+ const unit = match[2].toLowerCase();
45429
+ switch (unit) {
45430
+ case "s":
45431
+ case "sec":
45432
+ case "second":
45433
+ case "seconds":
45434
+ return value * 1000;
45435
+ case "m":
45436
+ case "min":
45437
+ case "minute":
45438
+ case "minutes":
45439
+ return value * 60 * 1000;
45440
+ case "h":
45441
+ case "hr":
45442
+ case "hour":
45443
+ case "hours":
45444
+ return value * 60 * 60 * 1000;
45445
+ default:
45446
+ return null;
45447
+ }
45448
+ }
45449
+ var DESCRIPTION6 = `Schedule a future reminder (alarm).
45450
+
45451
+ When the alarm fires, you'll receive a message with your note.
45452
+ Use this to:
45453
+ - Check back on something later ("check if deploy succeeded")
45454
+ - Set a reminder for a task
45455
+ - Schedule periodic check-ins
45456
+
45457
+ Examples:
45458
+ - set_alarm({ delay: "5m", note: "check deployment status" })
45459
+ - set_alarm({ delay: "1h", note: "follow up on PR review" })
45460
+ - set_alarm({ delay: "30s", note: "verify test results" })`;
45461
+ var parameters7 = exports_external.object({
45462
+ delay: exports_external.string().describe('How long until the alarm fires (e.g., "5m", "1h", "30s")'),
45463
+ note: exports_external.string().describe("The reminder message you'll receive when the alarm fires")
45464
+ });
45465
+ var SetAlarmTool = Tool.define("set_alarm", {
45466
+ description: DESCRIPTION6,
45467
+ parameters: parameters7,
45468
+ async execute({ delay: delay2, note }, ctx) {
45469
+ const tasksStorage = ctx.extra?.tasks;
45470
+ if (!tasksStorage) {
45471
+ return {
45472
+ output: "Error: Tasks storage not available",
45473
+ title: "Set alarm failed",
45474
+ metadata: {
45475
+ alarmId: "",
45476
+ firesAt: "",
45477
+ delayMs: 0
45478
+ }
45479
+ };
45480
+ }
45481
+ const delayMs = parseDelay(delay2);
45482
+ if (delayMs === null) {
45483
+ return {
45484
+ output: `Invalid delay format: "${delay2}". Use formats like "5m", "1h", "30s".`,
45485
+ title: "Invalid delay",
45486
+ metadata: {
45487
+ alarmId: "",
45488
+ firesAt: "",
45489
+ delayMs: 0
45490
+ }
45491
+ };
45492
+ }
45493
+ const firesAt = new Date(Date.now() + delayMs).toISOString();
45494
+ const alarmId = await tasksStorage.createAlarm({
45495
+ firesAt,
45496
+ note
45497
+ });
45498
+ const displayDelay = delayMs >= 3600000 ? `${Math.round(delayMs / 3600000 * 10) / 10}h` : delayMs >= 60000 ? `${Math.round(delayMs / 60000)}m` : `${Math.round(delayMs / 1000)}s`;
45499
+ return {
45500
+ output: `\u23F0 Alarm set for ${displayDelay} from now.
45501
+ Note: "${note}"
45502
+ Fires at: ${firesAt}`,
45503
+ title: `Alarm in ${displayDelay}`,
45504
+ metadata: {
45505
+ alarmId,
45506
+ firesAt,
45507
+ delayMs
45508
+ }
45509
+ };
45510
+ }
45511
+ });
45512
+ // src/tool/background-research.ts
45513
+ var log9 = Log.create({ service: "background-research" });
45514
+ var DESCRIPTION7 = `Start a research task in the background.
45515
+
45516
+ This spawns a research sub-agent that runs asynchronously while you continue working.
45517
+ When the research completes, you'll receive the results automatically.
45518
+
45519
+ Use this when:
45520
+ - You want to research something without blocking your current work
45521
+ - The research might take a while and you have other things to do
45522
+ - You want to parallelize multiple research tasks
45523
+
45524
+ The research agent can:
45525
+ - Search and update your long-term knowledge base
45526
+ - Search the web and fetch documentation
45527
+ - Search your conversation history
45528
+ - Read files in the codebase
45529
+
45530
+ Example: background_research({ topic: "How does Stripe's payment intent API work?" })`;
45531
+ var parameters8 = exports_external.object({
45532
+ topic: exports_external.string().describe("The topic to research. Be specific about what you want to learn.")
45533
+ });
45534
+ var BackgroundResearchTool = Tool.define("background_research", {
45535
+ description: DESCRIPTION7,
45536
+ parameters: parameters8,
45537
+ async execute({ topic }, ctx) {
45538
+ const storage = ctx.extra?.storage;
45539
+ if (!storage) {
45540
+ return {
45541
+ output: "Error: Storage not available for background research",
45542
+ title: "Background research failed",
45543
+ metadata: {
45544
+ taskId: "",
45545
+ topic
45546
+ }
45547
+ };
45548
+ }
45549
+ const MAX_CONCURRENT_TASKS = 3;
45550
+ const runningTasks = await storage.tasks.listTasks({ status: "running" });
45551
+ if (runningTasks.length >= MAX_CONCURRENT_TASKS) {
45552
+ return {
45553
+ output: `Too many background tasks running (${runningTasks.length}/${MAX_CONCURRENT_TASKS}). Wait for some to complete or cancel them with cancel_task.`,
45554
+ title: "Too many tasks",
45555
+ metadata: {
45556
+ taskId: "",
45557
+ topic
45558
+ }
45559
+ };
45560
+ }
45561
+ const taskId = await storage.tasks.createTask({
45562
+ type: "research",
45563
+ description: topic.slice(0, 100) + (topic.length > 100 ? "..." : "")
45564
+ });
45565
+ log9.info("spawning background research", { taskId, topic: topic.slice(0, 50) });
45566
+ runBackgroundResearch(storage, taskId, topic).catch((error2) => {
45567
+ log9.error("background research failed", { taskId, error: error2 });
45568
+ });
45569
+ return {
45570
+ output: `\uD83D\uDD2C Research started in background.
45571
+ Task ID: ${taskId}
45572
+ Topic: "${topic}"
45573
+
45574
+ You'll receive the results when the research completes. Use list_tasks to check status.`,
45575
+ title: `Research started: ${topic.slice(0, 30)}...`,
45576
+ metadata: {
45577
+ taskId,
45578
+ topic
45579
+ }
45580
+ };
45581
+ }
45582
+ });
45583
+ async function runBackgroundResearch(storage, taskId, topic) {
45584
+ try {
45585
+ const result = await runResearch(storage, topic);
45586
+ const entriesCreated = result.entriesCreated.length;
45587
+ const entriesUpdated = result.entriesUpdated.length;
45588
+ const report = [
45589
+ `## Research Complete: ${topic}`,
45590
+ "",
45591
+ result.report,
45592
+ "",
45593
+ `---`,
45594
+ `*Research used ${result.turnsUsed} turns, ${result.usage.inputTokens} input / ${result.usage.outputTokens} output tokens*`,
45595
+ entriesCreated > 0 ? `*Created ${entriesCreated} LTM entries: ${result.entriesCreated.join(", ")}*` : "",
45596
+ entriesUpdated > 0 ? `*Updated ${entriesUpdated} LTM entries: ${result.entriesUpdated.join(", ")}*` : ""
45597
+ ].filter(Boolean).join(`
45598
+ `);
45599
+ await storage.tasks.completeTask(taskId, {
45600
+ report: result.report,
45601
+ entriesCreated: result.entriesCreated,
45602
+ entriesUpdated: result.entriesUpdated,
45603
+ turnsUsed: result.turnsUsed,
45604
+ usage: result.usage
45605
+ });
45606
+ await storage.tasks.queueResult(taskId, report);
45607
+ log9.info("background research completed", {
45608
+ taskId,
45609
+ turnsUsed: result.turnsUsed,
45610
+ entriesCreated,
45611
+ entriesUpdated
45612
+ });
45613
+ } catch (error2) {
45614
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
45615
+ await storage.tasks.failTask(taskId, errorMsg);
45616
+ await storage.tasks.queueResult(taskId, `## Research Failed: ${topic}
45617
+
45618
+ Error: ${errorMsg}`);
45619
+ log9.error("background research failed", { taskId, error: errorMsg });
45620
+ }
45621
+ }
45622
+ // src/tool/background-reflect.ts
45623
+ var log10 = Log.create({ service: "background-reflect" });
45624
+ var DESCRIPTION8 = `Start a reflection task in the background.
45625
+
45626
+ This spawns a reflection sub-agent that runs asynchronously while you continue working.
45627
+ When the reflection completes, you'll receive the answer automatically.
45628
+
45629
+ Use this when:
45630
+ - You want to search your memory without blocking your current work
45631
+ - The question might require extensive searching
45632
+ - You want to parallelize multiple reflection tasks
45633
+
45634
+ The reflection agent can:
45635
+ - Search your conversation history with full-text search
45636
+ - Retrieve specific messages with surrounding context
45637
+ - Search and read your long-term knowledge base
45638
+
45639
+ Example: background_reflect({ question: "What did we decide about the API authentication approach?" })`;
45640
+ var parameters9 = exports_external.object({
45641
+ question: exports_external.string().describe("The question to answer or research task to complete. Be specific about what you're looking for.")
45642
+ });
45643
+ var BackgroundReflectTool = Tool.define("background_reflect", {
45644
+ description: DESCRIPTION8,
45645
+ parameters: parameters9,
45646
+ async execute({ question }, ctx) {
45647
+ const storage = ctx.extra?.storage;
45648
+ if (!storage) {
45649
+ return {
45650
+ output: "Error: Storage not available for background reflection",
45651
+ title: "Background reflection failed",
45652
+ metadata: {
45653
+ taskId: "",
45654
+ question
45655
+ }
45656
+ };
45657
+ }
45658
+ const MAX_CONCURRENT_TASKS = 3;
45659
+ const runningTasks = await storage.tasks.listTasks({ status: "running" });
45660
+ if (runningTasks.length >= MAX_CONCURRENT_TASKS) {
45661
+ return {
45662
+ output: `Too many background tasks running (${runningTasks.length}/${MAX_CONCURRENT_TASKS}). Wait for some to complete or cancel them with cancel_task.`,
45663
+ title: "Too many tasks",
45664
+ metadata: {
45665
+ taskId: "",
45666
+ question
45667
+ }
45668
+ };
45669
+ }
45670
+ const taskId = await storage.tasks.createTask({
45671
+ type: "reflect",
45672
+ description: question.slice(0, 100) + (question.length > 100 ? "..." : "")
45673
+ });
45674
+ log10.info("spawning background reflection", { taskId, question: question.slice(0, 50) });
45675
+ runBackgroundReflection(storage, taskId, question).catch((error2) => {
45676
+ log10.error("background reflection failed", { taskId, error: error2 });
45677
+ });
45678
+ return {
45679
+ output: `\uD83D\uDD0D Reflection started in background.
45680
+ Task ID: ${taskId}
45681
+ Question: "${question}"
45682
+
45683
+ You'll receive the answer when the reflection completes. Use list_tasks to check status.`,
45684
+ title: `Reflection started: ${question.slice(0, 30)}...`,
45685
+ metadata: {
45686
+ taskId,
45687
+ question
45688
+ }
45689
+ };
45690
+ }
45691
+ });
45692
+ async function runBackgroundReflection(storage, taskId, question) {
45693
+ try {
45694
+ const result = await runReflection(storage, question);
45695
+ const report = [
45696
+ `## Reflection Complete: ${question}`,
45697
+ "",
45698
+ result.answer,
45699
+ "",
45700
+ `---`,
45701
+ `*Reflection used ${result.turnsUsed} turns, ${result.usage.inputTokens} input / ${result.usage.outputTokens} output tokens*`
45702
+ ].join(`
45703
+ `);
45704
+ await storage.tasks.completeTask(taskId, {
45705
+ answer: result.answer,
45706
+ turnsUsed: result.turnsUsed,
45707
+ usage: result.usage
45708
+ });
45709
+ await storage.tasks.queueResult(taskId, report);
45710
+ log10.info("background reflection completed", {
45711
+ taskId,
45712
+ turnsUsed: result.turnsUsed
45713
+ });
45714
+ } catch (error2) {
45715
+ const errorMsg = error2 instanceof Error ? error2.message : String(error2);
45716
+ await storage.tasks.failTask(taskId, errorMsg);
45717
+ await storage.tasks.queueResult(taskId, `## Reflection Failed: ${question}
45718
+
45719
+ Error: ${errorMsg}`);
45720
+ log10.error("background reflection failed", { taskId, error: errorMsg });
45721
+ }
45722
+ }
45723
+ // src/tool/cancel-task.ts
45724
+ var DESCRIPTION9 = `Cancel a running background task.
45725
+
45726
+ Note: This marks the task as cancelled, but cannot stop work already in progress.
45727
+ The task may still complete, but its results will be discarded.
45728
+
45729
+ Use list_tasks to see running tasks and their IDs.`;
45730
+ var parameters10 = exports_external.object({
45731
+ taskId: exports_external.string().describe("The task ID to cancel (from list_tasks)")
45732
+ });
45733
+ var CancelTaskTool = Tool.define("cancel_task", {
45734
+ description: DESCRIPTION9,
45735
+ parameters: parameters10,
45736
+ async execute({ taskId }, ctx) {
45737
+ const tasksStorage = ctx.extra?.tasks;
45738
+ if (!tasksStorage) {
45739
+ return {
45740
+ output: "Error: Tasks storage not available",
45741
+ title: "Cancel task failed",
45742
+ metadata: {
45743
+ taskId,
45744
+ success: false
45745
+ }
45746
+ };
45747
+ }
45748
+ const task = await tasksStorage.getTask(taskId);
45749
+ if (!task) {
45750
+ return {
45751
+ output: `Task not found: ${taskId}`,
45752
+ title: "Task not found",
45753
+ metadata: {
45754
+ taskId,
45755
+ success: false
45756
+ }
45757
+ };
45758
+ }
45759
+ if (task.status !== "running") {
45760
+ return {
45761
+ output: `Task ${taskId} is not running (status: ${task.status})`,
45762
+ title: "Cannot cancel",
45763
+ metadata: {
45764
+ taskId,
45765
+ success: false
45766
+ }
45767
+ };
45768
+ }
45769
+ await tasksStorage.failTask(taskId, "Cancelled by user");
45770
+ return {
45771
+ output: `Task ${taskId} marked as cancelled.
45772
+ Note: The task may still complete in the background, but results will be discarded.`,
45773
+ title: "Task cancelled",
45774
+ metadata: {
45775
+ taskId,
45776
+ success: true
45777
+ }
45778
+ };
45779
+ }
45780
+ });
45108
45781
  // src/ltm/tools.ts
45109
45782
  var AGENT_TYPE2 = "ltm-consolidate";
45110
45783
  function buildConsolidationTools(storage) {
@@ -45344,7 +46017,7 @@ function buildConsolidationTools(storage) {
45344
46017
  }
45345
46018
 
45346
46019
  // src/ltm/consolidation.ts
45347
- var log9 = Log.create({ service: "consolidation-agent" });
46020
+ var log11 = Log.create({ service: "consolidation-agent" });
45348
46021
  var MAX_CONSOLIDATION_TURNS = 20;
45349
46022
  function isConversationNoteworthy(messages) {
45350
46023
  if (messages.length < 5) {
@@ -45499,14 +46172,14 @@ async function runConsolidation(storage, messages) {
45499
46172
  usage: { inputTokens: 0, outputTokens: 0 }
45500
46173
  };
45501
46174
  if (!isConversationNoteworthy(messages)) {
45502
- log9.info("skipping consolidation - conversation not noteworthy", {
46175
+ log11.info("skipping consolidation - conversation not noteworthy", {
45503
46176
  messageCount: messages.length
45504
46177
  });
45505
46178
  result.summary = "Skipped - conversation not noteworthy";
45506
46179
  return result;
45507
46180
  }
45508
46181
  result.ran = true;
45509
- log9.info("starting consolidation", { messageCount: messages.length });
46182
+ log11.info("starting consolidation", { messageCount: messages.length });
45510
46183
  const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
45511
46184
  const allEntries = await storage.ltm.glob("/**");
45512
46185
  const recentlyUpdated = allEntries.filter((e) => e.updatedAt > oneHourAgo && e.slug !== "identity" && e.slug !== "behavior");
@@ -45554,7 +46227,7 @@ async function runConsolidation(storage, messages) {
45554
46227
  if (!result.summary) {
45555
46228
  result.summary = "Consolidation ended without explicit finish";
45556
46229
  }
45557
- log9.info("consolidation complete", {
46230
+ log11.info("consolidation complete", {
45558
46231
  entriesCreated: result.entriesCreated,
45559
46232
  entriesUpdated: result.entriesUpdated,
45560
46233
  entriesArchived: result.entriesArchived,
@@ -45673,13 +46346,13 @@ async function runDistillation(storage, threshold, target, force) {
45673
46346
  }
45674
46347
 
45675
46348
  // src/agent/index.ts
45676
- var log10 = Log.create({ service: "agent" });
46349
+ var log12 = Log.create({ service: "agent" });
45677
46350
  var MAX_TURNS = 200;
45678
46351
  async function surfaceBackgroundReports(storage) {
45679
46352
  const reports = await storage.background.getUnsurfaced();
45680
46353
  if (reports.length === 0)
45681
46354
  return;
45682
- log10.info("surfacing background reports", { count: reports.length });
46355
+ log12.info("surfacing background reports", { count: reports.length });
45683
46356
  for (const report of reports) {
45684
46357
  const toolCallId = Identifier.ascending("toolcall");
45685
46358
  await storage.temporal.appendMessage({
@@ -45849,6 +46522,16 @@ function createToolContextFactory(storage, sessionId, messageId, abortSignal) {
45849
46522
  storage
45850
46523
  };
45851
46524
  return ctx;
46525
+ },
46526
+ createListTasksContext(callId) {
46527
+ const ctx = Tool.createContext({
46528
+ ...baseContext,
46529
+ callID: callId
46530
+ });
46531
+ ctx.extra = {
46532
+ tasks: storage.tasks
46533
+ };
46534
+ return ctx;
45852
46535
  }
45853
46536
  };
45854
46537
  }
@@ -45961,15 +46644,30 @@ function buildTools(storage, sessionId, messageId, abortSignal) {
45961
46644
  parameters: LTMReadTool.definition.parameters,
45962
46645
  execute: async (args, { toolCallId }) => safeExecute("ltm_read", () => LTMReadTool.definition.execute(args, factory.createLTMContext(toolCallId)))
45963
46646
  });
45964
- tools.reflect = tool({
45965
- description: ReflectTool.definition.description,
45966
- parameters: ReflectTool.definition.parameters,
45967
- execute: async (args, { toolCallId }) => safeExecute("reflect", () => ReflectTool.definition.execute(args, factory.createReflectContext(toolCallId)))
46647
+ tools.list_tasks = tool({
46648
+ description: ListTasksTool.definition.description,
46649
+ parameters: ListTasksTool.definition.parameters,
46650
+ execute: async (args, { toolCallId }) => safeExecute("list_tasks", () => ListTasksTool.definition.execute(args, factory.createListTasksContext(toolCallId)))
46651
+ });
46652
+ tools.set_alarm = tool({
46653
+ description: SetAlarmTool.definition.description,
46654
+ parameters: SetAlarmTool.definition.parameters,
46655
+ execute: async (args, { toolCallId }) => safeExecute("set_alarm", () => SetAlarmTool.definition.execute(args, factory.createListTasksContext(toolCallId)))
46656
+ });
46657
+ tools.background_research = tool({
46658
+ description: BackgroundResearchTool.definition.description,
46659
+ parameters: BackgroundResearchTool.definition.parameters,
46660
+ execute: async (args, { toolCallId }) => safeExecute("background_research", () => BackgroundResearchTool.definition.execute(args, factory.createResearchContext(toolCallId)))
45968
46661
  });
45969
- tools.research = tool({
45970
- description: ResearchTool.definition.description,
45971
- parameters: ResearchTool.definition.parameters,
45972
- execute: async (args, { toolCallId }) => safeExecute("research", () => ResearchTool.definition.execute(args, factory.createResearchContext(toolCallId)))
46662
+ tools.background_reflect = tool({
46663
+ description: BackgroundReflectTool.definition.description,
46664
+ parameters: BackgroundReflectTool.definition.parameters,
46665
+ execute: async (args, { toolCallId }) => safeExecute("background_reflect", () => BackgroundReflectTool.definition.execute(args, factory.createReflectContext(toolCallId)))
46666
+ });
46667
+ tools.cancel_task = tool({
46668
+ description: CancelTaskTool.definition.description,
46669
+ parameters: CancelTaskTool.definition.parameters,
46670
+ execute: async (args, { toolCallId }) => safeExecute("cancel_task", () => CancelTaskTool.definition.execute(args, factory.createListTasksContext(toolCallId)))
45973
46671
  });
45974
46672
  return tools;
45975
46673
  }
@@ -45979,14 +46677,14 @@ async function initializeMcp() {
45979
46677
  if (didInitialize) {
45980
46678
  const status = Mcp.getStatus();
45981
46679
  if (status.length > 0) {
45982
- log10.info("MCP servers connected", {
46680
+ log12.info("MCP servers connected", {
45983
46681
  servers: status.map((s) => `${s.name} (${s.toolCount} tools)`)
45984
46682
  });
45985
46683
  }
45986
46684
  }
45987
46685
  return didInitialize;
45988
46686
  } catch (error2) {
45989
- log10.error("Failed to initialize MCP", { error: error2 });
46687
+ log12.error("Failed to initialize MCP", { error: error2 });
45990
46688
  return false;
45991
46689
  }
45992
46690
  }
@@ -46000,11 +46698,11 @@ async function runAgent(prompt, options) {
46000
46698
  const hardLimit = config2.tokenBudgets.compactionHardLimit;
46001
46699
  const tokensBefore = await getEffectiveViewTokens(storage.temporal);
46002
46700
  if (tokensBefore > hardLimit) {
46003
- log10.error("context overflow - refusing turn", { tokens: tokensBefore, hardLimit });
46701
+ log12.error("context overflow - refusing turn", { tokens: tokensBefore, hardLimit });
46004
46702
  throw new Error(`Context overflow: ${tokensBefore} tokens exceeds hard limit of ${hardLimit}. ` + `Run 'miriad-code --compact' to reduce context size before continuing.`);
46005
46703
  }
46006
46704
  if (tokensBefore > softLimit) {
46007
- log10.warn("approaching token limit, running compaction before turn", {
46705
+ log12.warn("approaching token limit, running compaction before turn", {
46008
46706
  tokens: tokensBefore,
46009
46707
  softLimit,
46010
46708
  target: config2.tokenBudgets.compactionTarget
@@ -46012,13 +46710,13 @@ async function runAgent(prompt, options) {
46012
46710
  await runMemoryCuration(storage, { force: true });
46013
46711
  const tokensAfter = await getEffectiveViewTokens(storage.temporal);
46014
46712
  if (tokensAfter > softLimit) {
46015
- log10.warn("compaction didn't reduce tokens below soft limit", {
46713
+ log12.warn("compaction didn't reduce tokens below soft limit", {
46016
46714
  before: tokensBefore,
46017
46715
  after: tokensAfter,
46018
46716
  softLimit
46019
46717
  });
46020
46718
  } else {
46021
- log10.info("pre-turn compaction successful", { before: tokensBefore, after: tokensAfter });
46719
+ log12.info("pre-turn compaction successful", { before: tokensBefore, after: tokensAfter });
46022
46720
  }
46023
46721
  }
46024
46722
  const model = Provider.getModelForTier("reasoning");
@@ -46033,7 +46731,7 @@ async function runAgent(prompt, options) {
46033
46731
  createdAt: new Date().toISOString()
46034
46732
  });
46035
46733
  } catch (error2) {
46036
- log10.error("failed to persist user message", {
46734
+ log12.error("failed to persist user message", {
46037
46735
  messageId: userMessageId,
46038
46736
  error: error2 instanceof Error ? error2.message : String(error2)
46039
46737
  });
@@ -46067,7 +46765,7 @@ async function runAgent(prompt, options) {
46067
46765
  });
46068
46766
  lastLoggedText = text3;
46069
46767
  } catch (error2) {
46070
- log10.error("failed to persist assistant message", {
46768
+ log12.error("failed to persist assistant message", {
46071
46769
  messageId: assistantMessageId,
46072
46770
  error: error2 instanceof Error ? error2.message : String(error2)
46073
46771
  });
@@ -46088,7 +46786,7 @@ async function runAgent(prompt, options) {
46088
46786
  createdAt: new Date().toISOString()
46089
46787
  });
46090
46788
  } catch (error2) {
46091
- log10.error("failed to persist tool call", {
46789
+ log12.error("failed to persist tool call", {
46092
46790
  messageId: toolCallMsgId,
46093
46791
  toolName,
46094
46792
  toolCallId,
@@ -46117,7 +46815,7 @@ async function runAgent(prompt, options) {
46117
46815
  createdAt: new Date().toISOString()
46118
46816
  });
46119
46817
  } catch (error2) {
46120
- log10.error("failed to persist tool result", {
46818
+ log12.error("failed to persist tool result", {
46121
46819
  messageId: toolResultMsgId,
46122
46820
  toolName,
46123
46821
  toolCallId,
@@ -46145,7 +46843,7 @@ async function runAgent(prompt, options) {
46145
46843
  createdAt: new Date().toISOString()
46146
46844
  });
46147
46845
  } catch (error2) {
46148
- log10.error("failed to persist injected user message", {
46846
+ log12.error("failed to persist injected user message", {
46149
46847
  messageId: injectedMsgId,
46150
46848
  error: error2 instanceof Error ? error2.message : String(error2)
46151
46849
  });
@@ -46177,7 +46875,7 @@ async function runAgent(prompt, options) {
46177
46875
  }
46178
46876
  }
46179
46877
  }).catch((error2) => {
46180
- log10.error("background memory curation failed", { error: error2 instanceof Error ? error2.message : String(error2) });
46878
+ log12.error("background memory curation failed", { error: error2 instanceof Error ? error2.message : String(error2) });
46181
46879
  });
46182
46880
  return {
46183
46881
  response: result.finalText,
@@ -46869,7 +47567,7 @@ function systemMessage(subtype, data = {}) {
46869
47567
  function getModelId() {
46870
47568
  return Config.resolveModelTier("reasoning");
46871
47569
  }
46872
- var log11 = Log.create({ service: "server" });
47570
+ var log13 = Log.create({ service: "server" });
46873
47571
 
46874
47572
  class Server {
46875
47573
  options;
@@ -46879,33 +47577,49 @@ class Server {
46879
47577
  rl = null;
46880
47578
  processing = false;
46881
47579
  sessionId = "";
47580
+ alarmInterval = null;
47581
+ checkingAlarms = false;
47582
+ outputHandler;
47583
+ turnCompleteResolve = null;
46882
47584
  constructor(options) {
46883
47585
  this.options = options;
46884
47586
  this.storage = createStorage(options.dbPath);
47587
+ this.outputHandler = options.outputHandler ?? ((msg) => {
47588
+ process.stdout.write(JSON.stringify(msg) + `
47589
+ `);
47590
+ });
46885
47591
  }
46886
47592
  async start() {
46887
47593
  await cleanupStaleWorkers(this.storage);
46888
47594
  await initializeDefaultEntries(this.storage);
47595
+ await this.recoverKilledTasks();
46889
47596
  this.sessionId = await this.storage.session.getId();
46890
47597
  await Mcp.initialize();
46891
47598
  const mcpTools = Mcp.getToolNames();
46892
- process.on("SIGTERM", () => this.shutdown("SIGTERM"));
46893
- process.on("SIGINT", () => this.shutdown("SIGINT"));
46894
- this.rl = readline.createInterface({
46895
- input: process.stdin,
46896
- output: process.stdout,
46897
- terminal: false
46898
- });
46899
- this.rl.on("line", (line) => {
46900
- this.handleLine(line).catch((error2) => {
46901
- log11.error("unhandled error in line handler", { error: error2 });
47599
+ if (!this.options.noStdin) {
47600
+ process.on("SIGTERM", () => this.shutdown("SIGTERM"));
47601
+ process.on("SIGINT", () => this.shutdown("SIGINT"));
47602
+ this.rl = readline.createInterface({
47603
+ input: process.stdin,
47604
+ output: process.stdout,
47605
+ terminal: false
46902
47606
  });
46903
- });
46904
- this.rl.on("close", () => {
46905
- log11.info("stdin closed, shutting down");
46906
- this.shutdown("stdin closed");
46907
- });
46908
- log11.info("server started", { dbPath: this.options.dbPath, sessionId: this.sessionId });
47607
+ this.rl.on("line", (line) => {
47608
+ this.handleLine(line).catch((error2) => {
47609
+ log13.error("unhandled error in line handler", { error: error2 });
47610
+ });
47611
+ });
47612
+ this.rl.on("close", () => {
47613
+ log13.info("stdin closed, shutting down");
47614
+ this.shutdown("stdin closed");
47615
+ });
47616
+ }
47617
+ this.alarmInterval = setInterval(() => {
47618
+ this.checkAlarms().catch((error2) => {
47619
+ log13.error("error checking alarms", { error: error2 });
47620
+ });
47621
+ }, 1000);
47622
+ log13.info("server started", { dbPath: this.options.dbPath, sessionId: this.sessionId });
46909
47623
  this.send(systemMessage("init", {
46910
47624
  session_id: this.sessionId,
46911
47625
  model: getModelId(),
@@ -46932,7 +47646,7 @@ class Server {
46932
47646
  switch (request.action) {
46933
47647
  case "interrupt":
46934
47648
  if (this.currentTurn) {
46935
- log11.info("interrupting current turn", { sessionId: this.currentTurn.sessionId });
47649
+ log13.info("interrupting current turn", { sessionId: this.currentTurn.sessionId });
46936
47650
  this.currentTurn.abortController.abort();
46937
47651
  this.send(systemMessage("interrupted", { session_id: this.currentTurn.sessionId }));
46938
47652
  } else {
@@ -46954,8 +47668,29 @@ class Server {
46954
47668
  break;
46955
47669
  }
46956
47670
  }
47671
+ async recoverKilledTasks() {
47672
+ const killedTasks = await this.storage.tasks.recoverKilledTasks();
47673
+ if (killedTasks.length === 0)
47674
+ return;
47675
+ log13.info("recovered killed tasks", { count: killedTasks.length });
47676
+ for (const task of killedTasks) {
47677
+ await this.storage.background.fileReport({
47678
+ subsystem: "task_recovery",
47679
+ report: {
47680
+ message: `Background task was killed when agent restarted: ${task.type} - "${task.description}". You may want to restart it.`,
47681
+ taskId: task.id,
47682
+ type: task.type,
47683
+ description: task.description
47684
+ }
47685
+ });
47686
+ }
47687
+ }
46957
47688
  async shutdown(reason) {
46958
- log11.info("shutting down", { reason });
47689
+ log13.info("shutting down", { reason });
47690
+ if (this.alarmInterval) {
47691
+ clearInterval(this.alarmInterval);
47692
+ this.alarmInterval = null;
47693
+ }
46959
47694
  if (this.currentTurn) {
46960
47695
  this.currentTurn.abortController.abort();
46961
47696
  }
@@ -46963,10 +47698,74 @@ class Server {
46963
47698
  this.rl?.close();
46964
47699
  process.exit(0);
46965
47700
  }
47701
+ interrupt() {
47702
+ if (this.currentTurn) {
47703
+ log13.info("interrupting current turn", { sessionId: this.currentTurn.sessionId });
47704
+ this.currentTurn.abortController.abort();
47705
+ this.send(systemMessage("interrupted", { session_id: this.currentTurn.sessionId }));
47706
+ }
47707
+ }
47708
+ async sendUserMessage(prompt) {
47709
+ const userMessage = {
47710
+ type: "user",
47711
+ session_id: this.sessionId,
47712
+ message: {
47713
+ role: "user",
47714
+ content: prompt
47715
+ }
47716
+ };
47717
+ const turnComplete = new Promise((resolve5) => {
47718
+ this.turnCompleteResolve = resolve5;
47719
+ });
47720
+ await this.handleUserMessage(userMessage);
47721
+ await turnComplete;
47722
+ }
47723
+ async checkAlarms() {
47724
+ if (this.checkingAlarms)
47725
+ return;
47726
+ this.checkingAlarms = true;
47727
+ try {
47728
+ const dueAlarms = await this.storage.tasks.getDueAlarms();
47729
+ if (dueAlarms.length > 0) {
47730
+ log13.info("alarms fired", { count: dueAlarms.length });
47731
+ for (const alarm of dueAlarms) {
47732
+ await this.storage.tasks.markAlarmFired(alarm.id);
47733
+ await this.storage.tasks.queueResult(alarm.id, `\u23F0 **Alarm fired**: ${alarm.note}`);
47734
+ }
47735
+ }
47736
+ const hasResults = await this.storage.tasks.hasQueuedResults();
47737
+ if (hasResults && !this.currentTurn && !this.processing) {
47738
+ await this.triggerSelfTurn();
47739
+ }
47740
+ } finally {
47741
+ this.checkingAlarms = false;
47742
+ }
47743
+ }
47744
+ async triggerSelfTurn() {
47745
+ const results = await this.storage.tasks.drainQueue();
47746
+ if (results.length === 0)
47747
+ return;
47748
+ log13.info("triggering self-turn", { resultCount: results.length });
47749
+ const content = results.map((r) => r.content).join(`
47750
+
47751
+ `);
47752
+ const selfMessage = {
47753
+ type: "user",
47754
+ session_id: this.sessionId,
47755
+ message: {
47756
+ role: "user",
47757
+ content: `[SYSTEM: Background events occurred]
47758
+
47759
+ ${content}`
47760
+ }
47761
+ };
47762
+ await this.processTurn(selfMessage);
47763
+ await this.processQueue();
47764
+ }
46966
47765
  async handleUserMessage(userMessage) {
46967
47766
  if (this.currentTurn) {
46968
47767
  this.messageQueue.push(userMessage);
46969
- log11.info("queued message", {
47768
+ log13.info("queued message", {
46970
47769
  sessionId: userMessage.session_id,
46971
47770
  queueLength: this.messageQueue.length,
46972
47771
  currentSession: this.currentTurn.sessionId
@@ -46992,7 +47791,7 @@ class Server {
46992
47791
  }
46993
47792
  if (userMessage.environment !== undefined) {
46994
47793
  setEnvironment(userMessage.environment);
46995
- log11.info("applied environment from message", {
47794
+ log13.info("applied environment from message", {
46996
47795
  count: Object.keys(userMessage.environment).length
46997
47796
  });
46998
47797
  } else {
@@ -47005,7 +47804,7 @@ class Server {
47005
47804
  numTurns: 0,
47006
47805
  startTime: Date.now()
47007
47806
  };
47008
- log11.debug("starting turn", { sessionId, promptLength: prompt.length });
47807
+ log13.debug("starting turn", { sessionId, promptLength: prompt.length });
47009
47808
  try {
47010
47809
  const agentOptions = {
47011
47810
  storage: this.storage,
@@ -47029,12 +47828,16 @@ class Server {
47029
47828
  return;
47030
47829
  }
47031
47830
  const message = error2 instanceof Error ? error2.message : String(error2);
47032
- log11.error("turn failed", { sessionId, error: message });
47831
+ log13.error("turn failed", { sessionId, error: message });
47033
47832
  this.send(resultMessage(sessionId, "error", Date.now() - (this.currentTurn?.startTime ?? Date.now()), this.currentTurn?.numTurns ?? 0, {
47034
47833
  result: message
47035
47834
  }));
47036
47835
  } finally {
47037
47836
  this.currentTurn = null;
47837
+ if (this.turnCompleteResolve) {
47838
+ this.turnCompleteResolve();
47839
+ this.turnCompleteResolve = null;
47840
+ }
47038
47841
  }
47039
47842
  }
47040
47843
  async processQueue() {
@@ -47044,7 +47847,7 @@ class Server {
47044
47847
  try {
47045
47848
  while (this.messageQueue.length > 0 && !this.currentTurn) {
47046
47849
  const nextMessage = this.messageQueue.shift();
47047
- log11.info("processing queued message", {
47850
+ log13.info("processing queued message", {
47048
47851
  sessionId: nextMessage.session_id,
47049
47852
  remainingInQueue: this.messageQueue.length
47050
47853
  });
@@ -47063,7 +47866,7 @@ class Server {
47063
47866
  const combined = contents.join(`
47064
47867
 
47065
47868
  `);
47066
- log11.info("injecting mid-turn messages", {
47869
+ log13.info("injecting mid-turn messages", {
47067
47870
  messageCount: messages.length,
47068
47871
  combinedLength: combined.length
47069
47872
  });
@@ -47122,14 +47925,13 @@ class Server {
47122
47925
  };
47123
47926
  const reinitialized = await Mcp.initialize(mergedConfig);
47124
47927
  if (reinitialized) {
47125
- log11.info("MCP reinitialized with message config", {
47928
+ log13.info("MCP reinitialized with message config", {
47126
47929
  serverCount: Object.keys(mergedConfig.mcpServers ?? {}).length
47127
47930
  });
47128
47931
  }
47129
47932
  }
47130
47933
  send(message) {
47131
- process.stdout.write(JSON.stringify(message) + `
47132
- `);
47934
+ this.outputHandler(message);
47133
47935
  }
47134
47936
  }
47135
47937
  async function runServer(options) {
@@ -47148,18 +47950,20 @@ var MAX_HISTORY = 1000;
47148
47950
 
47149
47951
  class ReplSession {
47150
47952
  options;
47151
- storage;
47953
+ server;
47152
47954
  rl = null;
47153
- abortController = null;
47154
- isRunning = false;
47155
47955
  history = [];
47956
+ isRunning = false;
47156
47957
  constructor(options) {
47157
47958
  this.options = options;
47158
- this.storage = createStorage(options.dbPath);
47959
+ this.server = new Server({
47960
+ dbPath: options.dbPath,
47961
+ outputHandler: (message) => this.handleServerOutput(message),
47962
+ noStdin: true
47963
+ });
47159
47964
  }
47160
47965
  async start() {
47161
- await cleanupStaleWorkers(this.storage);
47162
- await initializeDefaultEntries(this.storage);
47966
+ await this.server.start();
47163
47967
  this.loadHistory();
47164
47968
  this.rl = readline2.createInterface({
47165
47969
  input: process.stdin,
@@ -47172,8 +47976,8 @@ class ReplSession {
47172
47976
  this.rl.history?.unshift(line);
47173
47977
  }
47174
47978
  this.rl.on("SIGINT", () => {
47175
- if (this.isRunning && this.abortController) {
47176
- this.abortController.abort();
47979
+ if (this.isRunning) {
47980
+ this.server.interrupt();
47177
47981
  process.stdout.write(`
47178
47982
  ^C - Request cancelled
47179
47983
  `);
@@ -47188,7 +47992,7 @@ class ReplSession {
47188
47992
  this.saveHistory();
47189
47993
  console.log(`
47190
47994
  Goodbye!`);
47191
- process.exit(0);
47995
+ this.server.shutdown("user exit");
47192
47996
  });
47193
47997
  this.rl.on("line", (line) => {
47194
47998
  this.handleLine(line).catch((error2) => {
@@ -47227,7 +48031,7 @@ Goodbye!`);
47227
48031
  case "q":
47228
48032
  this.saveHistory();
47229
48033
  console.log("Goodbye!");
47230
- process.exit(0);
48034
+ this.server.shutdown("user exit");
47231
48035
  break;
47232
48036
  case "inspect":
47233
48037
  try {
@@ -47274,75 +48078,64 @@ Goodbye!`);
47274
48078
  }
47275
48079
  async runPrompt(prompt) {
47276
48080
  this.isRunning = true;
47277
- this.abortController = new AbortController;
47278
- let hasOutput = false;
47279
- const agentOptions = {
47280
- storage: this.storage,
47281
- verbose: false,
47282
- abortSignal: this.abortController.signal,
47283
- onEvent: (event) => this.handleAgentEvent(event, () => {
47284
- hasOutput = true;
47285
- })
47286
- };
47287
48081
  try {
47288
- await runAgent(prompt, agentOptions);
47289
- if (hasOutput) {
47290
- console.log();
47291
- }
47292
- } catch (error2) {
47293
- if (error2 instanceof AgentLoopCancelledError) {} else {
47294
- console.error(`
47295
- Error: ${error2 instanceof Error ? error2.message : String(error2)}`);
47296
- }
48082
+ await this.server.sendUserMessage(prompt);
47297
48083
  } finally {
47298
48084
  this.isRunning = false;
47299
- this.abortController = null;
47300
48085
  console.log();
47301
48086
  this.rl?.prompt();
47302
48087
  }
47303
48088
  }
47304
- handleAgentEvent(event, markOutput) {
47305
- switch (event.type) {
47306
- case "assistant":
47307
- process.stdout.write(event.content);
47308
- markOutput();
47309
- break;
47310
- case "tool_call":
47311
- if (event.toolName) {
47312
- const displayName = this.formatToolName(event.toolName);
47313
- const args = this.formatToolArgs(event.content);
47314
- process.stdout.write(`
48089
+ handleServerOutput(message) {
48090
+ const msg = message;
48091
+ const type = msg.type;
48092
+ switch (type) {
48093
+ case "assistant": {
48094
+ const assistantMsg = msg.message;
48095
+ if (assistantMsg?.content) {
48096
+ for (const block of assistantMsg.content) {
48097
+ if (block.type === "text" && block.text) {
48098
+ process.stdout.write(block.text);
48099
+ } else if (block.type === "tool_use" && block.name) {
48100
+ const displayName = this.formatToolName(block.name);
48101
+ const args = this.formatToolArgs(block.input);
48102
+ process.stdout.write(`
47315
48103
  [${displayName}${args}...]
47316
48104
  `);
47317
- markOutput();
48105
+ }
48106
+ }
47318
48107
  }
47319
48108
  break;
47320
- case "tool_result":
47321
- break;
47322
- case "error":
47323
- process.stdout.write(`
47324
- [Error: ${event.content}]
47325
- `);
47326
- markOutput();
47327
- break;
47328
- case "consolidation":
47329
- if (event.consolidationResult?.ran) {
47330
- const r = event.consolidationResult;
47331
- const changes = r.entriesCreated + r.entriesUpdated + r.entriesArchived;
47332
- if (changes > 0) {
48109
+ }
48110
+ case "system": {
48111
+ const subtype = msg.subtype;
48112
+ switch (subtype) {
48113
+ case "init":
48114
+ break;
48115
+ case "tool_result":
48116
+ break;
48117
+ case "error":
47333
48118
  process.stdout.write(`
48119
+ [Error: ${msg.message}]
48120
+ `);
48121
+ break;
48122
+ case "consolidation": {
48123
+ const changes = (msg.entries_created ?? 0) + (msg.entries_updated ?? 0) + (msg.entries_archived ?? 0);
48124
+ if (changes > 0) {
48125
+ process.stdout.write(`
47334
48126
  [LTM updated: ${changes} change(s)]
47335
48127
  `);
48128
+ }
48129
+ break;
47336
48130
  }
48131
+ case "interrupted":
48132
+ break;
47337
48133
  }
47338
- markOutput();
47339
48134
  break;
47340
- case "compaction":
47341
- process.stdout.write(`
47342
- [Memory compacted]
47343
- `);
47344
- markOutput();
48135
+ }
48136
+ case "result": {
47345
48137
  break;
48138
+ }
47346
48139
  }
47347
48140
  }
47348
48141
  formatToolName(name17) {
@@ -47355,25 +48148,35 @@ Error: ${error2 instanceof Error ? error2.message : String(error2)}`);
47355
48148
  grep: "Searching content",
47356
48149
  present_set_mission: "Setting mission",
47357
48150
  present_set_status: "Setting status",
47358
- present_update_tasks: "Updating tasks"
48151
+ present_update_tasks: "Updating tasks",
48152
+ set_alarm: "Setting alarm",
48153
+ list_tasks: "Listing tasks",
48154
+ background_research: "Starting research",
48155
+ background_reflect: "Starting reflection",
48156
+ cancel_task: "Cancelling task"
47359
48157
  };
47360
48158
  return displayNames[name17] || name17;
47361
48159
  }
47362
- formatToolArgs(content) {
47363
- try {
47364
- const match = content.match(/\((\{.+?\})\.\.\.\)$/);
47365
- if (match) {
47366
- const args = JSON.parse(match[1] + "}");
47367
- if (args.path)
47368
- return ` ${args.path}`;
47369
- if (args.file)
47370
- return ` ${args.file}`;
47371
- if (args.pattern)
47372
- return ` ${args.pattern}`;
47373
- if (args.command)
47374
- return ` ${args.command.slice(0, 50)}${args.command.length > 50 ? "..." : ""}`;
47375
- }
47376
- } catch {}
48160
+ formatToolArgs(input) {
48161
+ if (!input || typeof input !== "object")
48162
+ return "";
48163
+ const args = input;
48164
+ if (args.path)
48165
+ return ` ${args.path}`;
48166
+ if (args.filePath)
48167
+ return ` ${args.filePath}`;
48168
+ if (args.pattern)
48169
+ return ` ${args.pattern}`;
48170
+ if (args.command) {
48171
+ const cmd = String(args.command);
48172
+ return ` ${cmd.slice(0, 50)}${cmd.length > 50 ? "..." : ""}`;
48173
+ }
48174
+ if (args.delay)
48175
+ return ` ${args.delay}`;
48176
+ if (args.topic)
48177
+ return ` "${String(args.topic).slice(0, 40)}..."`;
48178
+ if (args.question)
48179
+ return ` "${String(args.question).slice(0, 40)}..."`;
47377
48180
  return "";
47378
48181
  }
47379
48182
  loadHistory() {
@@ -47408,8 +48211,8 @@ async function runRepl(options) {
47408
48211
  }
47409
48212
 
47410
48213
  // src/version.ts
47411
- var VERSION = "0.2.2";
47412
- var GIT_HASH = "0764a52";
48214
+ var VERSION = "0.3.0";
48215
+ var GIT_HASH = "18005cc";
47413
48216
  var VERSION_STRING = `miriad-code v${VERSION} (${GIT_HASH})`;
47414
48217
 
47415
48218
  // src/cli/index.ts