@evandu/super-pi 0.1.144 → 0.1.146
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/agents/super-pi-task/core.ts +15 -2
- package/agents/super-pi-task/index.ts +3 -0
- package/dist/.super-pi-check-inputs.json +2 -2
- package/dist/client/.super-pi-client-sources.json +9 -9
- package/dist/client/assets/{CodeMirrorEditor-DfQHX1Hy.js → CodeMirrorEditor-CEwBCVM6.js} +2 -2
- package/dist/client/assets/{dist-Cu3B0JQX.js → dist-76ArZ5M4.js} +1 -1
- package/dist/client/assets/{dist-BEbEv2KM.js → dist-9F1wL-T5.js} +1 -1
- package/dist/client/assets/{dist-BRjyQs_L.js → dist-B0RXZeXQ.js} +1 -1
- package/dist/client/assets/{dist-SzZEAk24.js → dist-BCNaDgNW.js} +1 -1
- package/dist/client/assets/{dist-qAUj_2Ue.js → dist-BXBSIuTD.js} +1 -1
- package/dist/client/assets/{dist-BGBinEie.js → dist-Bh8Q7Y_n.js} +1 -1
- package/dist/client/assets/{dist-DKkMk3XX.js → dist-BoyICDlL.js} +1 -1
- package/dist/client/assets/{dist-ISoxXlmW.js → dist-BuR5Iagd.js} +1 -1
- package/dist/client/assets/{dist-LRNzDUVb.js → dist-Bx6ZWYUW.js} +1 -1
- package/dist/client/assets/{dist-DOvzruAQ.js → dist-CAOcjKAQ.js} +1 -1
- package/dist/client/assets/{dist-CDtFD-PW.js → dist-CHt8PX8R.js} +1 -1
- package/dist/client/assets/{dist-BuPimjY-.js → dist-CSCUT1PQ.js} +1 -1
- package/dist/client/assets/{dist-BsMikSTM.js → dist-CUB_Chdh.js} +1 -1
- package/dist/client/assets/{dist-KQJCVwxy.js → dist-CbVf2Cjn.js} +1 -1
- package/dist/client/assets/{dist-Dnru8_uX.js → dist-DBUqwiWG.js} +1 -1
- package/dist/client/assets/{dist-B8oH6dsc.js → dist-DC1IocrS.js} +1 -1
- package/dist/client/assets/{dist-D7aAMRTb.js → dist-DFMtP3wG.js} +1 -1
- package/dist/client/assets/{dist-Cw_KCDH9.js → dist-DN0GlnnZ.js} +1 -1
- package/dist/client/assets/{dist-BCLhQXYY.js → dist-DftD4ibj.js} +1 -1
- package/dist/client/assets/{dist-CgQ7zaWY.js → dist-Do52MRwm.js} +1 -1
- package/dist/client/assets/{dist-BS-FBUKW.js → dist-JvQIIdqB.js} +1 -1
- package/dist/client/assets/{dist-C5BQsmB7.js → dist-oLq6HXLG.js} +1 -1
- package/dist/client/assets/{index-Xaui0uUI.css → index-D-OxlUxN.css} +1 -1
- package/dist/client/assets/index-E7yaLa2C.js +102 -0
- package/dist/client/index.html +2 -2
- package/dist/server/agents.d.ts +1 -1
- package/dist/server/agents.js +3 -1
- package/dist/server/application/conversation-service.d.ts +1 -1
- package/dist/server/application/conversation-service.js +6 -4
- package/dist/server/application/run-service.d.ts +16 -2
- package/dist/server/application/run-service.js +121 -14
- package/dist/server/application/scheduled-task-service.d.ts +14 -1
- package/dist/server/application/scheduled-task-service.js +27 -2
- package/dist/server/application/session-orchestrator-service.d.ts +11 -2
- package/dist/server/application/session-orchestrator-service.js +103 -27
- package/dist/server/command-handler.js +1 -1
- package/dist/server/conversation-usage.d.ts +19 -0
- package/dist/server/conversation-usage.js +40 -0
- package/dist/server/file-diff.d.ts +3 -0
- package/dist/server/file-diff.js +4 -4
- package/dist/server/file-index.d.ts +4 -2
- package/dist/server/file-index.js +14 -9
- package/dist/server/file-name-index.d.ts +5 -2
- package/dist/server/file-name-index.js +22 -5
- package/dist/server/git.d.ts +3 -3
- package/dist/server/git.js +10 -9
- package/dist/server/http-server.js +195 -33
- package/dist/server/model-context-window.d.ts +46 -0
- package/dist/server/model-context-window.js +121 -0
- package/dist/server/pi-session.d.ts +6 -0
- package/dist/server/pi-session.js +15 -1
- package/dist/server/runtime.d.ts +3 -0
- package/dist/server/runtime.js +21 -6
- package/dist/server/session-cwd.d.ts +17 -0
- package/dist/server/session-cwd.js +43 -0
- package/dist/server/session-ledger.d.ts +7 -0
- package/dist/server/store.d.ts +22 -0
- package/dist/server/store.js +52 -0
- package/dist/server/task-tool.d.ts +2 -1
- package/dist/server/task-tool.js +3 -1
- package/dist/server/terminal.d.ts +1 -0
- package/dist/server/terminal.js +5 -3
- package/dist/server/turn-failure.js +16 -1
- package/dist/server/types.d.ts +18 -0
- package/dist/server/version.d.ts +25 -2
- package/dist/server/version.js +98 -26
- package/dist/server/ws-server.d.ts +1 -1
- package/dist/server/ws-server.js +7 -5
- package/package.json +20 -3
- package/dist/client/assets/index-BLWr70ej.js +0 -100
|
@@ -27,6 +27,7 @@ import { existsSync, statSync } from "node:fs";
|
|
|
27
27
|
import { open, readdir } from "node:fs/promises";
|
|
28
28
|
import { StringDecoder } from "node:string_decoder";
|
|
29
29
|
import { dirname, join, resolve } from "node:path";
|
|
30
|
+
import { effectiveContextWindow, readDeclaredWindows } from "./model-context-window.js";
|
|
30
31
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
31
32
|
const traceSession = (conversationId, phase, startedAt, extra = "") => {
|
|
32
33
|
if (process.env.SUPER_PI_TRACE_LATENCY !== "1")
|
|
@@ -326,11 +327,24 @@ export async function createPiSession(creation) {
|
|
|
326
327
|
// It is attached to the session rather than kept beside it because the session is what outlives a
|
|
327
328
|
// turn: a model change applies to a LIVE session, and the live session is the thing that has to be
|
|
328
329
|
// able to answer "give me this model whole".
|
|
330
|
+
// THE WINDOW THE MODEL REALLY HAS. Pi's remote catalogue can overwrite the `contextWindow` the
|
|
331
|
+
// PERSON configured in `models.json` (Pi's own merge lets the catalogue win), and a provider
|
|
332
|
+
// that reports a million tokens makes Pi's auto-compaction wait for 983,616 tokens - while the
|
|
333
|
+
// model refuses at its real 128,000. The overflow recovery then asks the same too-long
|
|
334
|
+
// conversation of the same too-small model, and the session stops working entirely.
|
|
335
|
+
//
|
|
336
|
+
// So the person's file wins: a number somebody typed is a decision, and a number a catalogue
|
|
337
|
+
// fetched is a claim. See `model-context-window.ts` for the whole reasoning.
|
|
338
|
+
const declared = readDeclaredWindows(agentDir);
|
|
329
339
|
live.resolveModel = (provider, modelId) => {
|
|
330
340
|
const found = modelRuntime.getModel?.(provider, modelId);
|
|
331
341
|
if (!found || typeof found !== "object")
|
|
332
342
|
return undefined;
|
|
333
|
-
|
|
343
|
+
const model = found;
|
|
344
|
+
const window = effectiveContextWindow(declared, provider, modelId, model.contextWindow);
|
|
345
|
+
// The model is handed to Pi WHOLE - it is what the agent runs on - so this is where the
|
|
346
|
+
// corrected window has to be. A model Super Pi has no opinion about is handed over untouched.
|
|
347
|
+
return window === undefined ? model : { ...model, contextWindow: window };
|
|
334
348
|
};
|
|
335
349
|
return live;
|
|
336
350
|
})();
|
package/dist/server/runtime.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type RpcRecord } from "./rpc-channel.js";
|
|
|
2
2
|
export { RpcChannel, type RpcRecord } from "./rpc-channel.js";
|
|
3
3
|
import type { ActiveMemory } from "./active-memory.js";
|
|
4
4
|
import { type KnowledgeBase } from "./knowledge.js";
|
|
5
|
+
import { type TurnUsageReadout } from "./conversation-usage.js";
|
|
5
6
|
export { forkSessionFile, truncateSessionFile } from "./session-repository.js";
|
|
6
7
|
export { describeDeliveredUserMessage, describePiQueue } from "./pi-session-events.js";
|
|
7
8
|
import { type PiSessionFactory } from "./pi-session.js";
|
|
@@ -171,6 +172,8 @@ export interface AnswerInput {
|
|
|
171
172
|
onThinking?(delta: string): void | Promise<void>;
|
|
172
173
|
/** Called when the reasoning phase ends - the only signal that it has. */
|
|
173
174
|
onThinkingEnd?(): void;
|
|
175
|
+
/** One turn's completed-message usage and final context snapshot, also reported on abort/failure. */
|
|
176
|
+
onUsage?(readout: TurnUsageReadout): void | Promise<void>;
|
|
174
177
|
}
|
|
175
178
|
/**
|
|
176
179
|
* What Pi answered when a `steer` was written to it, from the `disposition` of that command's response
|
package/dist/server/runtime.js
CHANGED
|
@@ -7,6 +7,8 @@ import { DOCUMENT_READ_TOOL_NAME, DOCUMENT_WRITE_TOOL_NAME } from "./documents.j
|
|
|
7
7
|
import { FEISHU_READ_TOOL_NAME, FEISHU_WRITE_TOOL_NAME } from "./feishu-cli.js";
|
|
8
8
|
import { WEB_FETCH_TOOL_NAME, WEB_SEARCH_TOOL_NAME } from "./web.js";
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
|
+
import { ConversationUsageCollector } from "./conversation-usage.js";
|
|
11
|
+
import { defaultAgentDir } from "./pi-session.js";
|
|
10
12
|
import { mkdir, stat } from "node:fs/promises";
|
|
11
13
|
import { forkSessionFile, truncateSessionFile } from "./session-repository.js";
|
|
12
14
|
export { forkSessionFile, truncateSessionFile } from "./session-repository.js";
|
|
@@ -21,7 +23,7 @@ import { describeDeliveredUserMessage, describePiQueue, handleSessionEvent, Turn
|
|
|
21
23
|
// paths recognise a delivery identically. Re-exported because they were part of this module's surface.
|
|
22
24
|
export { describeDeliveredUserMessage, describePiQueue } from "./pi-session-events.js";
|
|
23
25
|
import { createPiSession, createSdkCatalog, piSdkAvailable, resolveTranscriptFile, superPiExtensionPaths } from "./pi-session.js";
|
|
24
|
-
import { TASK_CONVERSATION_ENV, PARENT_CONVERSATION_ENV, TASK_DEPTH_ENV, TASK_ENDPOINT_ENV, TASK_EXEC_ENDPOINT_ENV, TASK_ROSTER_ENV, TASK_TOOL_NAME, TODO_ENDPOINT_ENV, TODO_TOOL_NAME, SCHEDULED_TASK_ENDPOINT_ENV, SCHEDULED_TASK_TOOL_NAME, WORKFLOW_ENDPOINT_ENV, WORKFLOW_TOOL_NAME, RUN_ASYNC_TOOL_NAME, ORCHESTRATOR_ENDPOINT_ENV, SESSION_SPAWN_TOOL_NAME, SESSION_STATUS_TOOL_NAME, SESSION_DISPATCH_TOOL_NAME, SESSION_STOP_TOOL_NAME, rosterPayload, } from "./task-tool.js";
|
|
26
|
+
import { TASK_CONVERSATION_ENV, PARENT_CONVERSATION_ENV, TASK_DEPTH_ENV, TASK_ENDPOINT_ENV, TASK_EXEC_ENDPOINT_ENV, TASK_ROSTER_ENV, TASK_TOOL_NAME, TODO_ENDPOINT_ENV, TODO_TOOL_NAME, SCHEDULED_TASK_ENDPOINT_ENV, SCHEDULED_TASK_TOOL_NAME, WORKFLOW_ENDPOINT_ENV, WORKFLOW_TOOL_NAME, RUN_ASYNC_TOOL_NAME, ORCHESTRATOR_ENDPOINT_ENV, ORCHESTRATOR_WORKSPACE_ENV, SESSION_SPAWN_TOOL_NAME, SESSION_STATUS_TOOL_NAME, SESSION_DISPATCH_TOOL_NAME, SESSION_STOP_TOOL_NAME, rosterPayload, } from "./task-tool.js";
|
|
25
27
|
import { BLOCKING_UI_METHODS, EXTENSION_UI_METHODS, } from "./types.js";
|
|
26
28
|
import { loadConversationImages } from "./upload.js";
|
|
27
29
|
import { describeToolCall, describeToolCallArgs, describeToolCallInProgress, extractPartialResultText, extractToolResultText, readQuestionCall } from "./tool-calls.js";
|
|
@@ -696,7 +698,7 @@ export class PiRpcRuntime {
|
|
|
696
698
|
* its environment once, when it is loaded) and a token minted per turn would be replaced before the
|
|
697
699
|
* second turn of the conversation, leaving the extension holding a value the server had forgotten.
|
|
698
700
|
*/
|
|
699
|
-
async taskExtensionEnv(conversationId, parentConversationId) {
|
|
701
|
+
async taskExtensionEnv(conversationId, workspaceId, parentConversationId) {
|
|
700
702
|
const { agents, problems } = await readAgents(this.agents.dir, { mode: "subagent" });
|
|
701
703
|
for (const problem of problems)
|
|
702
704
|
console.warn(`PiRpcRuntime: ${problem.file} is not a usable sub-agent - ${problem.message}`);
|
|
@@ -711,6 +713,7 @@ export class PiRpcRuntime {
|
|
|
711
713
|
[SCHEDULED_TASK_ENDPOINT_ENV]: this.options.scheduledTaskEndpoint ?? "",
|
|
712
714
|
[WORKFLOW_ENDPOINT_ENV]: this.options.workflowEndpoint ?? "",
|
|
713
715
|
[ORCHESTRATOR_ENDPOINT_ENV]: this.options.orchestratorEndpoint ?? "",
|
|
716
|
+
[ORCHESTRATOR_WORKSPACE_ENV]: workspaceId,
|
|
714
717
|
SUPER_PI_ORCHESTRATOR_CONVERSATION: conversationId,
|
|
715
718
|
SUPER_PI_QUESTION_ENDPOINT: this.options.questionEndpoint ?? "",
|
|
716
719
|
SUPER_PI_TOOLS_ENDPOINT: this.options.toolsEndpoint ?? "",
|
|
@@ -738,9 +741,9 @@ export class PiRpcRuntime {
|
|
|
738
741
|
* here. A session lives for many turns, so its credential is minted when the session is created (see
|
|
739
742
|
* `PiSessionPool.acquire`) and the server separately requires a running turn for it to be usable.
|
|
740
743
|
*/
|
|
741
|
-
async oneShotExtensionEnv(conversationId, capability, parentConversationId) {
|
|
744
|
+
async oneShotExtensionEnv(conversationId, workspaceId, capability, parentConversationId) {
|
|
742
745
|
return {
|
|
743
|
-
...(await this.taskExtensionEnv(conversationId, parentConversationId)),
|
|
746
|
+
...(await this.taskExtensionEnv(conversationId, workspaceId, parentConversationId)),
|
|
744
747
|
[TASK_CAPABILITY_ENV]: capability,
|
|
745
748
|
SUPER_PI_TOOLS_CAPABILITY: capability,
|
|
746
749
|
};
|
|
@@ -801,7 +804,7 @@ export class PiRpcRuntime {
|
|
|
801
804
|
...(systemPromptFile ? { systemPromptFile } : {}),
|
|
802
805
|
...(agent?.promptMode ? { systemPromptMode: agent.promptMode } : {}),
|
|
803
806
|
extensionPaths: superPiExtensionPaths(repositoryRootFrom()),
|
|
804
|
-
extensionEnv: await this.taskExtensionEnv(conversation.id, conversation.parentConversationId),
|
|
807
|
+
extensionEnv: await this.taskExtensionEnv(conversation.id, conversation.workspaceId, conversation.parentConversationId),
|
|
805
808
|
...(conversation.model ? { model: { provider: conversation.model.provider, modelId: conversation.model.modelId } } : {}),
|
|
806
809
|
...(conversation.thinkingLevel ? { thinkingLevel: conversation.thinkingLevel } : {}),
|
|
807
810
|
};
|
|
@@ -902,8 +905,12 @@ export class PiRpcRuntime {
|
|
|
902
905
|
// turn failed. The bridge clears it when a new assistant message starts, and raises it when Pi
|
|
903
906
|
// settles.
|
|
904
907
|
const failure = { current: undefined };
|
|
908
|
+
const usage = new ConversationUsageCollector();
|
|
905
909
|
const unsubscribe = session.subscribe((event) => {
|
|
906
910
|
try {
|
|
911
|
+
const record = event;
|
|
912
|
+
if (record?.type === "message_end" && record.message?.role === "assistant")
|
|
913
|
+
usage.add(record.message.usage);
|
|
907
914
|
handleSessionEvent(event, queue, handlers, report, failure);
|
|
908
915
|
}
|
|
909
916
|
catch (error) {
|
|
@@ -1086,6 +1093,14 @@ export class PiRpcRuntime {
|
|
|
1086
1093
|
if (input.host)
|
|
1087
1094
|
this.sessionHosts.delete(conversation.id);
|
|
1088
1095
|
await finished.catch(() => undefined);
|
|
1096
|
+
if (input.onUsage) {
|
|
1097
|
+
let context;
|
|
1098
|
+
try {
|
|
1099
|
+
context = session.getContextUsage?.();
|
|
1100
|
+
}
|
|
1101
|
+
catch { /* Unavailable context must not discard usage or keep a stale occupancy. */ }
|
|
1102
|
+
await input.onUsage(usage.readout(context, session.model, defaultAgentDir()));
|
|
1103
|
+
}
|
|
1089
1104
|
}
|
|
1090
1105
|
}
|
|
1091
1106
|
/**
|
|
@@ -1477,7 +1492,7 @@ export class PiRpcRuntime {
|
|
|
1477
1492
|
// every turn would silently replace the child's environment with whatever THIS process has at
|
|
1478
1493
|
// spawn time. Inheriting is what an omitted `env` already does, so the two paths differ in nothing
|
|
1479
1494
|
// but the four documented additions.
|
|
1480
|
-
...((extensionArgs.length || memoryEnabled || knowledgeEnabled || documentsEnabled || webEnabled || feishuEnabled) ? { env: { ...process.env, ...(oneShotCapability ? await this.oneShotExtensionEnv(conversation.id, oneShotCapability, conversation.parentConversationId) : {}), ...(memoryEnabled ? {
|
|
1495
|
+
...((extensionArgs.length || memoryEnabled || knowledgeEnabled || documentsEnabled || webEnabled || feishuEnabled) ? { env: { ...process.env, ...(oneShotCapability ? await this.oneShotExtensionEnv(conversation.id, conversation.workspaceId, oneShotCapability, conversation.parentConversationId) : {}), ...(memoryEnabled ? {
|
|
1481
1496
|
SUPER_PI_MEMORY_ENDPOINT: process.env.SUPER_PI_MEMORY_ENDPOINT,
|
|
1482
1497
|
SUPER_PI_MEMORY_TOKEN: memoryToken,
|
|
1483
1498
|
SUPER_PI_MEMORY_REVIEW: memoryReview ? "1" : "0",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a conversation's optional relative cwd without ever letting it leave the workspace.
|
|
3
|
+
* An absent value preserves the workspace root; an invalid or escaping value is rejected back to
|
|
4
|
+
* that same root rather than thrown or allowed to select another directory.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveSessionCwd(workspaceRoot: string, cwd?: string | null): Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Validates an explicitly requested session cwd at creation time. Unlike the request-time fallback
|
|
9
|
+
* above, an invalid value is an error: the caller must not receive a session rooted somewhere else.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertSessionCwdInsideWorkspace(workspaceRoot: string, cwd?: string | null): Promise<void>;
|
|
12
|
+
/** The canonical, workspace-relative spelling persisted on a conversation. */
|
|
13
|
+
export declare function normalizeSessionCwd(workspaceRoot: string, cwd?: string | null): Promise<string | undefined>;
|
|
14
|
+
/** Converts a path relative to the effective session root into a workspace-relative Git path. */
|
|
15
|
+
export declare function workspacePathFromSession(workspaceRoot: string, sessionRoot: string, path: string): Promise<string>;
|
|
16
|
+
/** One map identity per workspace/cwd pair. The root-session key remains the bare workspace id. */
|
|
17
|
+
export declare function sessionScopeKey(workspaceId: string, cwd?: string): string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { relative, resolve, sep } from "node:path";
|
|
2
|
+
import { resolveInsideRoot } from "./files.js";
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a conversation's optional relative cwd without ever letting it leave the workspace.
|
|
5
|
+
* An absent value preserves the workspace root; an invalid or escaping value is rejected back to
|
|
6
|
+
* that same root rather than thrown or allowed to select another directory.
|
|
7
|
+
*/
|
|
8
|
+
export async function resolveSessionCwd(workspaceRoot, cwd) {
|
|
9
|
+
if (!cwd?.trim())
|
|
10
|
+
return resolveInsideRoot(workspaceRoot, "");
|
|
11
|
+
try {
|
|
12
|
+
return await resolveInsideRoot(workspaceRoot, cwd);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return resolveInsideRoot(workspaceRoot, "");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Validates an explicitly requested session cwd at creation time. Unlike the request-time fallback
|
|
20
|
+
* above, an invalid value is an error: the caller must not receive a session rooted somewhere else.
|
|
21
|
+
*/
|
|
22
|
+
export async function assertSessionCwdInsideWorkspace(workspaceRoot, cwd) {
|
|
23
|
+
if (!cwd?.trim())
|
|
24
|
+
return;
|
|
25
|
+
await resolveInsideRoot(workspaceRoot, cwd);
|
|
26
|
+
}
|
|
27
|
+
/** The canonical, workspace-relative spelling persisted on a conversation. */
|
|
28
|
+
export async function normalizeSessionCwd(workspaceRoot, cwd) {
|
|
29
|
+
if (!cwd?.trim())
|
|
30
|
+
return undefined;
|
|
31
|
+
const effective = await resolveSessionCwd(workspaceRoot, cwd);
|
|
32
|
+
const normalized = relative(resolve(workspaceRoot), effective).split(sep).join("/");
|
|
33
|
+
return normalized ? normalized : undefined;
|
|
34
|
+
}
|
|
35
|
+
/** Converts a path relative to the effective session root into a workspace-relative Git path. */
|
|
36
|
+
export async function workspacePathFromSession(workspaceRoot, sessionRoot, path) {
|
|
37
|
+
const absolute = await resolveInsideRoot(sessionRoot, path);
|
|
38
|
+
return relative(resolve(workspaceRoot), absolute).split(sep).join("/");
|
|
39
|
+
}
|
|
40
|
+
/** One map identity per workspace/cwd pair. The root-session key remains the bare workspace id. */
|
|
41
|
+
export function sessionScopeKey(workspaceId, cwd) {
|
|
42
|
+
return cwd ? `${workspaceId}\0${cwd}` : workspaceId;
|
|
43
|
+
}
|
|
@@ -19,6 +19,13 @@ export interface SessionLedgerEntry {
|
|
|
19
19
|
updatedAt: number;
|
|
20
20
|
/** High-level progress snippet or latest outcome */
|
|
21
21
|
lastProgress?: string;
|
|
22
|
+
/** Currently executing tool, if any */
|
|
23
|
+
activeTool?: {
|
|
24
|
+
label: string;
|
|
25
|
+
detail?: string;
|
|
26
|
+
};
|
|
27
|
+
/** Error message if execution failed */
|
|
28
|
+
error?: string;
|
|
22
29
|
}
|
|
23
30
|
/**
|
|
24
31
|
* In-memory relationship ledger for sibling and child sessions.
|
package/dist/server/store.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { defaultJsonStatePath } from "./persistence/json-state-repository.js";
|
|
|
3
3
|
import type { ScheduledTask } from "./scheduled-tasks.js";
|
|
4
4
|
import type { WorkflowRun } from "./workflow.js";
|
|
5
5
|
import type { AppState, Conversation, Message, MessageDelivery, MessageImage, ModelSelection, SubagentRun, SubagentRunUpdate, TodoItem, ToolCallQuestion, ToolCallArg, ToolCallBlock, ToolResultSummary, Workspace } from "./types.js";
|
|
6
|
+
import type { TurnUsageReadout } from "./conversation-usage.js";
|
|
6
7
|
/** Mints the id a forked conversation will have, BEFORE its Pi transcript exists.
|
|
7
8
|
*
|
|
8
9
|
* Exported because the ordering demands it and no other arrangement provides it. Pi's copied file's header
|
|
@@ -271,6 +272,7 @@ export declare class Store {
|
|
|
271
272
|
thinkingLevel?: string | null;
|
|
272
273
|
modelNotice?: string;
|
|
273
274
|
agentNotice?: string;
|
|
275
|
+
cwd?: string;
|
|
274
276
|
parentConversationId?: string;
|
|
275
277
|
origin?: "user" | "orchestrator";
|
|
276
278
|
}): Promise<Conversation>;
|
|
@@ -388,6 +390,24 @@ export declare class Store {
|
|
|
388
390
|
* whatever was typed into a box that was not listening yet. `openTurnOnMessage` applies them at the
|
|
389
391
|
* moment a turn genuinely begins.
|
|
390
392
|
*/
|
|
393
|
+
/**
|
|
394
|
+
* Writes ONE INSTRUCTION INTO THE TRANSCRIPT AS SOMETHING THE SYSTEM SAID.
|
|
395
|
+
*
|
|
396
|
+
* `addUserMessage` above is the only way a person's words come into being, and everything written
|
|
397
|
+
* through it wears the reader's own name and the reader's bubble. That is right for a person and
|
|
398
|
+
* wrong for the orchestrator: when a primary agent dispatches a task to a child session, the
|
|
399
|
+
* child receives an ORDER, not something the reader typed - and the child transcript showed it
|
|
400
|
+
* under the reader's avatar, so the reader saw their own name on an instruction they never wrote.
|
|
401
|
+
*
|
|
402
|
+
* The role is `tool` for a reason that is not cosmetic: `tool` is already the product's word for
|
|
403
|
+
* "the system delivered this", it renders as the 系统 side of the transcript rather than as a
|
|
404
|
+
* person, and Pi's queue already knows how to hand a `tool` message to a model (see
|
|
405
|
+
* `composePrompt` in the run service). So the instruction reaches the model the same way, and the
|
|
406
|
+
* reader sees who spoke.
|
|
407
|
+
*/
|
|
408
|
+
addAgentMessage(conversationId: string, content: string, options?: {
|
|
409
|
+
delivery?: Message["delivery"];
|
|
410
|
+
}): Promise<Message>;
|
|
391
411
|
addUserMessage(conversationId: string, content: string, images?: MessageImage[], attachments?: import("./types.js").MessageAttachments, options?: {
|
|
392
412
|
clientId?: string;
|
|
393
413
|
delivery?: Message["delivery"];
|
|
@@ -578,6 +598,8 @@ export declare class Store {
|
|
|
578
598
|
*/
|
|
579
599
|
clearPreparingToolCalls(conversationId: string, messageId: string, rows: readonly number[]): Promise<ToolCallBlock[]>;
|
|
580
600
|
setStatus(conversationId: string, status: Conversation["status"]): Promise<void>;
|
|
601
|
+
/** Commit once at turn end; metadata transactions work for both JSON and SQLite repositories. */
|
|
602
|
+
recordConversationUsage(conversationId: string, readout: TurnUsageReadout): Promise<void>;
|
|
581
603
|
/**
|
|
582
604
|
* Records WHY a turn failed in a way the reader can see. `Conversation.error` is rendered as a red
|
|
583
605
|
* banner above the transcript (`App.tsx:conversation-error`), and it is the only field that survives
|
package/dist/server/store.js
CHANGED
|
@@ -1103,6 +1103,8 @@ export class Store {
|
|
|
1103
1103
|
// the sidebar draws after a restart is the tree that was true when each session was opened.
|
|
1104
1104
|
// Written only when present, so a conversation created without them (the user's own "+") reads
|
|
1105
1105
|
// as a top-level one on every code path, including every state.json from before this field.
|
|
1106
|
+
if (configuration.cwd)
|
|
1107
|
+
conversation.cwd = configuration.cwd;
|
|
1106
1108
|
if (configuration.parentConversationId)
|
|
1107
1109
|
conversation.parentConversationId = configuration.parentConversationId;
|
|
1108
1110
|
if (configuration.origin)
|
|
@@ -1212,6 +1214,8 @@ export class Store {
|
|
|
1212
1214
|
conversation.thinkingLevel = source.thinkingLevel;
|
|
1213
1215
|
if (source.agentId)
|
|
1214
1216
|
conversation.agentId = source.agentId;
|
|
1217
|
+
if (source.cwd)
|
|
1218
|
+
conversation.cwd = source.cwd;
|
|
1215
1219
|
this.state.conversations.unshift(conversation);
|
|
1216
1220
|
await this.persist();
|
|
1217
1221
|
return structuredClone(conversation);
|
|
@@ -1350,6 +1354,33 @@ export class Store {
|
|
|
1350
1354
|
* whatever was typed into a box that was not listening yet. `openTurnOnMessage` applies them at the
|
|
1351
1355
|
* moment a turn genuinely begins.
|
|
1352
1356
|
*/
|
|
1357
|
+
/**
|
|
1358
|
+
* Writes ONE INSTRUCTION INTO THE TRANSCRIPT AS SOMETHING THE SYSTEM SAID.
|
|
1359
|
+
*
|
|
1360
|
+
* `addUserMessage` above is the only way a person's words come into being, and everything written
|
|
1361
|
+
* through it wears the reader's own name and the reader's bubble. That is right for a person and
|
|
1362
|
+
* wrong for the orchestrator: when a primary agent dispatches a task to a child session, the
|
|
1363
|
+
* child receives an ORDER, not something the reader typed - and the child transcript showed it
|
|
1364
|
+
* under the reader's avatar, so the reader saw their own name on an instruction they never wrote.
|
|
1365
|
+
*
|
|
1366
|
+
* The role is `tool` for a reason that is not cosmetic: `tool` is already the product's word for
|
|
1367
|
+
* "the system delivered this", it renders as the 系统 side of the transcript rather than as a
|
|
1368
|
+
* person, and Pi's queue already knows how to hand a `tool` message to a model (see
|
|
1369
|
+
* `composePrompt` in the run service). So the instruction reaches the model the same way, and the
|
|
1370
|
+
* reader sees who spoke.
|
|
1371
|
+
*/
|
|
1372
|
+
async addAgentMessage(conversationId, content, options) {
|
|
1373
|
+
console.error("STORE addAgentMessage conv=" + conversationId + " content=" + content.slice(0, 60));
|
|
1374
|
+
const conversation = this.requireConversation(conversationId);
|
|
1375
|
+
if (conversation.archivedAt)
|
|
1376
|
+
throw new Error("无法向已归档的会话下发指令,请先恢复该会话");
|
|
1377
|
+
const message = { id: id("message"), role: "tool", content, createdAt: now() };
|
|
1378
|
+
if (options?.delivery)
|
|
1379
|
+
message.delivery = options.delivery;
|
|
1380
|
+
conversation.messages.push(message);
|
|
1381
|
+
await this.persistMessages(conversation);
|
|
1382
|
+
return structuredClone(message);
|
|
1383
|
+
}
|
|
1353
1384
|
async addUserMessage(conversationId, content, images, attachments, options) {
|
|
1354
1385
|
const conversation = this.requireConversation(conversationId);
|
|
1355
1386
|
// The browser cannot reach an archived conversation, so this guard exists for any other caller -
|
|
@@ -1429,6 +1460,7 @@ export class Store {
|
|
|
1429
1460
|
* state it also enters `waitingMessages` and `runConversationTurns` will pick it up next.
|
|
1430
1461
|
*/
|
|
1431
1462
|
async addToolMessage(conversationId, content, options) {
|
|
1463
|
+
console.error("STORE addToolMessage conv=" + conversationId + " content=" + content.slice(0, 60));
|
|
1432
1464
|
const conversation = this.requireConversation(conversationId);
|
|
1433
1465
|
if (conversation.archivedAt)
|
|
1434
1466
|
throw new Error("无法向已归档的会话追加后台回传,请先恢复该会话");
|
|
@@ -1836,6 +1868,26 @@ export class Store {
|
|
|
1836
1868
|
return structuredClone(calls);
|
|
1837
1869
|
}
|
|
1838
1870
|
async setStatus(conversationId, status) { const conversation = this.requireConversation(conversationId); conversation.status = status; conversation.updatedAt = now(); await this.persistMetadata(conversationId); }
|
|
1871
|
+
/** Commit once at turn end; metadata transactions work for both JSON and SQLite repositories. */
|
|
1872
|
+
async recordConversationUsage(conversationId, readout) {
|
|
1873
|
+
const conversation = this.requireConversation(conversationId);
|
|
1874
|
+
if (readout.usage) {
|
|
1875
|
+
const previous = conversation.usage;
|
|
1876
|
+
const total = { ...readout.usage };
|
|
1877
|
+
if (previous)
|
|
1878
|
+
for (const key of ["input", "output", "cacheRead", "cacheWrite", "cost", "turns"])
|
|
1879
|
+
total[key] += previous[key];
|
|
1880
|
+
conversation.usage = total;
|
|
1881
|
+
conversation.usageCostKnown = (conversation.usageCostKnown ?? true) && readout.costKnown;
|
|
1882
|
+
}
|
|
1883
|
+
if (readout.contextUsage)
|
|
1884
|
+
conversation.contextUsage = { ...readout.contextUsage };
|
|
1885
|
+
else
|
|
1886
|
+
delete conversation.contextUsage;
|
|
1887
|
+
if (readout.usage || readout.contextUsage)
|
|
1888
|
+
conversation.usageUpdatedAt = now();
|
|
1889
|
+
await this.persistMetadata(conversationId);
|
|
1890
|
+
}
|
|
1839
1891
|
/**
|
|
1840
1892
|
* Records WHY a turn failed in a way the reader can see. `Conversation.error` is rendered as a red
|
|
1841
1893
|
* banner above the transcript (`App.tsx:conversation-error`), and it is the only field that survives
|
|
@@ -251,13 +251,14 @@ export declare function todoSummary(todos: readonly TodoItem[]): string;
|
|
|
251
251
|
* `agents/super-pi-task/core.ts` is the other end; `scripts/task-tool.test.mjs` asserts the two copies agree.
|
|
252
252
|
*/
|
|
253
253
|
export declare const ORCHESTRATOR_ENDPOINT_ENV = "SUPER_PI_ORCHESTRATOR_ENDPOINT";
|
|
254
|
+
export declare const ORCHESTRATOR_WORKSPACE_ENV = "SUPER_PI_ORCHESTRATOR_WORKSPACE";
|
|
254
255
|
/** Tool names registered with Pi. */
|
|
255
256
|
export declare const SESSION_SPAWN_TOOL_NAME = "session_spawn";
|
|
256
257
|
export declare const SESSION_STATUS_TOOL_NAME = "session_status";
|
|
257
258
|
export declare const SESSION_DISPATCH_TOOL_NAME = "session_dispatch";
|
|
258
259
|
export declare const SESSION_STOP_TOOL_NAME = "session_stop";
|
|
259
260
|
/** Model-facing description for session_spawn. */
|
|
260
|
-
export declare const SESSION_SPAWN_DESCRIPTION = "Create a new sibling conversation in the current workspace and immediately dispatch its initial task.\n\nDIFFERENCE FROM THE 'task' TOOL:\n- The 'task' tool delegates to a transient, one-shot subagent process (scout, critic, patcher) that runs once, returns its final text, and terminates. It has no conversation history and does not appear in the workspace sidebar.\n- 'session_spawn' creates a real, persistent sibling CONVERSATION in Super Pi. It appears in the sidebar, retains multi-turn message history, can receive follow-up messages via session_dispatch, can be inspected via session_status, and can be stopped via session_stop. The user can also switch to it in the Web UI.\n\nWHEN TO USE THIS:\n- Long-running, multi-phase tracks of work that require ongoing context, multiple turns of dialogue, or independent user visibility.\n- Parallel work streams (e.g., Track A: backend refactor, Track B: frontend component update) where both need full conversational capabilities.\n- When the user asks you to act as an orchestrator/manager who delegates to multiple sessions.\n\nWHEN NOT TO USE THIS:\n- For one-off, focused delegation with a single answer: use 'task' (with scout/critic/patcher). It is faster, cheaper, and returns directly to your transcript.\n- For running shell commands or build watchers: use 'run_async'.\n- For a linear multi-step subagent pipeline: use 'workflow'.\n\nUsage notes:\n1. Spawning is ASYNCHRONOUS by default: it creates the conversation, enqueues the initial task, and returns the new conversationId immediately. The sibling session runs in the background.\n2. The initial task should provide clear context and goals since the new session starts with a fresh conversation history.\n3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.\n4. The new session reports completion to its DIRECT parent automatically. 'parentConversationId' optionally selects that parent; by default it is the calling session.";
|
|
261
|
+
export declare const SESSION_SPAWN_DESCRIPTION = "Create a new sibling conversation in the current workspace and immediately dispatch its initial task.\n\nDIFFERENCE FROM THE 'task' TOOL:\n- The 'task' tool delegates to a transient, one-shot subagent process (scout, critic, patcher) that runs once, returns its final text, and terminates. It has no conversation history and does not appear in the workspace sidebar.\n- 'session_spawn' creates a real, persistent sibling CONVERSATION in Super Pi. It appears in the sidebar, retains multi-turn message history, can receive follow-up messages via session_dispatch, can be inspected via session_status, and can be stopped via session_stop. The user can also switch to it in the Web UI.\n\nWHEN TO USE THIS:\n- Long-running, multi-phase tracks of work that require ongoing context, multiple turns of dialogue, or independent user visibility.\n- Parallel work streams (e.g., Track A: backend refactor, Track B: frontend component update) where both need full conversational capabilities.\n- When the user asks you to act as an orchestrator/manager who delegates to multiple sessions.\n\nWHEN NOT TO USE THIS:\n- For one-off, focused delegation with a single answer: use 'task' (with scout/critic/patcher). It is faster, cheaper, and returns directly to your transcript.\n- For running shell commands or build watchers: use 'run_async'.\n- For a linear multi-step subagent pipeline: use 'workflow'.\n\nUsage notes:\n1. Spawning is ASYNCHRONOUS by default: it creates the conversation, enqueues the initial task, and returns the new conversationId immediately. The sibling session runs in the background.\n2. The initial task should provide clear context and goals since the new session starts with a fresh conversation history.\n3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.\n4. The new session reports completion to its DIRECT parent automatically. 'parentConversationId' optionally selects that parent; by default it is the calling session.\n5. The new session always belongs to the calling session's workspace. Pass 'cwd' as a workspace-relative subdirectory (for example, 'packages/a') when it should work below the workspace root; absolute paths and traversal are rejected.";
|
|
261
262
|
/** Model-facing description for session_status. */
|
|
262
263
|
export declare const SESSION_STATUS_DESCRIPTION = "Inspect the progress, run state, and latest activity of sibling conversations created by the orchestrator.\n\nWHEN TO USE THIS:\n- To check whether a spawned sibling conversation is still running, has finished (idle), is stopped, or encountered an error.\n- To read the latest assistant progress snippet without bloating your context with the session's entire raw message transcript.\n- To list all sibling conversations under your coordination when conversationId is omitted.\n\nUsage notes:\n1. When 'conversationId' is provided: returns a compact summary for that specific session, including its run status ('idle', 'running', 'waiting', 'stopped', 'failed'), messageCount, turnCount, lastAssistantSnippet, lastUserSnippet, activeTool, and error (if any).\n2. When 'conversationId' is omitted: returns a summary list of sibling conversations whose direct parent is 'parentConversationId' (the calling session by default).\n3. By default it never returns full transcripts; concise summaries keep your context window lean. Set 'includeOutput' with a conversationId to read the final output from its result file, capped to the last 64Ki characters with an explicit truncation notice.";
|
|
263
264
|
/** Model-facing description for session_dispatch. */
|
package/dist/server/task-tool.js
CHANGED
|
@@ -356,6 +356,7 @@ export function todoSummary(todos) {
|
|
|
356
356
|
* `agents/super-pi-task/core.ts` is the other end; `scripts/task-tool.test.mjs` asserts the two copies agree.
|
|
357
357
|
*/
|
|
358
358
|
export const ORCHESTRATOR_ENDPOINT_ENV = "SUPER_PI_ORCHESTRATOR_ENDPOINT";
|
|
359
|
+
export const ORCHESTRATOR_WORKSPACE_ENV = "SUPER_PI_ORCHESTRATOR_WORKSPACE";
|
|
359
360
|
/** Tool names registered with Pi. */
|
|
360
361
|
export const SESSION_SPAWN_TOOL_NAME = "session_spawn";
|
|
361
362
|
export const SESSION_STATUS_TOOL_NAME = "session_status";
|
|
@@ -382,7 +383,8 @@ Usage notes:
|
|
|
382
383
|
1. Spawning is ASYNCHRONOUS by default: it creates the conversation, enqueues the initial task, and returns the new conversationId immediately. The sibling session runs in the background.
|
|
383
384
|
2. The initial task should provide clear context and goals since the new session starts with a fresh conversation history.
|
|
384
385
|
3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.
|
|
385
|
-
4. The new session reports completion to its DIRECT parent automatically. 'parentConversationId' optionally selects that parent; by default it is the calling session
|
|
386
|
+
4. The new session reports completion to its DIRECT parent automatically. 'parentConversationId' optionally selects that parent; by default it is the calling session.
|
|
387
|
+
5. The new session always belongs to the calling session's workspace. Pass 'cwd' as a workspace-relative subdirectory (for example, 'packages/a') when it should work below the workspace root; absolute paths and traversal are rejected.`;
|
|
386
388
|
/** Model-facing description for session_status. */
|
|
387
389
|
export const SESSION_STATUS_DESCRIPTION = `Inspect the progress, run state, and latest activity of sibling conversations created by the orchestrator.
|
|
388
390
|
|
package/dist/server/terminal.js
CHANGED
|
@@ -52,6 +52,7 @@ import { createRequire } from "node:module";
|
|
|
52
52
|
import { existsSync, accessSync, constants } from "node:fs";
|
|
53
53
|
import { delimiter, join } from "node:path";
|
|
54
54
|
import { isLocalOrPrivateAddress } from "./http-server.js";
|
|
55
|
+
import { resolveSessionCwd } from "./session-cwd.js";
|
|
55
56
|
let ptyModule;
|
|
56
57
|
let ptyLoadFailure;
|
|
57
58
|
/**
|
|
@@ -305,10 +306,11 @@ export class TerminalService {
|
|
|
305
306
|
send({ type: "terminal.error", sessionId: command.sessionId, message: "工作区不存在,无法打开终端。" });
|
|
306
307
|
return;
|
|
307
308
|
}
|
|
309
|
+
const cwd = await resolveSessionCwd(workspace.path, command.cwd);
|
|
308
310
|
// A workspace whose directory was deleted or renamed must not spawn a shell in `process.cwd()`,
|
|
309
311
|
// which would silently run the terminal in the SERVER's directory instead of an error.
|
|
310
|
-
if (!existsSync(
|
|
311
|
-
send({ type: "terminal.error", sessionId: command.sessionId, message:
|
|
312
|
+
if (!existsSync(cwd)) {
|
|
313
|
+
send({ type: "terminal.error", sessionId: command.sessionId, message: `会话工作目录不存在:${cwd}` });
|
|
312
314
|
return;
|
|
313
315
|
}
|
|
314
316
|
const pty = loadPty();
|
|
@@ -319,7 +321,7 @@ export class TerminalService {
|
|
|
319
321
|
name: "xterm-256color",
|
|
320
322
|
cols: clamp(command.cols, MIN_COLS, MAX_COLS, 80),
|
|
321
323
|
rows: clamp(command.rows, MIN_ROWS, MAX_ROWS, 24),
|
|
322
|
-
cwd
|
|
324
|
+
cwd,
|
|
323
325
|
env: { ...process.env, TERM: "xterm-256color" },
|
|
324
326
|
});
|
|
325
327
|
const session = { pty: child, closed: false, killed: false, pending: "" };
|
|
@@ -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
|
-
|
|
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,6 +65,19 @@ 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;
|
package/dist/server/types.d.ts
CHANGED
|
@@ -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. */
|
|
@@ -519,6 +525,8 @@ export type Conversation = {
|
|
|
519
525
|
* deleted agent degrades to "no role" instead of leaving a stale snapshot in state.json.
|
|
520
526
|
*/
|
|
521
527
|
agentId?: string;
|
|
528
|
+
/** Relative to the workspace root. Absent or empty preserves the original workspace-root cwd. */
|
|
529
|
+
cwd?: string;
|
|
522
530
|
/**
|
|
523
531
|
* The conversation that opened this one, when an orchestrator agent created it. Absent for a
|
|
524
532
|
* conversation the user started. This is the session tree's edge, and it PERSISTS: the sidebar
|
|
@@ -655,6 +663,12 @@ export type SubagentUsage = {
|
|
|
655
663
|
cost: number;
|
|
656
664
|
turns: number;
|
|
657
665
|
};
|
|
666
|
+
/** Current context occupancy, distinct from cumulative billed tokens; null means unknown after compaction. */
|
|
667
|
+
export type ConversationContextUsage = {
|
|
668
|
+
tokens: number | null;
|
|
669
|
+
contextWindow: number;
|
|
670
|
+
percent: number | null;
|
|
671
|
+
};
|
|
658
672
|
/**
|
|
659
673
|
* ONE TOOL CALL, as the card shows it, and the boundary that keeps a child's turn out of `state.json`.
|
|
660
674
|
*
|
|
@@ -1022,6 +1036,7 @@ export type ClientCommandBody =
|
|
|
1022
1036
|
type: "terminal.open";
|
|
1023
1037
|
sessionId: string;
|
|
1024
1038
|
workspaceId: string;
|
|
1039
|
+
cwd?: string;
|
|
1025
1040
|
cols?: number;
|
|
1026
1041
|
rows?: number;
|
|
1027
1042
|
}
|
|
@@ -1182,6 +1197,7 @@ export type ClientCommandBody =
|
|
|
1182
1197
|
} | {
|
|
1183
1198
|
type: "conversation.create";
|
|
1184
1199
|
workspaceId: string;
|
|
1200
|
+
cwd?: string;
|
|
1185
1201
|
} | {
|
|
1186
1202
|
type: "conversation.select";
|
|
1187
1203
|
conversationId: string;
|
|
@@ -1284,6 +1300,7 @@ export type ClientCommandBody =
|
|
|
1284
1300
|
| {
|
|
1285
1301
|
type: "files.index.subscribe";
|
|
1286
1302
|
workspaceId?: string;
|
|
1303
|
+
cwd?: string;
|
|
1287
1304
|
} | {
|
|
1288
1305
|
type: "state.subscribe";
|
|
1289
1306
|
conversationId?: string;
|
|
@@ -1601,6 +1618,7 @@ export type ServerEvent = {
|
|
|
1601
1618
|
} | {
|
|
1602
1619
|
type: "files.index";
|
|
1603
1620
|
workspaceId: string;
|
|
1621
|
+
cwd?: string;
|
|
1604
1622
|
version: number;
|
|
1605
1623
|
changed: {
|
|
1606
1624
|
path: string;
|
package/dist/server/version.d.ts
CHANGED
|
@@ -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
|
|
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;
|