@evandu/super-pi 0.1.145 → 0.1.147

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/agents/super-pi-task/core.ts +74 -2
  2. package/agents/super-pi-task/index.ts +7 -0
  3. package/dist/.super-pi-check-inputs.json +2 -2
  4. package/dist/client/.super-pi-client-sources.json +11 -11
  5. package/dist/client/assets/{CodeMirrorEditor-A9KvbIkm.js → CodeMirrorEditor-DQCjEnxG.js} +2 -2
  6. package/dist/client/assets/{dist-DM4Olwfx.js → dist-1Innu17E.js} +1 -1
  7. package/dist/client/assets/{dist-bIDce5j-.js → dist-1lynFOc5.js} +1 -1
  8. package/dist/client/assets/{dist-BlxwDnWr.js → dist-B1qLZVMK.js} +1 -1
  9. package/dist/client/assets/{dist-BTVbZigv.js → dist-B4pz_xdd.js} +1 -1
  10. package/dist/client/assets/{dist-UJ46aPS3.js → dist-B6KwDpeI.js} +1 -1
  11. package/dist/client/assets/{dist-CK2KFFtT.js → dist-BJVI1Unv.js} +1 -1
  12. package/dist/client/assets/{dist-CRnm1rnO.js → dist-BWvzD8Pe.js} +1 -1
  13. package/dist/client/assets/{dist-DSHM-PAs.js → dist-BayWssd0.js} +1 -1
  14. package/dist/client/assets/{dist-B9DE7sc5.js → dist-Bk_pkBVH.js} +1 -1
  15. package/dist/client/assets/{dist-D-o7iQ8c.js → dist-BpeClAv0.js} +1 -1
  16. package/dist/client/assets/{dist-CQFASxyb.js → dist-BsmJg5PX.js} +1 -1
  17. package/dist/client/assets/{dist-Ds5gfeAi.js → dist-Bx7s4rni.js} +1 -1
  18. package/dist/client/assets/{dist-UPJPMjWv.js → dist-CDBKM9tL.js} +1 -1
  19. package/dist/client/assets/{dist-CIgjifH-.js → dist-CGzXCzCG.js} +1 -1
  20. package/dist/client/assets/{dist-BAiV8I_O.js → dist-CRxCwn-Z.js} +1 -1
  21. package/dist/client/assets/{dist-Br-AuQUG.js → dist-CTDgUnBG.js} +1 -1
  22. package/dist/client/assets/{dist-4JbFTwg2.js → dist-D1VfMWmf.js} +1 -1
  23. package/dist/client/assets/{dist-BYOpvW02.js → dist-DTZLNEci.js} +1 -1
  24. package/dist/client/assets/{dist-CQV5kOCE.js → dist-DuUi29km.js} +1 -1
  25. package/dist/client/assets/{dist-CjszAoXk.js → dist-F3S0t1oM.js} +1 -1
  26. package/dist/client/assets/{dist-2boAf_pN.js → dist-QZ-FAVJV.js} +1 -1
  27. package/dist/client/assets/{dist-B1p4YF9C.js → dist-u0nBg0fZ.js} +1 -1
  28. package/dist/client/assets/index-BMpqviwU.css +1 -0
  29. package/dist/client/assets/index-Dzyp_7sf.js +102 -0
  30. package/dist/client/index.html +2 -2
  31. package/dist/server/active-memory.d.ts +17 -0
  32. package/dist/server/active-memory.js +39 -0
  33. package/dist/server/agents.d.ts +1 -1
  34. package/dist/server/agents.js +3 -1
  35. package/dist/server/application/conversation-service.d.ts +1 -1
  36. package/dist/server/application/conversation-service.js +12 -4
  37. package/dist/server/application/run-service.d.ts +32 -2
  38. package/dist/server/application/run-service.js +296 -40
  39. package/dist/server/application/scheduled-task-service.d.ts +14 -1
  40. package/dist/server/application/scheduled-task-service.js +27 -2
  41. package/dist/server/application/session-orchestrator-service.d.ts +114 -3
  42. package/dist/server/application/session-orchestrator-service.js +406 -35
  43. package/dist/server/application/session-watchdog.d.ts +159 -0
  44. package/dist/server/application/session-watchdog.js +244 -0
  45. package/dist/server/command-handler.js +1 -1
  46. package/dist/server/conversation-usage.d.ts +19 -0
  47. package/dist/server/conversation-usage.js +40 -0
  48. package/dist/server/file-diff.d.ts +3 -0
  49. package/dist/server/file-diff.js +4 -4
  50. package/dist/server/file-index.d.ts +4 -2
  51. package/dist/server/file-index.js +14 -9
  52. package/dist/server/file-name-index.d.ts +5 -2
  53. package/dist/server/file-name-index.js +22 -5
  54. package/dist/server/git.d.ts +3 -3
  55. package/dist/server/git.js +10 -9
  56. package/dist/server/http-server.js +256 -34
  57. package/dist/server/index.js +3 -0
  58. package/dist/server/knowledge-vectors.d.ts +15 -0
  59. package/dist/server/knowledge-vectors.js +118 -37
  60. package/dist/server/knowledge.d.ts +7 -8
  61. package/dist/server/knowledge.js +133 -55
  62. package/dist/server/length-continuation.d.ts +50 -0
  63. package/dist/server/length-continuation.js +55 -0
  64. package/dist/server/model-context-window.d.ts +46 -0
  65. package/dist/server/model-context-window.js +121 -0
  66. package/dist/server/pi-session-events.js +5 -1
  67. package/dist/server/pi-session-pool.d.ts +2 -0
  68. package/dist/server/pi-session-pool.js +16 -0
  69. package/dist/server/pi-session.d.ts +6 -0
  70. package/dist/server/pi-session.js +15 -1
  71. package/dist/server/runtime.d.ts +3 -0
  72. package/dist/server/runtime.js +23 -7
  73. package/dist/server/session-cwd.d.ts +17 -0
  74. package/dist/server/session-cwd.js +43 -0
  75. package/dist/server/session-ledger.d.ts +7 -0
  76. package/dist/server/session-tree-scope.d.ts +125 -0
  77. package/dist/server/session-tree-scope.js +337 -0
  78. package/dist/server/store.d.ts +35 -0
  79. package/dist/server/store.js +87 -2
  80. package/dist/server/task-tool.d.ts +2 -1
  81. package/dist/server/task-tool.js +4 -1
  82. package/dist/server/terminal.d.ts +1 -0
  83. package/dist/server/terminal.js +5 -3
  84. package/dist/server/turn-failure.d.ts +26 -0
  85. package/dist/server/turn-failure.js +67 -1
  86. package/dist/server/types.d.ts +33 -1
  87. package/dist/server/version.d.ts +25 -2
  88. package/dist/server/version.js +98 -26
  89. package/dist/server/ws-server.d.ts +1 -1
  90. package/dist/server/ws-server.js +7 -5
  91. package/package.json +36 -3
  92. package/dist/client/assets/index-BGsP8pt_.js +0 -100
  93. package/dist/client/assets/index-CiIc734l.css +0 -1
