@geminixiang/mikan 1.2.2 → 1.3.0-beta.1

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 +45 -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 +186 -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,73 @@ 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
+ !recordsUsage(message)) {
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
+ function recordsUsage(message) {
527
+ return message?.role === "assistant" && isRecord(message.usage);
528
+ }
529
+ async function recordImportedUsage(harness, conversationId, imported) {
530
+ await harness.commit(async (tx) => {
531
+ const doc = await tx.doc(SessionDoc, conversationId);
532
+ if (doc.importedUsage)
533
+ return;
534
+ const ledger = await tx.doc(UsageDoc, conversationId);
535
+ for (const [key, usage] of imported) {
536
+ const merged = createEmptyUsage();
537
+ const recorded = ledger.models[key];
538
+ if (recorded)
539
+ addUsage(merged, recorded);
540
+ addUsage(merged, usage);
541
+ ledger.models[key] = merged;
542
+ }
543
+ doc.importedUsage = true;
544
+ }, context);
545
+ }
432
546
  export async function readImportedContexts(path) {
433
547
  const harness = await Harness.open(await openNodeSqliteStorage(path), { models: {}, registry: createRegistry(), settings: { extensions: [] } }, context);
434
548
  try {
@@ -486,8 +600,8 @@ export class SessionStore {
486
600
  static open(office, key) {
487
601
  return SessionStore.openOrCreate(office, key, (storage) => createSession(storage, key));
488
602
  }
489
- static openTask(office, key, requesterKey) {
490
- return SessionStore.openOrCreate(office, key, (storage) => createTaskSession(storage, key, requesterKey));
603
+ static openTask(office, key, requesterKey, brief) {
604
+ return SessionStore.openOrCreate(office, key, (storage) => createTaskSession(storage, key, requesterKey, brief));
491
605
  }
492
606
  static async openOrCreate(office, key, create) {
493
607
  const storage = await OfficeStorage.acquire(office.sessionsPath);
@@ -637,6 +751,25 @@ export class SessionStore {
637
751
  return interrupted;
638
752
  });
639
753
  }
754
+ static async listTasks(office, requesterKey) {
755
+ if (!existsSync(office.sessionsPath))
756
+ return [];
757
+ return withOfficeStorage(office, async (storage) => {
758
+ const index = await storage.index();
759
+ const requester = index[requesterKey];
760
+ if (!requester)
761
+ return [];
762
+ const keyOf = new Map(Object.entries(index).map(([key, record]) => [record.conversationId, key]));
763
+ const tasks = [];
764
+ for (const child of await ownedConversations(storage, requester.conversationId)) {
765
+ const sessionKey = keyOf.get(child.id);
766
+ if (sessionKey === undefined || child.owner === undefined)
767
+ continue;
768
+ tasks.push(await readTaskState(storage, sessionKey, child.id, child.owner.taskId));
769
+ }
770
+ return tasks;
771
+ });
772
+ }
640
773
  static async inspectExecution(office, key) {
641
774
  if (!existsSync(office.sessionsPath))
642
775
  return { open: false, started: false };
@@ -644,19 +777,52 @@ export class SessionStore {
644
777
  const session = await lookupSession(storage, key);
645
778
  if (!session)
646
779
  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;
780
+ return readExecution(storage, session.conversation.id);
781
+ });
782
+ }
783
+ static async spend(office, key) {
784
+ if (!existsSync(office.sessionsPath))
785
+ return undefined;
786
+ return withOfficeStorage(office, async (storage) => {
787
+ const session = await lookupSession(storage, key);
788
+ if (!session)
789
+ return undefined;
790
+ const ledger = await storage.harness.snapshot(UsageDoc, session.conversation.id, context);
791
+ const newest = (await session.conversation.entries({}, 1, undefined, context)).items[0];
792
+ const timestamp = newest && toSessionEntry(newest, session.record.id)?.timestamp;
651
793
  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,
794
+ usage: sumUsageState(ledger ?? { models: {}, tools: {} }),
795
+ updatedAt: timestamp || session.record.createdAt,
657
796
  };
658
797
  });
659
798
  }
799
+ static async ownResponseUsage(office, key) {
800
+ if (!existsSync(office.sessionsPath))
801
+ return [];
802
+ return withOfficeStorage(office, async (storage) => {
803
+ const session = await lookupSession(storage, key);
804
+ if (!session)
805
+ return [];
806
+ const id = session.conversation.id;
807
+ return (await readRecords(session.conversation)).flatMap((record) => {
808
+ const message = record.model?.[0];
809
+ return record.conversationId === id && recordsUsage(message)
810
+ ? [{ timestamp: message.timestamp, usage: message.usage }]
811
+ : [];
812
+ });
813
+ });
814
+ }
815
+ static async isTask(office, key) {
816
+ if (!existsSync(office.sessionsPath))
817
+ return false;
818
+ return withOfficeStorage(office, async (storage) => {
819
+ const record = (await storage.index())[key];
820
+ if (!record)
821
+ return false;
822
+ const conversation = await storage.harness.commit((tx) => tx.conversation(record.conversationId), context);
823
+ return conversation?.owner !== undefined;
824
+ });
825
+ }
660
826
  getHeader() {
661
827
  this.assertOpen();
662
828
  return structuredClone(this.header);
@@ -774,7 +940,12 @@ export class SessionStore {
774
940
  });
775
941
  }
776
942
  async reset() {
777
- await this.mutate(() => this.conversation.reset(undefined, context));
943
+ await this.mutate(async () => {
944
+ await this.conversation.reset(undefined, context);
945
+ await this.conversation.commit(async (tx) => {
946
+ (await tx.doc(ScriptStoreDoc, this.conversation.id)).values = {};
947
+ }, context);
948
+ });
778
949
  }
779
950
  async setSessionName(name) {
780
951
  await this.updateSessionDoc((doc) => {