@co0ontty/wand 4.85.1 → 4.87.0-beta.gd2f0f8f
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/README.md +4 -0
- package/dist/agent-dispatch.js +5 -3
- package/dist/ai-team-runner.js +1 -0
- package/dist/ai-team-types.d.ts +1 -0
- package/dist/ai-team-types.js +6 -2
- package/dist/build-info.json +4 -4
- package/dist/cli.js +12 -6
- package/dist/config.d.ts +1 -1
- package/dist/config.js +33 -0
- package/dist/core-runner.js +1 -1
- package/dist/daemon-maintenance-state.d.ts +6 -0
- package/dist/daemon-maintenance-state.js +9 -0
- package/dist/daemon-maintenance-targets.d.ts +20 -0
- package/dist/daemon-maintenance-targets.js +183 -0
- package/dist/daemon-maintenance.d.ts +53 -0
- package/dist/daemon-maintenance.js +164 -0
- package/dist/decision-expert-employee.d.ts +6 -0
- package/dist/decision-expert-employee.js +22 -0
- package/dist/decision-expert-identity.d.ts +7 -0
- package/dist/decision-expert-identity.js +7 -0
- package/dist/decision-hardware.d.ts +19 -0
- package/dist/decision-hardware.js +19 -0
- package/dist/decision-service.d.ts +6 -1
- package/dist/decision-service.js +60 -1
- package/dist/decision-types.d.ts +1 -0
- package/dist/decision-types.js +12 -0
- package/dist/git-quick-commit.js +1 -1
- package/dist/laya-model-manifest.d.ts +11 -0
- package/dist/laya-model-manifest.js +18 -0
- package/dist/local-model-setup.d.ts +46 -0
- package/dist/local-model-setup.js +248 -0
- package/dist/local-model-types.d.ts +34 -0
- package/dist/local-model-types.js +1 -0
- package/dist/message-truncator.d.ts +4 -2
- package/dist/message-truncator.js +20 -6
- package/dist/missions.js +6 -3
- package/dist/model-file-store.d.ts +13 -0
- package/dist/model-file-store.js +108 -0
- package/dist/model-groups.js +2 -2
- package/dist/model-setup-process.d.ts +2 -0
- package/dist/model-setup-process.js +80 -0
- package/dist/models.js +3 -3
- package/dist/process-manager.js +21 -8
- package/dist/provider-catalog.d.ts +13 -1
- package/dist/provider-catalog.js +14 -2
- package/dist/render-binary.d.ts +1 -1
- package/dist/render-binary.js +4 -4
- package/dist/render-daemon-client.d.ts +12 -1
- package/dist/render-daemon-client.js +72 -4
- package/dist/render-host.d.ts +1 -0
- package/dist/render-host.js +9 -56
- package/dist/render-structured-host.d.ts +1 -0
- package/dist/render-structured-host.js +2 -2
- package/dist/server-employee-routes.js +3 -3
- package/dist/server-local-model-routes.d.ts +8 -0
- package/dist/server-local-model-routes.js +62 -0
- package/dist/server-resume-routes.js +1 -1
- package/dist/server-session-routes.js +34 -8
- package/dist/server-settings-routes.js +2 -0
- package/dist/server-speech-routes.d.ts +8 -0
- package/dist/server-speech-routes.js +87 -0
- package/dist/server-task-routes.js +3 -1
- package/dist/server-workspace-routes.js +2 -0
- package/dist/server.js +63 -18
- package/dist/session-completion-state.d.ts +1 -0
- package/dist/session-completion-state.js +1 -0
- package/dist/session-transport.js +1 -0
- package/dist/silicon-employee-dispatch.js +5 -2
- package/dist/silicon-employee-draft.js +1 -1
- package/dist/speech-models.d.ts +24 -0
- package/dist/speech-models.js +16 -0
- package/dist/speech-service.d.ts +57 -0
- package/dist/speech-service.js +399 -0
- package/dist/speech-types.d.ts +47 -0
- package/dist/speech-types.js +3 -0
- package/dist/storage.d.ts +2 -0
- package/dist/storage.js +13 -0
- package/dist/structured-failure.js +1 -1
- package/dist/structured-session-manager.d.ts +15 -2
- package/dist/structured-session-manager.js +58 -19
- package/dist/terminal-daemon-build.d.ts +4 -0
- package/dist/terminal-daemon-build.js +54 -0
- package/dist/terminal-daemon-protocol.d.ts +1 -1
- package/dist/terminal-daemon-server.js +20 -1
- package/dist/terminal-host.d.ts +4 -0
- package/dist/terminal-host.js +7 -4
- package/dist/tui/commands.js +1 -1
- package/dist/types.d.ts +14 -2
- package/dist/web-ui/chat-history-window.d.ts +23 -0
- package/dist/web-ui/chat-history-window.js +29 -0
- package/dist/web-ui/content/ai-teams.js +12 -12
- package/dist/web-ui/content/scripts.js +316 -233
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +1 -1
- package/dist/web-ui/embedded-assets.js +3 -3
- package/dist/web-ui/index.js +3 -3
- package/dist/web-ui/provider-identity.d.ts +28 -1
- package/dist/web-ui/provider-identity.js +31 -2
- package/dist/web-ui/session-activity.d.ts +1 -0
- package/dist/web-ui/session-activity.js +5 -2
- package/dist/web-ui/speech-audio.d.ts +2 -0
- package/dist/web-ui/speech-audio.js +41 -0
- package/dist/web-ui/vendor-loader.d.ts +2 -0
- package/dist/web-ui/vendor-loader.js +50 -0
- package/dist/ws-broadcast.js +10 -4
- package/package.json +7 -3
- package/scripts/install-laya-runtime.js +67 -0
- package/scripts/install-speech-runtime.js +82 -0
|
@@ -1362,9 +1362,12 @@ export class StructuredSessionManager {
|
|
|
1362
1362
|
const selectedModel = options.model?.trim() || null;
|
|
1363
1363
|
const initialThinkingEffort = normalizeThinkingEffort(options.thinkingEffort);
|
|
1364
1364
|
const autoCompaction = this.config.harness?.compaction?.enabled ?? true;
|
|
1365
|
+
// 显式引擎优先:用户新建会话时选了 Wand Agent(core)就不要再被员工候选或默认推断改口。
|
|
1366
|
+
const explicitEngine = provider === "pi" ? options.engine : undefined;
|
|
1365
1367
|
// 员工 SDK 会话不静默继承用户的全局扩展,也不继承 CLI 专属的资源/CodeMode 默认。
|
|
1366
1368
|
const sdkEmployeeSession = provider === "pi"
|
|
1367
|
-
&&
|
|
1369
|
+
&& (explicitEngine ? explicitEngine === "core"
|
|
1370
|
+
: options.employeeCandidates?.[options.employeeCandidateIndex ?? 0]?.engine === "sdk");
|
|
1368
1371
|
// CLI Pi 会话从「上次设置」起步:用户在任一 Pi 会话改过的自动选择 / CodeMode / 资源选择
|
|
1369
1372
|
// 就是新会话的默认,不需要每开一个会话重配一次。autoCompaction 始终取当前配置。
|
|
1370
1373
|
const rememberedPiSettings = provider === "pi" && !sdkEmployeeSession ? this.storage.getPiSessionDefaults() : null;
|
|
@@ -1420,6 +1423,7 @@ export class StructuredSessionManager {
|
|
|
1420
1423
|
structuredState: {
|
|
1421
1424
|
provider,
|
|
1422
1425
|
runner,
|
|
1426
|
+
...(explicitEngine ? { engine: explicitEngine } : {}),
|
|
1423
1427
|
model: selectedModel ?? undefined,
|
|
1424
1428
|
inFlight: false,
|
|
1425
1429
|
activeRequestId: null,
|
|
@@ -1502,6 +1506,7 @@ export class StructuredSessionManager {
|
|
|
1502
1506
|
const selected = candidates[index];
|
|
1503
1507
|
// Explicit tool/model changes, including changes during the pending request, remain authoritative.
|
|
1504
1508
|
if (!selected || original.provider !== selected.provider
|
|
1509
|
+
|| (original.structuredState?.engine && (original.structuredState.engine === "core" ? "sdk" : "cli") !== (selected.engine ?? "cli"))
|
|
1505
1510
|
|| (original.selectedModel && original.selectedModel !== this.employeeCandidateModel(selected)))
|
|
1506
1511
|
return null;
|
|
1507
1512
|
const current = this.sessions.get(id);
|
|
@@ -1812,8 +1817,10 @@ export class StructuredSessionManager {
|
|
|
1812
1817
|
}
|
|
1813
1818
|
else if (provider === "pi") {
|
|
1814
1819
|
const employeeAgent = updated.employeeCandidates?.[updated.employeeCandidateIndex ?? 0];
|
|
1815
|
-
|
|
1816
|
-
|
|
1820
|
+
// 会话已裁决过引擎就沿用(新建会话选了 Wand Agent 或 Pi CLI);未裁决的旧会话/员工会话回落到候选配置。
|
|
1821
|
+
const decidedEngine = updated.structuredState?.engine;
|
|
1822
|
+
const sdkRequested = decidedEngine ? decidedEngine === "core" : employeeAgent?.engine === "sdk";
|
|
1823
|
+
// 没显式选择时对话框固定走 CLI;只有选定 Wand Agent 或员工候选配置 SDK 才是 core。
|
|
1817
1824
|
const resolution = sdkRequested
|
|
1818
1825
|
? resolveHarnessEngineSync({ ...this.config.harness, engine: "core" }, "pi", {
|
|
1819
1826
|
coreRunnerSupplied: this.coreRunnerSupplied,
|
|
@@ -1832,7 +1839,7 @@ export class StructuredSessionManager {
|
|
|
1832
1839
|
await this.runClaudeStreaming(id, enginePatched, prompt, requestId, {
|
|
1833
1840
|
runner: this.coreRunner,
|
|
1834
1841
|
provider: "pi",
|
|
1835
|
-
commandLabel: "
|
|
1842
|
+
commandLabel: "Wand Agent(SDK)",
|
|
1836
1843
|
logKind: "pi-core",
|
|
1837
1844
|
});
|
|
1838
1845
|
}
|
|
@@ -2063,22 +2070,39 @@ export class StructuredSessionManager {
|
|
|
2063
2070
|
});
|
|
2064
2071
|
return updated;
|
|
2065
2072
|
}
|
|
2066
|
-
/**
|
|
2067
|
-
setSessionProvider(sessionId, provider) {
|
|
2073
|
+
/** 空白对话原位换执行工具;引擎也是工具身份,不跨工具搬历史或能力设置。 */
|
|
2074
|
+
setSessionProvider(sessionId, provider, engine) {
|
|
2068
2075
|
const session = this.requireSession(sessionId);
|
|
2069
2076
|
if (!isSessionProvider(provider))
|
|
2070
|
-
throw new Error("
|
|
2077
|
+
throw new Error("请选择有效的执行工具。");
|
|
2078
|
+
if (engine !== undefined && engine !== "cli" && engine !== "sdk")
|
|
2079
|
+
throw new Error("执行引擎必须是 cli 或 sdk。");
|
|
2080
|
+
if (engine === "sdk" && provider !== "pi")
|
|
2081
|
+
throw new Error("Wand Agent 只支持 Pi 结构化会话。");
|
|
2071
2082
|
if (session.status !== "idle" || session.archived || session.structuredState?.inFlight ||
|
|
2072
2083
|
this.pendingRunnerExecutions.has(sessionId) || session.claudeSessionId ||
|
|
2084
|
+
session.resumedFromSessionId || session.autoRecovered ||
|
|
2073
2085
|
(session.messages?.length ?? 0) > 0 || (session.queuedMessages?.length ?? 0) > 0 ||
|
|
2074
2086
|
session.automationId || (session.sessionSource && session.sessionSource !== "interactive") ||
|
|
2075
2087
|
this.relayHandlerFor(session)) {
|
|
2076
2088
|
throw new Error("只有尚未发送消息的新建空白对话可以更换工具。");
|
|
2077
2089
|
}
|
|
2078
|
-
if (session.provider === provider)
|
|
2079
|
-
return session;
|
|
2080
2090
|
const candidates = session.employeeCandidates ?? [];
|
|
2081
|
-
const
|
|
2091
|
+
const currentCandidate = candidates[session.employeeCandidateIndex ?? 0];
|
|
2092
|
+
const currentEngine = session.provider === "pi" && (session.structuredState?.engine === "core"
|
|
2093
|
+
|| (!session.structuredState?.engine && currentCandidate?.provider === "pi" && currentCandidate.engine === "sdk")) ? "sdk" : "cli";
|
|
2094
|
+
// 旧客户端只发 provider 时,同 provider 保持原引擎;显式 Pi 选项始终传 cli。
|
|
2095
|
+
const targetEngine = provider === "pi" ? engine ?? (session.provider === provider ? currentEngine : "cli") : "cli";
|
|
2096
|
+
if (session.provider === provider && currentEngine === targetEngine)
|
|
2097
|
+
return session;
|
|
2098
|
+
if (targetEngine === "sdk" && session.provider === "pi" && (session.piSettings?.resources?.skills.length
|
|
2099
|
+
|| session.piSettings?.resources?.mcpServers.length || session.piSettings?.lockedSkills?.length
|
|
2100
|
+
|| session.piSettings?.autoResources || session.piSettings?.codemodeOverride !== undefined)) {
|
|
2101
|
+
throw new Error("Wand Agent 不支持会话级 Skills / MCP、自动选择或 CodeMode 覆盖;请先在 Pi 设置中关闭这些选项再切换。");
|
|
2102
|
+
}
|
|
2103
|
+
const resolution = provider === "pi" ? this.resolveNewSessionPiEngine(targetEngine) : undefined;
|
|
2104
|
+
const candidateIndex = candidates.findIndex((candidate) => candidate.provider === provider
|
|
2105
|
+
&& (candidate.engine ?? "cli") === targetEngine);
|
|
2082
2106
|
const candidate = candidateIndex >= 0 ? candidates[candidateIndex] : undefined;
|
|
2083
2107
|
const runner = defaultStructuredRunner(provider);
|
|
2084
2108
|
const model = candidate && candidate.model !== "default" ? candidate.model
|
|
@@ -2094,18 +2118,19 @@ export class StructuredSessionManager {
|
|
|
2094
2118
|
...session,
|
|
2095
2119
|
provider,
|
|
2096
2120
|
runner,
|
|
2097
|
-
//
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
command: recoveredCommandLabel(runner),
|
|
2121
|
+
// 换引擎也是换工具:使用目标自己的默认设置,不复用另一路的能力配置。
|
|
2122
|
+
piSettings: provider !== "pi" ? undefined : targetEngine === "sdk"
|
|
2123
|
+
? defaultPiSessionSettings(this.config.harness?.compaction?.enabled ?? true)
|
|
2124
|
+
: { ...(this.storage.getPiSessionDefaults() ?? defaultPiCliSessionSettings(this.config.harness?.compaction?.enabled ?? true)),
|
|
2125
|
+
autoCompaction: this.config.harness?.compaction?.enabled ?? true },
|
|
2126
|
+
command: targetEngine === "sdk" ? "Wand Agent(SDK)" : recoveredCommandLabel(runner),
|
|
2103
2127
|
mode,
|
|
2104
2128
|
autoApprovePermissions: shouldAutoApproveForMode(mode),
|
|
2105
2129
|
selectedModel: model,
|
|
2106
2130
|
thinkingEffort,
|
|
2107
2131
|
employeeCandidateIndex: candidateIndex >= 0 ? candidateIndex : undefined,
|
|
2108
|
-
structuredState: { ...defaultStructuredState(provider, runner), model: model ?? undefined
|
|
2132
|
+
structuredState: { ...defaultStructuredState(provider, runner), model: model ?? undefined,
|
|
2133
|
+
...(resolution ? { engine: resolution.engine, engineReason: resolution.reason } : {}) },
|
|
2109
2134
|
};
|
|
2110
2135
|
// 身份、知识归属、任务/目录和候选快照不变;只更新本会话的执行参数。
|
|
2111
2136
|
this.storage.saveSession(updated);
|
|
@@ -2118,12 +2143,26 @@ export class StructuredSessionManager {
|
|
|
2118
2143
|
});
|
|
2119
2144
|
return updated;
|
|
2120
2145
|
}
|
|
2146
|
+
/**
|
|
2147
|
+
* 新建 pi 结构化会话前的引擎裁决。
|
|
2148
|
+
*
|
|
2149
|
+
* 用户在新建会话里选的是两个不同的东西:Pi CLI(`cli`)与 Wand Agent(`sdk`,进程内 harness)。
|
|
2150
|
+
* 显式选了 Wand Agent 就必须真的可用:不可用时抛错由路由告诉用户,不静默退回 CLI 冒充。
|
|
2151
|
+
*/
|
|
2152
|
+
resolveNewSessionPiEngine(engine) {
|
|
2153
|
+
if (engine !== "sdk")
|
|
2154
|
+
return { engine: "cli", reason: "Pi 主功能固定使用 CLI JSON" };
|
|
2155
|
+
return resolveHarnessEngineSync({ ...this.config.harness, engine: "core" }, "pi", {
|
|
2156
|
+
coreRunnerSupplied: this.coreRunnerSupplied,
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2121
2159
|
getPiSettings(sessionId) {
|
|
2122
2160
|
const session = this.requireSession(sessionId);
|
|
2123
2161
|
if (session.provider !== "pi")
|
|
2124
2162
|
throw new Error("这些设置仅适用于 Pi 结构化会话。");
|
|
2125
2163
|
const employeeAgent = session.employeeCandidates?.[session.employeeCandidateIndex ?? 0];
|
|
2126
|
-
const
|
|
2164
|
+
const decidedEngine = session.structuredState?.engine;
|
|
2165
|
+
const sdkRequested = decidedEngine ? decidedEngine === "core" : employeeAgent?.engine === "sdk";
|
|
2127
2166
|
let resolution;
|
|
2128
2167
|
if (sdkRequested) {
|
|
2129
2168
|
try {
|
|
@@ -2157,7 +2196,7 @@ export class StructuredSessionManager {
|
|
|
2157
2196
|
|| patch.codemodeOverride !== undefined
|
|
2158
2197
|
|| patch.autoResources !== undefined
|
|
2159
2198
|
|| patch.lockedSkills !== undefined)) {
|
|
2160
|
-
throw new Error("
|
|
2199
|
+
throw new Error("Wand Agent 尚不支持此会话级设置;没有改用全局资源或 CodeMode 配置。");
|
|
2161
2200
|
}
|
|
2162
2201
|
if (resolution.engine !== "core") {
|
|
2163
2202
|
// 写入裁决与启动参数用同一套能力边界:表达不出的字段/取值不落库。
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Captured at process start: replacing files cannot make an old owner current. */
|
|
2
|
+
export declare const TERMINAL_DAEMON_BUILD_ID: string;
|
|
3
|
+
/** Pause replacement while npm is changing this installation or awaiting Server relaunch. */
|
|
4
|
+
export declare function terminalDaemonBuildIsCurrent(): boolean;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
/** Component content, not the Wand release version or timestamp. The runtime
|
|
6
|
+
* import graph is covered by tests/terminal-daemon-build.test.ts. */
|
|
7
|
+
function daemonBuildId() {
|
|
8
|
+
const extension = import.meta.url.endsWith(".ts") ? "ts" : "js";
|
|
9
|
+
const hash = createHash("sha256");
|
|
10
|
+
hash.update(JSON.stringify([process.versions.node, process.platform, process.arch]));
|
|
11
|
+
for (const name of ["terminal-daemon-server", "terminal-daemon-protocol", "terminal-daemon-build", "terminal-host",
|
|
12
|
+
"structured-exec-host", "pty-terminal-state", "pty-shell-launch", "unix-socket-keepalive",
|
|
13
|
+
"ensure-node-pty-helper", "signal-utils", "pty-text-utils", "shell-quote", "error-utils", "render-protocol"]) {
|
|
14
|
+
hash.update(name).update("\0").update(readFileSync(new URL(`./${name}.${extension}`, import.meta.url)));
|
|
15
|
+
}
|
|
16
|
+
// Read installed versions afresh, not require()'s cached JSON. Unrelated
|
|
17
|
+
// Server/UI dependencies and package metadata must not replace terminald.
|
|
18
|
+
for (const name of ["node-pty", "@xterm/headless", "@xterm/addon-serialize", "@xterm/addon-unicode11"]) {
|
|
19
|
+
const { version } = JSON.parse(readFileSync(require.resolve(`${name}/package.json`), "utf8"));
|
|
20
|
+
if (typeof version !== "string" || !version)
|
|
21
|
+
throw new Error(`Missing daemon dependency version: ${name}`);
|
|
22
|
+
hash.update(name).update("\0").update(version);
|
|
23
|
+
}
|
|
24
|
+
return hash.digest("hex");
|
|
25
|
+
}
|
|
26
|
+
/** Captured at process start: replacing files cannot make an old owner current. */
|
|
27
|
+
export const TERMINAL_DAEMON_BUILD_ID = daemonBuildId();
|
|
28
|
+
// The installation fence has a different purpose from component equality:
|
|
29
|
+
// even a Server-only install must pause the old worker until Server relaunch.
|
|
30
|
+
function installationId(buildId) {
|
|
31
|
+
const hash = createHash("sha256").update(buildId);
|
|
32
|
+
for (const file of ["./build-info.json", "../package.json"]) {
|
|
33
|
+
hash.update(file).update("\0");
|
|
34
|
+
try {
|
|
35
|
+
hash.update(readFileSync(new URL(file, import.meta.url)));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error.code !== "ENOENT")
|
|
39
|
+
throw error;
|
|
40
|
+
hash.update("missing"); // source checkouts have no build stamp
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return hash.digest("hex");
|
|
44
|
+
}
|
|
45
|
+
const INSTALLATION_ID = installationId(TERMINAL_DAEMON_BUILD_ID);
|
|
46
|
+
/** Pause replacement while npm is changing this installation or awaiting Server relaunch. */
|
|
47
|
+
export function terminalDaemonBuildIsCurrent() {
|
|
48
|
+
try {
|
|
49
|
+
return installationId(daemonBuildId()) === INSTALLATION_ID;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -10,7 +10,7 @@ export type TerminalDaemonRequest = {
|
|
|
10
10
|
id: number;
|
|
11
11
|
token: string;
|
|
12
12
|
protocolVersion: number;
|
|
13
|
-
method: "hello" | "list" | "createOrAttach" | "write" | "resize" | "kill" | "forget" | "structuredSpawn" | "structuredAttach" | "structuredList" | "structuredKill" | "structuredForget";
|
|
13
|
+
method: "hello" | "list" | "createOrAttach" | "write" | "resize" | "kill" | "forget" | "structuredSpawn" | "structuredAttach" | "structuredList" | "structuredKill" | "structuredForget" | "shutdownIfIdle";
|
|
14
14
|
params?: Record<string, unknown>;
|
|
15
15
|
};
|
|
16
16
|
export type TerminalDaemonResponse = {
|
|
@@ -6,6 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import { StringDecoder } from "node:string_decoder";
|
|
7
7
|
import pty from "node-pty";
|
|
8
8
|
import { keepUnixSocketAlive } from "./unix-socket-keepalive.js";
|
|
9
|
+
import { TERMINAL_DAEMON_BUILD_ID } from "./terminal-daemon-build.js";
|
|
9
10
|
import { ensureNodePtyHelperExecutable } from "./ensure-node-pty-helper.js";
|
|
10
11
|
import { signalNumberFromName } from "./signal-utils.js";
|
|
11
12
|
import { STRUCTURED_RUN_LOG_MAX_CHARS, createUtf8TextDecoder, } from "./structured-exec-host.js";
|
|
@@ -81,6 +82,7 @@ export async function runTerminalDaemon(configPath) {
|
|
|
81
82
|
const sessions = new Map();
|
|
82
83
|
const clients = new Set();
|
|
83
84
|
const structuredRuns = new Map();
|
|
85
|
+
let retiring = false;
|
|
84
86
|
const send = (socket, message) => {
|
|
85
87
|
if (!socket.destroyed && socket.writable)
|
|
86
88
|
socket.write(`${JSON.stringify(message)}\n`);
|
|
@@ -356,12 +358,25 @@ export async function runTerminalDaemon(configPath) {
|
|
|
356
358
|
const params = request.params ?? {};
|
|
357
359
|
switch (request.method) {
|
|
358
360
|
case "hello":
|
|
359
|
-
result = { protocolVersion: TERMINAL_DAEMON_PROTOCOL_VERSION, pid: process.pid
|
|
361
|
+
result = { protocolVersion: TERMINAL_DAEMON_PROTOCOL_VERSION, pid: process.pid,
|
|
362
|
+
buildId: TERMINAL_DAEMON_BUILD_ID, shutdownIfIdle: true };
|
|
360
363
|
break;
|
|
364
|
+
case "shutdownIfIdle": {
|
|
365
|
+
const busy = [...sessions.values(), ...structuredRuns.values()].some((entry) => entry.status === "running");
|
|
366
|
+
result = { accepted: !busy };
|
|
367
|
+
if (!busy && !retiring) {
|
|
368
|
+
// The idle check and admission closure happen in the same event-loop turn.
|
|
369
|
+
retiring = true;
|
|
370
|
+
setTimeout(shutdown, 25).unref();
|
|
371
|
+
}
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
361
374
|
case "list":
|
|
362
375
|
result = Array.from(sessions.values(), serialize);
|
|
363
376
|
break;
|
|
364
377
|
case "createOrAttach":
|
|
378
|
+
if (retiring)
|
|
379
|
+
throw new Error("Terminal daemon is updating");
|
|
365
380
|
result = createOrAttach(params);
|
|
366
381
|
break;
|
|
367
382
|
case "write": {
|
|
@@ -399,6 +414,8 @@ export async function runTerminalDaemon(configPath) {
|
|
|
399
414
|
result = { ok: true };
|
|
400
415
|
break;
|
|
401
416
|
case "structuredSpawn":
|
|
417
|
+
if (retiring)
|
|
418
|
+
throw new Error("Terminal daemon is updating");
|
|
402
419
|
result = structuredSpawn(params);
|
|
403
420
|
break;
|
|
404
421
|
case "structuredAttach": {
|
|
@@ -425,6 +442,8 @@ export async function runTerminalDaemon(configPath) {
|
|
|
425
442
|
structuredForget(String(params.runId ?? ""));
|
|
426
443
|
result = { ok: true };
|
|
427
444
|
break;
|
|
445
|
+
default:
|
|
446
|
+
throw new Error("Unsupported terminal daemon method");
|
|
428
447
|
}
|
|
429
448
|
send(client.socket, { kind: "response", id: request.id, ok: true, result });
|
|
430
449
|
}
|
package/dist/terminal-host.d.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface TerminalHost {
|
|
|
68
68
|
attach(sessionId: string, afterSeq?: number): TerminalAttachResult | null;
|
|
69
69
|
createOrAttach(request: TerminalSpawnRequest, afterSeq?: number): Promise<TerminalAttachResult>;
|
|
70
70
|
forget(sessionId: string): void;
|
|
71
|
+
/** Publish already-read bytes before synchronous snapshots or owner teardown. */
|
|
72
|
+
flush?(): void;
|
|
71
73
|
disconnect(): void;
|
|
72
74
|
}
|
|
73
75
|
/** Legacy/local adapter and deterministic test adapter for the TerminalHost seam. */
|
|
@@ -81,3 +83,5 @@ export declare class InProcessTerminalHost implements TerminalHost {
|
|
|
81
83
|
}
|
|
82
84
|
/** Keep a bounded replay log while preserving chunk sequence numbers. */
|
|
83
85
|
export declare function appendTerminalChunkWindow(chunks: readonly TerminalDataEvent[], event: TerminalDataEvent): TerminalDataEvent[];
|
|
86
|
+
/** Same replay window, computed once for a batch instead of per wire frame. */
|
|
87
|
+
export declare function appendTerminalChunkBatch(chunks: readonly TerminalDataEvent[], events: readonly TerminalDataEvent[]): TerminalDataEvent[];
|
package/dist/terminal-host.js
CHANGED
|
@@ -103,10 +103,13 @@ function emptyRunningState(process, cols, rows, launchMarkerToken) {
|
|
|
103
103
|
}
|
|
104
104
|
/** Keep a bounded replay log while preserving chunk sequence numbers. */
|
|
105
105
|
export function appendTerminalChunkWindow(chunks, event) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
return appendTerminalChunkBatch(chunks, [event]);
|
|
107
|
+
}
|
|
108
|
+
/** Same replay window, computed once for a batch instead of per wire frame. */
|
|
109
|
+
export function appendTerminalChunkBatch(chunks, events) {
|
|
110
|
+
const next = [...chunks, ...events.map((event) => event.data.length > PTY_OUTPUT_MAX_SIZE
|
|
111
|
+
? { ...event, data: event.data.slice(-PTY_OUTPUT_MAX_SIZE) }
|
|
112
|
+
: event)];
|
|
110
113
|
let size = 0;
|
|
111
114
|
let first = next.length;
|
|
112
115
|
while (first > 0) {
|
package/dist/tui/commands.js
CHANGED
|
@@ -783,7 +783,7 @@ function staleDaemonNote(configPath) {
|
|
|
783
783
|
if (daemonVersion && compareSemver(daemonVersion, binaryVersion) >= 0)
|
|
784
784
|
return null;
|
|
785
785
|
const shown = daemonVersion ? `Render ${daemonVersion}` : "Render daemon";
|
|
786
|
-
return
|
|
786
|
+
return `;底层组件将在所有会话结束后自动更新(${shown} → ${binaryVersion})。等待期间部分功能可能暂时异常,无需操作`;
|
|
787
787
|
}
|
|
788
788
|
/** daemon 端点状态摘要,给服务命令的 detail 用。 */
|
|
789
789
|
function describeDaemonEndpoints(configPath) {
|
package/dist/types.d.ts
CHANGED
|
@@ -144,6 +144,11 @@ export interface WandConfig {
|
|
|
144
144
|
defaultProvider?: SessionProvider;
|
|
145
145
|
/** 新建会话时默认使用的承载类型。 */
|
|
146
146
|
defaultSessionKind?: SessionKind;
|
|
147
|
+
/**
|
|
148
|
+
* 新建会话时默认的执行引擎;只对支持多引擎的 provider 有意义(目前只有 pi):
|
|
149
|
+
* `cli` = Pi CLI,`sdk` = Wand Agent(进程内 SDK)。缺省视为 CLI。
|
|
150
|
+
*/
|
|
151
|
+
defaultEngine?: "cli" | "sdk";
|
|
147
152
|
/** 新建任务时是否默认开启独立 worktree。 */
|
|
148
153
|
defaultTaskWorktree?: boolean;
|
|
149
154
|
defaultMode: ExecutionMode;
|
|
@@ -615,6 +620,8 @@ export interface ConversationAuthor {
|
|
|
615
620
|
avatar?: string;
|
|
616
621
|
leader?: boolean;
|
|
617
622
|
provider?: SessionProvider;
|
|
623
|
+
/** 本条发言实际候选的执行引擎;历史缺省仍按 CLI 显示。 */
|
|
624
|
+
engine?: "cli" | "sdk";
|
|
618
625
|
/**
|
|
619
626
|
* 这条发言**实际使用候选**的模型 id(真值,不是展示文案;`"default"` 表示跟随该 provider
|
|
620
627
|
* 的服务端默认模型)。老数据没有这个字段,读端要能只按 provider 显示。
|
|
@@ -761,10 +768,15 @@ export interface SessionSnapshot extends SessionCompletionState {
|
|
|
761
768
|
/**
|
|
762
769
|
* PTY 会话本轮是否正在生成回复(provider CLI busy)。语义对齐 structured 的
|
|
763
770
|
* structuredState.inFlight:turn 开始置 true,回复结束/进程退出清 false。
|
|
764
|
-
*
|
|
765
|
-
* 其余 provider 不产生该信号(undefined),客户端应回落到旧行为。
|
|
771
|
+
* 运行时信号,不持久化;Claude 使用 bridge,其余 provider 使用前台资格与输出静默窗口。
|
|
766
772
|
*/
|
|
767
773
|
ptyBusy?: boolean;
|
|
774
|
+
/**
|
|
775
|
+
* 空白 PTY 中有前台命令(如手动启动 CLI / 构建),由内核前台进程组采样得出。
|
|
776
|
+
* 与 provider 回合无关,不产生成功完成回执或回合计时;无输出也可为 true。
|
|
777
|
+
* 仅运行时,不持久化;未探测、不支持的平台、提示符与已结束终端均为 false。
|
|
778
|
+
*/
|
|
779
|
+
ptyCommandRunning?: boolean;
|
|
768
780
|
/**
|
|
769
781
|
* PTY 本轮的时间锚点,语义与 structuredState.turnStartedAt / lastActivityAt 完全一致,
|
|
770
782
|
* 是 ptyBusy 的配套读数:turn 开始(或静默窗口被输出续期)时写入,ptyBusy 清 false
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Small transport pages; the browser fills them to a measured viewport buffer. */
|
|
2
|
+
export declare const CHAT_INITIAL_MESSAGE_COUNT = 8;
|
|
3
|
+
export declare const CHAT_HISTORY_BLOCK_BUDGET = 12;
|
|
4
|
+
export declare const CHAT_HISTORY_BYTE_BUDGET: number;
|
|
5
|
+
export declare const CHAT_HISTORY_BUFFER_SCREENS = 2;
|
|
6
|
+
export interface ChatScrollGeometry {
|
|
7
|
+
scrollHeight: number;
|
|
8
|
+
clientHeight: number;
|
|
9
|
+
scrollTop: number;
|
|
10
|
+
}
|
|
11
|
+
/** The chat scroller is column-reverse (latest at scrollTop=0). */
|
|
12
|
+
export declare function chatHistoryBufferRemaining(geometry: ChatScrollGeometry): number;
|
|
13
|
+
export declare function needsChatHistoryBuffer(geometry: ChatScrollGeometry): boolean;
|
|
14
|
+
export declare function chatHistoryRootMargin(height: number): string;
|
|
15
|
+
/** One attempt per cursor until progress or an explicit retry. Height decides
|
|
16
|
+
* how much to prefetch, never whether a successful page made progress:
|
|
17
|
+
* scrollHeight stays at clientHeight while the screen is underfilled, and
|
|
18
|
+
* folded tool pages may add no visible height at all. */
|
|
19
|
+
export declare class ChatHistoryPrefetchGate {
|
|
20
|
+
private attempt;
|
|
21
|
+
canAttempt(scope: string, cursor: string, manual?: boolean): boolean;
|
|
22
|
+
attempted(scope: string, cursor: string): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Small transport pages; the browser fills them to a measured viewport buffer. */
|
|
2
|
+
export const CHAT_INITIAL_MESSAGE_COUNT = 8;
|
|
3
|
+
export const CHAT_HISTORY_BLOCK_BUDGET = 12;
|
|
4
|
+
export const CHAT_HISTORY_BYTE_BUDGET = 96 * 1024;
|
|
5
|
+
export const CHAT_HISTORY_BUFFER_SCREENS = 2;
|
|
6
|
+
/** The chat scroller is column-reverse (latest at scrollTop=0). */
|
|
7
|
+
export function chatHistoryBufferRemaining(geometry) {
|
|
8
|
+
return Math.max(0, geometry.scrollHeight - geometry.clientHeight - Math.abs(geometry.scrollTop));
|
|
9
|
+
}
|
|
10
|
+
export function needsChatHistoryBuffer(geometry) {
|
|
11
|
+
return geometry.clientHeight > 0
|
|
12
|
+
&& chatHistoryBufferRemaining(geometry) < geometry.clientHeight * CHAT_HISTORY_BUFFER_SCREENS;
|
|
13
|
+
}
|
|
14
|
+
export function chatHistoryRootMargin(height) {
|
|
15
|
+
return `${Math.max(0, Math.ceil(height * CHAT_HISTORY_BUFFER_SCREENS))}px 0px 0px 0px`;
|
|
16
|
+
}
|
|
17
|
+
/** One attempt per cursor until progress or an explicit retry. Height decides
|
|
18
|
+
* how much to prefetch, never whether a successful page made progress:
|
|
19
|
+
* scrollHeight stays at clientHeight while the screen is underfilled, and
|
|
20
|
+
* folded tool pages may add no visible height at all. */
|
|
21
|
+
export class ChatHistoryPrefetchGate {
|
|
22
|
+
attempt = "";
|
|
23
|
+
canAttempt(scope, cursor, manual = false) {
|
|
24
|
+
return manual || this.attempt !== `${scope}\0${cursor}`;
|
|
25
|
+
}
|
|
26
|
+
attempted(scope, cursor) {
|
|
27
|
+
this.attempt = `${scope}\0${cursor}`;
|
|
28
|
+
}
|
|
29
|
+
}
|