@cortexkit/opencode-magic-context 0.21.1 → 0.21.2
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/hooks/magic-context/compartment-runner.d.ts +8 -0
- package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/index.js +13 -9
- package/dist/plugin/conflict-warning-hook.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/shared/session-messages-bounded.test.ts +112 -0
|
@@ -3,6 +3,14 @@ import type { CompartmentRunnerDeps } from "./compartment-runner-types";
|
|
|
3
3
|
export interface ActiveCompartmentRun {
|
|
4
4
|
promise: Promise<void>;
|
|
5
5
|
published: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Set to true once the 95%-emergency user-facing notification has been
|
|
8
|
+
* dispatched for this run. Prevents the notification from re-firing on
|
|
9
|
+
* every subsequent transform pass while the same compartment run is
|
|
10
|
+
* still active — which would otherwise persist a fresh ignored user
|
|
11
|
+
* message every pass and drive OpenCode's runLoop break condition false.
|
|
12
|
+
*/
|
|
13
|
+
notificationSent?: boolean;
|
|
6
14
|
}
|
|
7
15
|
export declare function getActiveCompartmentRun(sessionId: string): ActiveCompartmentRun | undefined;
|
|
8
16
|
export declare function markActiveCompartmentRunPublished(sessionId: string): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compartment-runner.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/compartment-runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAEH,KAAK,kBAAkB,EAC1B,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAExE,MAAM,WAAW,oBAAoB;IACjC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"compartment-runner.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/compartment-runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAEH,KAAK,kBAAkB,EAC1B,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAExE,MAAM,WAAW,oBAAoB;IACjC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAID,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAE3F;AAED,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGzE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,CACxC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GACvB,oBAAoB,CAetB;AAYD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,qBAAqB,GAAG,IAAI,CA6BvE;AAED,MAAM,WAAW,2BAA2B;IACxC;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED,wBAAsB,oBAAoB,CACtC,IAAI,EAAE,qBAAqB,EAC3B,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAED,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,YAAY,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform-compartment-phase.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/transform-compartment-phase.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,eAAe,EAAqB,MAAM,sCAAsC,CAAC;AAC/F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAYxD,OAAO,EACH,KAAK,4BAA4B,EAEpC,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAc1D,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE/D;AAED,UAAU,uBAAuB;IAC7B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE;QAAE,qBAAqB,EAAE,OAAO,CAAA;KAAE,CAAC;IAChD,YAAY,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,MAAM,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,EAAE,EAAE,eAAe,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,2BAA2B,EAAE,4BAA4B,GAAG,IAAI,CAAC;IACjE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uBAAuB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,OAAO,6BAA6B,EAAE,kBAAkB,CAAC;IACvF,6EAA6E;IAC7E,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,yFAAyF;IACzF,qCAAqC,CAAC,EAAE,OAAO,CAAC;IAChD,8BAA8B,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5C,yFAAyF;IACzF,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,0FAA0F;IAC1F,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,0EAA0E;IAC1E,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,+EAA+E;IAC/E,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kFAAkF;IAClF,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,yFAAyF;IACzF,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,uEAAuE;IACvE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+FAA+F;IAC/F,2BAA2B,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7D;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC;IAC9E,2BAA2B,EAAE,4BAA4B,GAAG,IAAI,CAAC;IACjE,qBAAqB,EAAE,OAAO,CAAC;IAC/B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,sBAAsB,EAAE,OAAO,CAAC;IAChC,sBAAsB,EAAE,OAAO,CAAC;CACnC,CAAC,
|
|
1
|
+
{"version":3,"file":"transform-compartment-phase.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/transform-compartment-phase.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,eAAe,EAAqB,MAAM,sCAAsC,CAAC;AAC/F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAYxD,OAAO,EACH,KAAK,4BAA4B,EAEpC,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAc1D,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE/D;AAED,UAAU,uBAAuB;IAC7B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE;QAAE,qBAAqB,EAAE,OAAO,CAAA;KAAE,CAAC;IAChD,YAAY,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,MAAM,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,EAAE,EAAE,eAAe,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,2BAA2B,EAAE,4BAA4B,GAAG,IAAI,CAAC;IACjE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uBAAuB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,OAAO,6BAA6B,EAAE,kBAAkB,CAAC;IACvF,6EAA6E;IAC7E,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,yFAAyF;IACzF,qCAAqC,CAAC,EAAE,OAAO,CAAC;IAChD,8BAA8B,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5C,yFAAyF;IACzF,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,0FAA0F;IAC1F,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,0EAA0E;IAC1E,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,+EAA+E;IAC/E,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kFAAkF;IAClF,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,yFAAyF;IACzF,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,mEAAmE;IACnE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,uEAAuE;IACvE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+FAA+F;IAC/F,2BAA2B,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7D;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC;IAC9E,2BAA2B,EAAE,4BAA4B,GAAG,IAAI,CAAC;IACjE,qBAAqB,EAAE,OAAO,CAAC;IAC/B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,sBAAsB,EAAE,OAAO,CAAC;IAChC,sBAAsB,EAAE,OAAO,CAAC;CACnC,CAAC,CAyRD"}
|
package/dist/index.js
CHANGED
|
@@ -157007,7 +157007,10 @@ async function getSessionMessages(client, sessionId) {
|
|
|
157007
157007
|
try {
|
|
157008
157008
|
const c = client;
|
|
157009
157009
|
if (typeof c.session?.messages === "function") {
|
|
157010
|
-
const result = await c.session.messages({
|
|
157010
|
+
const result = await c.session.messages({
|
|
157011
|
+
path: { id: sessionId },
|
|
157012
|
+
query: { limit: 50 }
|
|
157013
|
+
});
|
|
157011
157014
|
return result?.data ?? [];
|
|
157012
157015
|
}
|
|
157013
157016
|
} catch (error51) {
|
|
@@ -163695,7 +163698,7 @@ async function runHistorianPrompt(args) {
|
|
|
163695
163698
|
}
|
|
163696
163699
|
const messagesResponse = await client.session.messages({
|
|
163697
163700
|
path: { id: agentSessionId },
|
|
163698
|
-
query: { directory: sessionDirectory }
|
|
163701
|
+
query: { directory: sessionDirectory, limit: 50 }
|
|
163699
163702
|
});
|
|
163700
163703
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
163701
163704
|
preferResponseOnMissingData: true
|
|
@@ -165653,7 +165656,7 @@ async function runCompressorPass(args) {
|
|
|
165653
165656
|
});
|
|
165654
165657
|
const messagesResponse = await client.session.messages({
|
|
165655
165658
|
path: { id: agentSessionId },
|
|
165656
|
-
query: { directory }
|
|
165659
|
+
query: { directory, limit: 50 }
|
|
165657
165660
|
});
|
|
165658
165661
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
165659
165662
|
preferResponseOnMissingData: true
|
|
@@ -167265,7 +167268,7 @@ async function runSidekick(deps) {
|
|
|
167265
167268
|
}, { timeoutMs: deps.config.timeout_ms, fallbackModels, callContext: "sidekick" });
|
|
167266
167269
|
const messagesResponse = await deps.client.session.messages({
|
|
167267
167270
|
path: { id: agentSessionId },
|
|
167268
|
-
query: { directory: deps.sessionDirectory ?? deps.projectPath }
|
|
167271
|
+
query: { directory: deps.sessionDirectory ?? deps.projectPath, limit: 50 }
|
|
167269
167272
|
});
|
|
167270
167273
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
167271
167274
|
preferResponseOnMissingData: true
|
|
@@ -168513,7 +168516,7 @@ async function runKeyFilesLlm(args) {
|
|
|
168513
168516
|
});
|
|
168514
168517
|
const messagesResponse = await args.client.session.messages({
|
|
168515
168518
|
path: { id: agentSessionId },
|
|
168516
|
-
query: { directory: args.projectPath }
|
|
168519
|
+
query: { directory: args.projectPath, limit: 50 }
|
|
168517
168520
|
});
|
|
168518
168521
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
168519
168522
|
preferResponseOnMissingData: true
|
|
@@ -168709,7 +168712,7 @@ If no promotions are warranted, return empty arrays. Always consume reviewed can
|
|
|
168709
168712
|
});
|
|
168710
168713
|
const messagesResponse = await args.client.session.messages({
|
|
168711
168714
|
path: { id: agentSessionId },
|
|
168712
|
-
query: { directory: args.sessionDirectory }
|
|
168715
|
+
query: { directory: args.sessionDirectory, limit: 50 }
|
|
168713
168716
|
});
|
|
168714
168717
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
168715
168718
|
preferResponseOnMissingData: true
|
|
@@ -169014,7 +169017,7 @@ async function runDream(args) {
|
|
|
169014
169017
|
}
|
|
169015
169018
|
const messagesResponse = await args.client.session.messages({
|
|
169016
169019
|
path: { id: agentSessionId },
|
|
169017
|
-
query: { directory: args.sessionDirectory ?? args.projectIdentity }
|
|
169020
|
+
query: { directory: args.sessionDirectory ?? args.projectIdentity, limit: 50 }
|
|
169018
169021
|
});
|
|
169019
169022
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
169020
169023
|
preferResponseOnMissingData: true
|
|
@@ -169337,7 +169340,7 @@ Only include notes whose conditions you could definitively evaluate. Skip notes
|
|
|
169337
169340
|
});
|
|
169338
169341
|
const messagesResponse = await args.client.session.messages({
|
|
169339
169342
|
path: { id: agentSessionId },
|
|
169340
|
-
query: { directory: args.sessionDirectory ?? args.projectIdentity }
|
|
169343
|
+
query: { directory: args.sessionDirectory ?? args.projectIdentity, limit: 50 }
|
|
169341
169344
|
});
|
|
169342
169345
|
const messages = normalizeSDKResponse(messagesResponse, [], {
|
|
169343
169346
|
preferResponseOnMissingData: true
|
|
@@ -172477,7 +172480,8 @@ async function runCompartmentPhase(args) {
|
|
|
172477
172480
|
sessionLog(args.sessionId, "transform: cannot force-start compartment agent without client");
|
|
172478
172481
|
}
|
|
172479
172482
|
if (activeRun) {
|
|
172480
|
-
if (args.client) {
|
|
172483
|
+
if (args.client && !activeRun.notificationSent) {
|
|
172484
|
+
activeRun.notificationSent = true;
|
|
172481
172485
|
const notifParams = args.getNotificationParams?.() ?? {};
|
|
172482
172486
|
sendIgnoredMessage(args.client, args.sessionId, `⏳ Context at ${args.contextUsage.percentage.toFixed(0)}% — Magic Context is compacting history before continuing. This may take up to 2 minutes.`, notifParams);
|
|
172483
172487
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conflict-warning-hook.d.ts","sourceRoot":"","sources":["../../src/plugin/conflict-warning-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"conflict-warning-hook.d.ts","sourceRoot":"","sources":["../../src/plugin/conflict-warning-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAmLlE;;GAEG;AACH,wBAAsB,mBAAmB,CACrC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,cAAc,GAC/B,OAAO,CAAC,IAAI,CAAC,CA+Cf;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAqHf;AAkCD;;;GAGG;AACH,wBAAsB,wBAAwB,CAC1C,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAkEf"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
|
|
3
|
+
// Regression guard: every `client.session.messages(...)` call in plugin code
|
|
4
|
+
// must include `limit` in its `query`. Without `limit`, OpenCode's legacy
|
|
5
|
+
// messages endpoint hydrates the ENTIRE session into RAM — catastrophic on
|
|
6
|
+
// huge sessions (10k+ messages) which is exactly when Magic Context shines.
|
|
7
|
+
//
|
|
8
|
+
// Background: the plugin only ever needs the latest assistant message of a
|
|
9
|
+
// helper subagent (historian / dreamer / sidekick / key-files / user-memory)
|
|
10
|
+
// or a bounded tail of the active session (conflict-warning cleanup). Both
|
|
11
|
+
// fit comfortably in `limit: 50` with massive headroom.
|
|
12
|
+
//
|
|
13
|
+
// If you add a new `session.messages(...)` call, this test fails until you
|
|
14
|
+
// include a `limit` in the query. The test does a static source-text scan
|
|
15
|
+
// so it catches the issue at lint-time without runtime mocking overhead.
|
|
16
|
+
|
|
17
|
+
import { describe, expect, it } from "bun:test";
|
|
18
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
|
|
21
|
+
const PLUGIN_SRC = join(__dirname, "..", "..", "src");
|
|
22
|
+
|
|
23
|
+
/** Recursively walk a directory and return paths of all `.ts` files
|
|
24
|
+
* excluding `.test.ts`, `.gen.ts`, and node_modules. */
|
|
25
|
+
function walkSourceFiles(dir: string, out: string[] = []): string[] {
|
|
26
|
+
for (const entry of readdirSync(dir)) {
|
|
27
|
+
if (entry === "node_modules" || entry === "dist") continue;
|
|
28
|
+
const full = join(dir, entry);
|
|
29
|
+
const s = statSync(full);
|
|
30
|
+
if (s.isDirectory()) {
|
|
31
|
+
walkSourceFiles(full, out);
|
|
32
|
+
} else if (
|
|
33
|
+
s.isFile() &&
|
|
34
|
+
entry.endsWith(".ts") &&
|
|
35
|
+
!entry.endsWith(".test.ts") &&
|
|
36
|
+
!entry.endsWith(".gen.ts")
|
|
37
|
+
) {
|
|
38
|
+
out.push(full);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Find call expressions matching `session.messages(...)` and return the
|
|
45
|
+
* source text of each call (from "session.messages(" to its matching ")"). */
|
|
46
|
+
function findSessionMessagesCalls(source: string): string[] {
|
|
47
|
+
const calls: string[] = [];
|
|
48
|
+
const needle = "session.messages(";
|
|
49
|
+
let i = 0;
|
|
50
|
+
while (true) {
|
|
51
|
+
const idx = source.indexOf(needle, i);
|
|
52
|
+
if (idx === -1) break;
|
|
53
|
+
// Skip comments — if the line starts with `*` or `//` we ignore.
|
|
54
|
+
const lineStart = source.lastIndexOf("\n", idx) + 1;
|
|
55
|
+
const linePrefix = source.slice(lineStart, idx).trimStart();
|
|
56
|
+
if (linePrefix.startsWith("//") || linePrefix.startsWith("*")) {
|
|
57
|
+
i = idx + needle.length;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
// Walk forward to find the matching closing paren, respecting
|
|
61
|
+
// brace/bracket/paren nesting.
|
|
62
|
+
let depth = 1;
|
|
63
|
+
let j = idx + needle.length;
|
|
64
|
+
while (j < source.length && depth > 0) {
|
|
65
|
+
const ch = source[j];
|
|
66
|
+
if (ch === "(" || ch === "{" || ch === "[") depth++;
|
|
67
|
+
else if (ch === ")" || ch === "}" || ch === "]") depth--;
|
|
68
|
+
j++;
|
|
69
|
+
}
|
|
70
|
+
if (depth === 0) {
|
|
71
|
+
calls.push(source.slice(idx, j));
|
|
72
|
+
}
|
|
73
|
+
i = j;
|
|
74
|
+
}
|
|
75
|
+
return calls;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe("session.messages() callsites must include query.limit", () => {
|
|
79
|
+
const files = walkSourceFiles(PLUGIN_SRC);
|
|
80
|
+
const violations: Array<{ file: string; callText: string }> = [];
|
|
81
|
+
|
|
82
|
+
for (const file of files) {
|
|
83
|
+
// Skip the regression test itself
|
|
84
|
+
if (file.endsWith("session-messages-bounded.test.ts")) continue;
|
|
85
|
+
const source = readFileSync(file, "utf-8");
|
|
86
|
+
const calls = findSessionMessagesCalls(source);
|
|
87
|
+
for (const call of calls) {
|
|
88
|
+
// The call text spans from "session.messages(" to the closing
|
|
89
|
+
// ")". A correct call must mention `limit` (the SDK key) somewhere
|
|
90
|
+
// inside that span — typically `query: { ..., limit: N }`.
|
|
91
|
+
if (!/\blimit\b/.test(call)) {
|
|
92
|
+
violations.push({ file: file.replace(PLUGIN_SRC, "<plugin>/src"), callText: call });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
it("has no unbounded session.messages() calls in plugin code", () => {
|
|
98
|
+
if (violations.length > 0) {
|
|
99
|
+
const report = violations
|
|
100
|
+
.map(
|
|
101
|
+
(v) =>
|
|
102
|
+
`\n in ${v.file}:\n ${v.callText.replace(/\n/g, "\n ").slice(0, 300)}`,
|
|
103
|
+
)
|
|
104
|
+
.join("\n");
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Found ${violations.length} unbounded session.messages() call(s). ` +
|
|
107
|
+
`Add \`limit: 50\` (or appropriate bound) to query.${report}`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
expect(violations).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
});
|