@cortexkit/opencode-magic-context 0.24.1 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +4 -2
  2. package/dist/agents/magic-context-prompt.d.ts.map +1 -1
  3. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +8 -0
  4. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
  5. package/dist/features/magic-context/memory/embedding-local.d.ts +4 -0
  6. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  7. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +6 -0
  8. package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
  9. package/dist/features/magic-context/project-embedding-registry.d.ts +38 -0
  10. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  11. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  12. package/dist/features/magic-context/storage-meta-session.d.ts +1 -0
  13. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  14. package/dist/features/magic-context/storage-meta-shared.d.ts +2 -1
  15. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  16. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  17. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  18. package/dist/features/magic-context/storage-tags.d.ts +10 -0
  19. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  20. package/dist/features/magic-context/storage.d.ts +2 -2
  21. package/dist/features/magic-context/storage.d.ts.map +1 -1
  22. package/dist/features/magic-context/types.d.ts +1 -0
  23. package/dist/features/magic-context/types.d.ts.map +1 -1
  24. package/dist/hooks/magic-context/apply-operations.d.ts +3 -25
  25. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  26. package/dist/hooks/magic-context/caveman-cleanup.d.ts +1 -0
  27. package/dist/hooks/magic-context/caveman-cleanup.d.ts.map +1 -1
  28. package/dist/hooks/magic-context/channel2-delivery.d.ts +2 -0
  29. package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/command-handler.d.ts +7 -5
  31. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  32. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +7 -2
  33. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/embed-session-state.d.ts +14 -0
  35. package/dist/hooks/magic-context/embed-session-state.d.ts.map +1 -0
  36. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/format-embed-status.d.ts +9 -0
  38. package/dist/hooks/magic-context/format-embed-status.d.ts.map +1 -0
  39. package/dist/hooks/magic-context/heuristic-cleanup.d.ts +1 -0
  40. package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
  41. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  43. package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts +1 -1
  45. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/strip-content.d.ts +0 -1
  47. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/tag-content-primitives.d.ts +2 -0
  49. package/dist/hooks/magic-context/tag-content-primitives.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  51. package/dist/hooks/magic-context/tool-drop-target.d.ts +1 -1
  52. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/tool-reclaim.d.ts +12 -0
  54. package/dist/hooks/magic-context/tool-reclaim.d.ts.map +1 -0
  55. package/dist/hooks/magic-context/transform-operations.d.ts +1 -1
  56. package/dist/hooks/magic-context/transform-operations.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  58. package/dist/hooks/magic-context/transform.d.ts +2 -0
  59. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +1103 -408
  62. package/dist/plugin/conflict-warning-hook.d.ts.map +1 -1
  63. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  64. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  65. package/dist/shared/announcement.d.ts +1 -1
  66. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  67. package/dist/shared/rpc-types.d.ts +20 -0
  68. package/dist/shared/rpc-types.d.ts.map +1 -1
  69. package/dist/shared/sqlite.d.ts +5 -1
  70. package/dist/shared/sqlite.d.ts.map +1 -1
  71. package/dist/tools/ctx-expand/constants.d.ts +1 -1
  72. package/dist/tools/ctx-expand/constants.d.ts.map +1 -1
  73. package/dist/tools/ctx-expand/render.d.ts +43 -0
  74. package/dist/tools/ctx-expand/render.d.ts.map +1 -0
  75. package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
  76. package/dist/tools/ctx-expand/types.d.ts +6 -2
  77. package/dist/tools/ctx-expand/types.d.ts.map +1 -1
  78. package/dist/tools/ctx-reduce/constants.d.ts +1 -1
  79. package/dist/tools/ctx-reduce/constants.d.ts.map +1 -1
  80. package/dist/tui/data/context-db.d.ts +4 -2
  81. package/dist/tui/data/context-db.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/src/shared/announcement.ts +6 -6
  84. package/src/shared/model-suggestion-retry.test.ts +61 -1
  85. package/src/shared/model-suggestion-retry.ts +22 -0
  86. package/src/shared/rpc-types.ts +11 -0
  87. package/src/shared/sqlite-bind-style.test.ts +82 -0
  88. package/src/shared/sqlite.ts +30 -1
  89. package/src/shared/tag-transcript.test.ts +3 -1
  90. package/src/shared/tag-transcript.ts +19 -17
  91. package/src/tui/data/context-db.ts +34 -2
  92. package/src/tui/index.tsx +58 -4
  93. package/src/tui/slots/sidebar-content.tsx +7 -2
