@cortexkit/opencode-magic-context 0.8.3 → 0.8.4

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 (33) hide show
  1. package/README.md +17 -7
  2. package/dist/cli.js +2 -2
  3. package/dist/features/builtin-commands/commands.d.ts.map +1 -1
  4. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  5. package/dist/hooks/magic-context/send-session-notification.d.ts.map +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +62246 -61171
  8. package/dist/plugin/rpc-handlers.d.ts +11 -0
  9. package/dist/plugin/rpc-handlers.d.ts.map +1 -0
  10. package/dist/shared/conflict-detector.d.ts +0 -4
  11. package/dist/shared/conflict-detector.d.ts.map +1 -1
  12. package/dist/shared/rpc-client.d.ts +16 -0
  13. package/dist/shared/rpc-client.d.ts.map +1 -0
  14. package/dist/shared/rpc-notifications.d.ts +21 -0
  15. package/dist/shared/rpc-notifications.d.ts.map +1 -0
  16. package/dist/shared/rpc-server.d.ts +17 -0
  17. package/dist/shared/rpc-server.d.ts.map +1 -0
  18. package/dist/shared/rpc-types.d.ts +59 -0
  19. package/dist/shared/rpc-types.d.ts.map +1 -0
  20. package/dist/shared/rpc-utils.d.ts +8 -0
  21. package/dist/shared/rpc-utils.d.ts.map +1 -0
  22. package/dist/tui/data/context-db.d.ts +17 -69
  23. package/dist/tui/data/context-db.d.ts.map +1 -1
  24. package/package.json +1 -1
  25. package/src/shared/conflict-detector.ts +1 -17
  26. package/src/shared/rpc-client.ts +123 -0
  27. package/src/shared/rpc-notifications.ts +44 -0
  28. package/src/shared/rpc-server.ts +136 -0
  29. package/src/shared/rpc-types.ts +58 -0
  30. package/src/shared/rpc-utils.ts +16 -0
  31. package/src/tui/data/context-db.ts +99 -625
  32. package/src/tui/index.tsx +53 -55
  33. package/src/tui/slots/sidebar-content.tsx +8 -7
