@geminixiang/mikan 1.2.2 → 1.3.0-beta.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +1 -0
  3. package/dist/adapters/commands/compact.d.ts +5 -0
  4. package/dist/adapters/commands/compact.d.ts.map +1 -0
  5. package/dist/adapters/commands/compact.js +36 -0
  6. package/dist/adapters/commands/compact.js.map +1 -0
  7. package/dist/adapters/commands/manifest.d.ts.map +1 -1
  8. package/dist/adapters/commands/manifest.js +14 -0
  9. package/dist/adapters/commands/manifest.js.map +1 -1
  10. package/dist/adapters/commands/registry.d.ts.map +1 -1
  11. package/dist/adapters/commands/registry.js +2 -0
  12. package/dist/adapters/commands/registry.js.map +1 -1
  13. package/dist/adapters/commands/types.d.ts +3 -1
  14. package/dist/adapters/commands/types.d.ts.map +1 -1
  15. package/dist/adapters/commands/types.js.map +1 -1
  16. package/dist/adapters/slack/bot.d.ts.map +1 -1
  17. package/dist/adapters/slack/bot.js +28 -27
  18. package/dist/adapters/slack/bot.js.map +1 -1
  19. package/dist/adapters/slack/task-status.d.ts +0 -1
  20. package/dist/adapters/slack/task-status.d.ts.map +1 -1
  21. package/dist/adapters/slack/task-status.js +47 -53
  22. package/dist/adapters/slack/task-status.js.map +1 -1
  23. package/dist/adapters/web/admin/portal.d.ts.map +1 -1
  24. package/dist/adapters/web/admin/portal.js +13 -31
  25. package/dist/adapters/web/admin/portal.js.map +1 -1
  26. package/dist/harness/mcp-result.d.ts +3 -7
  27. package/dist/harness/mcp-result.d.ts.map +1 -1
  28. package/dist/harness/mcp-result.js +4 -48
  29. package/dist/harness/mcp-result.js.map +1 -1
  30. package/dist/harness/runner.d.ts.map +1 -1
  31. package/dist/harness/runner.js +1 -0
  32. package/dist/harness/runner.js.map +1 -1
  33. package/dist/harness/session.d.ts +5 -0
  34. package/dist/harness/session.d.ts.map +1 -1
  35. package/dist/harness/session.js +25 -2
  36. package/dist/harness/session.js.map +1 -1
  37. package/dist/harness/tools/codemode.d.ts.map +1 -1
  38. package/dist/harness/tools/codemode.js +19 -2
  39. package/dist/harness/tools/codemode.js.map +1 -1
  40. package/dist/migrations/index.d.ts.map +1 -1
  41. package/dist/migrations/index.js +2 -0
  42. package/dist/migrations/index.js.map +1 -1
  43. package/dist/migrations/session-usage.d.ts +3 -0
  44. package/dist/migrations/session-usage.d.ts.map +1 -0
  45. package/dist/migrations/session-usage.js +22 -0
  46. package/dist/migrations/session-usage.js.map +1 -0
  47. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  48. package/dist/runtime/conversation-runtime.js +26 -18
  49. package/dist/runtime/conversation-runtime.js.map +1 -1
  50. package/dist/runtime/session-lifecycle.d.ts +0 -4
  51. package/dist/runtime/session-lifecycle.d.ts.map +1 -1
  52. package/dist/runtime/session-lifecycle.js +0 -57
  53. package/dist/runtime/session-lifecycle.js.map +1 -1
  54. package/dist/sessions/session-store.d.ts +12 -10
  55. package/dist/sessions/session-store.d.ts.map +1 -1
  56. package/dist/sessions/session-store.js +183 -15
  57. package/dist/sessions/session-store.js.map +1 -1
  58. package/dist/sessions/types.d.ts +24 -1
  59. package/dist/sessions/types.d.ts.map +1 -1
  60. package/dist/sessions/types.js.map +1 -1
  61. package/dist/types.d.ts +14 -1
  62. package/dist/types.d.ts.map +1 -1
  63. package/dist/types.js.map +1 -1
  64. package/package.json +1 -1
@@ -3,10 +3,11 @@ import { randomUUID } from "node:crypto";
3
3
  import { dirname } from "node:path";
4
4
  import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
5
5
  import { calculateContextTokens } from "@earendil-works/pi-ai/utils/estimate";
