@retaw106/mmc-oc 2.6.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 (41) hide show
  1. package/dist/{context-cfbjrr0n.js → context-9q17e52a.js} +140 -53
  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/search.d.ts +4 -0
  5. package/dist/features/magic-context/search.d.ts.map +1 -1
  6. package/dist/hooks/magic-context/auto-search-observation.d.ts +13 -0
  7. package/dist/hooks/magic-context/auto-search-observation.d.ts.map +1 -0
  8. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  9. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  10. package/dist/{index-1np16gxq.js → index-cgjyhwdk.js} +1 -1
  11. package/dist/{index-xk1dz6q9.js → index-xrxpbe74.js} +34 -1
  12. package/dist/index.js +14 -4
  13. package/dist/{recomp-orchestrator-51xyaexp.js → recomp-orchestrator-gzd1at06.js} +2 -2
  14. package/dist/shared/search-text.d.ts +2 -0
  15. package/dist/shared/search-text.d.ts.map +1 -1
  16. package/dist/tools/ctx-expand/render.d.ts +5 -3
  17. package/dist/tools/ctx-expand/render.d.ts.map +1 -1
  18. package/dist/tools/ctx-expand/tools.d.ts +2 -0
  19. package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
  20. package/dist/tools/parameter-descriptions.d.ts +6 -6
  21. package/dist/tools/parameter-descriptions.d.ts.map +1 -1
  22. package/dist/tui.js +2144 -0
  23. package/dist/v2/server.js +194 -54
  24. package/dist/v2/tui/client.d.ts +57 -0
  25. package/dist/v2/tui/client.d.ts.map +1 -0
  26. package/dist/v2/tui/context-rpc.d.ts +63 -0
  27. package/dist/v2/tui/context-rpc.d.ts.map +1 -0
  28. package/dist/v2/tui/entry.d.ts +8 -0
  29. package/dist/v2/tui/entry.d.ts.map +1 -0
  30. package/dist/v2/tui/notification-socket.d.ts +26 -0
  31. package/dist/v2/tui/notification-socket.d.ts.map +1 -0
  32. package/dist/v2/tui/session-directory.d.ts +22 -0
  33. package/dist/v2/tui/session-directory.d.ts.map +1 -0
  34. package/dist/v2/tui/types.d.ts +110 -0
  35. package/dist/v2/tui/types.d.ts.map +1 -0
  36. package/dist/{wrapup-orchestrator-g0mww1h4.js → wrapup-orchestrator-ghq4y406.js} +2 -2
  37. package/package.json +13 -3
  38. package/src/shared/prompt-surface-a1-golden.md +5 -5
  39. package/src/shared/search-text.test.ts +36 -1
  40. package/src/shared/search-text.ts +48 -0
  41. package/tui.js +4 -0
package/dist/v2/server.js CHANGED
@@ -25111,6 +25111,37 @@ function keywordRelevance(query, content) {
25111
25111
  const words = new Set(terms(cleanSearchText(content)));
25112
25112
  return 0.8 * needles.filter((term) => words.has(term)).length / needles.length;
25113
25113
  }
