@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
@@ -1,7 +1,6 @@
1
1
  import type { Office } from "../../office/types.js";
2
2
  import type { TaskStatus, RunningSession } from "../../types.js";
3
3
  export declare function isTaskStatusQuestion(text: string): boolean;
4
- export declare function readTaskRoots(office: Office): Map<string, string>;
5
4
  export declare function querySlackTasks(office: Office, channel: string, running: RunningSession[], sessionKey?: string): Promise<TaskStatus[]>;
6
5
  export declare function formatTaskStatus(tasks: TaskStatus[]): string;
7
6
  //# sourceMappingURL=task-status.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"task-status.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/task-status.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAIpD,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEjE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI1D;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAOjE;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,EAAE,EACzB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,UAAU,EAAE,CAAC,CAkDvB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAwB5D"}
1
+ {"version":3,"file":"task-status.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/task-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAKpD,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEjE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI1D;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,EAAE,EACzB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,UAAU,EAAE,CAAC,CA6BvB;AA6BD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAwB5D"}
@@ -1,66 +1,60 @@
1
- import { readOfficeLog } from "../../office/log.js";
2
- import { resolveSlackSessionKey } from "./session.js";
1
+ import { parseSlackSessionKey } from "./session.js";
3
2
  import { reportUserFacingError } from "../../observability/index.js";
4
3
  import { SessionStore } from "../../sessions/session-store.js";