6
- import { AssistantEntry, CompactionEntry, Harness, LiveDoc, MemoryStorage, ProviderDoc, ROOT_CONVERSATION_ID, ToolResultEntry, UserEntry, createRegistry, defineDoc, defineEntry, defineExtension, defineTask, } from "@earendil-works/pi-durable";
6
+ import { AssistantEntry, CompactionEntry, Harness, LiveDoc, MemoryStorage, ProviderDoc, ROOT_CONVERSATION_ID, ToolResultEntry, UsageDoc, UserEntry, createRegistry, defineDoc, defineEntry, defineExtension, defineTask, } from "@earendil-works/pi-durable";
7
7
  import { openNodeSqliteStorage } from "@earendil-works/pi-durable/storage/sqlite/node";
8
8
  import { RUN_CAUSE_CUSTOM_TYPE } from "./types.js";
9
9
  import { loadMcpTools } from "../harness/mcp.js";
10
+ import { addUsage, createEmptyUsage, sumUsageState } from "../harness/session.js";
10
11
  import * as log from "../log.js";
11
12
  import { compactionSummaryOf, wrapCompactionSummary } from "./compaction-summary.js";
12
13
  import { isThreadSessionKey } from "./session-key.js";
@@ -31,6 +32,14 @@ const SessionDoc = defineDoc({
31
32
  fork: "initial",
32
33
  initial: () => ({}),
33
34
  });