@@ -19,4 +19,30 @@
19
19
  * needs no server, socket or Pi process.
20
20
  */
21
21
  export declare function formatTurnFailure(error: unknown): string;
22
+ /**
23
+ * THE MODEL FILLED ITS OUTPUT BUDGET AND STOPPED MID-ANSWER.
24
+ *
25
+ * A length stop is NOT a crash. The provider answered, streamed real text, and stopped because it
26
+ * ran out of `max_tokens` - which is what happens every time an answer is longer than one output
27
+ * budget. Treating it as a failure is what made an orchestrated child session die: the run service
28
+ * stamped the conversation `failed`, the orchestrator reported a failure to the parent, and a task
29
+ * that was one sentence from done was thrown away.
30
+ *
31
+ * It carries a CLASS so the layers above can tell it apart from a provider error by identity rather
32
+ * than by matching a Chinese substring - the message is written for a reader, and a reader-facing
33
+ * sentence is not a reliable machine contract.
34
+ */
35
+ export declare class LengthExceededError extends Error {
36
+ readonly stopReason: "length";
37
+ constructor(message?: string);
38
+ }
39
+ /**
40
+ * Whether this failure is a length stop rather than a real failure.
41
+ *
42
+ * The class is the answer; the message match is the BACKSTOP for a throw site that never got to
43
+ * construct the class - an error that crossed a process boundary, or one that was rebuilt from its
44
+ * text somewhere along the way. Both are checked because a length stop that loses its class on the
45
+ * way up is exactly the case that used to kill a child session.
46
+ */
47
+ export declare function isLengthExceededError(error: unknown): boolean;
22
48
  export declare function controllerAborted(error: unknown): boolean;
