@cortexkit/opencode-magic-context 0.31.3 → 0.31.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.
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/project-security.d.ts +16 -12
- package/dist/config/project-security.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify-prompt.d.ts +2 -2
- package/dist/features/magic-context/dreamer/classify-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify.d.ts +8 -0
- package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/lease.d.ts +1 -0
- package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/manifest-parser.d.ts +7 -0
- package/dist/features/magic-context/dreamer/manifest-parser.d.ts.map +1 -0
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +2 -3
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts +8 -0
- package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/promote-primers.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts +5 -0
- package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts +2 -2
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts +4 -0
- package/dist/features/magic-context/memory/storage-memory-embeddings.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts +12 -0
- package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
- package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts +6 -0
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/capabilities.d.ts +1 -0
- package/dist/features/magic-context/smart-notes/capabilities.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts +3 -2
- package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts +3 -2
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/schedule.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts +17 -6
- package/dist/features/magic-context/smart-notes/ssrf-guard.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/types.d.ts +6 -1
- package/dist/features/magic-context/smart-notes/types.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +8 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -1
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/workspaces.d.ts +8 -0
- package/dist/features/magic-context/workspaces.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts +2 -0
- package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +1 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/todo-view.d.ts +27 -2
- package/dist/hooks/magic-context/todo-view.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-message-helpers.d.ts +6 -3
- package/dist/hooks/magic-context/transform-message-helpers.d.ts.map +1 -1
- package/dist/hooks/magic-context/wrapup-orchestrator.d.ts +1 -0
- package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
- package/dist/index.js +1039 -331
- package/dist/shared/assistant-message-extractor.d.ts +1 -0
- package/dist/shared/assistant-message-extractor.d.ts.map +1 -1
- package/dist/shared/redaction.d.ts.map +1 -1
- package/dist/shared/rpc-notifications.d.ts +21 -16
- package/dist/shared/rpc-notifications.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts +1 -0
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/rpc-utils.d.ts +3 -1
- package/dist/shared/rpc-utils.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/types.d.ts +1 -1
- package/dist/tools/ctx-search/types.d.ts.map +1 -1
- package/dist/tui/data/notification-socket.d.ts +1 -0
- package/dist/tui/data/notification-socket.d.ts.map +1 -1
- package/dist/tui-compiled/badge-contrast.d.ts +46 -0
- package/dist/tui-compiled/badge-contrast.d.ts.map +1 -0
- package/dist/tui-compiled/data/context-db.d.ts +45 -0
- package/dist/tui-compiled/data/context-db.d.ts.map +1 -0
- package/dist/tui-compiled/data/notification-socket.d.ts +40 -0
- package/dist/tui-compiled/data/notification-socket.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/shared/assistant-message-extractor.ts +20 -0
- package/src/shared/redaction.ts +1 -0
- package/src/shared/rpc-client.test.ts +164 -5
- package/src/shared/rpc-notifications.ts +73 -21
- package/src/shared/rpc-server.ts +69 -18
- package/src/shared/rpc-utils.ts +12 -2
- package/src/tui/data/notification-socket.test.ts +131 -0
- package/src/tui/data/notification-socket.ts +81 -16
- package/src/tui/entry.mjs +23 -3
- package/src/tui-compiled/data/notification-socket.ts +81 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-message-extractor.d.ts","sourceRoot":"","sources":["../../src/shared/assistant-message-extractor.ts"],"names":[],"mappings":"AAwDA,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAiB3E"}
|
|
1
|
+
{"version":3,"file":"assistant-message-extractor.d.ts","sourceRoot":"","sources":["../../src/shared/assistant-message-extractor.ts"],"names":[],"mappings":"AAwDA,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAiB3E;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAkB7D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../../src/shared/redaction.ts"],"names":[],"mappings":"AAyEA,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAkChD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAcxD;AA0ED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAatD;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5D;AAsBD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOlE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,MAAM,EAAO,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../../src/shared/redaction.ts"],"names":[],"mappings":"AAyEA,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAkChD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAcxD;AA0ED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAatD;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5D;AAsBD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOlE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,MAAM,EAAO,GAAG,OAAO,CAmBnF"}
|
|
@@ -32,25 +32,30 @@ export declare function registerNotificationSink(sink: NotificationSink): () =>
|
|
|
32
32
|
* At-least-once: a live push that the socket drops is re-delivered from the
|
|
33
33
|
* queue on reconnect (pruned only when the client acks via `lastReceivedId`). */
|
|
34
34
|
export declare function pushNotification(type: string, payload: Record<string, unknown>, sessionId?: string): void;
|
|
35
|
-
|
|
35
|
+
export interface DrainNotificationsOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Cursor for global notifications when a session-scoped client sends separate
|
|
38
|
+
* session and global watermarks.
|
|
39
|
+
*/
|
|
40
|
+
globalLastReceivedId?: number;
|
|
41
|
+
/** Ack/drain only the named session, not global notifications. */
|
|
42
|
+
sessionOnly?: boolean;
|
|
43
|
+
/** Ack/drain only session-less global notifications. */
|
|
44
|
+
globalOnly?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** Return pending notifications after pruning only the scopes the client acked.
|
|
36
47
|
*
|
|
37
|
-
* Session
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* newer RPC server that the TUI's port discovery (newest-pid-wins) then selects,
|
|
44
|
-
* so a Desktop-session upgrade-dialog action would otherwise surface in an
|
|
45
|
-
* unrelated TUI session. Each client also tracks its own `lastReceivedId`, so a
|
|
46
|
-
* global watermark prune would let session A's ack drop session B's still-unseen
|
|
47
|
-
* notification — scoping the prune to the acking session prevents that too.
|
|
48
|
+
* Session-scoped and global notifications have independent cursors. A TUI can
|
|
49
|
+
* switch from session A to session B after handling a high id in A; that high
|
|
50
|
+
* watermark must never prune B's lower, still-unseen ids. Global notifications
|
|
51
|
+
* are also tracked separately so a global dialog does not become a session
|
|
52
|
+
* watermark. Legacy callers that omit options keep the original single-cursor
|
|
53
|
+
* behavior.
|
|
48
54
|
*
|
|
49
55
|
* Delivery is at-least-once (non-destructive return + prune-on-ack): a returned
|
|
50
|
-
* notification stays queued until a later call acks it via
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
export declare function drainNotifications(lastReceivedId?: number, sessionId?: string): RpcNotification[];
|
|
56
|
+
* notification stays queued until a later call acks it via the matching scope's
|
|
57
|
+
* cursor, so a dropped WS socket re-delivers unhandled backlog on reconnect. */
|
|
58
|
+
export declare function drainNotifications(lastReceivedId?: number, sessionId?: string, options?: DrainNotificationsOptions): RpcNotification[];
|
|
54
59
|
/** Whether a TUI client is connected via a live notification socket.
|
|
55
60
|
* Now exact socket liveness (a registered WS sink), not a poll-drain timestamp.
|
|
56
61
|
*
|
|
@@ -1 +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,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC7B,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,IAAI,EAAE,CAAC,YAAY,EAAE,eAAe,KAAK,IAAI,CAAC;CACjD;AAYD,iFAAiF;AACjF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,IAAI,CAK3E;AAcD;;;;kFAIkF;AAClF,wBAAgB,gBAAgB,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,SAAS,CAAC,EAAE,MAAM,GACnB,IAAI,CAoCN;AAED
|
|
1
|
+
{"version":3,"file":"rpc-notifications.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC7B,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,IAAI,EAAE,CAAC,YAAY,EAAE,eAAe,KAAK,IAAI,CAAC;CACjD;AAYD,iFAAiF;AACjF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,IAAI,CAK3E;AAcD;;;;kFAIkF;AAClF,wBAAgB,gBAAgB,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,SAAS,CAAC,EAAE,MAAM,GACnB,IAAI,CAoCN;AAED,MAAM,WAAW,yBAAyB;IACtC;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wDAAwD;IACxD,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB;AAMD;;;;;;;;;;;iFAWiF;AACjF,wBAAgB,kBAAkB,CAC9B,cAAc,SAAI,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE,yBAA8B,GACxC,eAAe,EAAE,CAqDnB;AAED;;;;;;;;sEAQsE;AACtE,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAO1D"}
|
|
@@ -18,6 +18,7 @@ export declare class MagicContextRpcServer {
|
|
|
18
18
|
private portFilePath;
|
|
19
19
|
private portDir;
|
|
20
20
|
private startedAt;
|
|
21
|
+
private readonly instanceId;
|
|
21
22
|
/** Every authenticated WS socket, so dispose can close them all. */
|
|
22
23
|
private sockets;
|
|
23
24
|
private readonly token;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-server.ts"],"names":[],"mappings":"AAqBA,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-server.ts"],"names":[],"mappings":"AAqBA,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AA0CxF;;;;;;;;;;;GAWG;AACH,qBAAa,qBAAqB;IAC9B,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,oEAAoE;IACpE,OAAO,CAAC,OAAO,CAAsC;IAMrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;gBAE7C,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAKjD,sCAAsC;IACtC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAIjD,6DAA6D;IACvD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAqF9B,6EAA6E;IAC7E,IAAI,IAAI,IAAI;IAuBZ,OAAO,CAAC,uBAAuB;IAgB/B;+CAC2C;YAC7B,WAAW;IAyDzB;8EAC0E;IAC1E,OAAO,CAAC,eAAe;CA+F1B"}
|
|
@@ -11,6 +11,8 @@ export interface RpcPortFileRecord {
|
|
|
11
11
|
* auth required" only when the server itself didn't set one).
|
|
12
12
|
*/
|
|
13
13
|
token?: string;
|
|
14
|
+
/** Per-server filename nonce; prevents same-process instances from sharing one file. */
|
|
15
|
+
instance_id?: string;
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* Stable hash for a project directory — scopes RPC port files per-project
|
|
@@ -20,7 +22,7 @@ export declare function projectHash(directory: string): string;
|
|
|
20
22
|
/** Directory containing per-process RPC discovery files for a project. */
|
|
21
23
|
export declare function rpcPortDir(storageDir: string, directory: string): string;
|
|
22
24
|
/** Per-process RPC port file path. */
|
|
23
|
-
export declare function rpcPortFilePath(storageDir: string, directory: string, pid?: number): string;
|
|
25
|
+
export declare function rpcPortFilePath(storageDir: string, directory: string, pid?: number, instanceId?: string): string;
|
|
24
26
|
/** Legacy single-port file used by v0.18.0 and earlier. */
|
|
25
27
|
export declare function legacyRpcPortFilePath(storageDir: string, directory: string): string;
|
|
26
28
|
export declare function isPidAlive(pid: number): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-utils.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-utils.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"rpc-utils.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-utils.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wFAAwF;IACxF,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,sCAAsC;AACtC,wBAAgB,eAAe,CAC3B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,GAAG,SAAc,EACjB,UAAU,CAAC,EAAE,MAAM,GACpB,MAAM,CAGR;AAED,2DAA2D;AAC3D,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQ/C;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,SAAI,GAAG,iBAAiB,GAAG,IAAI,CA2B3F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-memory/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AA0ChE,OAAO,EAKH,KAAK,iBAAiB,EACzB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-memory/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AA0ChE,OAAO,EAKH,KAAK,iBAAiB,EACzB,MAAM,SAAS,CAAC;AAitBjB,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAI5F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-search/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAahE,OAAO,KAAK,EAAkC,iBAAiB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-search/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAahE,OAAO,KAAK,EAAkC,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAyOjF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAI5F"}
|
|
@@ -6,7 +6,7 @@ export type CtxSearchSource = "memory" | "message" | "git_commit" | "primer" | "
|
|
|
6
6
|
export interface CtxSearchArgs {
|
|
7
7
|
query: string;
|
|
8
8
|
limit?: number;
|
|
9
|
-
/** Restrict search to specific sources. Omit to search all. */
|
|
9
|
+
/** Restrict search to specific sources. Omit to search all; [] searches none. */
|
|
10
10
|
sources?: CtxSearchSource[];
|
|
11
11
|
}
|
|
12
12
|
export interface CtxSearchToolDeps {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-search/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;iEAEiE;AACjE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEtF,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-search/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;iEAEiE;AACjE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEtF,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAC9B,EAAE,EAAE,QAAQ,CAAC;IACb,uBAAuB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E;;;;;OAKG;IACH,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,8EAA8E;IAC9E,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,KAAK,CAAC;QACxC,OAAO,EAAE,MAAM,CAAC;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,OAAO,EAAE,CAAC;KACpB,CAAC,CAAC;CACN"}
|
|
@@ -35,5 +35,6 @@ interface NotificationSocketOptions {
|
|
|
35
35
|
export declare function startNotificationSocket(options: NotificationSocketOptions): void;
|
|
36
36
|
/** Close the socket and stop reconnecting. Call on TUI dispose. */
|
|
37
37
|
export declare function stopNotificationSocket(): void;
|
|
38
|
+
export declare function _resetNotificationSocketStateForTesting(): void;
|
|
38
39
|
export {};
|
|
39
40
|
//# sourceMappingURL=notification-socket.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notification-socket.d.ts","sourceRoot":"","sources":["../../../src/tui/data/notification-socket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,MAAM,WAAW,kBAAkB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,yBAAyB;IAC/B,8EAA8E;IAC9E,YAAY,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAClC;+EAC2E;IAC3E,cAAc,EAAE,CAAC,YAAY,EAAE,kBAAkB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;
|
|
1
|
+
{"version":3,"file":"notification-socket.d.ts","sourceRoot":"","sources":["../../../src/tui/data/notification-socket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,MAAM,WAAW,kBAAkB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,yBAAyB;IAC/B,8EAA8E;IAC9E,YAAY,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAClC;+EAC2E;IAC3E,cAAc,EAAE,CAAC,YAAY,EAAE,kBAAkB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;AAkCD;wDACwD;AACxD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAQhF;AAED,mEAAmE;AACnE,wBAAgB,sBAAsB,IAAI,IAAI,CAkB7C;AA6KD,wBAAgB,uCAAuC,IAAI,IAAI,CAM9D"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pick the text color for the sidebar header badge (a bold label drawn on a
|
|
3
|
+
* `theme.accent` background).
|
|
4
|
+
*
|
|
5
|
+
* Primary rule (matches AFT's sidebar by construction): paint the theme's own
|
|
6
|
+
* `background` color as the label, the inverse-of-panel look. Because it is a
|
|
7
|
+
* fixed theme token rather than an accent-derived computation, MC's badge and
|
|
8
|
+
* AFT's badge agree on EVERY accent automatically, so the same theme can never
|
|
9
|
+
* make one badge black and the other white (issue #198).
|
|
10
|
+
*
|
|
11
|
+
* Fallback rule (the reason a luminance pick exists at all): `theme.background`
|
|
12
|
+
* can be unusable as a label color in two degenerate cases, where it would
|
|
13
|
+
* render the label invisible on the accent:
|
|
14
|
+
* 1. Transparent background. Themes that respect terminal transparency set
|
|
15
|
+
* `background: "none"`, which resolves to `RGBA(0,0,0,0)`; drawing it as
|
|
16
|
+
* text renders fully transparent and the label disappears (issue #186).
|
|
17
|
+
* 2. Background ~= accent. If the theme's background and accent are nearly the
|
|
18
|
+
* same color, background-on-accent text has no contrast.
|
|
19
|
+
* In either case we fall back to a black/white pick that is guaranteed visible
|
|
20
|
+
* on the always-opaque accent.
|
|
21
|
+
*
|
|
22
|
+
* `RGBA` channels from @opentui/core are normalized 0..1 floats (alpha included).
|
|
23
|
+
* We accept the minimal `{ r, g, b, a? }` shape so this stays a pure, trivially
|
|
24
|
+
* testable function independent of the native color class, and we return the
|
|
25
|
+
* passed-in `background` object unchanged on the primary path so it stays the
|
|
26
|
+
* exact same theme token AFT uses.
|
|
27
|
+
*/
|
|
28
|
+
type Color = {
|
|
29
|
+
r: number;
|
|
30
|
+
g: number;
|
|
31
|
+
b: number;
|
|
32
|
+
a?: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Pure black/white pick by accent luminance. Used as the badge fallback and kept
|
|
36
|
+
* exported for callers that only have the accent.
|
|
37
|
+
*/
|
|
38
|
+
export declare function readableTextColorOn(bg: Color): string;
|
|
39
|
+
/**
|
|
40
|
+
* Badge label color on the accent: the theme background (AFT parity) when it is
|
|
41
|
+
* usable, else a guaranteed-visible black/white fallback. Returns the passed-in
|
|
42
|
+
* `background` reference unchanged on the primary path.
|
|
43
|
+
*/
|
|
44
|
+
export declare function badgeTextColor<T extends Color>(accent: T, background: T): T | string;
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=badge-contrast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"badge-contrast.d.ts","sourceRoot":"","sources":["../../src/tui-compiled/badge-contrast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,KAAK,KAAK,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAoC7D;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,KAAK,GAAG,MAAM,CAErD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,GAAG,CAAC,GAAG,MAAM,CAMpF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { MagicContextRpcClient } from "../../shared/rpc-client";
|
|
2
|
+
import type { EmbedDetail, SidebarSnapshot, StatusDetail } from "../../shared/rpc-types";
|
|
3
|
+
export type { EmbedDetail, SidebarSnapshot, StatusDetail };
|
|
4
|
+
/** Initialize the RPC client. Call once on TUI startup. */
|
|
5
|
+
export declare function initRpcClient(directory: string): void;
|
|
6
|
+
export declare function getRpcGeneration(): number;
|
|
7
|
+
/** The live RPC client (for the WS notification socket's endpoint discovery).
|
|
8
|
+
* Null before init / after close. */
|
|
9
|
+
export declare function getRpcClient(): MagicContextRpcClient | null;
|
|
10
|
+
/** Clean up the RPC client. */
|
|
11
|
+
export declare function closeRpc(): void;
|
|
12
|
+
/** Fetch sidebar snapshot from the server via RPC. */
|
|
13
|
+
export declare function loadSidebarSnapshot(sessionId: string, directory: string): Promise<SidebarSnapshot>;
|
|
14
|
+
/** Fetch full status detail from the server via RPC. */
|
|
15
|
+
export declare function loadStatusDetail(sessionId: string, directory: string, modelKey?: string): Promise<StatusDetail>;
|
|
16
|
+
/** Fetch embedding coverage status for `/ctx-embed` via RPC. */
|
|
17
|
+
export declare function loadEmbedDetail(sessionId: string, directory: string): Promise<EmbedDetail>;
|
|
18
|
+
/** Get compartment count via RPC. */
|
|
19
|
+
export declare function getCompartmentCount(sessionId: string): Promise<number>;
|
|
20
|
+
/** Send recomp request to server via RPC. */
|
|
21
|
+
export declare function requestRecomp(sessionId: string): Promise<boolean>;
|
|
22
|
+
/** Run `/ctx-session-upgrade` for the session (full recomp + once-per-project
|
|
23
|
+
* memory migration). Fired from the upgrade dialog's "Run upgrade now" action. */
|
|
24
|
+
export declare function requestUpgrade(sessionId: string): Promise<boolean>;
|
|
25
|
+
/** Mark the upgrade reminder dismissed (the user made an explicit Confirm/Cancel
|
|
26
|
+
* choice), setting the durable stamp so the FRESH dialog won't re-show. Resume
|
|
27
|
+
* prompts are staging-driven and unaffected. */
|
|
28
|
+
export declare function dismissUpgradeReminder(sessionId: string): Promise<boolean>;
|
|
29
|
+
/** Resolve global toast duration from server config via RPC. */
|
|
30
|
+
export declare function loadToastDurationMs(): Promise<number>;
|
|
31
|
+
/**
|
|
32
|
+
* Fetch the current startup announcement from the server, if any.
|
|
33
|
+
* Returns `{show: false}` when there's nothing to announce or when the
|
|
34
|
+
* configured ANNOUNCEMENT_VERSION has already been dismissed.
|
|
35
|
+
*/
|
|
36
|
+
export interface AnnouncementResponse {
|
|
37
|
+
show: boolean;
|
|
38
|
+
version?: string;
|
|
39
|
+
features?: string[];
|
|
40
|
+
footer?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare function getAnnouncement(): Promise<AnnouncementResponse>;
|
|
43
|
+
/** Mark the current ANNOUNCEMENT_VERSION as dismissed on the server. */
|
|
44
|
+
export declare function markAnnounced(): Promise<boolean>;
|
|
45
|
+
//# sourceMappingURL=context-db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-db.d.ts","sourceRoot":"","sources":["../../../src/tui-compiled/data/context-db.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEzF,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC;AAc3D,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAOrD;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;sCACsC;AACtC,wBAAgB,YAAY,IAAI,qBAAqB,GAAG,IAAI,CAE3D;AAED,+BAA+B;AAC/B,wBAAgB,QAAQ,IAAI,IAAI,CAM/B;AA2FD,sDAAsD;AACtD,wBAAsB,mBAAmB,CACrC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,CAAC,CA4B1B;AAED,wDAAwD;AACxD,wBAAsB,gBAAgB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CA0CvB;AAYD,gEAAgE;AAChE,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAchG;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;mFACmF;AACnF,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQxE;AAED;;iDAEiD;AACjD,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUhF;AAED,gEAAgE;AAChE,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAQ3D;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAkBrE;AAED,wEAAwE;AACxE,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAQtD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent WebSocket to the server plugin's RPC server, replacing the old
|
|
3
|
+
* 500ms HTTP notification poll.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: the TUI plugin and the server plugin run in separate Bun
|
|
6
|
+
* runners in the same process, so they bridge over a localhost socket. The old
|
|
7
|
+
* bridge polled `pending-notifications` over HTTP every 500ms — and each poll
|
|
8
|
+
* opened a NEW loopback TCP connection (Bun's fetch isn't pooled to our server),
|
|
9
|
+
* which was the entire source of idle TUI CPU (#200). A single long-lived WS
|
|
10
|
+
* carries server→TUI pushes with zero per-event connection cost, and the server
|
|
11
|
+
* pushes notifications the instant they're queued (no polling latency).
|
|
12
|
+
*
|
|
13
|
+
* Session scope: the socket carries the TUI's active session in its `hello` so
|
|
14
|
+
* the server delivers only that session's (plus global) notifications and its
|
|
15
|
+
* `isTuiConnected(session)` routing stays correct. The active session is tracked
|
|
16
|
+
* with a cheap watcher that only reads `api.route.current` (a property access,
|
|
17
|
+
* no IPC) and re-scopes the socket ONLY when the session actually changes — so
|
|
18
|
+
* unlike the old poll it does no network work at idle.
|
|
19
|
+
*/
|
|
20
|
+
export interface SocketNotification {
|
|
21
|
+
id: number;
|
|
22
|
+
type: string;
|
|
23
|
+
payload: Record<string, unknown>;
|
|
24
|
+
sessionId?: string;
|
|
25
|
+
}
|
|
26
|
+
interface NotificationSocketOptions {
|
|
27
|
+
/** Current active session id (re-read cheaply to follow session switches). */
|
|
28
|
+
getSessionId: () => string | null;
|
|
29
|
+
/** Handle one delivered notification. Returns true if it was consumed (so its
|
|
30
|
+
* id can advance the ack cursor). Async because dialog handlers await. */
|
|
31
|
+
onNotification: (notification: SocketNotification) => boolean | Promise<boolean>;
|
|
32
|
+
}
|
|
33
|
+
/** Open the persistent notification socket. Idempotent: a second call while open
|
|
34
|
+
* is a no-op. Reconnects on its own after any drop. */
|
|
35
|
+
export declare function startNotificationSocket(options: NotificationSocketOptions): void;
|
|
36
|
+
/** Close the socket and stop reconnecting. Call on TUI dispose. */
|
|
37
|
+
export declare function stopNotificationSocket(): void;
|
|
38
|
+
export declare function _resetNotificationSocketStateForTesting(): void;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=notification-socket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-socket.d.ts","sourceRoot":"","sources":["../../../src/tui-compiled/data/notification-socket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,MAAM,WAAW,kBAAkB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,yBAAyB;IAC/B,8EAA8E;IAC9E,YAAY,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAClC;+EAC2E;IAC3E,cAAc,EAAE,CAAC,YAAY,EAAE,kBAAkB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpF;AAkCD;wDACwD;AACxD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAQhF;AAED,mEAAmE;AACnE,wBAAgB,sBAAsB,IAAI,IAAI,CAkB7C;AA6KD,wBAAgB,uCAAuC,IAAI,IAAI,CAM9D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/opencode-magic-context",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.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",
|
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
"README.md"
|
|
31
31
|
],
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "bun build src/index.ts --outdir dist --target node --format esm --external @opencode-ai/plugin --external @huggingface/transformers --external onnxruntime-web --external bun:sqlite --external node:sqlite && tsc --emitDeclarationOnly",
|
|
33
|
+
"build": "bun run build:tui && bun build src/index.ts --outdir dist --target node --format esm --external @opencode-ai/plugin --external @huggingface/transformers --external onnxruntime-web --external bun:sqlite --external node:sqlite && tsc --emitDeclarationOnly",
|
|
34
34
|
"build:tui": "bun scripts/build-tui.ts",
|
|
35
|
+
"check:tui-compiled": "bun run build:tui && git diff --exit-code -- src/tui-compiled && test -z \"$(git status --porcelain -- src/tui-compiled)\"",
|
|
35
36
|
"typecheck": "tsc --noEmit && tsc -p tsconfig.scripts.json",
|
|
36
37
|
"test": "bun test",
|
|
37
38
|
"lint": "biome check .",
|
|
@@ -72,3 +72,23 @@ export function extractLatestAssistantText(messages: unknown): string | null {
|
|
|
72
72
|
.join("\n") || null
|
|
73
73
|
);
|
|
74
74
|
}
|
|
75
|
+
|
|
76
|
+
export function hasLengthCappedOutput(value: unknown): boolean {
|
|
77
|
+
if (Array.isArray(value)) return value.some((item) => hasLengthCappedOutput(item));
|
|
78
|
+
if (!isRecord(value)) return false;
|
|
79
|
+
|
|
80
|
+
if (value.length_capped === true || value.lengthCapped === true) return true;
|
|
81
|
+
const finishReason = value.finish_reason ?? value.finishReason;
|
|
82
|
+
if (typeof finishReason === "string") {
|
|
83
|
+
const normalized = finishReason.toLowerCase();
|
|
84
|
+
if (
|
|
85
|
+
normalized === "length" ||
|
|
86
|
+
normalized === "max_tokens" ||
|
|
87
|
+
normalized === "max_output_tokens"
|
|
88
|
+
) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return Object.values(value).some((item) => hasLengthCappedOutput(item));
|
|
94
|
+
}
|
package/src/shared/redaction.ts
CHANGED
|
@@ -244,6 +244,7 @@ export function hasShareabilitySensitiveText(text: string): boolean {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
export function sanitizeConfigValue(value: unknown, keyPath: string[] = []): unknown {
|
|
247
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") return value;
|
|
247
248
|
const key = keyPath.at(-1) ?? "";
|
|
248
249
|
if (key && isSecretKey(key)) {
|
|
249
250
|
return `<REDACTED:${redactionTypeForKey(key)}>`;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { MagicContextRpcClient } from "./rpc-client";
|
|
7
|
+
import { drainNotifications, isTuiConnected, pushNotification } from "./rpc-notifications";
|
|
7
8
|
import { MagicContextRpcServer } from "./rpc-server";
|
|
8
|
-
import { parseRpcPortFile, rpcPortFilePath } from "./rpc-utils";
|
|
9
|
+
import { parseRpcPortFile, type RpcPortFileRecord, rpcPortDir, rpcPortFilePath } from "./rpc-utils";
|
|
9
10
|
|
|
10
11
|
interface TestServer {
|
|
11
12
|
port: number;
|
|
@@ -56,6 +57,78 @@ function writePortFileForPid(
|
|
|
56
57
|
writeFileSync(portFile, JSON.stringify({ port, pid, started_at: startedAt }), "utf-8");
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
function readNewestPortRecord(storageDir: string, directory: string): RpcPortFileRecord | null {
|
|
61
|
+
const records: RpcPortFileRecord[] = [];
|
|
62
|
+
for (const entry of readdirSync(rpcPortDir(storageDir, directory))) {
|
|
63
|
+
if (!entry.startsWith("port-") || !entry.endsWith(".json")) continue;
|
|
64
|
+
const record = parseRpcPortFile(
|
|
65
|
+
readFileSync(join(rpcPortDir(storageDir, directory), entry), "utf-8"),
|
|
66
|
+
);
|
|
67
|
+
if (record) records.push(record);
|
|
68
|
+
}
|
|
69
|
+
records.sort((a, b) => b.started_at - a.started_at);
|
|
70
|
+
return records[0] ?? null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function waitFor(condition: () => boolean, label: string, timeoutMs = 2_000): Promise<void> {
|
|
74
|
+
const start = Date.now();
|
|
75
|
+
while (Date.now() - start < timeoutMs) {
|
|
76
|
+
if (condition()) return;
|
|
77
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
78
|
+
}
|
|
79
|
+
throw new Error(`Timed out waiting for ${label}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function openSocket(port: number, token: string): Promise<WebSocket> {
|
|
83
|
+
const ws = new WebSocket(`ws://127.0.0.1:${port}/ws?token=${encodeURIComponent(token)}`);
|
|
84
|
+
await new Promise<void>((resolve, reject) => {
|
|
85
|
+
const timeout = setTimeout(() => reject(new Error("socket open timed out")), 2_000);
|
|
86
|
+
ws.addEventListener(
|
|
87
|
+
"open",
|
|
88
|
+
() => {
|
|
89
|
+
clearTimeout(timeout);
|
|
90
|
+
resolve();
|
|
91
|
+
},
|
|
92
|
+
{ once: true },
|
|
93
|
+
);
|
|
94
|
+
ws.addEventListener(
|
|
95
|
+
"error",
|
|
96
|
+
() => {
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
reject(new Error("socket open failed"));
|
|
99
|
+
},
|
|
100
|
+
{ once: true },
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
return ws;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function waitForJsonMessage<T extends { type?: string }>(
|
|
107
|
+
ws: WebSocket,
|
|
108
|
+
predicate: (message: T) => boolean,
|
|
109
|
+
timeoutMs = 2_000,
|
|
110
|
+
): Promise<T> {
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
const timeout = setTimeout(() => {
|
|
113
|
+
ws.removeEventListener("message", onMessage);
|
|
114
|
+
reject(new Error("socket message timed out"));
|
|
115
|
+
}, timeoutMs);
|
|
116
|
+
const onMessage = (event: MessageEvent) => {
|
|
117
|
+
let message: T;
|
|
118
|
+
try {
|
|
119
|
+
message = JSON.parse(String(event.data)) as T;
|
|
120
|
+
} catch {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (!predicate(message)) return;
|
|
124
|
+
clearTimeout(timeout);
|
|
125
|
+
ws.removeEventListener("message", onMessage);
|
|
126
|
+
resolve(message);
|
|
127
|
+
};
|
|
128
|
+
ws.addEventListener("message", onMessage);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
59
132
|
async function startRpcServer(handler: (method: string) => Response | object): Promise<TestServer> {
|
|
60
133
|
const server = createServer(async (req, res) => {
|
|
61
134
|
if (req.method === "GET" && req.url === "/health") {
|
|
@@ -145,9 +218,7 @@ describe("MagicContextRpcClient", () => {
|
|
|
145
218
|
const port = await server.start();
|
|
146
219
|
try {
|
|
147
220
|
// Sanity: the port file carries a non-empty token.
|
|
148
|
-
const record =
|
|
149
|
-
readFileSync(rpcPortFilePath(storageDir, directory), "utf-8"),
|
|
150
|
-
);
|
|
221
|
+
const record = readNewestPortRecord(storageDir, directory);
|
|
151
222
|
expect(typeof record?.token).toBe("string");
|
|
152
223
|
expect((record?.token ?? "").length).toBeGreaterThan(0);
|
|
153
224
|
|
|
@@ -167,6 +238,94 @@ describe("MagicContextRpcClient", () => {
|
|
|
167
238
|
}
|
|
168
239
|
});
|
|
169
240
|
|
|
241
|
+
test("websocket upgrade rejects missing bearer token before a socket is created", async () => {
|
|
242
|
+
const storageDir = makeTempDir();
|
|
243
|
+
const directory = "/repo-ws-auth";
|
|
244
|
+
const server = new MagicContextRpcServer(storageDir, directory);
|
|
245
|
+
const port = await server.start();
|
|
246
|
+
try {
|
|
247
|
+
const res = await fetch(`http://127.0.0.1:${port}/ws`);
|
|
248
|
+
expect(res.status).toBe(401);
|
|
249
|
+
expect(isTuiConnected()).toBe(false);
|
|
250
|
+
} finally {
|
|
251
|
+
server.stop();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("re-hello replaces the previous websocket notification sink", async () => {
|
|
256
|
+
drainNotifications(Number.MAX_SAFE_INTEGER);
|
|
257
|
+
const storageDir = makeTempDir();
|
|
258
|
+
const directory = "/repo-ws-rehello";
|
|
259
|
+
const server = new MagicContextRpcServer(storageDir, directory);
|
|
260
|
+
const port = await server.start();
|
|
261
|
+
const record = readNewestPortRecord(storageDir, directory);
|
|
262
|
+
expect(typeof record?.token).toBe("string");
|
|
263
|
+
|
|
264
|
+
const ws = await openSocket(port, record?.token ?? "");
|
|
265
|
+
const notifications: unknown[] = [];
|
|
266
|
+
ws.addEventListener("message", (event) => {
|
|
267
|
+
const message = JSON.parse(String(event.data)) as {
|
|
268
|
+
type?: string;
|
|
269
|
+
notification?: unknown;
|
|
270
|
+
};
|
|
271
|
+
if (message.type === "notification") notifications.push(message.notification);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
ws.send(JSON.stringify({ type: "hello", token: record?.token, sessionId: "ses_A" }));
|
|
276
|
+
await waitForJsonMessage(ws, (message) => message.type === "hello-ack");
|
|
277
|
+
expect(isTuiConnected("ses_A")).toBe(true);
|
|
278
|
+
|
|
279
|
+
ws.send(JSON.stringify({ type: "hello", token: record?.token, sessionId: "ses_B" }));
|
|
280
|
+
await waitForJsonMessage(ws, (message) => message.type === "hello-ack");
|
|
281
|
+
expect(isTuiConnected("ses_A")).toBe(false);
|
|
282
|
+
expect(isTuiConnected("ses_B")).toBe(true);
|
|
283
|
+
|
|
284
|
+
ws.send(JSON.stringify({ type: "hello", token: record?.token, sessionId: "ses_B" }));
|
|
285
|
+
await waitForJsonMessage(ws, (message) => message.type === "hello-ack");
|
|
286
|
+
pushNotification("live", { ok: true }, "ses_B");
|
|
287
|
+
await waitFor(() => notifications.length >= 1, "one live notification");
|
|
288
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
289
|
+
expect(notifications).toHaveLength(1);
|
|
290
|
+
|
|
291
|
+
ws.close();
|
|
292
|
+
await waitFor(() => !isTuiConnected(), "socket sink cleanup");
|
|
293
|
+
} finally {
|
|
294
|
+
try {
|
|
295
|
+
ws.close();
|
|
296
|
+
} catch {
|
|
297
|
+
// best-effort
|
|
298
|
+
}
|
|
299
|
+
server.stop();
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("same-process servers keep distinct port files during overlap", async () => {
|
|
304
|
+
const storageDir = makeTempDir();
|
|
305
|
+
const directory = "/repo-port-collision";
|
|
306
|
+
const first = new MagicContextRpcServer(storageDir, directory);
|
|
307
|
+
const second = new MagicContextRpcServer(storageDir, directory);
|
|
308
|
+
await first.start();
|
|
309
|
+
const secondPort = await second.start();
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const files = readdirSync(rpcPortDir(storageDir, directory)).filter(
|
|
313
|
+
(entry) => entry.startsWith("port-") && entry.endsWith(".json"),
|
|
314
|
+
);
|
|
315
|
+
expect(files.length).toBeGreaterThanOrEqual(2);
|
|
316
|
+
|
|
317
|
+
first.stop();
|
|
318
|
+
const remaining = readNewestPortRecord(storageDir, directory);
|
|
319
|
+
expect(remaining?.port).toBe(secondPort);
|
|
320
|
+
|
|
321
|
+
const client = new MagicContextRpcClient(storageDir, directory);
|
|
322
|
+
expect((await client.resolveEndpoint())?.port).toBe(secondPort);
|
|
323
|
+
} finally {
|
|
324
|
+
first.stop();
|
|
325
|
+
second.stop();
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
170
329
|
test("gives up when the port file points at a dead server", async () => {
|
|
171
330
|
const storageDir = makeTempDir();
|
|
172
331
|
const directory = "/repo";
|