35
+ export const ScriptStoreDoc = defineDoc({
36
+ kind: "mikan.script-store",
37
+ version: 1,
38
+ scope: "conversation",
39
+ history: "rewindable",
40
+ fork: "asOf",
41
+ initial: () => ({ values: {} }),
42
+ });
34
43
  const SessionIndexDoc = defineDoc({
35
44
  kind: "mikan.sessions",
36
45
  version: 1,
@@ -276,6 +285,44 @@ class OfficeStorage {
276
285
  await this.harness.close(context);
277
286
  }
278
287
  }
288
+ const OWNED_CONVERSATION_PAGE_SIZE = 100;
289
+ async function ownedConversations(storage, ownerConversationId) {
290
+ return storage.harness.commit(async (tx) => {
291
+ const owned = [];
292
+ let cursor;
293
+ do {
294
+ const page = await tx.scanConversations({ ownerConversationId: ownerConversationId, order: "descending" }, OWNED_CONVERSATION_PAGE_SIZE, cursor);
295
+ owned.push(...page.items);
296
+ cursor = page.next;
297
+ } while (cursor !== undefined);
298
+ return owned;
299
+ }, context);
300
+ }
301
+ async function readTaskState(storage, sessionKey, conversationId, anchorId) {
302
+ const anchor = await storage.harness.commit((tx) => tx.task(anchorId), context);
303
+ return {
304
+ sessionKey,
305
+ acknowledgement: briefAcknowledgement(anchor?.input),
306
+ ...(await readExecution(storage, conversationId)),
307
+ };
308
+ }
309
+ async function readExecution(storage, conversationId) {
310
+ const live = await storage.harness.snapshot(LiveDoc, conversationId, context);
311
+ const run = (await storage.harness.snapshot(SessionDoc, conversationId, context))?.run;
312
+ const open = live?.run !== undefined;
313
+ return {
314
+ open,
315
+ started: open || run !== undefined,
316
+ result: run?.endedAt !== undefined && run.status !== undefined
317
+ ? { status: run.status, endedAt: run.endedAt }
318
+ : undefined,
319
+ };
320
+ }
321
+ function briefAcknowledgement(input) {
322
+ return isRecord(input) && typeof input.acknowledgement === "string"
323
+ ? input.acknowledgement
324
+ : undefined;
325
+ }
279
326
  function writeSessionIndex(tx, key, record) {
280
327
  return tx.doc(SessionIndexDoc).then((doc) => {
281
328
  doc.sessions[key] = record;
@@ -304,12 +351,12 @@ async function createSession(storage, key) {
304
351
  }
305
352
  return { conversation, record };
306
353
  }
307
- async function createTaskSession(storage, key, requesterKey) {
354
+ async function createTaskSession(storage, key, requesterKey, brief) {
308
355
  const requester = await lookupSession(storage, requesterKey);
309
356
  if (!requester)
310
357
  throw new Error(`Task requester session not found: ${requesterKey}`);
311
358
  const record = await requester.conversation.commit(async (tx) => {
312
- const anchor = await tx.createTask(TaskAnchor, null, {
359
+ const anchor = await tx.createTask(TaskAnchor, brief, {
313
360
  ownership: { kind: "conversation" },
314
361
  background: true,
315
362
  });
@@ -429,6 +476,70 @@ export async function importOfficeSessions(path, sessions) {
429
476
  }
430
477
  protectStorageFiles(path);
431
478
  }
479
+ export async function backfillImportedUsage(path, dryRun) {
480
+ const harness = await Harness.open(await openNodeSqliteStorage(path), { models: {}, registry: createRegistry(), settings: { extensions: [] } }, context);
481
+ try {
482
+ let backfilled = 0;
483
+ let cursor;
484
+ do {
485
+ const page = await harness.commit((tx) => tx.scanConversations({}, 100, cursor), context);
486
+ for (const conversation of page.items) {
487
+ const imported = await importedUsage(harness, conversation.id);
488
+ if (imported.size === 0)
489
+ continue;
490
+ backfilled += 1;
491
+ if (!dryRun)
492
+ await recordImportedUsage(harness, conversation.id, imported);
493
+ }
494
+ cursor = page.next;
495
+ } while (cursor !== undefined);
496
+ return backfilled;
497
+ }
498
+ finally {
499
+ await harness.close(context);
500
+ }
501
+ }
502
+ async function importedUsage(harness, conversationId) {
503
+ const byModel = new Map();
504
+ if ((await harness.snapshot(SessionDoc, conversationId, context))?.importedUsage)
505
+ return byModel;
506
+ let cursor;
507
+ do {
508
+ const page = await harness.commit((tx) => tx.scanEntries({ conversationId }, ENTRY_PAGE_SIZE, cursor), context);
509
+ for (const record of page.items) {
510
+ const message = record.model?.[0];
511
+ if (record.conversationId !== conversationId ||
512
+ record.byTaskId !== undefined ||
513
+ !AssistantEntry.is(record) ||
514
+ message?.role !== "assistant") {
515
+ continue;
516
+ }
517
+ const key = `${message.provider}/${message.model}`;
518
+ const total = byModel.get(key) ?? createEmptyUsage();
519
+ addUsage(total, message.usage);
520
+ byModel.set(key, total);
521
+ }
522
+ cursor = page.next;
523
+ } while (cursor !== undefined);
524
+ return byModel;
525
+ }
526
+ async function recordImportedUsage(harness, conversationId, imported) {
527
+ await harness.commit(async (tx) => {
528
+ const doc = await tx.doc(SessionDoc, conversationId);
529
+ if (doc.importedUsage)
530
+ return;
531
+ const ledger = await tx.doc(UsageDoc, conversationId);
532
+ for (const [key, usage] of imported) {
533
+ const merged = createEmptyUsage();
534
+ const recorded = ledger.models[key];
535
+ if (recorded)
536
+ addUsage(merged, recorded);
537
+ addUsage(merged, usage);
538
+ ledger.models[key] = merged;
539
+ }
540
+ doc.importedUsage = true;
541
+ }, context);
542
+ }
432
543
  export async function readImportedContexts(path) {
433
544
  const harness = await Harness.open(await openNodeSqliteStorage(path), { models: {}, registry: createRegistry(), settings: { extensions: [] } }, context);
434
545
  try {
@@ -486,8 +597,8 @@ export class SessionStore {
486
597
  static open(office, key) {
487
598
  return SessionStore.openOrCreate(office, key, (storage) => createSession(storage, key));
488
599
  }
489
- static openTask(office, key, requesterKey) {
490
- return SessionStore.openOrCreate(office, key, (storage) => createTaskSession(storage, key, requesterKey));
600
+ static openTask(office, key, requesterKey, brief) {
601
+ return SessionStore.openOrCreate(office, key, (storage) => createTaskSession(storage, key, requesterKey, brief));
491
602
  }
492
603
  static async openOrCreate(office, key, create) {
493
604
  const storage = await OfficeStorage.acquire(office.sessionsPath);
@@ -637,6 +748,25 @@ export class SessionStore {
637
748
  return interrupted;
638
749
  });
639
750
  }
751
+ static async listTasks(office, requesterKey) {
752
+ if (!existsSync(office.sessionsPath))
753
+ return [];
754
+ return withOfficeStorage(office, async (storage) => {
755
+ const index = await storage.index();
756
+ const requester = index[requesterKey];
757
+ if (!requester)
758
+ return [];
759
+ const keyOf = new Map(Object.entries(index).map(([key, record]) => [record.conversationId, key]));
760
+ const tasks = [];
761
+ for (const child of await ownedConversations(storage, requester.conversationId)) {
762
+ const sessionKey = keyOf.get(child.id);
763
+ if (sessionKey === undefined || child.owner === undefined)
764
+ continue;
765
+ tasks.push(await readTaskState(storage, sessionKey, child.id, child.owner.taskId));
766
+ }
767
+ return tasks;
768
+ });
769
+ }
640
770
  static async inspectExecution(office, key) {
641
771
  if (!existsSync(office.sessionsPath))
642
772
  return { open: false, started: false };
@@ -644,19 +774,52 @@ export class SessionStore {
644
774
  const session = await lookupSession(storage, key);
645
775
  if (!session)
646
776
  return { open: false, started: false };
647
- const id = session.conversation.id;
648
- const live = await storage.harness.snapshot(LiveDoc, id, context);
649
- const run = (await storage.harness.snapshot(SessionDoc, id, context))?.run;
650
- const open = live?.run !== undefined;
777
+ return readExecution(storage, session.conversation.id);
778
+ });
779
+ }
780
+ static async spend(office, key) {
781
+ if (!existsSync(office.sessionsPath))
782
+ return undefined;
783
+ return withOfficeStorage(office, async (storage) => {
784
+ const session = await lookupSession(storage, key);
785
+ if (!session)
786
+ return undefined;
787
+ const ledger = await storage.harness.snapshot(UsageDoc, session.conversation.id, context);
788
+ const newest = (await session.conversation.entries({}, 1, undefined, context)).items[0];
789
+ const timestamp = newest && toSessionEntry(newest, session.record.id)?.timestamp;
651
790
  return {
652
- open,
653
- started: open || run !== undefined,
654
- result: run?.endedAt !== undefined && run.status !== undefined
655
- ? { status: run.status, endedAt: run.endedAt }
656
- : undefined,
791
+ usage: sumUsageState(ledger ?? { models: {}, tools: {} }),
792
+ updatedAt: timestamp || session.record.createdAt,
657
793
  };
658
794
  });
659
795
  }
796
+ static async ownResponseUsage(office, key) {
797
+ if (!existsSync(office.sessionsPath))
798
+ return [];
799
+ return withOfficeStorage(office, async (storage) => {
800
+ const session = await lookupSession(storage, key);
801
+ if (!session)
802
+ return [];
803
+ const id = session.conversation.id;
804
+ return (await readRecords(session.conversation)).flatMap((record) => {
805
+ const message = record.model?.[0];
806
+ return record.conversationId === id && message?.role === "assistant"
807
+ ? [{ timestamp: message.timestamp, usage: message.usage }]
808
+ : [];
809
+ });
810
+ });
811
+ }
812
+ static async isTask(office, key) {
813
+ if (!existsSync(office.sessionsPath))
814
+ return false;
815
+ return withOfficeStorage(office, async (storage) => {
816
+ const record = (await storage.index())[key];
817
+ if (!record)
818
+ return false;
819
+ const conversation = await storage.harness.commit((tx) => tx.conversation(record.conversationId), context);
820
+ return conversation?.owner !== undefined;
821
+ });
822
+ }
660
823
  getHeader() {
661
824
  this.assertOpen();
662
825
  return structuredClone(this.header);
@@ -774,7 +937,12 @@ export class SessionStore {
774
937
  });
775
938
  }
776
939
  async reset() {
777
- await this.mutate(() => this.conversation.reset(undefined, context));
940
+ await this.mutate(async () => {
941
+ await this.conversation.reset(undefined, context);
942
+ await this.conversation.commit(async (tx) => {
943
+ (await tx.doc(ScriptStoreDoc, this.conversation.id)).values = {};
944
+ }, context);
945
+ });
778
946
  }
779
947
  async setSessionName(name) {
780
948
  await this.updateSessionDoc((doc) => {