@evandu/super-pi 0.1.132 → 0.1.133

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 (46) hide show
  1. package/dist/.super-pi-check-inputs.json +4 -0
  2. package/dist/client/.super-pi-client-sources.json +5 -5
  3. package/dist/client/assets/{CodeMirrorEditor-ktyL2PcX.js → CodeMirrorEditor-CZZfGD0U.js} +2 -2
  4. package/dist/client/assets/{dist-8pdzh6h1.js → dist-0FH-oFco.js} +1 -1
  5. package/dist/client/assets/{dist-Bg9zLfex.js → dist-4Pts9xPO.js} +1 -1
  6. package/dist/client/assets/{dist-Hj_dWgPr.js → dist-B5ruVtUG.js} +1 -1
  7. package/dist/client/assets/{dist-Cu75NyQ0.js → dist-BJinarhP.js} +1 -1
  8. package/dist/client/assets/{dist-CMscy2Nn.js → dist-BffE00vI.js} +1 -1
  9. package/dist/client/assets/{dist-CoszOk5k.js → dist-BtTFCNVQ.js} +1 -1
  10. package/dist/client/assets/{dist-DvHghuGd.js → dist-C2nuBCP4.js} +1 -1
  11. package/dist/client/assets/{dist-BAJCrbnX.js → dist-CApAttfj.js} +1 -1
  12. package/dist/client/assets/{dist-CPLCQDqy.js → dist-CE5BnwJa.js} +1 -1
  13. package/dist/client/assets/{dist-BRjGckmQ.js → dist-CFPXj4ws.js} +1 -1
  14. package/dist/client/assets/{dist-BjZUYxLd.js → dist-CUKaDTyO.js} +1 -1
  15. package/dist/client/assets/{dist-vsMBLoxV.js → dist-CYD2SqmG.js} +1 -1
  16. package/dist/client/assets/{dist-B8OZ-n_C.js → dist-CxsunljU.js} +1 -1
  17. package/dist/client/assets/{dist-D_Zdnhm8.js → dist-D4x1XTQb.js} +1 -1
  18. package/dist/client/assets/{dist-Zhdkfvng.js → dist-DHqqpxo4.js} +1 -1
  19. package/dist/client/assets/{dist-DNZMXYNq.js → dist-DOYWHMIq.js} +1 -1
  20. package/dist/client/assets/{dist-BJi5HmH4.js → dist-DQf5nNxa.js} +1 -1
  21. package/dist/client/assets/{dist-CgmISAMr.js → dist-DbbBgSwL.js} +1 -1
  22. package/dist/client/assets/{dist-Bv6P2Rcp.js → dist-LFfokYJs.js} +1 -1
  23. package/dist/client/assets/{dist-C6iAHGvb.js → dist-aL2NGji_.js} +1 -1
  24. package/dist/client/assets/{dist--igDAzRS.js → dist-azDN8a65.js} +1 -1
  25. package/dist/client/assets/{dist-60usx__o.js → dist-fYp8Xwrx.js} +1 -1
  26. package/dist/client/assets/index-YEN4ysHF.js +100 -0
  27. package/dist/client/index.html +1 -1
  28. package/dist/server/agents.js +21 -1
  29. package/dist/server/application/feishu-service.d.ts +57 -10
  30. package/dist/server/application/feishu-service.js +118 -24
  31. package/dist/server/application/run-service.js +15 -1
  32. package/dist/server/feishu-cli.d.ts +18 -2
  33. package/dist/server/feishu-cli.js +46 -12
  34. package/dist/server/feishu-config.d.ts +68 -0
  35. package/dist/server/feishu-config.js +84 -0
  36. package/dist/server/feishu.d.ts +187 -0
  37. package/dist/server/feishu.js +695 -0
  38. package/dist/server/http-server.js +20 -0
  39. package/dist/server/persistence/sqlite-state-repository.js +18 -0
  40. package/dist/server/pi-session-events.js +10 -2
  41. package/dist/server/pi-session-pool.d.ts +33 -1
  42. package/dist/server/pi-session-pool.js +20 -2
  43. package/dist/server/runtime.d.ts +26 -1
  44. package/dist/server/runtime.js +52 -9
  45. package/package.json +10 -1
  46. package/dist/client/assets/index-ChyemF2I.js +0 -100
@@ -913,6 +913,26 @@ export function createHttpServer(options) {
913
913
  }
914
914
  return;
915
915
  }
