@retaw106/mmc-oc 2.5.0 → 2.7.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 (121) hide show
  1. package/dist/{context-xx09sh1g.js → context-9q17e52a.js} +553 -214
  2. package/dist/features/magic-context/auto-search-diagnostics.d.ts +27 -0
  3. package/dist/features/magic-context/auto-search-diagnostics.d.ts.map +1 -0
  4. package/dist/features/magic-context/compaction-marker.d.ts +34 -6
  5. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  6. package/dist/features/magic-context/message-index.d.ts +1 -24
  7. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  8. package/dist/features/magic-context/search.d.ts +4 -0
  9. package/dist/features/magic-context/search.d.ts.map +1 -1
  10. package/dist/features/magic-context/session-decision-calibration.d.ts.map +1 -1
  11. package/dist/features/magic-context/session-retention.fixture.d.ts +4 -0
  12. package/dist/features/magic-context/session-retention.fixture.d.ts.map +1 -0
  13. package/dist/features/magic-context/storage-meta-persisted.d.ts +21 -1
  14. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  15. package/dist/features/magic-context/storage-native-replay.d.ts +2 -0
  16. package/dist/features/magic-context/storage-native-replay.d.ts.map +1 -1
  17. package/dist/features/magic-context/storage-session-tables.d.ts +4 -11
  18. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
  19. package/dist/features/magic-context/tool-definition-tokens.d.ts +113 -58
  20. package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
  21. package/dist/features/magic-context/work-metrics.d.ts +5 -3
  22. package/dist/features/magic-context/work-metrics.d.ts.map +1 -1
  23. package/dist/hooks/magic-context/apply-operations.d.ts +32 -0
  24. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  25. package/dist/hooks/magic-context/ask-answer-notice.d.ts +25 -0
  26. package/dist/hooks/magic-context/ask-answer-notice.d.ts.map +1 -0
  27. package/dist/hooks/magic-context/auto-search-observation.d.ts +13 -0
  28. package/dist/hooks/magic-context/auto-search-observation.d.ts.map +1 -0
  29. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/compaction-marker-manager.d.ts +5 -5
  31. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
  32. package/dist/hooks/magic-context/compaction-marker-publication.d.ts +14 -0
  33. package/dist/hooks/magic-context/compaction-marker-publication.d.ts.map +1 -0
  34. package/dist/hooks/magic-context/compaction-off-transition.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts +11 -0
  36. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +11 -3
  39. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/historian-history-wait.d.ts +16 -0
  41. package/dist/hooks/magic-context/historian-history-wait.d.ts.map +1 -0
  42. package/dist/hooks/magic-context/history-boundary-repair.d.ts +1 -1
  43. package/dist/hooks/magic-context/history-boundary-repair.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/lkg-measured-request.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/lkg-recovery-prefix.d.ts +3 -1
  47. package/dist/hooks/magic-context/lkg-recovery-prefix.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/lkg-replay-fit.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/lkg-replay.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/lkg-slot.d.ts +2 -0
  51. package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/read-session-db.d.ts +8 -2
  54. package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/read-session-raw.d.ts +20 -0
  56. package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/signed-thinking-fit-snapshot.d.ts +42 -0
  58. package/dist/hooks/magic-context/signed-thinking-fit-snapshot.d.ts.map +1 -0
  59. package/dist/hooks/magic-context/tag-messages.d.ts +2 -0
  60. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/todo-view.d.ts +2 -2
  62. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  63. package/dist/{index-bpjjcdy9.js → index-8rmt6s4f.js} +77 -31
  64. package/dist/{index-mjf0p7yq.js → index-cgjyhwdk.js} +1 -1
  65. package/dist/{index-k25thxq7.js → index-s3ebcy63.js} +1 -1
  66. package/dist/{index-1vxffj5j.js → index-t0zx3tgz.js} +408 -89
  67. package/dist/{index-0d40b4q8.js → index-xrxpbe74.js} +9355 -9215
  68. package/dist/index.js +14 -4
  69. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  70. package/dist/{read-session-chunk-eb3fy45y.js → read-session-chunk-2s8x6xps.js} +2 -2
  71. package/dist/{recomp-orchestrator-mmpy5cb7.js → recomp-orchestrator-gzd1at06.js} +5 -5
  72. package/dist/{send-session-notification-zpqs213d.js → send-session-notification-vdppp5x6.js} +2 -2
  73. package/dist/shared/search-text.d.ts +2 -0
  74. package/dist/shared/search-text.d.ts.map +1 -1
  75. package/dist/shared/tag-transcript.d.ts +2 -0
  76. package/dist/shared/tag-transcript.d.ts.map +1 -1
  77. package/dist/shared/transcript.d.ts +3 -1
  78. package/dist/shared/transcript.d.ts.map +1 -1
  79. package/dist/tools/ctx-expand/render.d.ts +5 -3
  80. package/dist/tools/ctx-expand/render.d.ts.map +1 -1
  81. package/dist/tools/ctx-expand/tools.d.ts +2 -0
  82. package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
  83. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  84. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  85. package/dist/tools/parameter-descriptions.d.ts +10 -10
  86. package/dist/tools/parameter-descriptions.d.ts.map +1 -1
  87. package/dist/tui.js +2144 -0
  88. package/dist/v2/hooks/context.d.ts +5 -1
  89. package/dist/v2/hooks/context.d.ts.map +1 -1
  90. package/dist/v2/hooks/store.d.ts.map +1 -1
  91. package/dist/v2/message-times.d.ts +17 -0
  92. package/dist/v2/message-times.d.ts.map +1 -0
  93. package/dist/v2/server.js +11211 -10345
  94. package/dist/v2/store-reader.d.ts +14 -4
  95. package/dist/v2/store-reader.d.ts.map +1 -1
  96. package/dist/v2/tui/client.d.ts +57 -0
  97. package/dist/v2/tui/client.d.ts.map +1 -0
  98. package/dist/v2/tui/context-rpc.d.ts +63 -0
  99. package/dist/v2/tui/context-rpc.d.ts.map +1 -0
  100. package/dist/v2/tui/entry.d.ts +8 -0
  101. package/dist/v2/tui/entry.d.ts.map +1 -0
  102. package/dist/v2/tui/notification-socket.d.ts +26 -0
  103. package/dist/v2/tui/notification-socket.d.ts.map +1 -0
  104. package/dist/v2/tui/session-directory.d.ts +22 -0
  105. package/dist/v2/tui/session-directory.d.ts.map +1 -0
  106. package/dist/v2/tui/types.d.ts +110 -0
  107. package/dist/v2/tui/types.d.ts.map +1 -0
  108. package/dist/web/main.js +85 -11
  109. package/dist/{wrapup-orchestrator-jq589pgr.js → wrapup-orchestrator-ghq4y406.js} +5 -5
  110. package/package.json +13 -3
  111. package/src/config/latch-permanence-guard.test.ts +0 -5
  112. package/src/shared/prompt-surface-a1-golden.md +8 -9
  113. package/src/shared/search-text.test.ts +36 -1
  114. package/src/shared/search-text.ts +48 -0
  115. package/src/shared/sqlite-transaction-mode-guard.test.ts +2 -0
  116. package/src/shared/tag-transcript.ts +47 -4
  117. package/src/shared/transcript.ts +1 -1
  118. package/src/shared/write-transaction-attribution.test.ts +1 -6
  119. package/tui.js +4 -0
  120. package/dist/features/magic-context/message-index-sweep.d.ts +0 -11
  121. package/dist/features/magic-context/message-index-sweep.d.ts.map +0 -1