@@ -611,7 +611,7 @@ function tagToolPart(args: TagToolPartArgs): void {
611
611
  args.part.setText(tagged);
612
612
  }
613
613
 
614
- args.targets.set(tagId, buildToolTarget(args.part, args.message));
614
+ args.targets.set(tagId, buildToolTarget(args.part, args.message, tagId));
615
615
  }
616
616
 
617
617
  function setToolContentOrText(part: TranscriptPart, content: string): boolean {
@@ -679,10 +679,11 @@ function buildAggregateTarget(tagId: number, occurrences: ToolOccurrence[]): Tag
679
679
  return any ? "removed" : "absent";
680
680
  },
681
681
  truncate(): "truncated" | "absent" {
682
- // Truncate BOTH halves. For tool_use, this typically truncates
683
- // the args; for tool_result, the output. The sentinel string
684
- // matches OpenCode's truncate sentinel exactly.
685
- const sentinel = "[truncated]";
682
+ // Skeleton-drop: replace BOTH halves' content with the one
683
+ // canonical `[dropped §N§]` placeholder (byte-identical to a full
684
+ // drop and to OpenCode). Frozen by the dropMode column → replays
685
+ // the same string every pass. The tool_use call survives intact.
686
+ const sentinel = `[dropped \u00a7${tagId}\u00a7]`;
686
687
  let any = false;
687
688
  for (const occ of occurrences) {
688
689
  if (setToolContentOrText(occ.part, sentinel)) {
@@ -739,16 +740,17 @@ function buildTextTarget(
739
740
  }
740
741
 
741
742
  /**
742
- * TagTarget for a tag-eligible tool part. Tool parts get full-drop
743
- * (replace with `[dropped §N§]`) or truncated-drop (replace with
744
- * `[truncated]`) treatment from `applyFlushedStatuses` based on the
745
- * stored `drop_mode` column. We expose both via the standard target
746
- * surface; replaceWithSentinel is the canonical mutation, with
747
- * truncated-drop using the "[truncated]" string.
743
+ * TagTarget for a tag-eligible tool part. Tool parts get full-drop or
744
+ * skeleton-drop treatment from `applyFlushedStatuses` based on the stored
745
+ * `drop_mode` column. Both render the SAME canonical `[dropped §N§]`
746
+ * placeholder full-drop replaces the whole pair, skeleton-drop keeps the
747
+ * tool_use call and replaces only its output. One placeholder string,
748
+ * byte-identical across passes and across harnesses.
748
749
  */
749
750
  function buildToolTarget(
750
751
  part: TranscriptPart,
751
752
  message: { info: { id?: string; role: string } },
753
+ tagId: number,
752
754
  ): TagTarget {
753
755
  return {
754
756
  setContent(content: string): boolean {
@@ -765,15 +767,15 @@ function buildToolTarget(
765
767
  // For Pi the current Transcript contract treats both
766
768
  // invocation and result parts symmetrically — both expose
767
769
  // setText / setToolOutput.
768
- const replaced = part.replaceWithSentinel(`[dropped \u00a7${part.id ?? "?"}\u00a7]`);
770
+ const replaced = part.replaceWithSentinel(`[dropped \u00a7${tagId}\u00a7]`);
769
771
  return replaced ? "removed" : "absent";
770
772
  },
771
773
  truncate(): "truncated" | "absent" {
772
- // Truncate the tool output to a fixed sentinel string. Done
773
- // via setToolOutput so the underlying tool_result content
774
- // gets the truncation; falls back to setText for cases
775
- // where the part type doesn't support setToolOutput.
776
- const ok = setToolContentOrText(part, "[truncated]");
774
+ // Skeleton-drop: replace the tool output with the one canonical
775
+ // `[dropped §N§]` placeholder (byte-identical to a full drop and to
776
+ // OpenCode). Frozen by the dropMode column, so it replays the same
777
+ // string every pass. The tool_use call itself survives intact.
778
+ const ok = setToolContentOrText(part, `[dropped \u00a7${tagId}\u00a7]`);
777
779
  return ok ? "truncated" : "absent";
778
780
  },
779
781
  message: {
@@ -5,9 +5,14 @@
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { MagicContextRpcClient } from "../../shared/rpc-client";
8
- import type { RpcNotificationMessage, SidebarSnapshot, StatusDetail } from "../../shared/rpc-types";
8
+ import type {
9
+ EmbedDetail,
10
+ RpcNotificationMessage,
11
+ SidebarSnapshot,
12
+ StatusDetail,
13
+ } from "../../shared/rpc-types";
9
14
 
10
- export type { SidebarSnapshot, StatusDetail };
15
+ export type { EmbedDetail, SidebarSnapshot, StatusDetail };
11
16
 
12
17
  let rpcClient: MagicContextRpcClient | null = null;
13
18
  let rpcGeneration = 0;
@@ -218,6 +223,33 @@ export async function loadStatusDetail(
218
223
  }
219
224
  }
220
225
 
226
+ const EMPTY_EMBED_DETAIL: EmbedDetail = {
227
+ enabled: false,
228
+ model: "off",
229
+ provider: "off",
230
+ session: { embedded: 0, total: 0 },
231
+ memories: { embedded: 0, total: 0 },
232
+ commits: { embedded: 0, total: 0, gitEnabled: false },
233
+ statusText: "Embedding is off (no provider configured).",
234
+ };
235
+
236
+ /** Fetch embedding coverage status for `/ctx-embed` via RPC. */
237
+ export async function loadEmbedDetail(sessionId: string, directory: string): Promise<EmbedDetail> {
238
+ if (!rpcClient) return EMPTY_EMBED_DETAIL;
239
+ try {
240
+ const result = await rpcClient.call<EmbedDetail>("embed-detail", {
241
+ sessionId,
242
+ directory,
243
+ });
244
+ if ((result as unknown as Record<string, unknown>).error) {
245
+ return EMPTY_EMBED_DETAIL;
246
+ }
247
+ return result;
248
+ } catch {
249
+ return EMPTY_EMBED_DETAIL;
250
+ }
251
+ }
252
+
221
253
  /** Get compartment count via RPC. */
222
254
  export async function getCompartmentCount(sessionId: string): Promise<number> {
223
255
  if (!rpcClient) return 0;
package/src/tui/index.tsx CHANGED
@@ -6,7 +6,7 @@ import { createMemo } from "solid-js"
6
6
  import type { TuiPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui"
7
7
  import { createSidebarContentSlot, kickRecompProgressRefresh } from "./slots/sidebar-content"
8
8
  import packageJson from "../../package.json"
9
- import { closeRpc, consumeTuiMessages, dismissUpgradeReminder, getAnnouncement, getCompartmentCount, getRpcGeneration, initRpcClient, loadStatusDetail, markAnnounced, markTuiMessagesHandled, requestRecomp, requestUpgrade, type TuiMessage, type StatusDetail } from "./data/context-db"
9
+ import { closeRpc, consumeTuiMessages, dismissUpgradeReminder, getAnnouncement, getCompartmentCount, getRpcGeneration, initRpcClient, loadEmbedDetail, loadStatusDetail, markAnnounced, markTuiMessagesHandled, requestRecomp, requestUpgrade, type EmbedDetail, type TuiMessage, type StatusDetail } from "./data/context-db"
10
10
  import { formatThresholdPercent } from "../shared/format-threshold"
11
11
  import { detectConflicts } from "../shared/conflict-detector"
12
12
  import { fixConflicts } from "../shared/conflict-fixer"
@@ -353,7 +353,7 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
353
353
  }
354
354
  if (p.phase === "migration") return <R t={t()} l="Status" v={p.note ?? "Migrating memories ⟳"} fg={t().warning} />
355
355
  if (p.phase === "done") return <R t={t()} l="Status" v={`✓ ${verb} complete`} fg={t().accent} />
356
- if (p.phase === "skipped") return <R t={t()} l="Status" v={`${verb} skipped — retry shortly${p.message ? `: ${p.message}` : ""}`} fg={t().textMuted} />
356
+ if (p.phase === "skipped") return <R t={t()} l="Status" v={p.message ?? `${verb} stopped early`} fg={t().textMuted} />
357
357
  return <R t={t()} l="Status" v={`✗ ${verb} failed${p.message ? `: ${p.message}` : ""}`} fg={t().error} />
358
358
  })()}
359
359
  </box>
@@ -580,14 +580,53 @@ async function showStatusDialog(api: TuiPluginApi, targetSessionId = getSessionI
580
580
  const directory = api.state.path.directory ?? ""
581
581
  const modelKey = getModelKeyFromMessages(api, sessionId)
582
582
  const detail = await loadStatusDetail(sessionId, directory, modelKey)
583
- // Ack only after the dialog is actually shown for the same active session;
584
- // route switches while the RPC detail load is in flight must leave it pending.
585
583
  if (getSessionId(api) !== sessionId) return false
586
584
 
587
585
  api.ui.dialog.replace(() => <StatusDialog api={api} s={detail} />)
588
586
  return true
589
587
  }
590
588
 
589
+ const EmbedDialog = (props: { api: TuiPluginApi; detail: EmbedDetail }) => {
590
+ const theme = createMemo(() => (props.api as any).theme.current)
591
+ const t = () => theme()
592
+ const lines = () => props.detail.statusText.split("\n")
593
+ return (
594
+ <box flexDirection="column" width="100%" paddingLeft={2} paddingRight={2} paddingTop={1} paddingBottom={1}>
595
+ <box justifyContent="center" width="100%" marginBottom={1}>
596
+ <text fg={t().accent}><b>Embedding</b></text>
597
+ </box>
598
+ {lines().map((line) => (
599
+ <text fg={t().text}>{line}</text>
600
+ ))}
601
+ </box>
602
+ )
603
+ }
604
+
605
+ async function showEmbedDialog(api: TuiPluginApi, targetSessionId = getSessionId(api)): Promise<boolean> {
606
+ const sessionId = targetSessionId
607
+ if (!sessionId) {
608
+ api.ui.toast({ message: "No active session", variant: "warning" })
609
+ return false
610
+ }
611
+ const directory = api.state.path.directory ?? ""
612
+ const detail = await loadEmbedDetail(sessionId, directory)
613
+ if (getSessionId(api) !== sessionId) return false
614
+ api.ui.dialog.replace(() => <EmbedDialog api={api} detail={detail} />)
615
+ return true
616
+ }
617
+
618
+ function showResultDialog(api: TuiPluginApi, title: string, message: string): boolean {
619
+ api.ui.dialog.replace(() => (
620
+ <api.ui.DialogAlert
621
+ title={title}
622
+ message={message}
623
+ onConfirm={() => {}}
624
+ />
625
+ ))
626
+ return true
627
+ }
628
+
629
+
591
630
  /**
592
631
  * Register Magic Context command palette entries, preferring the v1.14.42+
593
632
  * `keymap.registerLayer` API and falling back to the legacy
@@ -842,6 +881,21 @@ const tui: TuiPlugin = async (api, _options, meta) => {
842
881
  if (showUpgradeDialog(api, resume, requestedSessionId)) {
843
882
  handledMessageIds.add(msg.id)
844
883
  }
884
+ } else if (action === "show-embed-dialog") {
885
+ if (await showEmbedDialog(api, requestedSessionId)) {
886
+ handledMessageIds.add(msg.id)
887
+ }
888
+ } else if (action === "show-flush-dialog") {
889
+ const flushMsg = String(msg.payload?.message ?? "Flushed.")
890
+ if (showResultDialog(api, "Flush", flushMsg)) {
891
+ handledMessageIds.add(msg.id)
892
+ }
893
+ } else if (action === "show-result-dialog") {
894
+ const title = String(msg.payload?.title ?? "Magic Context")
895
+ const body = String(msg.payload?.message ?? "")
896
+ if (showResultDialog(api, title, body)) {
897
+ handledMessageIds.add(msg.id)
898
+ }
845
899
  }
846
900
  }
847
901
  }
@@ -394,8 +394,13 @@ const RecompProgressSection = (props: {
394
394
  case "done":
395
395
  return { text: `✓ ${verb()} complete`, color: props.theme.success ?? props.theme.accent }
396
396
  case "skipped":
397
- // Transient (lease busy) neutral, not an error.
398
- return { text: `${verb()} skipped retry shortly`, color: props.theme.textMuted }
397
+ // Neutral terse status next to the bold verb header; the full,
398
+ // self-contained reason (lease-busy "try again shortly" vs a
399
+ // partial-stall "run /ctx-embed start again") renders on its own
400
+ // line below. Don't re-prepend verb here (it's already the bold
401
+ // header — doing so read as "EmbedEmbed"), and don't hardcode
402
+ // "retry shortly" (wrong for a partial stall).
403
+ return { text: "stopped", color: props.theme.textMuted }
399
404
  case "failed":
400
405
  return { text: `✗ ${verb()} failed`, color: props.theme.error }
401
406
  }