5
4
  export function isTaskStatusQuestion(text) {
6
5
  return /^(好了嗎|完成了嗎|有進展嗎|現在做到哪了|進度如何|還要多久|還要多久啊|現在怎麼樣|怎麼樣了|how(?:'s| is) it going|are you done|any updates|status)[??!!。\s]*$/i.test(text.trim());
7
6
  }
8
- export function readTaskRoots(office) {
9
- const roots = new Map();
10
- for (const entry of readOfficeLog(office)) {
11
- if (entry.taskRoot === true && entry.isMessagingBot === true && typeof entry.ts === "string")
12
- roots.set(entry.ts, String(entry.text ?? ""));
13
- }
14
- return roots;
15
- }
16
7
  export async function querySlackTasks(office, channel, running, sessionKey) {
17
- const roots = readTaskRoots(office);
18
- const matching = [...roots]
19
- .toReversed()
20
- .filter(([root]) => !sessionKey || resolveSlackSessionKey(channel, root) === sessionKey);
21
8
  const activeByKey = new Map(running
22
9
  .filter((s) => s.address.platform === "slack" && s.address.conversationId === channel)
23
10
  .map((s) => [s.sessionKey, s]));
24
- const selected = matching.filter(([root], index) => index < 10 || activeByKey.has(resolveSlackSessionKey(channel, root)));
25
- const observations = [];
26
- for (const [root, acknowledgement] of selected) {
27
- const key = resolveSlackSessionKey(channel, root);
28
- const active = activeByKey.get(key);
29
- const observation = {
30
- sessionKey: key,
31
- threadTs: root,
32
- acknowledgement,
33
- observedAt: new Date().toISOString(),
34
- status: "unknown",
35
- };
36
- try {
37
- const state = await SessionStore.inspectExecution(office, key);
38
- const finishedThisRun = !state.open && state.result && (!active || state.result.endedAt >= active.startedAt);
39
- if (finishedThisRun && state.result) {
40
- observation.status = state.result.status;
41
- observation.endedAt = new Date(state.result.endedAt).toISOString();
42
- }
43
- else if (active) {
44
- observation.status = active.stopping ? "stopping" : "running";
45
- observation.currentTool = active.currentTool;
46
- }
47
- else if (!state.started) {
48
- observation.status = "queued";
49
- }
50
- }
51
- catch (error) {
52
- reportUserFacingError(error, {
53
- domain: "mikan",
54
- surface: "task_status",
55
- operation: "inspect_task_status",
56
- severity: "warning",
57
- platform: "slack",
58
- context: { conversationId: channel, sessionKey: key, threadTs: root },
59
- });
60
- }
61
- observations.push(observation);
11
+ let tasks;
12
+ try {
13
+ tasks = await SessionStore.listTasks(office, channel);
14
+ }
15
+ catch (error) {
16
+ reportUserFacingError(error, {
17
+ domain: "mikan",
18
+ surface: "task_status",
19
+ operation: "inspect_task_status",
20
+ severity: "warning",
21
+ platform: "slack",
22
+ context: { conversationId: channel },
23
+ });
24
+ return [];
25
+ }
26
+ const observedAt = new Date().toISOString();
27
+ return tasks
28
+ .filter((task) => !sessionKey || task.sessionKey === sessionKey)
29
+ .filter((task, index) => index < 10 || activeByKey.has(task.sessionKey))
30
+ .flatMap((task) => {
31
+ const ref = parseSlackSessionKey(task.sessionKey);
32
+ if (ref.kind !== "thread")
33
+ return [];
34
+ return [observeTask(task, ref.threadTs, activeByKey.get(task.sessionKey), observedAt)];
35
+ });
36
+ }
37
+ function observeTask(task, threadTs, active, observedAt) {
38
+ const observation = {
39
+ sessionKey: task.sessionKey,
40
+ threadTs,
41
+ acknowledgement: task.acknowledgement ?? "",
42
+ observedAt,
43
+ status: "unknown",
44
+ };
45
+ const finishedThisRun = !task.open && task.result && (!active || task.result.endedAt >= active.startedAt);
46
+ if (finishedThisRun && task.result) {
47
+ observation.status = task.result.status;
48
+ observation.endedAt = new Date(task.result.endedAt).toISOString();
49
+ }
50
+ else if (active) {
51
+ observation.status = active.stopping ? "stopping" : "running";
52
+ observation.currentTool = active.currentTool;
53
+ }
54
+ else if (!task.started) {
55
+ observation.status = "queued";
62
56
  }
63
- return observations;
57
+ return observation;
64
58
  }
65
59
  export function formatTaskStatus(tasks) {
66
60
  if (!tasks.length)
@@ -1 +1 @@
1
- {"version":3,"file":"task-status.js","sourceRoot":"","sources":["../../../src/adapters/slack/task-status.ts"],"names":[],"mappings":"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","sourcesContent":["import { readOfficeLog } from \"../../office/log.js\";\nimport type { Office } from \"../../office/types.js\";\nimport { resolveSlackSessionKey } from \"./session.js\";\nimport { reportUserFacingError } from \"../../observability/index.js\";\nimport { SessionStore } from \"../../sessions/session-store.js\";\nimport type { TaskStatus, RunningSession } from \"../../types.js\";\n\nexport function isTaskStatusQuestion(text: string): boolean {\n return /^(好了嗎|完成了嗎|有進展嗎|現在做到哪了|進度如何|還要多久|還要多久啊|現在怎麼樣|怎麼樣了|how(?:'s| is) it going|are you done|any updates|status)[??!!。\\s]*$/i.test(\n text.trim(),\n );\n}\n\nexport function readTaskRoots(office: Office): Map<string, string> {\n const roots = new Map<string, string>();\n for (const entry of readOfficeLog(office)) {\n if (entry.taskRoot === true && entry.isMessagingBot === true && typeof entry.ts === \"string\")\n roots.set(entry.ts, String(entry.text ?? \"\"));\n }\n return roots;\n}\n\nexport async function querySlackTasks(\n office: Office,\n channel: string,\n running: RunningSession[],\n sessionKey?: string,\n): Promise<TaskStatus[]> {\n const roots = readTaskRoots(office);\n const matching = [...roots]\n .toReversed()\n .filter(([root]) => !sessionKey || resolveSlackSessionKey(channel, root) === sessionKey);\n const activeByKey = new Map(\n running\n .filter((s) => s.address.platform === \"slack\" && s.address.conversationId === channel)\n .map((s) => [s.sessionKey, s]),\n );\n const selected = matching.filter(\n ([root], index) => index < 10 || activeByKey.has(resolveSlackSessionKey(channel, root)),\n );\n const observations: TaskStatus[] = [];\n for (const [root, acknowledgement] of selected) {\n const key = resolveSlackSessionKey(channel, root);\n const active = activeByKey.get(key);\n const observation: TaskStatus = {\n sessionKey: key,\n threadTs: root,\n acknowledgement,\n observedAt: new Date().toISOString(),\n status: \"unknown\",\n };\n try {\n const state = await SessionStore.inspectExecution(office, key);\n const finishedThisRun =\n !state.open && state.result && (!active || state.result.endedAt >= active.startedAt);\n if (finishedThisRun && state.result) {\n observation.status = state.result.status;\n observation.endedAt = new Date(state.result.endedAt).toISOString();\n } else if (active) {\n observation.status = active.stopping ? \"stopping\" : \"running\";\n observation.currentTool = active.currentTool;\n } else if (!state.started) {\n observation.status = \"queued\";\n }\n } catch (error) {\n reportUserFacingError(error, {\n domain: \"mikan\",\n surface: \"task_status\",\n operation: \"inspect_task_status\",\n severity: \"warning\",\n platform: \"slack\",\n context: { conversationId: channel, sessionKey: key, threadTs: root },\n });\n }\n observations.push(observation);\n }\n return observations;\n}\n\nexport function formatTaskStatus(tasks: TaskStatus[]): string {\n if (!tasks.length) return \"找不到這個任務的狀態。\";\n return tasks\n .map((task) => {\n switch (task.status) {\n case \"running\":\n return `還在處理${task.currentTool ? `,目前正在:${task.currentTool}` : \"\"}。目前無法可靠估計還要多久,結束後會通知你。`;\n case \"completed\":\n return \"這一輪執行已結束。這是執行狀態,不代表結果訊息已成功送達;請查看任務對話串。\";\n case \"aborted\":\n return \"這一輪已停止,不會自行繼續。你可以補充新要求後再繼續。\";\n case \"failed\":\n return \"這一輪執行失敗。\";\n case \"queued\":\n return \"任務尚未開始執行。\";\n case \"declined\":\n return \"這一輪沒有執行。\";\n case \"stopping\":\n return \"正在停止,還在等待執行收尾。\";\n default:\n return \"目前無法確認任務狀態,不能判定已完成。\";\n }\n })\n .join(\"\\n\\n\");\n}\n"]}
1
+ {"version":3,"file":"task-status.js","sourceRoot":"","sources":["../../../src/adapters/slack/task-status.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Office } from \"../../office/types.js\";\nimport { parseSlackSessionKey } from \"./session.js\";\nimport { reportUserFacingError } from \"../../observability/index.js\";\nimport { SessionStore } from \"../../sessions/session-store.js\";\nimport type { TaskSessionState } from \"../../sessions/types.js\";\nimport type { TaskStatus, RunningSession } from \"../../types.js\";\n\nexport function isTaskStatusQuestion(text: string): boolean {\n return /^(好了嗎|完成了嗎|有進展嗎|現在做到哪了|進度如何|還要多久|還要多久啊|現在怎麼樣|怎麼樣了|how(?:'s| is) it going|are you done|any updates|status)[??!!。\\s]*$/i.test(\n text.trim(),\n );\n}\n\nexport async function querySlackTasks(\n office: Office,\n channel: string,\n running: RunningSession[],\n sessionKey?: string,\n): Promise<TaskStatus[]> {\n const activeByKey = new Map(\n running\n .filter((s) => s.address.platform === \"slack\" && s.address.conversationId === channel)\n .map((s) => [s.sessionKey, s]),\n );\n let tasks: TaskSessionState[];\n try {\n tasks = await SessionStore.listTasks(office, channel);\n } catch (error) {\n reportUserFacingError(error, {\n domain: \"mikan\",\n surface: \"task_status\",\n operation: \"inspect_task_status\",\n severity: \"warning\",\n platform: \"slack\",\n context: { conversationId: channel },\n });\n return [];\n }\n const observedAt = new Date().toISOString();\n return tasks\n .filter((task) => !sessionKey || task.sessionKey === sessionKey)\n .filter((task, index) => index < 10 || activeByKey.has(task.sessionKey))\n .flatMap((task) => {\n const ref = parseSlackSessionKey(task.sessionKey);\n if (ref.kind !== \"thread\") return [];\n return [observeTask(task, ref.threadTs, activeByKey.get(task.sessionKey), observedAt)];\n });\n}\n\nfunction observeTask(\n task: TaskSessionState,\n threadTs: string,\n active: RunningSession | undefined,\n observedAt: string,\n): TaskStatus {\n const observation: TaskStatus = {\n sessionKey: task.sessionKey,\n threadTs,\n acknowledgement: task.acknowledgement ?? \"\",\n observedAt,\n status: \"unknown\",\n };\n const finishedThisRun =\n !task.open && task.result && (!active || task.result.endedAt >= active.startedAt);\n if (finishedThisRun && task.result) {\n observation.status = task.result.status;\n observation.endedAt = new Date(task.result.endedAt).toISOString();\n } else if (active) {\n observation.status = active.stopping ? \"stopping\" : \"running\";\n observation.currentTool = active.currentTool;\n } else if (!task.started) {\n observation.status = \"queued\";\n }\n return observation;\n}\n\nexport function formatTaskStatus(tasks: TaskStatus[]): string {\n if (!tasks.length) return \"找不到這個任務的狀態。\";\n return tasks\n .map((task) => {\n switch (task.status) {\n case \"running\":\n return `還在處理${task.currentTool ? `,目前正在:${task.currentTool}` : \"\"}。目前無法可靠估計還要多久,結束後會通知你。`;\n case \"completed\":\n return \"這一輪執行已結束。這是執行狀態,不代表結果訊息已成功送達;請查看任務對話串。\";\n case \"aborted\":\n return \"這一輪已停止,不會自行繼續。你可以補充新要求後再繼續。\";\n case \"failed\":\n return \"這一輪執行失敗。\";\n case \"queued\":\n return \"任務尚未開始執行。\";\n case \"declined\":\n return \"這一輪沒有執行。\";\n case \"stopping\":\n return \"正在停止,還在等待執行收尾。\";\n default:\n return \"目前無法確認任務狀態,不能判定已完成。\";\n }\n })\n .join(\"\\n\\n\");\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../../src/adapters/web/admin/portal.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAajE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAStE,qBAAa,uBAAwB,SAAQ,kBAAkB,CAAC,UAAU,CAAC;IACzE,MAAM,CAAC,IAAI,EAAE,uBAAuB,GAAG,UAAU,CAUhD;CACF;AAWD,OAAO,KAAK,EAAmB,aAAa,EAAE,MAAM,mBAAmB,CAAC;AA0BxE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAU9D,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,OAAO,CAAC,CA6BlB;AAyGD,UAAU,sBAAsB;IAC9B,OAAO,EAAE,aAAa,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,UAAU,GAChB,sBAAsB,CAexB;AAsvCD,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAmBjF"}
1
+ {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../../src/adapters/web/admin/portal.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAWjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAStE,qBAAa,uBAAwB,SAAQ,kBAAkB,CAAC,UAAU,CAAC;IACzE,MAAM,CAAC,IAAI,EAAE,uBAAuB,GAAG,UAAU,CAUhD;CACF;AAWD,OAAO,KAAK,EAAmB,aAAa,EAAE,MAAM,mBAAmB,CAAC;AA0BxE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAU9D,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,OAAO,CAAC,CA6BlB;AAyGD,UAAU,sBAAsB;IAC9B,OAAO,EAAE,aAAa,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,UAAU,GAChB,sBAAsB,CAexB;AA+tCD,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAmBjF"}
@@ -248,23 +248,11 @@ async function listConversationSessionUsage(workspace, office, label) {
248
248
  }
249
249
  async function readSessionUsage(office, sessionKey, conversationId, label) {
250
250
  try {
251
- const manager = await SessionStore.inspect(office, sessionKey);
252
- if (!manager)
251
+ const spend = await SessionStore.spend(office, sessionKey);
252
+ if (!spend)
253
253
  return [];
254
- const header = manager.getHeader();
255
- const entries = await manager.getEntries();
256
- const usage = entries.reduce((sum, entry) => {
257
- const item = assistantUsage(entry);
258
- if (!item)
259
- return sum;
260
- sum.input += numberOrZero(item.input);
261
- sum.output += numberOrZero(item.output);
262
- sum.cacheRead += numberOrZero(item.cacheRead);
263
- sum.cacheWrite += numberOrZero(item.cacheWrite);
264
- sum.cost += numberOrZero(item.cost?.total);
265
- return sum;
266
- }, { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 });
267
- const total = usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
254
+ const { input, output, cacheRead, cacheWrite, cost } = spend.usage;
255
+ const total = input + output + cacheRead + cacheWrite;
268
256
  if (total <= 0)
269
257
  return [];
270
258
  return [
@@ -272,10 +260,12 @@ async function readSessionUsage(office, sessionKey, conversationId, label) {
272
260
  conversationId,
273
261
  label,
274
262
  sessionKey,
275
- updatedAt: entries.length > 0
276
- ? new Date(entries.at(-1).timestamp).toISOString()
277
- : new Date(header.createdAt).toISOString(),
278
- ...usage,
263
+ updatedAt: new Date(spend.updatedAt).toISOString(),
264
+ input,
265
+ output,
266
+ cacheRead,
267
+ cacheWrite,
268
+ cost: cost.total,
279
269
  total,
280
270
  },
281
271
  ];
@@ -284,11 +274,6 @@ async function readSessionUsage(office, sessionKey, conversationId, label) {
284
274
  return [];
285
275
  }
286
276
  }
287
- function assistantUsage(entry) {
288
- if (entry.type !== "message" || entry.message.role !== "assistant")
289
- return undefined;
290
- return entry.message.usage;
291
- }
292
277
  function numberOrZero(value) {
293
278
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
294
279
  }
@@ -359,12 +344,9 @@ async function serveConversationUsage(res, url, services) {
359
344
  }
360
345
  async function accumulateSessionUsageByDay(office, sessionKey, cutoff, buckets, flags) {
361
346
  try {
362
- const manager = await SessionStore.inspect(office, sessionKey);
363
- if (!manager)
364
- return;
365
- for (const entry of await manager.getEntries()) {
366
- const usage = assistantUsage(entry);
367
- if (!usage || !entry.timestamp)
347
+ for (const entry of await SessionStore.ownResponseUsage(office, sessionKey)) {
348
+ const usage = entry.usage;
349
+ if (!entry.timestamp)
368
350
  continue;
369
351
  const when = new Date(entry.timestamp);
370
352
  if (Number.isNaN(when.getTime()))