25114
+ function hintTerms(text) {
25115
+ return [
25116
+ ...new Set(terms(text).flatMap((term) => term.split(CJK_RUN_SPLIT)).filter((term) => term.length > 0 && !QUERY_STOP_WORDS.has(term)))
25117
+ ];
25118
+ }
25119
+ function characterBigrams(run) {
25120
+ const characters = [...run];
25121
+ return characters.length === 1 ? characters : characters.slice(1).map((character, index) => characters[index] + character);
25122
+ }
25123
+ function hintKeywordRelevance(query, content) {
25124
+ const needles = hintTerms(query);
25125
+ if (needles.length === 0)
25126
+ return 0;
25127
+ const words = new Set(hintTerms(cleanSearchText(content)));
25128
+ const runs = [...words].filter((word) => CJK_RUN.test(word));
25129
+ const bigrams = new Set(runs.flatMap(characterBigrams));
25130
+ let matched = 0;
25131
+ for (const needle of needles) {
25132
+ if (!CJK_RUN.test(needle)) {
25133
+ matched += Number(words.has(needle));
25134
+ continue;
25135
+ }
25136
+ const grams = characterBigrams(needle);
25137
+ if ([...needle].length === 1) {
25138
+ matched += Number(runs.some((run) => run.includes(needle)));
25139
+ continue;
25140
+ }
25141
+ matched += Math.min(1, grams.filter((gram) => bigrams.has(gram)).length / Math.ceil(grams.length / 2));
25142
+ }
25143
+ return 0.8 * matched / needles.length;
25144
+ }
25114
25145
  function matchedSearchSnippet(content, query, cap = 220) {
25115
25146
  const text = cleanSearchText(content);
25116
25147
  const needles = new Set(terms(query));
@@ -25135,9 +25166,11 @@ function matchedSearchSnippet(content, query, cap = 220) {
25135
25166
  const passage = text.slice(bestStart, bestStart + cap).trim();
25136
25167
  return `${bestStart > 0 ? "…" : ""}${passage}${bestStart + cap < text.length ? "…" : ""}`;
25137
25168
  }
25138
- var QUERY_STOP_WORDS;
25169
+ var QUERY_STOP_WORDS, CJK_RUN, CJK_RUN_SPLIT;
25139
25170
  var init_search_text = __esm(() => {
25140
25171
  QUERY_STOP_WORDS = new Set("a an and are as at be been by can could did do does for from how i in is it me my of on or our please should that the their them there these they this to us was we were what when where which who why will with would you your".split(" "));
25172
+ CJK_RUN = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+$/u;
25173
+ CJK_RUN_SPLIT = /([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+)/u;
25141
25174
  });
25142
25175
 
25143
25176
  // src/features/magic-context/compression-depth-storage.ts
@@ -50979,6 +51012,42 @@ var init_cache_ttl_seed = __esm(async () => {
50979
51012
  await init_storage_meta();
50980
51013
  });
50981
51014
 
51015
+ // src/features/magic-context/auto-search-diagnostics.ts
51016
+ function withMemoryHintContent(result, fullContent) {
51017
+ memoryHintContent.set(result, fullContent);
51018
+ return result;
51019
+ }
51020
+ function createAutoSearchDiagnostics(threshold) {
51021
+ return {
51022
+ threshold,
51023
+ lanes: {},
51024
+ pool: "unknown",
51025
+ poolRows: null,
51026
+ peek: "not-run",
51027
+ workerStarted: false
51028
+ };
51029
+ }
51030
+ function autoSearchRelevance(query, result) {
51031
+ const content = result.source === "memory" ? memoryHintContent.get(result) ?? result.content : result.content;
51032
+ if (cleanSearchText(content).length === 0)
51033
+ return 0;
51034
+ return result.source === "message" || "matchType" in result ? hintKeywordRelevance(query, content) : result.score;
51035
+ }
51036
+ function recordAutoSearchLane(diagnostics, lane, query, results) {
51037
+ if (!diagnostics)
51038
+ return;
51039
+ const scores = results.map((result) => result.relevanceScore ?? autoSearchRelevance(query, result));
51040
+ diagnostics.lanes[lane] = {
51041
+ top: scores.length ? Math.max(...scores) : null,
51042
+ nearMisses: scores.filter((score) => score >= 0.5 && score < diagnostics.threshold).length
51043
+ };
51044
+ }
51045
+ var memoryHintContent;
51046
+ var init_auto_search_diagnostics = __esm(() => {
51047
+ init_search_text();
51048
+ memoryHintContent = new WeakMap;
51049
+ });
51050
+
50982
51051
  // src/features/magic-context/literal-probes.ts
50983
51052
  function looksLikeSha(token) {
50984
51053
  return /[0-9]/.test(token) && /^[0-9a-f]{7,40}$/i.test(token);
@@ -51434,7 +51503,7 @@ function mergeMemoryResults(args) {
51434
51503
  suppressedVisibleIds.push(id);
51435
51504
  continue;
51436
51505
  }
51437
- results.push({
51506
+ results.push(withMemoryHintContent({
51438
51507
  source: "memory",
51439
51508
  content: previewText(memory.content),
51440
51509
  score,
@@ -51442,7 +51511,7 @@ function mergeMemoryResults(args) {
51442
51511
  category: memory.category,
51443
51512
  matchType: "fts",
51444
51513
  sourceName: args.sourceNameByMemoryId?.get(memory.id)
51445
- });
51514
+ }, memory.content));
51446
51515
  }
51447
51516
  return {
51448
51517
  results: results.sort((left, right) => {
@@ -52034,8 +52103,12 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
52034
52103
  options.diagnostics.suppressedLiveMessageMatches = outcome.suppressedLiveMessageMatches;
52035
52104
  }
52036
52105
  messageResults = outcome.results.filter((result) => result.messageId !== options.excludeMessageId);
52106
+ recordAutoSearchLane(options.autoSearchDiagnostics, "messages", trimmedQuery, messageResults);
52107
+ options.onAutoSearchDiagnostics?.();
52037
52108
  }
52038
52109
  const workspace = resolveSearchWorkspaceContext(db, projectPath);
52110
+ if (options.autoSearchDiagnostics)
52111
+ options.autoSearchDiagnostics.pool = "off";
52039
52112
  const [memoryOutcome, primerResults, noteResults] = await Promise.all([
52040
52113
  runMemory ? searchMemories({
52041
52114
  db,
@@ -52070,6 +52143,10 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
52070
52143
  if (options.diagnostics) {
52071
52144
  options.diagnostics.suppressedVisibleMemoryIds = memoryOutcome.suppressedVisibleIds;
52072
52145
  }
52146
+ recordAutoSearchLane(options.autoSearchDiagnostics, "memory", trimmedQuery, memoryOutcome.results);
52147
+ recordAutoSearchLane(options.autoSearchDiagnostics, "primers", trimmedQuery, primerResults);
52148
+ recordAutoSearchLane(options.autoSearchDiagnostics, "notes", trimmedQuery, noteResults);
52149
+ options.onAutoSearchDiagnostics?.();
52073
52150
  const results = [...memoryOutcome.results, ...primerResults, ...messageResults, ...noteResults].sort(compareUnifiedResults).slice(0, limit);
52074
52151
  const countRetrievals = options.countRetrievals ?? true;
52075
52152
  if (countRetrievals) {
@@ -52091,7 +52168,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
52091
52168
  if (options.autoHint) {
52092
52169
  return results.map((result) => ({
52093
52170
  ...result,
52094
- relevanceScore: cleanSearchText(result.content).length === 0 ? 0 : keywordRelevance(trimmedQuery, result.content)
52171
+ relevanceScore: autoSearchRelevance(trimmedQuery, result)
52095
52172
  }));
52096
52173
  }
52097
52174
  return results;
@@ -52199,6 +52276,7 @@ var DEFAULT_UNIFIED_SEARCH_LIMIT = 10, MAX_UNIFIED_SEARCH_LIMIT = 25, FTS_SEMANT
52199
52276
  var init_search = __esm(async () => {
52200
52277
  init_logger();
52201
52278
  init_search_text();
52279
+ init_auto_search_diagnostics();
52202
52280
  init_literal_probes();
52203
52281
  init_message_fts_session_filter();
52204
52282
  init_storage_primers();
@@ -52800,6 +52878,37 @@ var init_auto_search_hint = __esm(() => {
52800
52878
  init_caveman();
52801
52879
  });
52802
52880
 
52881
+ // src/hooks/magic-context/auto-search-observation.ts
52882
+ function logDisabledAutoSearch(db, sessionId, messageId, threshold, startedAt) {
52883
+ const sessions = disabledTurns.get(db) ?? new Map;
52884
+ disabledTurns.set(db, sessions);
52885
+ if (sessions.get(sessionId) === messageId)
52886
+ return;
52887
+ sessions.set(sessionId, messageId);
52888
+ observeAutoSearch(sessionId, threshold, startedAt).finish("disabled");
52889
+ }
52890
+ function observeAutoSearch(sessionId, threshold, startedAt) {
52891
+ const diagnostics = createAutoSearchDiagnostics(threshold);
52892
+ let logged = false;
52893
+ return {
52894
+ diagnostics,
52895
+ finish(outcome, reason) {
52896
+ if (logged)
52897
+ return;
52898
+ logged = true;
52899
+ const lanes = Object.entries(diagnostics.lanes).map(([lane, value]) => `${lane}:${value.top?.toFixed(3) ?? "none"}`).join(",");
52900
+ const nearMisses = Object.values(diagnostics.lanes).reduce((sum, lane) => sum + lane.nearMisses, 0);
52901
+ sessionLog(sessionId, `auto-search: decision outcome=${outcome}${reason ? ` reason=${reason}` : ""} lanes=${lanes || "none"} nearMisses=${nearMisses} threshold=${threshold} pool=${diagnostics.pool} rows=${diagnostics.poolRows ?? "unknown"} workerStarted=${diagnostics.workerStarted ?? "unknown"} peek=${diagnostics.peek} elapsedMs=${Math.round(performance.now() - startedAt)}`);
52902
+ }
52903
+ };
52904
+ }
52905
+ var disabledTurns;
52906
+ var init_auto_search_observation = __esm(() => {
52907
+ init_auto_search_diagnostics();
52908
+ init_logger();
52909
+ disabledTurns = new WeakMap;
52910
+ });
52911
+
52803
52912
  // src/hooks/magic-context/transform-message-helpers.ts
52804
52913
  function isMeaningfulUserMessage(msg) {
52805
52914
  return msg.info.role === "user" && hasMeaningfulUserText(msg.parts);
@@ -52992,8 +53101,12 @@ async function coalesceAutoSearchTurn(db, sessionId, messageId, operation, repla
52992
53101
  async function runAutoSearchHint(args) {
52993
53102
  const startedAt = performance.now();
52994
53103
  const { sessionId, db, messages, options } = args;
52995
- if (!options.enabled)
53104
+ if (!options.enabled) {
53105
+ const tail = findLatestMeaningfulUserMessage(messages);
53106
+ if (tail && typeof tail.info.id === "string" && messages.at(-1) === tail)
53107
+ logDisabledAutoSearch(db, sessionId, tail.info.id, options.scoreThreshold, startedAt);
52996
53108
  return AUTO_SEARCH_OK;
53109
+ }
52997
53110
  const userMsg = findLatestMeaningfulUserMessage(messages);
52998
53111
  if (!userMsg || typeof userMsg.info.id !== "string")
52999
53112
  return AUTO_SEARCH_OK;
@@ -53036,6 +53149,7 @@ async function runAutoSearchHint(args) {
53036
53149
  } catch {}
53037
53150
  return AUTO_SEARCH_OK;
53038
53151
  }
53152
+ const observation = observeAutoSearch(sessionId, options.scoreThreshold, startedAt);
53039
53153
  const writeNoHintAndReconcile = (reason) => {
53040
53154
  let outcome;
53041
53155
  try {
@@ -53047,20 +53161,23 @@ async function runAutoSearchHint(args) {
53047
53161
  } catch (error) {
53048
53162
  if (isTransientSqliteError(error)) {
53049
53163
  rememberUnpublishedAutoSearchNoHint(sessionId, userMsgId, reason);
53164
+ observation.finish("error", "storage-busy");
53050
53165
  return { ok: false, kind: "storage-busy" };
53051
53166
  }
53052
53167
  throw error;
53053
53168
  }
53054
- if (!outcome.ok)
53169
+ if (!outcome.ok) {
53170
+ observation.finish("error", "cas-exhausted");
53055
53171
  return { ok: false, kind: "cas-exhaustion" };
53172
+ }
53056
53173
  if (outcome.kind === "already-present" && outcome.decision.decision === "hint") {
53057
53174
  appendReminderToUserMessageById(messages, userMsgId, outcome.decision.text);
53058
53175
  }
53176
+ observation.finish(outcome.decision.decision === "hint" ? "attached" : reason === "below-threshold" ? "below-threshold" : reason === "error" ? "error" : "no-candidates", reason);
53059
53177
  return AUTO_SEARCH_OK;
53060
53178
  };
53061
53179
  const rawPartsText = collectUserPromptParts(userMsg);
53062
53180
  if (hasStackedAugmentation(rawPartsText)) {
53063
- sessionLog(sessionId, "auto-search: skipping — user message already carries augmentation/hint");
53064
53181
  return writeNoHintAndReconcile("stacked");
53065
53182
  }
53066
53183
  const rawPrompt = extractUserPromptText(userMsg);
@@ -53076,6 +53193,7 @@ async function runAutoSearchHint(args) {
53076
53193
  if (checkDeadline())
53077
53194
  return null;
53078
53195
  const searchOptions = {
53196
+ autoSearchDiagnostics: observation.diagnostics,
53079
53197
  limit: 10,
53080
53198
  autoHint: true,
53081
53199
  maxMessageOrdinal: Math.max(0, getLastCompartmentEndMessage(db, sessionId)),
@@ -53089,16 +53207,15 @@ async function runAutoSearchHint(args) {
53089
53207
  countRetrievals: false
53090
53208
  });
53091
53209
  }, startedAt);
53092
- } catch (error) {
53210
+ } catch {
53093
53211
  if (lifecycleSignal.aborted)
53094
53212
  return AUTO_SEARCH_OK;
53095
- log(`[auto-search] unified search failed for session ${sessionId} (no-hint frozen for this turn): ${error instanceof Error ? error.message : String(error)}`);
53096
53213
  const outcome = writeNoHintAndReconcile("error");
53097
53214
  return outcome.ok ? { ok: false, kind: "search-failure" } : outcome;
53098
53215
  }
53099
53216
  if (results === null) {
53100
53217
  persistAutoSearchSkip(db, sessionId, userMsgId, "timeout");
53101
- sessionLog(sessionId, `auto-search: timed out after ${AUTO_SEARCH_TIMEOUT_MS}ms, skipping hint for this turn (frozen)`);
53218
+ observation.finish("timeout");
53102
53219
  return { ok: false, kind: "timeout" };
53103
53220
  }
53104
53221
  if (results.length === 0) {
@@ -53106,7 +53223,6 @@ async function runAutoSearchHint(args) {
53106
53223
  }
53107
53224
  results = selectAutoSearchResults(results, options.scoreThreshold);
53108
53225
  if (results.length === 0) {
53109
- sessionLog(sessionId, `auto-search: no fragment meets relevance threshold ${options.scoreThreshold}`);
53110
53226
  return writeNoHintAndReconcile("below-threshold");
53111
53227
  }
53112
53228
  const hintText = buildAutoSearchHint(results, {
@@ -53125,21 +53241,21 @@ ${hintText}`;
53125
53241
  text: payload
53126
53242
  });
53127
53243
  if (!outcome.ok) {
53128
- sessionLog(sessionId, `auto-search: CAS exhausted for ${userMsgId}; skipping wire append`);
53244
+ observation.finish("error", "cas-exhausted");
53129
53245
  return { ok: false, kind: "cas-exhaustion" };
53130
53246
  }
53131
53247
  if (outcome.decision.decision === "hint") {
53132
53248
  appendReminderToUserMessageById(messages, userMsgId, outcome.decision.text);
53133
53249
  }
53134
- sessionLog(sessionId, `auto-search: attached hint to ${userMsgId} (${results.length} fragments, top score ${results[0].score.toFixed(3)})`);
53250
+ observation.finish(outcome.decision.decision === "hint" ? "attached" : "no-candidates");
53135
53251
  return AUTO_SEARCH_OK;
53136
53252
  }, replayServedDecision);
53137
53253
  }
53138
53254
  var AUTO_SEARCH_OK, pendingTurns;
53139
53255
  var init_auto_search_runner = __esm(async () => {
53140
53256
  init_auto_search_lifecycle();
53141
- init_logger();
53142
53257
  init_auto_search_hint();
53258
+ init_auto_search_observation();
53143
53259
  init_read_session_formatting();
53144
53260
  init_transform_message_helpers();
53145
53261
  await __promiseAll([
@@ -65487,9 +65603,9 @@ var init_parameter_descriptions = __esm(() => {
65487
65603
  },
65488
65604
  mmc_memory: {
65489
65605
  action: "write | update | archive | merge | get",
65490
- content: "The memory text — one standalone fact (write, update, merge).",
65491
- category: "Kind of fact (required for write; on update/merge, pass the memory's current category to keep it).",
65492
- ids: "Memory ids from <project-memory>: one for update, one or more for archive, two or more for merge, 1–20 for get.",
65606
+ content: "The memory text — one standalone fact (write, update, merge). Archive and get take an empty string.",
65607
+ category: "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.",
65608
+ ids: "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.",
65493
65609
  reason: "Why it is being archived (optional)."
65494
65610
  },
65495
65611
  mmc_search: {
@@ -65521,9 +65637,9 @@ var init_parameter_descriptions = __esm(() => {
65521
65637
  },
65522
65638
  mmc_memory: {
65523
65639
  action: "write | update | archive | merge | get",
65524
- content: "One standalone fact (write, update, merge).",
65525
- category: "Kind of fact (required for write; on update/merge pass the current category to keep it).",
65526
- ids: "Ids from <project-memory>: one for update, 1+ for archive, 2+ for merge, 1–20 for get.",
65640
+ content: "One standalone fact (write, update, merge). Archive and get: empty string.",
65641
+ category: "Kind of fact (write; on update/merge pass the current category to keep it). Archive and get ignore it.",
65642
+ ids: "Ids from <project-memory>: one for update, 1+ for archive, 2+ for merge, 1–20 for get. Write: empty list.",
65527
65643
  reason: "Why it is archived (optional)."
65528
65644
  },
65529
65645
  mmc_search: {
@@ -82244,6 +82360,17 @@ var init_mode = () => {};
82244
82360
 
82245
82361
  // src/tools/ctx-expand/render.ts
82246
82362
  import { createHash as createHash24 } from "node:crypto";
82363
+ function recoveryParts(sessionId, message, attachments) {
82364
+ return attachments ? [...message.parts, ...attachments(sessionId, message.id)] : message.parts;
82365
+ }
82366
+ function attachmentNotice(part) {
82367
+ const field = (value) => typeof value === "string" ? value.trim().replace(/[\r\n]+/g, " ") : "";
82368
+ const source = isRecord7(part.source) ? part.source : {};
82369
+ const location = [part.url, part.path, source.uri, source.url, source.path].map(field).find((value) => value && !/^(data|blob):/i.test(value));
82370
+ const name = field(part.filename) || field(part.name);
82371
+ const mime = field(part.mime) || field(part.mimeType) || field(part.mediaType);
82372
+ return `[file]${name ? ` ${name}` : ""}${mime ? ` (${mime})` : ""}` + `${location ? ` — stored location: ${location}` : ""}` + "; content not recovered. If needed, ask the user to attach it again.";
82373
+ }
82247
82374
  function isRecord7(value) {
82248
82375
  return value !== null && typeof value === "object" && !Array.isArray(value);
82249
82376
  }
@@ -82350,8 +82477,8 @@ function renderPartPreview(part, expandTools) {
82350
82477
  if (reasoning !== null)
82351
82478
  return ` • [reasoning] ${truncate3(reasoning, 120)}`;
82352
82479
  const type = typeof part.type === "string" ? part.type : "part";
82353
- if (type === "file")
82354
- return " • [file]";
82480
+ if (type === "file" || type === "image")
82481
+ return ` • ${attachmentNotice(part)}`;
82355
82482
  if (type === "step-start" || type === "step-finish")
82356
82483
  return null;
82357
82484
  return ` • [${type}]`;
@@ -82381,13 +82508,11 @@ ${tool.output}`);
82381
82508
  `);
82382
82509
  }
82383
82510
  const type = typeof part.type === "string" ? part.type : "part";
82384
- if (type === "file") {
82385
- const name = typeof part.filename === "string" && part.filename || typeof part.url === "string" && part.url || "";
82386
- return ` [file]${name ? ` ${name}` : ""}`;
82387
- }
82511
+ if (type === "file" || type === "image")
82512
+ return ` ${attachmentNotice(part)}`;
82388
82513
  return null;
82389
82514
  }
82390
- function renderMessageByOrdinal(sessionId, ordinal) {
82515
+ function renderMessageByOrdinal(sessionId, ordinal, attachments) {
82391
82516
  const found = [];
82392
82517
  visitRawSessionMessages(sessionId, ordinal, ordinal, (m) => {
82393
82518
  if (m.ordinal === ordinal)
@@ -82398,7 +82523,7 @@ function renderMessageByOrdinal(sessionId, ordinal) {
82398
82523
  if (!msg) {
82399
82524
  return `No message at ordinal ${ordinal} is currently in this session's stored history — ` + `it may have been removed by a session prune/revert, or the ordinal may be wrong. ` + `Re-run the tool if you still need the data.`;
82400
82525
  }
82401
- const rendered = msg.parts.map(renderPartFull).filter((l) => l !== null);
82526
+ const rendered = recoveryParts(sessionId, msg, attachments).map(renderPartFull).filter((l) => l !== null);
82402
82527
  const lines = [`[${msg.ordinal}] ${roleLabel(msg.role)} — full recovery:`, ""];
82403
82528
  if (rendered.length === 0) {
82404
82529
  lines.push(" (no recoverable content — message had only structural/reasoning parts)");
@@ -82408,14 +82533,14 @@ function renderMessageByOrdinal(sessionId, ordinal) {
82408
82533
  return lines.join(`
82409
82534
  `);
82410
82535
  }
82411
- function renderVerboseRange(sessionId, start, end, tokenBudget, expandTools) {
82536
+ function renderVerboseRange(sessionId, start, end, tokenBudget, expandTools, attachments) {
82412
82537
  const out = [];
82413
82538
  let usedTokens = 0;
82414
82539
  let lastOrdinal = start - 1;
82415
82540
  let truncated = false;
82416
82541
  visitRawSessionMessages(sessionId, start, end, (msg) => {
82417
82542
  const header = `[${msg.ordinal}] ${verboseRoleLabel(msg)}`;
82418
- const partLines = msg.parts.map((part) => renderPartPreview(part, expandTools)).filter((l) => l !== null);
82543
+ const partLines = recoveryParts(sessionId, msg, attachments).map((part) => renderPartPreview(part, expandTools)).filter((l) => l !== null);
82419
82544
  const block = partLines.length > 0 ? `${header}
82420
82545
  ${partLines.join(`
82421
82546
  `)}` : header;
@@ -82433,7 +82558,23 @@ ${partLines.join(`
82433
82558
 
82434
82559
  `), lastOrdinal, truncated };
82435
82560
  }
82436
- function renderItemByTag(db, sessionId, number, textIndexDomain = "part") {
82561
+ function renderVerboseRangeForTool(sessionId, start, end, tokenBudget, expandTools, attachments) {
82562
+ const v = renderVerboseRange(sessionId, start, end, tokenBudget, expandTools, attachments);
82563
+ if (!v.text) {
82564
+ return `No messages found in range ${start}-${end}. The range may be outside this session's history.`;
82565
+ }
82566
+ const out = [
82567
+ `Messages ${start}-${v.lastOrdinal} (verbose). Recover any one in full with mmc_expand(message=<ordinal>):`,
82568
+ "",
82569
+ v.text
82570
+ ];
82571
+ if (v.truncated) {
82572
+ out.push("", `Truncated at message ${v.lastOrdinal} (budget: ~${tokenBudget} tokens). Call again with start=${v.lastOrdinal + 1} end=${end} verbose=true for more.`);
82573
+ }
82574
+ return out.join(`
82575
+ `);
82576
+ }
82577
+ function renderItemByTag(db, sessionId, number, textIndexDomain = "part", attachments) {
82437
82578
  const tag = getTagById(db, sessionId, number);
82438
82579
  if (!tag)
82439
82580
  return `no tag ${number} in this session; if ${number} came from a <session-history> heading or a ctx_search hit, it is an ordinal: use message=${number}`;
@@ -82458,17 +82599,19 @@ function renderItemByTag(db, sessionId, number, textIndexDomain = "part") {
82458
82599
  return rendered.length ? rendered.join(`
82459
82600
  `) : `Tag ${number}'s original tool call is no longer in stored history.`;
82460
82601
  }
82461
- const scoped = /^(.*):p(\d+)$/.exec(tag.messageId);
82602
+ const scoped = /^(.*):(?:p|file)(\d+)$/.exec(tag.messageId);
82462
82603
  const derived = /^(.*):mc-text-v1:([a-f0-9]+):([a-f0-9]+):o(\d+)$/.exec(tag.messageId);
82463
82604
  const owner = scoped?.[1] ?? derived?.[1] ?? tag.messageId;
82464
82605
  const message = readRawSessionMessageById(sessionId, owner);
82465
82606
  if (!message)
82466
82607
  return `Tag ${number}'s original text is no longer in stored history.`;
82467
82608
  const index = scoped ? Number(scoped[2]) : 0;
82468
- const piText = message.parts.filter((part) => isRecord7(part) && part.type === "text");
82469
- const part = message.parts[index];
82609
+ const attached = attachments?.(sessionId, message.id) ?? [];
82610
+ const parts = [...message.parts, ...attached];
82611
+ const piText = parts.filter((part) => isRecord7(part) && part.type === "text");
82612
+ const part = tag.type === "file" ? attached.find((part) => isRecord7(part) && part.partIndex === index) ?? message.parts[index] : parts[index];
82470
82613
  const matching = derived ? piText.filter((part) => isRecord7(part) && typeof part.text === "string" && createHash24("sha256").update(part.text).digest("hex") === derived[3]) : [];
82471
- const selected = derived ? matching[Number(derived[4])] : textIndexDomain === "text" ? piText[index] : part;
82614
+ const selected = derived ? matching[Number(derived[4])] : textIndexDomain === "text" && tag.type !== "file" ? piText[index] : part;
82472
82615
  if (!selected)
82473
82616
  return `Tag ${number}'s original text part is no longer in stored history.`;
82474
82617
  const renderedPart = renderPartFull(selected);
@@ -82561,10 +82704,10 @@ function createCtxExpandTool(deps) {
82561
82704
  return mode.message;
82562
82705
  }
82563
82706
  if (mode.kind === "tag") {
82564
- return renderItemByTag(deps.db, sessionId, mode.tag);
82707
+ return renderItemByTag(deps.db, sessionId, mode.tag, "part", deps.recoveryAttachments);
82565
82708
  }
82566
82709
  if (mode.kind === "message") {
82567
- return renderSearchArchive(deps.db, sessionId, mode.message, mode.message, CTX_EXPAND_TOKEN_BUDGET) ?? renderMessageByOrdinal(sessionId, mode.message);
82710
+ return renderSearchArchive(deps.db, sessionId, mode.message, mode.message, CTX_EXPAND_TOKEN_BUDGET) ?? renderMessageByOrdinal(sessionId, mode.message, deps.recoveryAttachments);
82568
82711
  }
82569
82712
  const { start, end, verbose } = mode;
82570
82713
  const lastCompartmentEnd = getLastCompartmentEndMessage(deps.db, sessionId);
@@ -82576,20 +82719,7 @@ function createCtxExpandTool(deps) {
82576
82719
  if (archived)
82577
82720
  return archived;
82578
82721
  if (verbose) {
82579
- const v = renderVerboseRange(sessionId, start, effectiveEnd, CTX_EXPAND_TOKEN_BUDGET, deps.expandTools);
82580
- if (!v.text) {
82581
- return `No messages found in range ${start}-${effectiveEnd}. The range may be outside this session's history.`;
82582
- }
82583
- const out = [
82584
- `Messages ${start}-${v.lastOrdinal} (verbose). Recover any one in full with mmc_expand(message=<ordinal>):`,
82585
- "",
82586
- v.text
82587
- ];
82588
- if (v.truncated) {
82589
- out.push("", `Truncated at message ${v.lastOrdinal} (budget: ~${CTX_EXPAND_TOKEN_BUDGET} tokens). Call again with start=${v.lastOrdinal + 1} end=${effectiveEnd} verbose=true for more.`);
82590
- }
82591
- return out.join(`
82592
- `);
82722
+ return renderVerboseRangeForTool(sessionId, start, effectiveEnd, CTX_EXPAND_TOKEN_BUDGET, deps.expandTools, deps.recoveryAttachments);
82593
82723
  }
82594
82724
  const chunk = readSessionChunk(sessionId, CTX_EXPAND_TOKEN_BUDGET, start, effectiveEnd + 1, { expand: false });
82595
82725
  if (!chunk.text || chunk.messageCount === 0) {
@@ -85502,7 +85632,7 @@ var init_context = __esm(async () => {
85502
85632
  // package.json
85503
85633
  var package_default = {
85504
85634
  name: "@retaw106/mmc-oc",
85505
- version: "2.6.0",
85635
+ version: "2.7.1",
85506
85636
  type: "module",
85507
85637
  description: "OpenCode plugin for Magic Context — cross-session memory and context management",
85508
85638
  main: "dist/index.js",
@@ -85533,14 +85663,16 @@ var package_default = {
85533
85663
  files: [
85534
85664
  "dist",
85535
85665
  "index.js",
85666
+ "tui.js",
85536
85667
  "src/shared",
85537
85668
  "src/config",
85538
85669
  "src/agents",
85539
85670
  "README.md"
85540
85671
  ],
85541
85672
  scripts: {
85542
- build: "bun ../../scripts/check-bun.mjs && bun ../../scripts/clean-dist-chunks.mjs dist index.js config-init.js cli.js && rm -rf dist/web && bun build src/index.ts --outdir dist --target node --format esm --splitting --external @opencode-ai/plugin --external bun:sqlite --external node:sqlite && bun run build:v2 && bun run build:web && bun run build:config-init && bun run build:cli && tsc --emitDeclarationOnly",
85673
+ build: "bun ../../scripts/check-bun.mjs && bun ../../scripts/clean-dist-chunks.mjs dist index.js config-init.js cli.js && rm -rf dist/web && bun build src/index.ts --outdir dist --target node --format esm --splitting --external @opencode-ai/plugin --external bun:sqlite --external node:sqlite && bun run build:v2 && bun run build:tui && bun run build:web && bun run build:config-init && bun run build:cli && tsc --emitDeclarationOnly",
85543
85674
  "build:v2": "bun build src/v2/server.ts --outfile dist/v2/server.js --target node --format esm --external @opencode/plugin && bun test src/v2/server.test.ts",
85675
+ "build:tui": "bun build src/v2/tui/entry.ts --outfile dist/tui.js --target node --format esm",
85544
85676
  "build:web": "bun build src/web/main.ts --outfile dist/web/main.js --target node --format esm --external bun:sqlite --external node:sqlite && mkdir -p dist/web/ui && cp src/web/ui/* dist/web/ui/",
85545
85677
  "build:config-init": "bun build scripts/config-init-entry.ts --outfile dist/config-init.js --target node --format esm",
85546
85678
  "build:cli": "bun build scripts/cli.ts --outfile dist/cli.js --target node --format esm",
@@ -85582,10 +85714,18 @@ var package_default = {
85582
85714
  ".": {
85583
85715
  types: "./dist/index.d.ts",
85584
85716
  import: "./dist/index.js"
85717
+ },
85718
+ "./tui": {
85719
+ import: "./tui.js"
85585
85720
  }
85586
85721
  },
85587
85722
  "oc-plugin": [
85588
- "server"
85723
+ "server",
85724
+ "tui"
85725
+ ],
85726
+ opencode: [
85727
+ "server",
85728
+ "tui"
85589
85729
  ],
85590
85730
  peerDependencies: {
85591
85731
  "@opencode-ai/plugin": ">=1.15.0"
@@ -0,0 +1,57 @@
1
+ import type { StatusDetail } from "../../shared/rpc-types";
2
+ import { type CommandRpcResult, type StatusDetailResult } from "./context-rpc";
3
+ import { type SocketNotification } from "./notification-socket";
4
+ import type { V2KeymapLayer, V2TuiContext } from "./types";
5
+ /**
6
+ * Plain-text projection of the status snapshot for the host's native text
7
+ * dialog. This tier mounts no component dialog, so this is the only arm the
8
+ * `/mmc-status` dialog ever draws.
9
+ *
10
+ * Exported for test access.
11
+ */
12
+ export declare function statusText(detail: Pick<StatusDetail, "contextLimit" | "usagePercentage" | "inputTokens" | "compaction_enabled" | "historianRunning" | "compartmentCount" | "memoryBlockCount" | "memoryCount" | "pendingOpsCount" | "configGeneration" | "configAdoptedAt" | "configReloadFailure" | "lastTransformError">): string;
13
+ /**
14
+ * Plain-text status for one checked result: the status lines above, or the
15
+ * "status unavailable" view as text when there is no usable snapshot. A
16
+ * server/UI version difference is named first in both cases.
17
+ *
18
+ * Exported for test access.
19
+ */
20
+ export declare function statusTextFor(status: StatusDetailResult): string;
21
+ /** The command runner `buildKeymapLayer` hands each slash command. */
22
+ export type CommandRunner = (title: string, call: (sessionID: string) => Promise<CommandRpcResult>, pending?: string) => Promise<boolean>;
23
+ /**
24
+ * Every /mmc-* command this host can reach from the palette goes through this
25
+ * runner: OpenCode 2 has no `command.execute.before` hook, so the server-side
26
+ * command handler the OpenCode 1 plugin uses never runs here. Each call
27
+ * invokes the RPC handler that does the same server-side work and reports
28
+ * back through a dialog or toast, because this host has no ignored-message
29
+ * chat carrier.
30
+ *
31
+ * Exported (with the context injected) so the branches are unit-testable
32
+ * without a live socket.
33
+ */
34
+ export declare function createCommandRunner(context: V2TuiContext): CommandRunner;
35
+ /**
36
+ * The five fork slash commands, built with the context and dialog hooks
37
+ * injected so the command set is unit-testable without a live socket.
38
+ */
39
+ export declare function buildKeymapLayer(context: V2TuiContext, hooks: {
40
+ showStatus: (target?: string | null) => Promise<boolean>;
41
+ showRecomp: (target?: string | null) => Promise<boolean>;
42
+ runCommand: CommandRunner;
43
+ }): V2KeymapLayer;
44
+ /**
45
+ * The four server→TUI notification branches this tier consumes. Everything the
46
+ * sidebar tier handled (refresh-sidebar and any unknown action) returns false,
47
+ * which leaves the notification unacknowledged instead of falsely consumed.
48
+ *
49
+ * Exported (with the dialog hooks injected) so the routing is unit-testable
50
+ * without a live socket.
51
+ */
52
+ export declare function createNotificationHandler(context: V2TuiContext, hooks: {
53
+ showStatus: (target?: string | null) => Promise<boolean>;
54
+ showRecomp: (target?: string | null) => Promise<boolean>;
55
+ }): (notification: SocketNotification) => Promise<boolean>;
56
+ export declare function setup(context: V2TuiContext): Promise<() => void>;
57
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/v2/tui/client.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAG3D,OAAO,EACH,KAAK,gBAAgB,EAQrB,KAAK,kBAAkB,EAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACH,KAAK,kBAAkB,EAG1B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAQ3D;;;;;;GAMG;AACH,wBAAgB,UAAU,CACtB,MAAM,EAAE,IAAI,CACR,YAAY,EACV,cAAc,GACd,iBAAiB,GACjB,aAAa,GACb,oBAAoB,GACpB,kBAAkB,GAClB,kBAAkB,GAClB,kBAAkB,GAClB,aAAa,GACb,iBAAiB,GACjB,kBAAkB,GAClB,iBAAiB,GACjB,qBAAqB,GACrB,oBAAoB,CACzB,GACF,MAAM,CA6BR;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAoBhE;AAOD,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,CACxB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,EACtD,OAAO,CAAC,EAAE,MAAM,KACf,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,aAAa,CAyBxE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC5B,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE;IACH,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,UAAU,EAAE,aAAa,CAAC;CAC7B,GACF,aAAa,CAmFf;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACrC,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE;IACH,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5D,GACF,CAAC,YAAY,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC,CAqCxD;AAqID,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAetE"}
@@ -0,0 +1,63 @@
1
+ import { MagicContextRpcClient } from "../../shared/rpc-client";
2
+ import { type OpenCodeStatusExtras, type StatusCheck } from "../../shared/status-view-check";
3
+ /** Initialize the RPC client. Call once on TUI startup. */
4
+ export declare function initRpcClient(directory: string): void;
5
+ /**
6
+ * The server the notification socket should subscribe to: the one that owns
7
+ * the shown session's directory, so a push from the command that session runs
8
+ * (for example `/mmc-status` asking to open its dialog) reaches this TUI. That
9
+ * is the startup directory's server when no session is shown or the session is
10
+ * in the startup directory. A session directory with no discovery file falls
11
+ * back to the startup server so session-less notifications still arrive
12
+ * (upstream additionally asks every local server which one owns the session;
13
+ * this fork's rpc-client has no session-owner discovery, so that arm is not
14
+ * ported).
15
+ */
16
+ export declare function resolveNotificationTarget(sessionDirectory: string | null): Promise<{
17
+ client: MagicContextRpcClient;
18
+ directory: string;
19
+ endpoint: {
20
+ port: number;
21
+ token: string | null;
22
+ instanceId: string | null;
23
+ };
24
+ } | null>;
25
+ /** The directory whose server a notification subscription for `sessionDirectory` targets first. */
26
+ export declare function notificationDirectoryFor(sessionDirectory: string | null): string;
27
+ export declare function getRpcGeneration(): number;
28
+ /** Clean up the RPC client. */
29
+ export declare function closeRpc(): void;
30
+ export type StatusDetailResult = StatusCheck<OpenCodeStatusExtras>;
31
+ /**
32
+ * Fetch the status for the `/mmc-status` dialog. Every reply, including a
33
+ * transport failure, comes back as a checked result: either a snapshot the
34
+ * view model can draw, or the reason there is none. The dialog never receives
35
+ * an unchecked payload.
36
+ */
37
+ export declare function loadStatusDetail(sessionId: string, directory: string, modelKey?: string): Promise<StatusDetailResult>;
38
+ /**
39
+ * Send recomp request to server via RPC. `directory` is the session's
40
+ * directory, so the request reaches the server instance that owns the session.
41
+ */
42
+ export declare function requestRecomp(sessionId: string, directory?: string): Promise<boolean>;
43
+ /** What a command RPC reports back: finished text, an acknowledgement that
44
+ * background work started (no text yet), or the failure. */
45
+ export type CommandRpcResult = {
46
+ ok: true;
47
+ message?: string;
48
+ started?: boolean;
49
+ } | {
50
+ ok: false;
51
+ error?: string;
52
+ };
53
+ /** Run `/mmc-flush` for the session: apply the queued operations now. */
54
+ export declare function requestFlush(sessionId: string): Promise<CommandRpcResult>;
55
+ /** Start `/mmc-wrapup`: compact the older live tail, keeping the newest N raw. */
56
+ export declare function requestWrapup(sessionId: string, messagesToKeep: number): Promise<CommandRpcResult>;
57
+ /**
58
+ * Start one memory-maintenance pass for the session (fork-specific endpoint:
59
+ * the server command's default `status` subcommand is reachable through its
60
+ * own reply, so the TUI command always asks for a run).
61
+ */
62
+ export declare function requestMaintenance(sessionId: string): Promise<CommandRpcResult>;
63
+ //# sourceMappingURL=context-rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-rpc.d.ts","sourceRoot":"","sources":["../../../src/v2/tui/context-rpc.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAEH,KAAK,oBAAoB,EACzB,KAAK,WAAW,EAEnB,MAAM,gCAAgC,CAAC;AAsBxC,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CASrD;AA6BD;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC;IACtF,MAAM,EAAE,qBAAqB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CAC/E,GAAG,IAAI,CAAC,CAkBR;AAED,mGAAmG;AACnG,wBAAgB,wBAAwB,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAIhF;AAaD,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,+BAA+B;AAC/B,wBAAgB,QAAQ,IAAI,IAAI,CAQ/B;AAED,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC,oBAAoB,CAAC,CAAC;AAEnE;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAY7B;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAU3F;AAED;6DAC6D;AAC7D,MAAM,MAAM,gBAAgB,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACjD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAiBpC,yEAAyE;AACzE,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAE/E;AAED,kFAAkF;AAClF,wBAAsB,aAAa,CAC/B,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAE3B;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAErF"}