@@ -0,0 +1,11 @@
1
+ import type { MagicContextConfig } from "../config/schema/magic-context";
2
+ import type { MagicContextRpcServer } from "../shared/rpc-server";
3
+ /**
4
+ * Register all RPC handlers on the server.
5
+ */
6
+ export declare function registerRpcHandlers(rpcServer: MagicContextRpcServer, args: {
7
+ directory: string;
8
+ config: MagicContextConfig;
9
+ client: unknown;
10
+ }): void;
11
+ //# sourceMappingURL=rpc-handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-handlers.d.ts","sourceRoot":"","sources":["../../src/plugin/rpc-handlers.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAKzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAmZlE;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,SAAS,EAAE,qBAAqB,EAChC,IAAI,EAAE;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;CACnB,GACF,IAAI,CAmFN"}
@@ -18,10 +18,6 @@ export interface ConflictResult {
18
18
  * Checks: OpenCode compaction, DCP plugin, OMO conflicting hooks.
19
19
  */
20
20
  export declare function detectConflicts(directory: string): ConflictResult;
21
- /**
22
- * Generate a user-facing summary of conflicts for display in dialogs/notifications.
23
- */
24
- export declare function formatConflictSummary(result: ConflictResult): string;
25
21
  /**
26
22
  * Generate a short conflict summary for ignored message display.
27
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"conflict-detector.d.ts","sourceRoot":"","sources":["../../src/shared/conflict-detector.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,cAAc;IAC3B,8CAA8C;IAC9C,WAAW,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2DAA2D;IAC3D,SAAS,EAAE;QACP,cAAc,EAAE,OAAO,CAAC;QACxB,eAAe,EAAE,OAAO,CAAC;QACzB,SAAS,EAAE,OAAO,CAAC;QACnB,uBAAuB,EAAE,OAAO,CAAC;QACjC,uBAAuB,EAAE,OAAO,CAAC;QACjC,oBAAoB,EAAE,OAAO,CAAC;KACjC,CAAC;CACL;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAyDjE;AA0LD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAWpE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAWlE"}
1
+ {"version":3,"file":"conflict-detector.d.ts","sourceRoot":"","sources":["../../src/shared/conflict-detector.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,cAAc;IAC3B,8CAA8C;IAC9C,WAAW,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2DAA2D;IAC3D,SAAS,EAAE;QACP,cAAc,EAAE,OAAO,CAAC;QACxB,eAAe,EAAE,OAAO,CAAC;QACzB,SAAS,EAAE,OAAO,CAAC;QACnB,uBAAuB,EAAE,OAAO,CAAC;QACjC,uBAAuB,EAAE,OAAO,CAAC;QACjC,oBAAoB,EAAE,OAAO,CAAC;KACjC,CAAC;CACL;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAyDjE;AA0LD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAWlE"}
@@ -0,0 +1,16 @@
1
+ export declare class MagicContextRpcClient {
2
+ private port;
3
+ private portFilePath;
4
+ private healthChecked;
5
+ constructor(storageDir: string, directory: string);
6
+ /** Call an RPC method. Retries port resolution if the server isn't ready yet. */
7
+ call<T = Record<string, unknown>>(method: string, params?: Record<string, unknown>): Promise<T>;
8
+ /** Check if the RPC server is reachable. */
9
+ isAvailable(): Promise<boolean>;
10
+ private resolvePort;
11
+ private readPortFile;
12
+ private healthCheck;
13
+ private fetchWithTimeout;
14
+ reset(): void;
15
+ }
16
+ //# sourceMappingURL=rpc-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-client.ts"],"names":[],"mappings":"AAOA,qBAAa,qBAAqB;IAC9B,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,aAAa,CAAS;gBAElB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAIjD,iFAAiF;IAC3E,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACrC,OAAO,CAAC,CAAC,CAAC;IAoBb,4CAA4C;IACtC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;YASvB,WAAW;IAkCzB,OAAO,CAAC,YAAY;YAaN,WAAW;YAWX,gBAAgB;IAU9B,KAAK,IAAI,IAAI;CAIhB"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * In-memory notification queue for server→TUI push.
3
+ * Replaces SQLite plugin_messages table.
4
+ *
5
+ * Also tracks whether a TUI client is actively connected (polling).
6
+ * The server plugin cannot use `process.env.OPENCODE_CLIENT` to detect TUI
7
+ * because the server runs in a separate process from the TUI client.
8
+ */
9
+ export interface RpcNotification {
10
+ type: string;
11
+ payload: Record<string, unknown>;
12
+ sessionId?: string;
13
+ }
14
+ /** Push a notification for TUI to pick up via polling. */
15
+ export declare function pushNotification(type: string, payload: Record<string, unknown>, sessionId?: string): void;
16
+ /** Drain and return all pending notifications atomically.
17
+ * Also marks TUI as connected since only TUI polls this. */
18
+ export declare function drainNotifications(): RpcNotification[];
19
+ /** Whether a TUI client has connected and is polling for notifications. */
20
+ export declare function isTuiConnected(): boolean;
21
+ //# sourceMappingURL=rpc-notifications.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-notifications.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,SAAS,CAAC,EAAE,MAAM,GACnB,IAAI,CAMN;AAED;6DAC6D;AAC7D,wBAAgB,kBAAkB,IAAI,eAAe,EAAE,CAKtD;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,IAAI,OAAO,CAExC"}
@@ -0,0 +1,17 @@
1
+ type RpcHandler = (params: Record<string, unknown>) => Promise<Record<string, unknown>>;
2
+ export declare class MagicContextRpcServer {
3
+ private server;
4
+ private port;
5
+ private handlers;
6
+ private portFilePath;
7
+ constructor(storageDir: string, directory: string);
8
+ /** Register an RPC method handler. */
9
+ handle(method: string, handler: RpcHandler): void;
10
+ /** Start the server on a random port, write port to disk. */
11
+ start(): Promise<number>;
12
+ /** Stop the server and clean up port file. */
13
+ stop(): void;
14
+ private dispatch;
15
+ }
16
+ export {};
17
+ //# sourceMappingURL=rpc-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-server.ts"],"names":[],"mappings":"AAMA,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAExF,qBAAa,qBAAqB;IAC9B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAAS;gBAEjB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAIjD,sCAAsC;IACtC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAIjD,6DAA6D;IACvD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAsC9B,8CAA8C;IAC9C,IAAI,IAAI,IAAI;IAYZ,OAAO,CAAC,QAAQ;CA4DnB"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Shared types for RPC between server and TUI plugins.
3
+ * Both sides import these — no SQLite dependency.
4
+ */
5
+ export interface SidebarSnapshot {
6
+ sessionId: string;
7
+ usagePercentage: number;
8
+ inputTokens: number;
9
+ systemPromptTokens: number;
10
+ compartmentCount: number;
11
+ factCount: number;
12
+ memoryCount: number;
13
+ memoryBlockCount: number;
14
+ pendingOpsCount: number;
15
+ historianRunning: boolean;
16
+ compartmentInProgress: boolean;
17
+ sessionNoteCount: number;
18
+ readySmartNoteCount: number;
19
+ cacheTtl: string;
20
+ lastDreamerRunAt: number | null;
21
+ projectIdentity: string | null;
22
+ compartmentTokens: number;
23
+ factTokens: number;
24
+ memoryTokens: number;
25
+ }
26
+ export interface StatusDetail extends SidebarSnapshot {
27
+ tagCounter: number;
28
+ activeTags: number;
29
+ droppedTags: number;
30
+ totalTags: number;
31
+ activeBytes: number;
32
+ lastResponseTime: number;
33
+ lastNudgeTokens: number;
34
+ lastNudgeBand: string;
35
+ lastTransformError: string | null;
36
+ isSubagent: boolean;
37
+ pendingOps: Array<{
38
+ tagId: number;
39
+ operation: string;
40
+ }>;
41
+ contextLimit: number;
42
+ cacheTtlMs: number;
43
+ cacheRemainingMs: number;
44
+ cacheExpired: boolean;
45
+ executeThreshold: number;
46
+ protectedTagCount: number;
47
+ nudgeInterval: number;
48
+ historyBudgetPercentage: number;
49
+ nextNudgeAfter: number;
50
+ historyBlockTokens: number;
51
+ compressionBudget: number | null;
52
+ compressionUsage: string | null;
53
+ }
54
+ export interface RpcNotificationMessage {
55
+ type: string;
56
+ payload: Record<string, unknown>;
57
+ sessionId?: string;
58
+ }
59
+ //# sourceMappingURL=rpc-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-types.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAa,SAAQ,eAAe;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,sBAAsB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Stable hash for a project directory — scopes RPC port files per-project
3
+ * so multiple OpenCode instances don't collide.
4
+ */
5
+ export declare function projectHash(directory: string): string;
6
+ /** Per-project RPC port file path. */
7
+ export declare function rpcPortFilePath(storageDir: string, directory: string): string;
8
+ //# sourceMappingURL=rpc-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-utils.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-utils.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED,sCAAsC;AACtC,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAE7E"}
@@ -1,74 +1,22 @@
1
- export interface SidebarSnapshot {
2
- sessionId: string;
3
- usagePercentage: number;
4
- inputTokens: number;
5
- systemPromptTokens: number;
6
- compartmentCount: number;
7
- factCount: number;
8
- memoryCount: number;
9
- memoryBlockCount: number;
10
- pendingOpsCount: number;
11
- historianRunning: boolean;
12
- compartmentInProgress: boolean;
13
- sessionNoteCount: number;
14
- readySmartNoteCount: number;
15
- cacheTtl: string;
16
- lastDreamerRunAt: number | null;
17
- projectIdentity: string | null;
18
- compartmentTokens: number;
19
- factTokens: number;
20
- memoryTokens: number;
21
- }
22
- /** Extended status info for the status dialog — reads more from DB */
23
- export interface StatusDetail extends SidebarSnapshot {
24
- tagCounter: number;
25
- activeTags: number;
26
- droppedTags: number;
27
- totalTags: number;
28
- activeBytes: number;
29
- lastResponseTime: number;
30
- lastNudgeTokens: number;
31
- lastNudgeBand: string;
32
- lastTransformError: string | null;
33
- isSubagent: boolean;
34
- pendingOps: Array<{
35
- tagId: number;
36
- operation: string;
37
- }>;
38
- contextLimit: number;
39
- cacheTtlMs: number;
40
- cacheRemainingMs: number;
41
- cacheExpired: boolean;
42
- executeThreshold: number;
43
- protectedTagCount: number;
44
- nudgeInterval: number;
45
- historyBudgetPercentage: number;
46
- nextNudgeAfter: number;
47
- historyBlockTokens: number;
48
- compressionBudget: number | null;
49
- compressionUsage: string | null;
50
- }
51
- export declare function closeDb(): void;
52
- export declare function loadSidebarSnapshot(sessionId: string, directory: string): SidebarSnapshot;
53
- export declare function loadStatusDetail(sessionId: string, directory: string, modelKey?: string): StatusDetail;
54
- /**
55
- * Get compartment count for a session (used by recomp confirmation dialog).
56
- */
57
- export declare function getCompartmentCount(sessionId: string): number;
58
- /**
59
- * Consume pending server→TUI messages from the plugin_messages table.
60
- * Returns consumed messages and marks them as consumed.
61
- */
1
+ import type { SidebarSnapshot, StatusDetail } from "../../shared/rpc-types";
2
+ export type { SidebarSnapshot, StatusDetail };
3
+ /** Initialize the RPC client. Call once on TUI startup. */
4
+ export declare function initRpcClient(directory: string): void;
5
+ /** Clean up the RPC client. */
6
+ export declare function closeRpc(): void;
7
+ /** Fetch sidebar snapshot from the server via RPC. */
8
+ export declare function loadSidebarSnapshot(sessionId: string, directory: string): Promise<SidebarSnapshot>;
9
+ /** Fetch full status detail from the server via RPC. */
10
+ export declare function loadStatusDetail(sessionId: string, directory: string, modelKey?: string): Promise<StatusDetail>;
11
+ /** Get compartment count via RPC. */
12
+ export declare function getCompartmentCount(sessionId: string): Promise<number>;
13
+ /** Send recomp request to server via RPC. */
14
+ export declare function requestRecomp(sessionId: string): Promise<boolean>;
62
15
  export interface TuiMessage {
63
- id: number;
64
16
  type: string;
65
17
  payload: Record<string, unknown>;
66
- sessionId: string | null;
67
- createdAt: number;
18
+ sessionId?: string;
68
19
  }