916
+ /**
917
+ * CHECKS THE CONNECTION, and says why it does not work when it does not.
918
+ *
919
+ * The status already verifies the credential against Feishu, so this is the same answer shaped
920
+ * for a button - the difference is that a failure comes back as a MESSAGE AND A HINT rather
921
+ * than as a state the panel has to interpret.
922
+ */
923
+ if (url.pathname === "/api/feishu/verify" && request.method === "POST") {
924
+ if (!context.feishu) {
925
+ response.writeHead(503).end();
926
+ return;
927
+ }
928
+ if (!isLocalOrPrivateAddress(remote)) {
929
+ json(403, { error: "测试飞书连接只允许来自本机或局域网的请求。" });
930
+ return;
931
+ }
932
+ const result = await context.feishu.verify();
933
+ json(result.ok ? 200 : 400, result);
934
+ return;
935
+ }
916
936
  if (url.pathname === "/api/shutdown") {
917
937
  if (request.method !== "POST" || !isLocalOrPrivateAddress(remote)) {
918
938
  response.writeHead(403, { "content-type": "text/plain; charset=utf-8" }).end("Shutdown requires a loopback or private network POST");
@@ -120,6 +120,24 @@ export class SqliteStateRepository {
120
120
  }
121
121
  else if (transaction.kind === "collections.remove") {
122
122
  this.db.prepare("DELETE FROM collections WHERE name=?").run(transaction.name);
123
+ // AND THE BASE SNAPSHOT MUST STOP CARRYING IT TOO. Deleting only the overlay row was not
124
+ // enough: this loader treats an overlay as a PURE INCREMENT - "no row for this name" means
125
+ // "read it from the base snapshot" - so the base's stale array came back on the next load,
126
+ // and a scheduled task the user had deleted was resurrected and ran again on its old
127
+ // schedule (ledger TEST-038). "This collection is empty" is stored as the ABSENCE of the key
128
+ // (app-state-repository.ts), so the base has to lose the key as well - the row's deletion
129
+ // alone cannot express it, because the base is where the value would come from.
130
+ //
131
+ // The json backend has no incremental path and falls back to a whole-state save, which is
132
+ // why it was always right; that asymmetry is what makes this a defect and not a choice.
133
+ const base = this.db.prepare("SELECT json FROM state WHERE id=1").get();
134
+ if (base) {
135
+ const parsed = JSON.parse(String(base.json));
136
+ if (transaction.name in parsed) {
137
+ delete parsed[transaction.name];
138
+ this.db.prepare("UPDATE state SET json=? WHERE id=1").run(JSON.stringify(parsed));
139
+ }
140
+ }
123
141
  }
124
142
  else if (transaction.kind === "conversation.messages") {
125
143
  // A DIFF, NOT A REWRITE. This used to DELETE the conversation's rows and re-INSERT every
@@ -16,7 +16,7 @@
16
16
  * be announced as unhandled is still announced as unhandled - this bridge changes WHERE the events come
17
17
  * from, not what they mean.
18
18
  */
19
- import { describeToolCall, describeToolCallArgs, extractPartialResultText } from "./tool-calls.js";
19
+ import { describeToolCall, describeToolCallArgs, extractPartialResultText, readQuestionCall } from "./tool-calls.js";
20
20
  import { toolTargetPath } from "./file-changes.js";
21
21
  /** Keeps only real strings, so a malformed queue snapshot cannot put objects into a broadcast. */
22
22
  const textList = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : undefined;
@@ -254,7 +254,15 @@ export function handleSessionEvent(event, queue, handlers, report, failure) {
254
254
  return true;
255
255
  const name = record.toolName ?? "unknown";
256
256
  try {
257
- handlers.onToolCall?.({ id, name, detail: describeToolCall(name, record.args), args: describeToolCallArgs(record.args) });
257
+ // A `question` CALL IS READ HERE, AT `tool_execution_start` - the same place the one-shot path
258
+ // reads it, and for the same reason: the card on screen must be the tool call the model is
259
+ // actually blocked in, with the same identity and the same lifecycle. This path left the
260
+ // question out, so the host never saw a pending question and the model was told the session
261
+ // had no question waiting for an answer - the `question` tool was simply dead on the SDK
262
+ // session path, which is the PRODUCTION default (ledger TEST-037). Two paths must give the
263
+ // same answer to "what is a question call".
264
+ const question = readQuestionCall(name, record.args);
265
+ handlers.onToolCall?.({ id, name, detail: describeToolCall(name, record.args), args: describeToolCallArgs(record.args), ...(question ? { question } : {}) });
258
266
  }
259
267
  catch (error) {
260
268
  console.warn(`PiRpcRuntime: the tool call handler threw: ${error instanceof Error ? error.message : "unknown error"}`);
@@ -87,6 +87,22 @@ export declare class PiSessionPool {
87
87
  * where that gets fixed.
88
88
  */
89
89
  private readonly modelEpoch;
90
+ /**
91
+ * Mints the OTHER per-session credentials the extensions need, at the moment the session is born.
92
+ *
93
+ * WHY A SECOND CALLBACK AND NOT PART OF THE CAPABILITY. The memory and knowledge extensions each
94
+ * need their own credential - the memory one even carries per-caller flags (read-only for a
95
+ * sub-agent) that a single session capability cannot express. The one-shot path has always minted
96
+ * them per turn; the SESSION path minted none, so those two extensions returned before registering
97
+ * a single tool or injecting a single line of prompt, and both features were absent in production -
98
+ * the SDK session path is the default backend, and only the test scripts use the one-shot path
99
+ * (ledger TEST-039).
100
+ *
101
+ * They are minted HERE, where the session is born, for the same reason the capability is: the
102
+ * extension reads its environment ONCE, when it is loaded, so a token minted per turn would be
103
+ * replaced before the second turn and the extension would still be holding the first turn's value.
104
+ */
105
+ private readonly mintExtensionTokens;
90
106
  private readonly sessions;
91
107
  constructor(
92
108
  /** Where Pi keeps its session files. */
@@ -122,7 +138,23 @@ export declare class PiSessionPool {
122
138
  * settings window makes every live session unable to reach the new model, and the next turn is
123
139
  * where that gets fixed.
124
140
  */
125
- modelEpoch?: () => number);
141
+ modelEpoch?: () => number,
142
+ /**
143
+ * Mints the OTHER per-session credentials the extensions need, at the moment the session is born.
144
+ *
145
+ * WHY A SECOND CALLBACK AND NOT PART OF THE CAPABILITY. The memory and knowledge extensions each
146
+ * need their own credential - the memory one even carries per-caller flags (read-only for a
147
+ * sub-agent) that a single session capability cannot express. The one-shot path has always minted
148
+ * them per turn; the SESSION path minted none, so those two extensions returned before registering
149
+ * a single tool or injecting a single line of prompt, and both features were absent in production -
150
+ * the SDK session path is the default backend, and only the test scripts use the one-shot path
151
+ * (ledger TEST-039).
152
+ *
153
+ * They are minted HERE, where the session is born, for the same reason the capability is: the
154
+ * extension reads its environment ONCE, when it is loaded, so a token minted per turn would be
155
+ * replaced before the second turn and the extension would still be holding the first turn's value.
156
+ */
157
+ mintExtensionTokens?: (conversationId: string) => Promise<Record<string, string>> | Record<string, string>);
126
158
  /**
127
159
  * The session for a conversation, built if there is none and REPLACED if the live one is stale.
128
160
  *
@@ -49,6 +49,7 @@ export class PiSessionPool {
49
49
  factory;
50
50
  mintCapability;
51
51
  modelEpoch;
52
+ mintExtensionTokens;
52
53
  sessions = new Map();
53
54
  constructor(
54
55
  /** Where Pi keeps its session files. */
@@ -84,7 +85,23 @@ export class PiSessionPool {
84
85
  * settings window makes every live session unable to reach the new model, and the next turn is
85
86
  * where that gets fixed.
86
87
  */
87
- modelEpoch = () => 0) {
88
+ modelEpoch = () => 0,
89
+ /**
90
+ * Mints the OTHER per-session credentials the extensions need, at the moment the session is born.
91
+ *
92
+ * WHY A SECOND CALLBACK AND NOT PART OF THE CAPABILITY. The memory and knowledge extensions each
93
+ * need their own credential - the memory one even carries per-caller flags (read-only for a
94
+ * sub-agent) that a single session capability cannot express. The one-shot path has always minted
95
+ * them per turn; the SESSION path minted none, so those two extensions returned before registering
96
+ * a single tool or injecting a single line of prompt, and both features were absent in production -
97
+ * the SDK session path is the default backend, and only the test scripts use the one-shot path
98
+ * (ledger TEST-039).
99
+ *
100
+ * They are minted HERE, where the session is born, for the same reason the capability is: the
101
+ * extension reads its environment ONCE, when it is loaded, so a token minted per turn would be
102
+ * replaced before the second turn and the extension would still be holding the first turn's value.
103
+ */
104
+ mintExtensionTokens = () => ({})) {
88
105
  this.sessionDir = sessionDir;
89
106
  this.repositoryRoot = repositoryRoot;
90
107
  this.agentDir = agentDir;
@@ -92,6 +109,7 @@ export class PiSessionPool {
92
109
  this.factory = factory;
93
110
  this.mintCapability = mintCapability;
94
111
  this.modelEpoch = modelEpoch;
112
+ this.mintExtensionTokens = mintExtensionTokens;
95
113
  }
96
114
  /**
97
115
  * The session for a conversation, built if there is none and REPLACED if the live one is stale.
@@ -138,7 +156,7 @@ export class PiSessionPool {
138
156
  // server separately requires that a turn is RUNNING for the token to be usable, which is what keeps a
139
157
  // longer-lived token from being a weaker credential than a per-turn one.
140
158
  const capability = this.mintCapability(request.conversationId);
141
- const extensionEnv = { ...request.extensionEnv, [CAPABILITY_ENV]: capability, [TOOLS_CAPABILITY_ENV]: capability };
159
+ const extensionEnv = { ...request.extensionEnv, [CAPABILITY_ENV]: capability, [TOOLS_CAPABILITY_ENV]: capability, ...await this.mintExtensionTokens(request.conversationId) };
142
160
  const building = this.build({ ...request, extensionEnv }, fingerprint);
143
161
  if (process.env.SUPER_PI_TRACE_LATENCY === "1")
144
162
  console.warn(`[super-pi latency] conversation=${request.conversationId} phase=session.build.begin`);
@@ -164,7 +164,11 @@ export interface AnswerInput {
164
164
  onToolPreparing?(call: ToolCallPreparing): void;
165
165
  /** Called while the primary model is emitting reasoning, so the UI can show live progress. A call means
166
166
  * the model is STILL THINKING - there is no separate flag, because Pi does not send one. */
167
- onThinking?(delta: string): void;
167
+ /** Called while the primary model is emitting reasoning, so the UI can show live progress. A call means
168
+ * the model is STILL THINKING - there is no separate flag, because Pi does not send one.
169
+ * The handler may be async: the reasoning signal is the same "the model is producing" signal as
170
+ * a text delta, and the caller waits for it so the state it writes is ordered with the delta. */
171
+ onThinking?(delta: string): void | Promise<void>;
168
172
  /** Called when the reasoning phase ends - the only signal that it has. */
169
173
  onThinkingEnd?(): void;
170
174
  }
@@ -560,6 +564,12 @@ export declare class PiRpcRuntime implements AgentRuntime {
560
564
  private readonly options;
561
565
  private memory?;
562
566
  setMemory(memory: ActiveMemory): void;
567
+ /**
568
+ * The memory and knowledge credentials minted for each LIVE session, so they can be released when
569
+ * the session goes. A credential whose session is gone must not keep working - that is exactly the
570
+ * shape of a secret with no owner.
571
+ */
572
+ private readonly sessionExtensionTokens;
563
573
  private knowledge?;
564
574
  setKnowledge(knowledge: KnowledgeBase): void;
565
575
  private readonly runs;
@@ -841,6 +851,21 @@ export declare class PiRpcRuntime implements AgentRuntime {
841
851
  * The TODO ENDPOINT is the one value here that is NOT per-turn derived from disk: it is this server's
842
852
  * own address, so it is the same string on every turn and is read once from the constructor's options.
843
853
  */
854
+ /**
855
+ * THE MEMORY AND KNOWLEDGE CREDENTIALS FOR A NEW SESSION.
856
+ *
857
+ * Both extensions read their environment ONCE, when they are loaded, so these are minted where the
858
+ * session is born - the same place, and for the same reason, as the session's capability. The
859
+ * one-shot path has always minted them (per turn); the session path minted none, so the memory and
860
+ * knowledge extensions returned before registering a tool or injecting a prompt, and both features
861
+ * were missing in production - the SDK session path is the DEFAULT backend, and only the test
862
+ * scripts take the one-shot path (ledger TEST-039).
863
+ *
864
+ * The endpoints are published as global environment variables (index.ts), and they are repeated
865
+ * here so the extension's contract is stated in one place: an endpoint and a credential travel
866
+ * together, and the credential is what proves the caller.
867
+ */
868
+ private mintSessionExtensionTokens;
844
869
  /**
845
870
  * The environment Super Pi's extensions read when they are loaded.
846
871
  *
@@ -289,6 +289,12 @@ export class PiRpcRuntime {
289
289
  options;
290
290
  memory;
291
291
  setMemory(memory) { this.memory = memory; }
292
+ /**
293
+ * The memory and knowledge credentials minted for each LIVE session, so they can be released when
294
+ * the session goes. A credential whose session is gone must not keep working - that is exactly the
295
+ * shape of a secret with no owner.
296
+ */
297
+ sessionExtensionTokens = new Map();
292
298
  knowledge;
293
299
  setKnowledge(knowledge) { this.knowledge = knowledge; }
294
300
  runs = new Map();
@@ -466,7 +472,16 @@ export class PiRpcRuntime {
466
472
  // ABOUT that session and stop being true with it.
467
473
  this.sessionHosts.delete(conversationId);
468
474
  this.reportedSessionFiles.delete(conversationId);
469
- }, options.sessionFactory ?? createPiSession, (conversationId) => mintCapability(conversationId), () => this.modelEpoch);
475
+ // The session held these credentials too, so they die with it.
476
+ const tokens = this.sessionExtensionTokens.get(conversationId);
477
+ if (tokens) {
478
+ if (tokens.memoryToken)
479
+ this.memory?.release(tokens.memoryToken);
480
+ if (tokens.knowledgeToken)
481
+ this.knowledge?.release(tokens.knowledgeToken);
482
+ this.sessionExtensionTokens.delete(conversationId);
483
+ }
484
+ }, options.sessionFactory ?? createPiSession, (conversationId) => mintCapability(conversationId), () => this.modelEpoch, (conversationId) => this.mintSessionExtensionTokens(conversationId));
470
485
  }
471
486
  /**
472
487
  * The configuration of the session that was built for a conversation, for tests to inspect.
@@ -628,6 +643,37 @@ export class PiRpcRuntime {
628
643
  * The TODO ENDPOINT is the one value here that is NOT per-turn derived from disk: it is this server's
629
644
  * own address, so it is the same string on every turn and is read once from the constructor's options.
630
645
  */
646
+ /**
647
+ * THE MEMORY AND KNOWLEDGE CREDENTIALS FOR A NEW SESSION.
648
+ *
649
+ * Both extensions read their environment ONCE, when they are loaded, so these are minted where the
650
+ * session is born - the same place, and for the same reason, as the session's capability. The
651
+ * one-shot path has always minted them (per turn); the session path minted none, so the memory and
652
+ * knowledge extensions returned before registering a tool or injecting a prompt, and both features
653
+ * were missing in production - the SDK session path is the DEFAULT backend, and only the test
654
+ * scripts take the one-shot path (ledger TEST-039).
655
+ *
656
+ * The endpoints are published as global environment variables (index.ts), and they are repeated
657
+ * here so the extension's contract is stated in one place: an endpoint and a credential travel
658
+ * together, and the credential is what proves the caller.
659
+ */
660
+ async mintSessionExtensionTokens(conversationId) {
661
+ const minted = {};
662
+ const env = {};
663
+ if (this.memory && (await this.memory.configuration()).enabled) {
664
+ minted.memoryToken = this.memory.issue(conversationId);
665
+ env.SUPER_PI_MEMORY_TOKEN = minted.memoryToken;
666
+ env.SUPER_PI_MEMORY_ENDPOINT = process.env.SUPER_PI_MEMORY_ENDPOINT ?? "";
667
+ }
668
+ if (this.knowledge && this.knowledge.enabled()) {
669
+ minted.knowledgeToken = this.knowledge.issue(conversationId);
670
+ env.SUPER_PI_KNOWLEDGE_TOKEN = minted.knowledgeToken;
671
+ env.SUPER_PI_KNOWLEDGE_ENDPOINT = process.env.SUPER_PI_KNOWLEDGE_ENDPOINT ?? "";
672
+ }
673
+ if (minted.memoryToken || minted.knowledgeToken)
674
+ this.sessionExtensionTokens.set(conversationId, minted);
675
+ return env;
676
+ }
631
677
  /**
632
678
  * The environment Super Pi's extensions read when they are loaded.
633
679
  *
@@ -906,13 +952,10 @@ export class PiRpcRuntime {
906
952
  // pushes the terminal item when Pi settles, and the consumer below reads it. This promise
907
953
  // waits for that signal, and the race with the prompt's own failure is what keeps a turn that
908
954
  // never settles from hanging forever - a hung turn is a conversation that never answers.
909
- await Promise.race([
910
- queue.settledSignal,
911
- // The safety net. A session that was aborted, or a Pi that died mid-turn, may never settle,
912
- // and the turn still has to end - otherwise the conversation is stuck on a turn that will
913
- // never produce anything and the next message can never be sent.
914
- new Promise((resolvePromise) => setTimeout(resolvePromise, 5_000)),
915
- ]);
955
+ // `prompt()` only means Pi accepted the request. Do not end this generator on a wall-clock
956
+ // timeout: the session may still be streaming or executing a tool, and ending here drops
957
+ // subsequent events while falsely allowing the next prompt onto a busy session.
958
+ await queue.settledSignal;
916
959
  traceLatency(conversation.id, "session.settled", latencyStartedAt);
917
960
  // THE REVERT BOUNDARY, recorded at the same moment the one-shot path records it: the turn has
918
961
  // settled, so every entry this turn wrote - the user message included - is in the transcript.
@@ -1144,7 +1187,7 @@ export class PiRpcRuntime {
1144
1187
  if (segment.kind === "text")
1145
1188
  yield segment.text;
1146
1189
  else if (segment.kind === "thinking")
1147
- input.onThinking?.(segment.text);
1190
+ await input.onThinking?.(segment.text);
1148
1191
  else
1149
1192
  input.onThinkingEnd?.();
1150
1193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evandu/super-pi",
3
- "version": "0.1.132",
3
+ "version": "0.1.133",
4
4
  "description": "Web control plane and companion for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -88,6 +88,10 @@
88
88
  "scripts/followup.test.mjs",
89
89
  "scripts/pi-followup-queue.test.mjs",
90
90
  "scripts/background-result-delivery.test.mjs",
91
+ "scripts/thinking-status-promotion.test.mjs",
92
+ "scripts/question-call-session-path.test.mjs",
93
+ "scripts/withdrawn-message-persistence.test.mjs",
94
+ "scripts/git-write-endpoint-guard.test.mjs",
91
95
  "scripts/question-stop.test.mjs",
92
96
  "scripts/background-result-body.test.mjs",
93
97
  "scripts/question-request-size.test.mjs",
@@ -119,10 +123,12 @@
119
123
  "scripts/styles-no-dangling.test.mjs",
120
124
  "scripts/theme-no-hardcoded-colours.test.mjs",
121
125
  "scripts/agent-config-regression.test.mjs",
126
+ "scripts/agents-directory-resilience.test.mjs",
122
127
  "scripts/model-filter.test.mjs",
123
128
  "scripts/settings-default-model.test.mjs",
124
129
  "scripts/provider-registry.test.mjs",
125
130
  "scripts/provider-pi-reads.test.mjs",
131
+ "scripts/session-extension-credentials.test.mjs",
126
132
  "scripts/active-memory.test.mjs",
127
133
  "scripts/knowledge-base.test.mjs",
128
134
  "scripts/knowledge-import-path.test.mjs",
@@ -171,6 +177,7 @@
171
177
  "scripts/scheduled-tasks-browser.test.mjs",
172
178
  "scripts/md-render-browser.test.mjs",
173
179
  "scripts/right-sidebar-browser.test.mjs",
180
+ "scripts/rail-workspace-follows-session-browser.test.mjs",
174
181
  "scripts/sidebar-tools-browser.test.mjs",
175
182
  "scripts/tool-runtime-browser.test.mjs",
176
183
  "scripts/session-context-menu-browser.test.mjs",
@@ -200,6 +207,8 @@
200
207
  "scripts/composer-resize-browser.test.mjs",
201
208
  "scripts/workspace-picker-browser.test.mjs",
202
209
  "scripts/optimistic-browser.test.mjs",
210
+ "scripts/stop-while-waiting-browser.test.mjs",
211
+ "scripts/new-session-stays-selected-browser.test.mjs",
203
212
  "scripts/active-memory-browser.test.mjs",
204
213
  "scripts/knowledge-base-browser.test.mjs",
205
214
  "scripts/terminal-browser.test.mjs",