@@ -1,6 +1,6 @@
1
1
  # Magic Context agent-facing surface (exported from source)
2
2
 
3
- Generated by `packages/plugin/scripts/export-agent-surface.ts` at 2026-10-01.
3
+ Generated by `packages/plugin/scripts/export-agent-surface.ts` at 2026-11-10.
4
4
  Token counts are Claude BPE estimates on the raw text.
5
5
 
6
6
  ## 1. System-prompt guidance section
@@ -201,7 +201,7 @@ Finer recovery:
201
201
  }
202
202
  ```
203
203
 
204
- ### mmc_note — description ~319 tokens, params ~203 tokens (total ~522)
204
+ ### mmc_note — description ~319 tokens, params ~196 tokens (total ~515)
205
205
 
206
206
  **Description:**
207
207
 
@@ -228,7 +228,7 @@ First line is the title (under 80 chars), followed by detail. Operations:
228
228
  ```json
229
229
  {
230
230
  "action": {
231
- "description": "write | read | update | dismiss. Defaults to write when content is given, else read.",
231
+ "description": "write | read | update | dismiss.",
232
232
  "type": "string",
233
233
  "enum": [
234
234
  "write",
@@ -259,8 +259,7 @@ First line is the title (under 80 chars), followed by detail. Operations:
259
259
  "type": "number"
260
260
  },
261
261
  "note_ids": {
262
- "description": "Note ids: one for update, 1–50 for dismiss or read (read returns full bodies). Ignored by write.",
263
- "minItems": 1,
262
+ "description": "Note ids: one for update, 1–50 for dismiss, or 1–50 to read those notes in full. Write and a plain read take an empty list.",
264
263
  "maxItems": 50,
265
264
  "type": "array",
266
265
  "items": {
@@ -272,7 +271,7 @@ First line is the title (under 80 chars), followed by detail. Operations:
272
271
  }
273
272
  ```
274
273
 
275
- ### mmc_memory — description ~279 tokens, params ~172 tokens (total ~451)
274
+ ### mmc_memory — description ~279 tokens, params ~198 tokens (total ~477)
276
275
 
277
276
  **Description:**
278
277
 
@@ -306,11 +305,11 @@ Examples: category="CONFIG_VALUES", content="OpenCode source is at ~/Work/OSS/op
306
305
  ]
307
306
  },
308
307
  "content": {
309
- "description": "The memory text — one standalone fact (write, update, merge).",
308
+ "description": "The memory text — one standalone fact (write, update, merge). Archive and get take an empty string.",
310
309
  "type": "string"
311
310
  },
312
311
  "category": {
313
- "description": "Kind of fact (required for write; on update/merge optional, omitted keeps the current category).",
312
+ "description": "Kind of fact: the new fact's kind for write; on update/merge, the memory's current category to keep it. Archive and get ignore it.",
314
313
  "type": "string",
315
314
  "enum": [
316
315
  "PROJECT_RULES",
@@ -321,7 +320,7 @@ Examples: category="CONFIG_VALUES", content="OpenCode source is at ~/Work/OSS/op
321
320
  ]
322
321
  },
323
322
  "ids": {
324
- "description": "Memory ids from <project-memory>: one for update, one or more for archive, two or more for merge, 1–20 for get.",
323
+ "description": "Memory ids from <project-memory>: one for update, one or more for archive, two or more for merge, 1–20 for get. Write takes an empty list.",
325
324
  "type": "array",
326
325
  "items": {
327
326
  "type": "number"
@@ -1,5 +1,40 @@
1
1
  import { expect, test } from "bun:test";
2
- import { cleanSearchText, keywordRelevance, matchedSearchSnippet } from "./search-text";
2
+ import {
3
+ cleanSearchText,
4
+ hintKeywordRelevance,
5
+ keywordRelevance,
6
+ matchedSearchSnippet,
7
+ } from "./search-text";
8
+
9
+ test("hint scoring preserves exact English and other space-separated keyword scores", () => {
10
+ const cases: Array<[string, string, number]> = [
11
+ ["explain historian cache wiring", "cache icons", 0.2],
12
+ ["historian cache wiring", "historian cache wiring details", 0.8000000000000002],
13
+ ["please explain cache wiring to me", "cache wiring", 0.5333333333333333],
14
+ ["CACHE cache cache_timeout", "cache_timeout", 0.4],
15
+ ["how are you", "how are you", 0],
16
+ ["historian cache", "<task>historian cache</task>", 0],
17
+ ["résumé über prüfen", "über prüfen", 0.5333333333333333],
18
+ ["память кеш", "кеш", 0.4],
19
+ ["ذاكرة تخزين", "تخزين", 0.4],
20
+ ];
21
+ for (const [query, content, expected] of cases) {
22
+ expect(keywordRelevance(query, content)).toBe(expected);
23
+ expect(hintKeywordRelevance(query, content)).toBe(expected);
24
+ }
25
+ });
26
+
27
+ test("only hint scoring splits unspaced CJK runs into overlapping character bigrams", () => {
28
+ expect(keywordRelevance("MC_Maze 残差怎样汇总?", "MC_Maze 残差按种子汇总。")).toBe(0.4);
29
+ expect(hintKeywordRelevance("残差", "残留")).toBe(0);
30
+ expect(hintKeywordRelevance("差", "残差")).toBe(0.8);
31
+ expect(hintKeywordRelevance("𠮷野家", "𠮷野川")).toBe(0.8);
32
+ expect(hintKeywordRelevance("かながき", "かながきです")).toBe(0.8);
33
+ expect(hintKeywordRelevance("カタカナ", "カタカナです")).toBe(0.8);
34
+ expect(hintKeywordRelevance("프로젝트캐시", "프로젝트의캐시")).toBe(0.8);
35
+ expect(hintKeywordRelevance("cache残差", "cache残差按种子汇总")).toBe(0.8);
36
+ expect(hintKeywordRelevance("差", "<task>残差</task> unrelated")).toBe(0);
37
+ });
3
38
 
4
39
  test("notification trees never contribute relevance or display text", () => {
5
40
  expect(
@@ -35,6 +35,54 @@ export function keywordRelevance(query: string, content: string): number {
35
35
  return (0.8 * needles.filter((term) => words.has(term)).length) / needles.length;
36
36
  }
37
37
 
38
+ const CJK_RUN = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+$/u;
39
+ const CJK_RUN_SPLIT = /([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+)/u;
40
+
41
+ function hintTerms(text: string): string[] {
42
+ return [
43
+ ...new Set(
44
+ terms(text)
45
+ .flatMap((term) => term.split(CJK_RUN_SPLIT))
46
+ .filter((term) => term.length > 0 && !QUERY_STOP_WORDS.has(term)),
47
+ ),
48
+ ];
49
+ }
50
+
51
+ function characterBigrams(run: string): string[] {
52
+ const characters = [...run];
53
+ return characters.length === 1
54
+ ? characters
55
+ : characters.slice(1).map((character, index) => characters[index] + character);
56
+ }
57
+
58
+ /** Match unspaced clauses for hints without changing retrieval or snippet scoring. */
59
+ export function hintKeywordRelevance(query: string, content: string): number {
60
+ const needles = hintTerms(query);
61
+ if (needles.length === 0) return 0;
62
+ const words = new Set(hintTerms(cleanSearchText(content)));
63
+ const runs = [...words].filter((word) => CJK_RUN.test(word));
64
+ const bigrams = new Set(runs.flatMap(characterBigrams));
65
+ let matched = 0;
66
+ for (const needle of needles) {
67
+ if (!CJK_RUN.test(needle)) {
68
+ matched += Number(words.has(needle));
69
+ continue;
70
+ }
71
+ const grams = characterBigrams(needle);
72
+ if ([...needle].length === 1) {
73
+ matched += Number(runs.some((run) => run.includes(needle)));
74
+ continue;
75
+ }
76
+ // Count each clause as one term so filler bigrams across word boundaries
77
+ // do not dilute it; require half coverage so shared function words alone do not pass.
78
+ matched += Math.min(
79
+ 1,
80
+ grams.filter((gram) => bigrams.has(gram)).length / Math.ceil(grams.length / 2),
81
+ );
82
+ }
83
+ return (0.8 * matched) / needles.length;
84
+ }
85
+
38
86
  /** Pick the densest matching passage before compression discards matched words. */
39
87
  export function matchedSearchSnippet(content: string, query: string, cap = 220): string {
40
88
  const text = cleanSearchText(content);
@@ -27,6 +27,8 @@ const READ_ONLY_DEFERRED_TRANSACTIONS: Record<string, string> = {
27
27
  "pins one read snapshot so the session's rowid coverage proof and the filtered message search see the same rows; never writes (read-only connections use it too)",
28
28
  "packages/plugin/src/hooks/magic-context/lkg-persist.ts#loadPersistedLkgSlot":
29
29
  "reads an LKG slot row and its prefix slices as one consistent snapshot even while another connection saves the slot; never writes (a failed verification clear opens its own transaction)",
30
+ "packages/plugin/src/hooks/magic-context/compaction-marker-manager.ts#reconcileForkOrphanedCompactionMarkers":
31
+ "reads publication intent and finalized ownership as one context.db snapshot while host-order probes use the already-open host connection; never writes context.db",
30
32
  };
31
33
 
32
34
  const REPOSITORY_ROOT = resolve(import.meta.dir, "../../../..");
@@ -75,6 +75,8 @@ import type { Transcript, TranscriptMessage, TranscriptPart } from "./transcript
75
75
  export { TEXT_TAG_IDENTITY_MARKER };
76
76
 
77
77
  export interface TagTranscriptOptions {
78
+ /** Independently validated text sources; does not authorize tool accounting reuse. */
79
+ reuseTextMessageIds?: ReadonlySet<string>;
78
80
  /**
79
81
  * When true, skip injecting `§N§` prefix into visible text. Tags
80
82
  * still get assigned in the DB so historian/drops can reference
@@ -305,6 +307,12 @@ interface ToolAggregate {
305
307
  maxByteSize: number;
306
308
  /** Token count paired with maxByteSize (the same output occurrence). */
307
309
  maxTokenCount: number;
310
+ /**
311
+ * Whether maxTokenCount is a real count. Legacy rows can store bytes with a
312
+ * NULL token_count; seeding 0 from that must not look like a stored count,
313
+ * or unchanged results would never be recounted.
314
+ */
315
+ tokenCountKnown: boolean;
308
316
  /** Tool name from the first occurrence we see one on. */
309
317
  toolName: string | null;
310
318
  /** Input byte size from the invocation occurrence (for storage projection). */
@@ -417,7 +425,10 @@ export function tagTranscript(
417
425
  targets,
418
426
  skipPrefixInjection,
419
427
  entryFingerprint: options.entryFingerprintByMessageId?.get(messageId) ?? null,
420
- reuseIdentity: reuseIdentity || contentDerivedTextIds !== undefined,
428
+ reuseIdentity:
429
+ reuseIdentity ||
430
+ options.reuseTextMessageIds?.has(messageId) === true ||
431
+ contentDerivedTextIds !== undefined,
421
432
  timing,
422
433
  textIdentitySourceCache: options.textIdentitySourceCache,
423
434
  textTokenCache: options.textTokenCache,
@@ -508,6 +519,13 @@ export function tagTranscript(
508
519
  timing,
509
520
  options.toolTokenCache,
510
521
  tokenCacheKey,
522
+ {
523
+ byteSize: existing.maxByteSize,
524
+ tokenCount: existing.tokenCountKnown
525
+ ? existing.maxTokenCount
526
+ : null,
527
+ inputTokenCount: existing.inputTokenCount,
528
+ },
511
529
  );
512
530
  text = accounting.text;
513
531
  if (part.kind === "tool_result") {
@@ -596,6 +614,7 @@ export function tagTranscript(
596
614
  // genuinely larger folded result can still bump accounting.
597
615
  maxByteSize: reusableAccounting.byteSize,
598
616
  maxTokenCount: reusableAccounting.tokenCount ?? 0,
617
+ tokenCountKnown: reusableAccounting.tokenCount !== null,
599
618
  toolName: null,
600
619
  inputByteSize: reusableAccounting.inputByteSize,
601
620
  inputTokenCount: reusableAccounting.inputTokenCount,
@@ -623,6 +642,7 @@ export function tagTranscript(
623
642
  timing,
624
643
  options.toolTokenCache,
625
644
  tokenCacheKey,
645
+ tagger.getToolTagAccounting(sessionId, callId, messageId),
626
646
  );
627
647
  text = accounting.text;
628
648
  const outputByteSize = part.kind === "tool_result" ? accounting.byteSize : 0;
@@ -657,6 +677,11 @@ export function tagTranscript(
657
677
  occurrences: [{ message, part, kind: part.kind }],
658
678
  maxByteSize: persistedAccounting?.byteSize ?? outputByteSize,
659
679
  maxTokenCount: persistedAccounting?.tokenCount ?? outputTokenCount,
680
+ // A fresh allocation just wrote outputTokenCount (0 for an
681
+ // invocation); an existing row keeps its NULL as unknown.
682
+ tokenCountKnown: persistedAccounting
683
+ ? persistedAccounting.tokenCount !== null
684
+ : true,
660
685
  toolName: accounting.toolName,
661
686
  inputByteSize:
662
687
  persistedAccounting?.inputByteSize ??
@@ -745,7 +770,9 @@ function syncToolAggregateAccounting(
745
770
  ): void {
746
771
  tagger.setToolTagAccounting(sessionId, aggregate.tagId, {
747
772
  byteSize: aggregate.maxByteSize,
748
- tokenCount: aggregate.maxTokenCount,
773
+ // Unknown stays unknown so the next occurrence/pass recounts instead of
774
+ // caching a fabricated 0 for a legacy row.
775
+ tokenCount: aggregate.tokenCountKnown ? aggregate.maxTokenCount : null,
749
776
  inputByteSize: aggregate.inputByteSize,
750
777
  inputTokenCount: aggregate.inputTokenCount,
751
778
  });
@@ -770,6 +797,7 @@ function applyGrownToolResultAccounting(args: GrownToolResultAccountingArgs): vo
770
797
  }
771
798
  args.aggregate.maxByteSize = args.byteSize;
772
799
  args.aggregate.maxTokenCount = tokenCount;
800
+ args.aggregate.tokenCountKnown = true;
773
801
  updateTagByteSize(args.db, args.sessionId, args.aggregate.tagId, args.byteSize);
774
802
  updateTagTokenCount(args.db, args.sessionId, args.aggregate.tagId, tokenCount);
775
803
  syncToolAggregateAccounting(args.tagger, args.sessionId, args.aggregate);
@@ -780,12 +808,22 @@ function readAggregateToolAccounting(
780
808
  timing: TagTranscriptTiming | undefined,
781
809
  tokenCache?: Map<string, { text: string; tokenCount: number }>,
782
810
  tokenCacheKey?: string,
811
+ persisted?: { byteSize: number; tokenCount: number | null; inputTokenCount: number | null },
783
812
  ): AggregateToolAccounting {
784
813
  const text = part.getText() ?? "";
785
814
  const byteSize = getToolPartByteSize(part, text);
786
- const metadata = part.getToolMetadata();
815
+ // Persisted tool-result accounting tracks the largest byte size seen. Skip
816
+ // output tokenization when that size has not grown and a count is stored;
817
+ // known input counts also need no tokenization. Growth and missing counts still take
818
+ // the original counting path, even when the identity was not reusable.
819
+ const metadata = part.getToolMetadata({
820
+ skipInputTokenCount: persisted?.inputTokenCount != null,
821
+ });
787
822
  let tokenCount = 0;
788
- if (part.kind === "tool_result") {
823
+ if (
824
+ part.kind === "tool_result" &&
825
+ (!persisted || persisted.tokenCount === null || byteSize > persisted.byteSize)
826
+ ) {
789
827
  const tokenStart = timing ? performance.now() : 0;
790
828
  tokenCount = getCachedToolPartTokenCount(part, text, tokenCache, tokenCacheKey);
791
829
  if (timing) timing.tokenCounting += performance.now() - tokenStart;
@@ -1414,6 +1452,11 @@ function buildAggregateTarget(
1414
1452
  },
1415
1453
  // Open invocations still belong to the current turn; only complete arcs reclaim.
1416
1454
  prepareDrop: () => !occurrences.some((occ) => occ.part.canRemove?.() === "defer"),
1455
+ prepareReplayDrop: () => {
1456
+ if (complete() && !requiresToolArcSkeleton) {
1457
+ for (const occ of occurrences) occ.part.canRemove?.();
1458
+ }
1459
+ },
1417
1460
  canDrop: () => complete() && !occurrences.some((occ) => occ.part.hasUserAnswer?.()),
1418
1461
  requiresToolArcSkeleton,
1419
1462
  // Non-mutating read of the invocation input (the tool_use occurrence
@@ -132,7 +132,7 @@ export interface TranscriptPart {
132
132
  *
133
133
  * For non-tool parts both byte fields are undefined/0.
134
134
  */
135
- getToolMetadata(): {
135
+ getToolMetadata(options?: { skipInputTokenCount?: boolean }): {
136
136
  toolName: string | undefined;
137
137
  inputByteSize: number;
138
138
  inputTokenCount: number;
@@ -14,11 +14,7 @@ const fencedSources: Array<{ path: string; sites: string[]; reporterCalls?: stri
14
14
  },
15
15
  {
16
16
  path: "packages/plugin/src/features/magic-context/message-index.ts",
17
- sites: [
18
- "message_index_incremental",
19
- "message_index_reconcile",
20
- "message_index_orphan_sweep",
21
- ],
17
+ sites: ["message_index_incremental", "message_index_reconcile"],
22
18
  },
23
19
  {
24
20
  path: "packages/plugin/src/features/magic-context/storage-meta-persisted.ts",
@@ -101,7 +97,6 @@ const newlyCoveredSites = [
101
97
  "opencode_soft_refresh_cache",
102
98
  "message_index_incremental",
103
99
  "message_index_reconcile",
104
- "message_index_orphan_sweep",
105
100
  "message_index_clear",
106
101
  "message_fts_rowid_backfill",
107
102
  "storage_meta_wrapup_expiry_cleanup",
package/tui.js ADDED
@@ -0,0 +1,4 @@
1
+ // mmc fork: root TUI entry mirror (T-003 B-tier), shape mirrors upstream's
2
+ // tui.js: the host's "./tui" export resolves here, re-exporting the bundled
3
+ // dist entry.
4
+ export { default } from "./dist/tui.js";
@@ -1,11 +0,0 @@
1
- import type { Database } from "../../shared/sqlite";
2
- import { type MessageHistoryOrphanSweepOptions, type MessageHistoryOrphanSweepResult } from "./message-index";
3
- /**
4
- * Scope wrapper for the background sweep. It lives beside `message-index` rather
5
- * than inside it: importing the transform-pass scope helpers into
6
- * `message-index.ts` itself changes the bundled module graph in a way that hits
7
- * a Bun 1.4.2 packaging bug (a top-level `__promiseAll` init call is emitted
8
- * without its definition, which crashes node hosts at import time).
9
- */
10
- export declare function sweepOrphanedOpenCodeMessageIndexes(db: Database, openReadableOpenCodeDb: () => Database | null, options?: MessageHistoryOrphanSweepOptions): MessageHistoryOrphanSweepResult;
11
- //# sourceMappingURL=message-index-sweep.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"message-index-sweep.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/message-index-sweep.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EACH,KAAK,gCAAgC,EACrC,KAAK,+BAA+B,EAEvC,MAAM,iBAAiB,CAAC;AAEzB;;;;;;GAMG;AACH,wBAAgB,mCAAmC,CAC/C,EAAE,EAAE,QAAQ,EACZ,sBAAsB,EAAE,MAAM,QAAQ,GAAG,IAAI,EAC7C,OAAO,GAAE,gCAAqC,GAC/C,+BAA+B,CAMjC"}