@@ -43,7 +43,9 @@ export function formatTurnFailure(error) {
43
43
  // WORKSPACE PATH - turn refused on directory the contract forbids. Refusal happens before
44
44
  // anything is on screen, so the banner is also the only place the reason lives.
45
45
  if (lower.includes("workspace directory") || lower.includes("does not exist or is not a directory")) {
46
- return `工作区目录无效:${raw}`;
46
+ const match = raw.match(/does not exist or is not a directory:\s*(.+?)(?:\.\s*Refusing|$)/);
47
+ const path = match?.[1]?.trim();
48
+ return `工作区目录不存在:${path || "未知路径"}`;
47
49
  }
48
50
  // PROVIDER UNREACHABLE - the request never reached the model. This is the branch a custom
49
51
  // provider with a wrong Base URL lands in, and the person editing that URL needs to be told that
@@ -63,15 +65,79 @@ export function formatTurnFailure(error) {
63
65
  if (code >= 500 && code <= 599 || /internal server error|bad gateway|service unavailable|gateway timeout/.test(lower)) {
64
66
  return `模型供应商服务端出错了${code ? `(HTTP ${code})` : ""},通常稍后重试即可。`;
65
67
  }
68
+ // CONTEXT OVERFLOW - the conversation outgrew the model's window.
69
+ //
70
+ // This is the branch a reader hits when the session stops working entirely, so it has to name the
71
+ // cause AND the way out. The upstream wording varies by provider, and the shapes that show up are
72
+ // "context length exceeded", "maximum context length", "too many tokens", "prompt is too long",
73
+ // "reduce the length of the messages", and the 400s some providers use for it.
74
+ //
75
+ // The action is the same in every case: the conversation is too long for one request, and the
76
+ // reader's choices are to start a new conversation or use a model with a bigger window. Saying
77
+ // "the model refused" would hide both.
78
+ if (/context[\s_]*(?:length|window|size)[\s_]*(?:exceeded|exceeds|too large|limit)|maximum context|context limit|too many tokens|prompt is too long|reduce the length of the messages|exceeds the maximum|上下文(?:超|过)限|上下文长度/.test(lower)) {
79
+ return "这一轮的内容超过了模型的上下文上限,模型没能读下整段对话。可以新开一个会话继续,或换一个上下文窗口更大的模型(Pi 会在超限前自动压缩,这轮没能完成压缩)。";
80
+ }
66
81
  // IMAGE MISMATCH - explicit by the caller, no need to translate.
67
82
  if (lower.includes("不支持图片输入")) {
68
83
  return raw;
69
84
  }
85
+ // LENGTH LIMIT - the model stopped because it filled the output budget, NOT because anything
86
+ // broke. The answer is incomplete, but everything that was said is still on screen, so the
87
+ // sentence has to offer the way out (say "continue") instead of calling it a failure.
88
+ //
89
+ // THE SENTENCE IS BUILT, NOT TAKEN FROM THE ERROR. The error's own message says the fact
90
+ // ("模型输出达到本轮长度上限,回答未完成") and stops there - which is a statement, not an action, and
91
+ // a reader who is told a thing stopped has no idea what to do next. What they need is the way
92
+ // out, so the sentence always carries it. Pi's own words are kept only when they add something
93
+ // the sentence does not already say.
94
+ if (isLengthExceededError(error)) {
95
+ const detail = raw && !/长度上限/.test(raw) ? `(${raw})` : "";
96
+ return `模型单次输出达到长度上限,回答没有写完${detail}。可以点「继续写完」或者自己发一句「继续」,模型会从被截断的地方接着写。`;
97
+ }
70
98
  // GENERIC - the literal message is more useful than a paraphrase, but it has to be the LAST
71
99
  // branch and the message has to be non-empty (a thrown `new Error()` is empty and renders
72
100
  // nothing, which is exactly the silence this whole change exists to break).
73
101
  return raw || "Pi 未能完成本轮回答,已标记为失败。可以重发任务继续。";
74
102
  }
103
+ /**
104
+ * THE MODEL FILLED ITS OUTPUT BUDGET AND STOPPED MID-ANSWER.
105
+ *
106
+ * A length stop is NOT a crash. The provider answered, streamed real text, and stopped because it
107
+ * ran out of `max_tokens` - which is what happens every time an answer is longer than one output
108
+ * budget. Treating it as a failure is what made an orchestrated child session die: the run service
109
+ * stamped the conversation `failed`, the orchestrator reported a failure to the parent, and a task
110
+ * that was one sentence from done was thrown away.
111
+ *
112
+ * It carries a CLASS so the layers above can tell it apart from a provider error by identity rather
113
+ * than by matching a Chinese substring - the message is written for a reader, and a reader-facing
114
+ * sentence is not a reliable machine contract.
115
+ */
116
+ export class LengthExceededError extends Error {
117
+ stopReason = "length";
118
+ constructor(message) {
119
+ super(message || "模型输出达到本轮长度上限,回答未完成。");
120
+ this.name = "LengthExceededError";
121
+ }
122
+ }
123
+ /**
124
+ * Whether this failure is a length stop rather than a real failure.
125
+ *
126
+ * The class is the answer; the message match is the BACKSTOP for a throw site that never got to
127
+ * construct the class - an error that crossed a process boundary, or one that was rebuilt from its
128
+ * text somewhere along the way. Both are checked because a length stop that loses its class on the
129
+ * way up is exactly the case that used to kill a child session.
130
+ */
131
+ export function isLengthExceededError(error) {
132
+ if (!error)
133
+ return false;
134
+ if (error instanceof LengthExceededError)
135
+ return true;
136
+ if (error?.stopReason === "length")
137
+ return true;
138
+ const msg = error instanceof Error ? error.message : typeof error === "string" ? error : "";
139
+ return msg.includes("模型输出达到本轮长度上限") || msg.includes("达到本轮长度上限") || msg.includes("单次输出达到长度上限");
140
+ }
75
141
  export function controllerAborted(error) {
76
142
  if (!error)
77
143
  return false;
@@ -18,7 +18,7 @@ import type { ScheduledTask } from "./scheduled-tasks.js";
18
18
  * `failed` is what any unhandled throw lands (with `Conversation.error` carrying the translated reason);
19
19
  * `completed` is the clean path.
20
20
  */
21
- export type RunStatus = "idle" | "waiting" | "running" | "completed" | "stopped" | "failed";
21
+ export type RunStatus = "idle" | "waiting" | "running" | "awaiting_background" | "completed" | "stopped" | "failed";
22
22
  export type MessageRole = "user" | "assistant" | "tool" | "system";
23
23
  /**
24
24
  * One message in `state.json`.
@@ -464,6 +464,12 @@ export type Conversation = {
464
464
  status: RunStatus;
465
465
  messages: Message[];
466
466
  archivedAt?: string;
467
+ /** Cumulative primary-assistant usage observed since readout collection began; excludes subagents. */
468
+ usage?: SubagentUsage;
469
+ /** False means cost is only a sum of reported costs, not a complete price (zero is not "free"). */
470
+ usageCostKnown?: boolean;
471
+ contextUsage?: ConversationContextUsage;
472
+ usageUpdatedAt?: string;
467
473
  /** Transport projection only: false means messages were omitted, rather than an empty transcript. */
468
474
  messagesLoaded?: boolean;
469
475
  /** Pending question summary, retained when a focused snapshot omits this conversation's messages. */
@@ -488,6 +494,20 @@ export type Conversation = {
488
494
  * would answer "what happened" with something the reader has to go and decode.
489
495
  */
490
496
  error?: string;
497
+ /**
498
+ * WHY THIS CONVERSATION'S LAST ANSWER IS INCOMPLETE - in words a person reads.
499
+ *
500
+ * A WARNING, not a failure. The model filled its single-response output budget and stopped
501
+ * mid-answer; everything it said is on screen, and the conversation is still usable. It is a
502
+ * separate field from `error` because the two mean different things to the reader: `error` says
503
+ * "this run broke, here is why", this says "this answer was cut short, and here is what to do
504
+ * about it". Collapsing them would paint a recoverable truncation as a crash - which is exactly
505
+ * what made an orchestrated child session die.
506
+ *
507
+ * Cleared by the next turn, the same way `error` is: it describes the turn that just ended and
508
+ * must never outlive it.
509
+ */
510
+ warning?: string;
491
511
  /**
492
512
  * WHY THIS CONVERSATION IS NOT ON THE MODEL THE USER SET FOR IT - in words a person reads.
493
513
  *
@@ -519,6 +539,8 @@ export type Conversation = {
519
539
  * deleted agent degrades to "no role" instead of leaving a stale snapshot in state.json.
520
540
  */
521
541
  agentId?: string;
542
+ /** Relative to the workspace root. Absent or empty preserves the original workspace-root cwd. */
543
+ cwd?: string;
522
544
  /**
523
545
  * The conversation that opened this one, when an orchestrator agent created it. Absent for a
524
546
  * conversation the user started. This is the session tree's edge, and it PERSISTS: the sidebar
@@ -655,6 +677,12 @@ export type SubagentUsage = {
655
677
  cost: number;
656
678
  turns: number;
657
679
  };
680
+ /** Current context occupancy, distinct from cumulative billed tokens; null means unknown after compaction. */
681
+ export type ConversationContextUsage = {
682
+ tokens: number | null;
683
+ contextWindow: number;
684
+ percent: number | null;
685
+ };
658
686
  /**
659
687
  * ONE TOOL CALL, as the card shows it, and the boundary that keeps a child's turn out of `state.json`.
660
688
  *
@@ -1022,6 +1050,7 @@ export type ClientCommandBody =
1022
1050
  type: "terminal.open";
1023
1051
  sessionId: string;
1024
1052
  workspaceId: string;
1053
+ cwd?: string;
1025
1054
  cols?: number;
1026
1055
  rows?: number;
1027
1056
  }
@@ -1182,6 +1211,7 @@ export type ClientCommandBody =
1182
1211
  } | {
1183
1212
  type: "conversation.create";
1184
1213
  workspaceId: string;
1214
+ cwd?: string;
1185
1215
  } | {
1186
1216
  type: "conversation.select";
1187
1217
  conversationId: string;
@@ -1284,6 +1314,7 @@ export type ClientCommandBody =
1284
1314
  | {
1285
1315
  type: "files.index.subscribe";
1286
1316
  workspaceId?: string;
1317
+ cwd?: string;
1287
1318
  } | {
1288
1319
  type: "state.subscribe";
1289
1320
  conversationId?: string;
@@ -1601,6 +1632,7 @@ export type ServerEvent = {
1601
1632
  } | {
1602
1633
  type: "files.index";
1603
1634
  workspaceId: string;
1635
+ cwd?: string;
1604
1636
  version: number;
1605
1637
  changed: {
1606
1638
  path: string;
@@ -1,10 +1,33 @@
1
1
  import type { ServerVersionInfo } from "./types.js";
2
+ export declare const UNKNOWN_VERSION = "0.0.0-unknown";
3
+ /**
4
+ * Reads and validates the version string from a package.json located in the given directory.
5
+ */
6
+ export declare function readPackageVersion(dir?: string): string | undefined;
7
+ /**
8
+ * Resolves the version of Super Pi itself by searching relative to this module's location.
9
+ * In both development (`src/server/`) and production build (`dist/server/`), two levels up
10
+ * is the repository / package root containing package.json.
11
+ */
12
+ export declare function resolveSelfVersion(fromDir?: string): string | undefined;
2
13
  /**
3
14
  * Initializes the version captured when this server process booted up.
15
+ * If the provided rootDir fails to yield a valid package.json version,
16
+ * attempts to self-heal using the module's own location before falling back to UNKNOWN_VERSION.
17
+ */
18
+ export declare function initRunningVersion(rootDir: string, selfFromDir?: string): string;
19
+ /**
20
+ * Returns the currently running version captured in memory.
21
+ */
22
+ export declare function getRunningVersion(): string;
23
+ /**
24
+ * Resets the in-memory running version, primarily for testing purposes.
4
25
  */
5
- export declare function initRunningVersion(rootDir: string): string;
26
+ export declare function resetRunningVersion(toVersion?: string): string;
6
27
  /**
7
28
  * Checks whether the package.json version on disk has been updated beyond
8
29
  * the version currently executing in this process.
30
+ * If rootDir fails to yield a valid package.json version, attempts to self-heal
31
+ * using the module's own location.
9
32
  */
10
- export declare function checkDiskVersion(rootDir: string): ServerVersionInfo;
33
+ export declare function checkDiskVersion(rootDir: string, selfFromDir?: string): ServerVersionInfo;
@@ -1,39 +1,111 @@
1
- import { readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- let runningVersion = "0.1.3";
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ export const UNKNOWN_VERSION = "0.0.0-unknown";
4
4
  /**
5
- * Initializes the version captured when this server process booted up.
5
+ * Reads and validates the version string from a package.json located in the given directory.
6
6
  */
7
- export function initRunningVersion(rootDir) {
7
+ export function readPackageVersion(dir) {
8
+ if (!dir || typeof dir !== "string")
9
+ return undefined;
8
10
  try {
9
- const pkg = JSON.parse(readFileSync(join(rootDir, "package.json"), "utf8"));
10
- if (pkg.version)
11
- runningVersion = pkg.version;
11
+ const pkgPath = join(dir, "package.json");
12
+ if (!existsSync(pkgPath))
13
+ return undefined;
14
+ const raw = readFileSync(pkgPath, "utf8");
15
+ const pkg = JSON.parse(raw);
16
+ if (pkg && typeof pkg.version === "string" && pkg.version.trim().length > 0) {
17
+ return pkg.version.trim();
18
+ }
12
19
  }
13
20
  catch {
14
- // fallback to default
21
+ // Read or JSON parse error - return undefined to allow self-healing / fallback
22
+ }
23
+ return undefined;
24
+ }
25
+ /**
26
+ * Resolves the version of Super Pi itself by searching relative to this module's location.
27
+ * In both development (`src/server/`) and production build (`dist/server/`), two levels up
28
+ * is the repository / package root containing package.json.
29
+ */
30
+ export function resolveSelfVersion(fromDir = import.meta.dirname) {
31
+ if (!fromDir || typeof fromDir !== "string")
32
+ return undefined;
33
+ // Primary path: two levels up from server module directory (src/server or dist/server)
34
+ const canonicalRoot = resolve(fromDir, "../..");
35
+ const canonicalVersion = readPackageVersion(canonicalRoot);
36
+ if (canonicalVersion)
37
+ return canonicalVersion;
38
+ // Secondary path: walk upwards to locate the nearest directory containing Super Pi's package.json
39
+ let current = resolve(fromDir);
40
+ while (true) {
41
+ try {
42
+ const pkgPath = join(current, "package.json");
43
+ if (existsSync(pkgPath)) {
44
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
45
+ if (pkg && pkg.name === "@evandu/super-pi" && typeof pkg.version === "string" && pkg.version.trim()) {
46
+ return pkg.version.trim();
47
+ }
48
+ }
49
+ }
50
+ catch {
51
+ // Continue searching upwards
52
+ }
53
+ const parent = dirname(current);
54
+ if (parent === current)
55
+ break;
56
+ current = parent;
57
+ }
58
+ return undefined;
59
+ }
60
+ let runningVersion = resolveSelfVersion() ?? UNKNOWN_VERSION;
61
+ /**
62
+ * Initializes the version captured when this server process booted up.
63
+ * If the provided rootDir fails to yield a valid package.json version,
64
+ * attempts to self-heal using the module's own location before falling back to UNKNOWN_VERSION.
65
+ */
66
+ export function initRunningVersion(rootDir, selfFromDir = import.meta.dirname) {
67
+ const direct = readPackageVersion(rootDir);
68
+ if (direct) {
69
+ runningVersion = direct;
70
+ return runningVersion;
71
+ }
72
+ const self = resolveSelfVersion(selfFromDir);
73
+ if (self) {
74
+ runningVersion = self;
75
+ return runningVersion;
15
76
  }
77
+ runningVersion = UNKNOWN_VERSION;
78
+ return runningVersion;
79
+ }
80
+ /**
81
+ * Returns the currently running version captured in memory.
82
+ */
83
+ export function getRunningVersion() {
84
+ return runningVersion;
85
+ }
86
+ /**
87
+ * Resets the in-memory running version, primarily for testing purposes.
88
+ */
89
+ export function resetRunningVersion(toVersion) {
90
+ runningVersion = toVersion ?? resolveSelfVersion() ?? UNKNOWN_VERSION;
16
91
  return runningVersion;
17
92
  }
18
93
  /**
19
94
  * Checks whether the package.json version on disk has been updated beyond
20
95
  * the version currently executing in this process.
96
+ * If rootDir fails to yield a valid package.json version, attempts to self-heal
97
+ * using the module's own location.
21
98
  */
22
- export function checkDiskVersion(rootDir) {
23
- try {
24
- const pkg = JSON.parse(readFileSync(join(rootDir, "package.json"), "utf8"));
25
- const diskVersion = pkg.version ?? runningVersion;
26
- return {
27
- runningVersion,
28
- diskVersion,
29
- needsRestart: diskVersion !== runningVersion,
30
- };
31
- }
32
- catch {
33
- return {
34
- runningVersion,
35
- diskVersion: runningVersion,
36
- needsRestart: false,
37
- };
38
- }
99
+ export function checkDiskVersion(rootDir, selfFromDir = import.meta.dirname) {
100
+ const diskVersion = readPackageVersion(rootDir) ??
101
+ resolveSelfVersion(selfFromDir) ??
102
+ runningVersion;
103
+ const needsRestart = Boolean(diskVersion !== runningVersion &&
104
+ runningVersion !== UNKNOWN_VERSION &&
105
+ diskVersion !== UNKNOWN_VERSION);
106
+ return {
107
+ runningVersion,
108
+ diskVersion,
109
+ needsRestart,
110
+ };
39
111
  }
@@ -71,7 +71,7 @@ export declare class WsBroadcaster implements EventBroadcaster {
71
71
  unregister(socket: WebSocket): void;
72
72
  subscribe(socket: WebSocket, conversationId?: string): void;
73
73
  sendState(socket: WebSocket): void;
74
- subscribeFileIndex(socket: WebSocket, workspaceId?: string): void;
74
+ subscribeFileIndex(socket: WebSocket, workspaceId?: string, cwd?: string): void;
75
75
  private receives;
76
76
  configureStreaming(socket: WebSocket, coalesce: boolean): void;
77
77
  private flushStreaming;
@@ -116,9 +116,9 @@ export class WsBroadcaster {
116
116
  this.flushStreaming(socket);
117
117
  safeSend(socket, JSON.stringify({ type: "state", state: this.getState(this.scopes.get(socket)) }));
118
118
  }
119
- subscribeFileIndex(socket, workspaceId) {
119
+ subscribeFileIndex(socket, workspaceId, cwd = "") {
120
120
  if (workspaceId)
121
- this.fileIndexScopes.set(socket, workspaceId);
121
+ this.fileIndexScopes.set(socket, { workspaceId, cwd });
122
122
  else
123
123
  this.fileIndexScopes.delete(socket);
124
124
  }
@@ -127,8 +127,10 @@ export class WsBroadcaster {
127
127
  if (!scope)
128
128
  return true;
129
129
  switch (event.type) {
130
- case "files.index":
131
- return this.fileIndexScopes.get(socket) === event.workspaceId;
130
+ case "files.index": {
131
+ const fileScope = this.fileIndexScopes.get(socket);
132
+ return fileScope?.workspaceId === event.workspaceId && fileScope.cwd === (event.cwd ?? "");
133
+ }
132
134
  case "message.delta":
133
135
  case "message.thinking":
134
136
  case "message.tools":
@@ -481,7 +483,7 @@ export class WsServer {
481
483
  return;
482
484
  }
483
485
  if (parsed.type === "files.index.subscribe") {
484
- this.broadcaster.subscribeFileIndex(socket, typeof parsed.workspaceId === "string" ? parsed.workspaceId : undefined);
486
+ this.broadcaster.subscribeFileIndex(socket, typeof parsed.workspaceId === "string" ? parsed.workspaceId : undefined, typeof parsed.cwd === "string" ? parsed.cwd : "");
485
487
  return;
486
488
  }
487
489
  if (parsed.type === "stream.configure") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evandu/super-pi",
3
- "version": "0.1.145",
3
+ "version": "0.1.147",
4
4
  "description": "Web control plane and companion for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -51,6 +51,7 @@
51
51
  "scripts/inline-thinking.test.mjs",
52
52
  "scripts/inline-thinking-close-tag.test.mjs",
53
53
  "scripts/pi-session.test.mjs",
54
+ "scripts/length-continuation.test.mjs",
54
55
  "scripts/pi-session-isolation.test.mjs",
55
56
  "scripts/installed-layout.test.mjs",
56
57
  "scripts/pi-model-switch.test.mjs",
@@ -67,6 +68,7 @@
67
68
  "scripts/verify-source-text.test.mjs",
68
69
  "scripts/runtime-extension-ui.test.mjs",
69
70
  "scripts/agent-mechanism.test.mjs",
71
+ "scripts/super-pi-tool-names.test.mjs",
70
72
  "scripts/agent-wiring.test.mjs",
71
73
  "scripts/message-timestamps.test.mjs",
72
74
  "scripts/pi-resolution.test.mjs",
@@ -83,6 +85,8 @@
83
85
  "scripts/session-tree-collapse.test.mjs",
84
86
  "scripts/archive-cascade.test.mjs",
85
87
  "scripts/workspace-cwd.test.mjs",
88
+ "scripts/session-cwd.test.mjs",
89
+ "scripts/session-cwd-panels.test.mjs",
86
90
  "scripts/subagents.test.mjs",
87
91
  "scripts/subagent-noise.test.mjs",
88
92
  "scripts/subagent-response-receipt.test.mjs",
@@ -99,6 +103,7 @@
99
103
  "scripts/queue-store.test.mjs",
100
104
  "scripts/git-api.test.mjs",
101
105
  "scripts/turn-failure-marks-failed.test.mjs",
106
+ "scripts/model-context-window.test.mjs",
102
107
  "scripts/update-ui-contract.test.mjs",
103
108
  "scripts/temp-sweep.test.mjs",
104
109
  "scripts/workspace-identity.test.mjs",
@@ -188,11 +193,30 @@
188
193
  "scripts/document-write-overwrite.test.mjs",
189
194
  "scripts/feishu.test.mjs",
190
195
  "scripts/scheduled-tasks.test.mjs",
196
+ "scripts/scheduled-tasks-timezone.test.mjs",
191
197
  "scripts/scheduled-task-model-clear.test.mjs",
192
198
  "scripts/scheduled-task-scope.test.mjs",
193
199
  "scripts/scheduled-task-tool.test.mjs",
194
200
  "scripts/run-state-sync.test.mjs",
195
- "scripts/performance-architecture.test.mjs"
201
+ "scripts/performance-architecture.test.mjs",
202
+ "scripts/upgrade-from-138-browser.test.mjs",
203
+ "scripts/session-orchestrator-role-prompt.test.mjs",
204
+ "scripts/context-overflow-failure.test.mjs",
205
+ "scripts/spawn-workspace-boundary.test.mjs",
206
+ "scripts/cwd-agent-spawn.test.mjs",
207
+ "scripts/capabilities-prompt.test.mjs",
208
+ "scripts/version-fallback.test.mjs",
209
+ "scripts/model-image-input-session.test.mjs",
210
+ "scripts/conversation-usage.test.mjs",
211
+ "scripts/orchestrator-observability.test.mjs",
212
+ "scripts/session-tab-preview.test.mjs",
213
+ "scripts/knowledge-retrieval-quality.test.mjs",
214
+ "scripts/rail-ui-contract.test.mjs",
215
+ "scripts/session-tree-scope.test.mjs",
216
+ "scripts/session-spawn-model.test.mjs",
217
+ "scripts/background-result-steer.test.mjs",
218
+ "scripts/session-report-watchdog.test.mjs",
219
+ "scripts/awaiting-background-status.test.mjs"
196
220
  ],
197
221
  "check:browser": [
198
222
  "scripts/interrupt-browser.test.mjs",
@@ -231,6 +255,7 @@
231
255
  "scripts/archive-restore-race-browser.test.mjs",
232
256
  "scripts/tool-call-blocks-browser.test.mjs",
233
257
  "scripts/model-notice-browser.test.mjs",
258
+ "scripts/length-warning-browser.test.mjs",
234
259
  "scripts/dead-default-agent-browser.test.mjs",
235
260
  "scripts/assistant-working-indicator-browser.test.mjs",
236
261
  "scripts/thinking-first-delta-once-browser.test.mjs",
@@ -268,7 +293,15 @@
268
293
  "scripts/ux-polish-browser.test.mjs",
269
294
  "scripts/prompt-templates-browser.test.mjs",
270
295
  "scripts/documents-browser.test.mjs",
271
- "scripts/feishu-browser.test.mjs"
296
+ "scripts/feishu-browser.test.mjs",
297
+ "scripts/session-read-state-regression.test.mjs",
298
+ "scripts/orchestrated-order-role-browser.test.mjs",
299
+ "scripts/result-file-link-browser.test.mjs",
300
+ "scripts/git-panel-confirm-browser.test.mjs",
301
+ "scripts/session-origin-badge-browser.test.mjs",
302
+ "scripts/rail-ui-layout-browser.test.mjs",
303
+ "scripts/session-landing-browser.test.mjs",
304
+ "scripts/responsive-rail-drawer-browser.test.mjs"
272
305
  ],
273
306
  "dependencies": {
274
307
  "@codemirror/commands": "^6.11.1",