69
- export declare function consumeTuiMessages(): TuiMessage[];
70
- /**
71
- * Send a message from TUI to server via plugin_messages.
72
- */
73
- export declare function sendMessageToServer(type: string, payload: Record<string, unknown>, sessionId?: string): boolean;
20
+ /** Poll for pending server→TUI notifications via RPC. */
21
+ export declare function consumeTuiMessages(): Promise<TuiMessage[]>;
74
22
  //# sourceMappingURL=context-db.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context-db.d.ts","sourceRoot":"","sources":["../../../src/tui/data/context-db.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAE/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,sEAAsE;AACtE,MAAM,WAAW,YAAa,SAAQ,eAAe;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAExD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IAEtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,cAAc,EAAE,MAAM,CAAC;IAEvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AA+BD,wBAAgB,OAAO,IAAI,IAAI,CAU9B;AA+BD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CAuNzF;AAED,wBAAgB,gBAAgB,CAC5B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAClB,YAAY,CAmMd;AAyCD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAW7D;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,kBAAkB,IAAI,UAAU,EAAE,CAoDjD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAiBT"}
1
+ {"version":3,"file":"context-db.d.ts","sourceRoot":"","sources":["../../../src/tui/data/context-db.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAA0B,eAAe,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEpG,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC;AAS9C,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGrD;AAED,+BAA+B;AAC/B,wBAAgB,QAAQ,IAAI,IAAI,CAG/B;AAwBD,sDAAsD;AACtD,wBAAsB,mBAAmB,CACrC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,CAAC,CAc1B;AAED,wDAAwD;AACxD,wBAAsB,gBAAgB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CA2CvB;AAED,qCAAqC;AACrC,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ5E;AAED,6CAA6C;AAC7C,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQvE;AAED,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,yDAAyD;AACzD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAchE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",
@@ -277,22 +277,6 @@ function readOmoDisabledHooks(directory: string): Set<string> {
277
277
  return disabled;
278
278
  }
279
279
 
280
- /**
281
- * Generate a user-facing summary of conflicts for display in dialogs/notifications.
282
- */
283
- export function formatConflictSummary(result: ConflictResult): string {
284
- if (!result.hasConflict) return "";
285
-
286
- const lines = [
287
- "⚠️ Magic Context is disabled due to conflicting configuration:\n",
288
- ...result.reasons.map((r) => ` • ${r}`),
289
- "",
290
- "Run `bunx @cortexkit/opencode-magic-context doctor` to fix,",
291
- "or resolve these conflicts manually in your OpenCode config.",
292
- ];
293
- return lines.join("\n");
294
- }
295
-
296
280
  /**
297
281
  * Generate a short conflict summary for ignored message display.
298
282
  */
@@ -304,7 +288,7 @@ export function formatConflictShort(result: ConflictResult): string {
304
288
  "",
305
289
  ...result.reasons.map((r) => `• ${r}`),
306
290
  "",
307
- "Fix: run `bunx @cortexkit/opencode-magic-context doctor`",
291
+ "Fix: run `bunx @cortexkit/opencode-magic-context@latest doctor`",
308
292
  ];
309
293
  return lines.join("\n");
310
294
  }
@@ -0,0 +1,123 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { rpcPortFilePath } from "./rpc-utils";
3
+
4
+ const MAX_RETRIES = 10;
5
+ const RETRY_DELAY_MS = 500;
6
+ const REQUEST_TIMEOUT_MS = 5000;
7
+
8
+ export class MagicContextRpcClient {
9
+ private port: number | null = null;
10
+ private portFilePath: string;
11
+ private healthChecked = false;
12
+
13
+ constructor(storageDir: string, directory: string) {
14
+ this.portFilePath = rpcPortFilePath(storageDir, directory);
15
+ }
16
+
17
+ /** Call an RPC method. Retries port resolution if the server isn't ready yet. */
18
+ async call<T = Record<string, unknown>>(
19
+ method: string,
20
+ params: Record<string, unknown> = {},
21
+ ): Promise<T> {
22
+ const port = await this.resolvePort();
23
+ if (!port) {
24
+ throw new Error("Magic Context RPC server not available");
25
+ }
26
+
27
+ const response = await this.fetchWithTimeout(`http://127.0.0.1:${port}/rpc/${method}`, {
28
+ method: "POST",
29
+ headers: { "Content-Type": "application/json" },
30
+ body: JSON.stringify(params),
31
+ });
32
+
33
+ if (!response.ok) {
34
+ const text = await response.text();
35
+ throw new Error(`RPC ${method} failed (${response.status}): ${text}`);
36
+ }
37
+
38
+ return (await response.json()) as T;
39
+ }
40
+
41
+ /** Check if the RPC server is reachable. */
42
+ async isAvailable(): Promise<boolean> {
43
+ try {
44
+ const port = await this.resolvePort();
45
+ return port !== null;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ private async resolvePort(): Promise<number | null> {
52
+ if (this.port && this.healthChecked) {
53
+ return this.port;
54
+ }
55
+
56
+ if (this.port) {
57
+ const alive = await this.healthCheck(this.port);
58
+ if (alive) {
59
+ this.healthChecked = true;
60
+ return this.port;
61
+ }
62
+ this.port = null;
63
+ this.healthChecked = false;
64
+ }
65
+
66
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
67
+ const port = this.readPortFile();
68
+ if (port) {
69
+ const alive = await this.healthCheck(port);
70
+ if (alive) {
71
+ this.port = port;
72
+ this.healthChecked = true;
73
+ return port;
74
+ }
75
+ }
76
+
77
+ if (attempt < MAX_RETRIES - 1) {
78
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
79
+ }
80
+ }
81
+
82
+ return null;
83
+ }
84
+
85
+ private readPortFile(): number | null {
86
+ try {
87
+ const content = readFileSync(this.portFilePath, "utf-8").trim();
88
+ const port = Number.parseInt(content, 10);
89
+ if (Number.isNaN(port) || port <= 0 || port > 65535) {
90
+ return null;
91
+ }
92
+ return port;
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ private async healthCheck(port: number): Promise<boolean> {
99
+ try {
100
+ const response = await this.fetchWithTimeout(`http://127.0.0.1:${port}/health`, {
101
+ method: "GET",
102
+ });
103
+ return response.ok;
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ private async fetchWithTimeout(url: string, options: RequestInit): Promise<Response> {
110
+ const controller = new AbortController();
111
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
112
+ try {
113
+ return await fetch(url, { ...options, signal: controller.signal });
114
+ } finally {
115
+ clearTimeout(timeout);
116
+ }
117
+ }
118
+
119
+ reset(): void {
120
+ this.port = null;
121
+ this.healthChecked = false;
122
+ }
123
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * In-memory notification queue for server→TUI push.
3
+ * Replaces SQLite plugin_messages table.
4
+ *
5
+ * Also tracks whether a TUI client is actively connected (polling).
6
+ * The server plugin cannot use `process.env.OPENCODE_CLIENT` to detect TUI
7
+ * because the server runs in a separate process from the TUI client.
8
+ */
9
+
10
+ export interface RpcNotification {
11
+ type: string;
12
+ payload: Record<string, unknown>;
13
+ sessionId?: string;
14
+ }
15
+
16
+ let queue: RpcNotification[] = [];
17
+ let tuiConnected = false;
18
+
19
+ /** Push a notification for TUI to pick up via polling. */
20
+ export function pushNotification(
21
+ type: string,
22
+ payload: Record<string, unknown>,
23
+ sessionId?: string,
24
+ ): void {
25
+ queue.push({ type, payload, sessionId });
26
+ // Cap queue size to prevent unbounded growth if TUI is not polling
27
+ if (queue.length > 100) {
28
+ queue = queue.slice(-50);
29
+ }
30
+ }
31
+
32
+ /** Drain and return all pending notifications atomically.
33
+ * Also marks TUI as connected since only TUI polls this. */
34
+ export function drainNotifications(): RpcNotification[] {
35
+ tuiConnected = true;
36
+ const result = queue;
37
+ queue = [];
38
+ return result;
39
+ }
40
+
41
+ /** Whether a TUI client has connected and is polling for notifications. */
42
+ export function isTuiConnected(): boolean {
43
+ return tuiConnected;
44
+ }
@@ -0,0 +1,136 @@
1
+ import { mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
3
+ import { dirname } from "node:path";
4
+ import { log } from "./logger";
5
+ import { rpcPortFilePath } from "./rpc-utils";
6
+
7
+ type RpcHandler = (params: Record<string, unknown>) => Promise<Record<string, unknown>>;
8
+
9
+ export class MagicContextRpcServer {
10
+ private server: Server | null = null;
11
+ private port = 0;
12
+ private handlers = new Map<string, RpcHandler>();
13
+ private portFilePath: string;
14
+
15
+ constructor(storageDir: string, directory: string) {
16
+ this.portFilePath = rpcPortFilePath(storageDir, directory);
17
+ }
18
+
19
+ /** Register an RPC method handler. */
20
+ handle(method: string, handler: RpcHandler): void {
21
+ this.handlers.set(method, handler);
22
+ }
23
+
24
+ /** Start the server on a random port, write port to disk. */
25
+ async start(): Promise<number> {
26
+ return new Promise((resolve, reject) => {
27
+ const server = createServer((req, res) => this.dispatch(req, res));
28
+
29
+ server.on("error", (err) => {
30
+ log(`[rpc] server error: ${err.message}`);
31
+ reject(err);
32
+ });
33
+
34
+ server.listen(0, "127.0.0.1", () => {
35
+ const addr = server.address();
36
+ if (!addr || typeof addr === "string") {
37
+ reject(new Error("Failed to get server address"));
38
+ return;
39
+ }
40
+ this.port = addr.port;
41
+ this.server = server;
42
+
43
+ // Write port file atomically
44
+ try {
45
+ const dir = dirname(this.portFilePath);
46
+ mkdirSync(dir, { recursive: true });
47
+ const tmpPath = `${this.portFilePath}.tmp`;
48
+ writeFileSync(tmpPath, String(this.port), "utf-8");
49
+ renameSync(tmpPath, this.portFilePath);
50
+ log(`[rpc] server listening on 127.0.0.1:${this.port}`);
51
+ } catch (err) {
52
+ log(`[rpc] failed to write port file: ${err}`);
53
+ }
54
+
55
+ resolve(this.port);
56
+ });
57
+
58
+ // Don't keep the process alive just for the RPC server
59
+ server.unref();
60
+ });
61
+ }
62
+
63
+ /** Stop the server and clean up port file. */
64
+ stop(): void {
65
+ if (this.server) {
66
+ this.server.close();
67
+ this.server = null;
68
+ }
69
+ try {
70
+ unlinkSync(this.portFilePath);
71
+ } catch {
72
+ // Intentional: port file may already be gone
73
+ }
74
+ }
75
+
76
+ private dispatch(req: IncomingMessage, res: ServerResponse): void {
77
+ const url = req.url ?? "";
78
+
79
+ // CORS headers for same-origin fetch
80
+ res.setHeader("Access-Control-Allow-Origin", "*");
81
+
82
+ if (req.method === "GET" && url === "/health") {
83
+ res.writeHead(200, { "Content-Type": "application/json" });
84
+ res.end(JSON.stringify({ ok: true, pid: process.pid }));
85
+ return;
86
+ }
87
+
88
+ if (req.method !== "POST" || !url.startsWith("/rpc/")) {
89
+ res.writeHead(404);
90
+ res.end("Not Found");
91
+ return;
92
+ }
93
+
94
+ const method = url.slice(5); // strip "/rpc/"
95
+ const handler = this.handlers.get(method);
96
+ if (!handler) {
97
+ res.writeHead(404, { "Content-Type": "application/json" });
98
+ res.end(JSON.stringify({ error: `Unknown method: ${method}` }));
99
+ return;
100
+ }
101
+
102
+ let body = "";
103
+ req.on("data", (chunk: Buffer) => {
104
+ body += chunk.toString();
105
+ if (body.length > 1_048_576) {
106
+ res.writeHead(413);
107
+ res.end("Request too large");
108
+ req.destroy();
109
+ }
110
+ });
111
+
112
+ req.on("end", () => {
113
+ let params: Record<string, unknown> = {};
114
+ try {
115
+ if (body.length > 0) {
116
+ params = JSON.parse(body);
117
+ }
118
+ } catch {
119
+ res.writeHead(400, { "Content-Type": "application/json" });
120
+ res.end(JSON.stringify({ error: "Invalid JSON" }));
121
+ return;
122
+ }
123
+
124
+ handler(params)
125
+ .then((result) => {
126
+ res.writeHead(200, { "Content-Type": "application/json" });
127
+ res.end(JSON.stringify(result));
128
+ })
129
+ .catch((err) => {
130
+ log(`[rpc] handler error: ${method} => ${err}`);
131
+ res.writeHead(500, { "Content-Type": "application/json" });
132
+ res.end(JSON.stringify({ error: String(err) }));
133
+ });
134
+ });
135
+ }
136
+ }