@co0ontty/wand 4.96.1-beta.202610161706 → 4.97.0
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 +6 -0
- package/dist/ai-text-runner.d.ts +9 -2
- package/dist/ai-text-runner.js +14 -9
- package/dist/auth.js +6 -11
- package/dist/build-info.json +4 -4
- package/dist/cli.js +1 -1
- package/dist/decision-skill.d.ts +2 -4
- package/dist/git-quick-commit.js +53 -17
- package/dist/managed-skill.d.ts +8 -3
- package/dist/managed-skill.js +10 -3
- package/dist/managed-skills-ensure.d.ts +12 -0
- package/dist/managed-skills-ensure.js +43 -0
- package/dist/middleware/auth.js +0 -13
- package/dist/model-group-runner.js +4 -1
- package/dist/pi-auto-resources.js +3 -1
- package/dist/pi-tmux-host.js +1 -1
- package/dist/process-manager.d.ts +3 -0
- package/dist/process-manager.js +68 -3
- package/dist/provider-history-scanner.d.ts +2 -0
- package/dist/provider-history-scanner.js +62 -48
- package/dist/render-structured-client.js +1 -1
- package/dist/scheduled-task-calendar.js +2 -3
- package/dist/scheduled-task-time.d.ts +2 -1
- package/dist/scheduled-task-time.js +27 -2
- package/dist/scheduled-task-types.d.ts +5 -0
- package/dist/server-session-list-routes.js +11 -4
- package/dist/server-task-routes.js +36 -8
- package/dist/server-update-routes.js +2 -1
- package/dist/server-workspace-routes.js +31 -3
- package/dist/server.js +9 -1
- package/dist/session-artifact-cleanup.d.ts +7 -0
- package/dist/session-artifact-cleanup.js +45 -0
- package/dist/session-registry.d.ts +9 -1
- package/dist/session-registry.js +59 -10
- package/dist/storage/internal.d.ts +3 -7
- package/dist/storage/internal.js +1 -1
- package/dist/storage/migrations.js +5 -0
- package/dist/storage/secrets-repository.js +0 -2
- package/dist/storage/tasks-repository.js +26 -4
- package/dist/storage-schema.js +3 -0
- package/dist/structured-claude-protocol.d.ts +2 -0
- package/dist/structured-claude-protocol.js +67 -2
- package/dist/structured-exec-host.d.ts +2 -0
- package/dist/structured-exec-pump.js +11 -2
- package/dist/structured-pi-adapter.js +1 -0
- package/dist/structured-runner.d.ts +2 -0
- package/dist/structured-session-manager.d.ts +4 -0
- package/dist/structured-session-manager.js +56 -2
- package/dist/system-cli.js +1 -1
- package/dist/system-skill.d.ts +1 -4
- package/dist/task-agent-parsing.d.ts +1 -1
- package/dist/task-agent-parsing.js +6 -11
- package/dist/task-dispatch-receipt.d.ts +4 -0
- package/dist/task-dispatch-receipt.js +59 -0
- package/dist/task-hierarchy.d.ts +15 -0
- package/dist/task-hierarchy.js +67 -0
- package/dist/task-read-model.d.ts +6 -0
- package/dist/task-read-model.js +12 -0
- package/dist/task-title-organize.js +23 -6
- package/dist/task-title-service.js +4 -0
- package/dist/task-types.d.ts +9 -0
- package/dist/terminal-daemon-client.js +2 -1
- package/dist/web-login-tickets.d.ts +1 -1
- package/dist/web-login-tickets.js +6 -6
- package/dist/web-ui/content/module-manifest.json +1 -1
- package/dist/web-ui/content/modules/0c96341a022aa360/host.js +1 -0
- package/dist/web-ui/content/modules/15e32e85e086f6b4/page.js +6 -0
- package/dist/web-ui/content/modules/1e43259bddbc6805/markdown.js +1 -0
- package/dist/web-ui/content/modules/271d488d262909c2/host.js +30 -0
- package/dist/web-ui/content/modules/{7762ec45d7726c43 → 2bce1137f402bd70}/chunk.js +1 -1
- package/dist/web-ui/content/modules/3086df78444998b3/host.js +1 -0
- package/dist/web-ui/content/modules/3172fe0b46d51f8d/chunk.js +1 -0
- package/dist/web-ui/content/modules/{f2aaa9ad807f9425 → 3338b55c7767bacb}/chunk.js +2 -2
- package/dist/web-ui/content/modules/{ed7ed2b0eeab084c → 349ee129eef5a182}/chunk.js +1 -1
- package/dist/web-ui/content/modules/3ddcbd8505bfcdf4/chunk.js +1 -0
- package/dist/web-ui/content/modules/{f8e0987f1a44bfb7 → 42ae495da5903b07}/sha256.js +1 -1
- package/dist/web-ui/content/modules/43746e7237d61da9/task-board-host.js +17 -0
- package/dist/web-ui/content/modules/4485e6c1f0459aa8/chunk.js +1 -0
- package/dist/web-ui/content/modules/{dcc8b270d7fb7336 → 5903104a2e0e3888}/host.js +2 -2
- package/dist/web-ui/content/modules/5b541e0f296a3592/employee-card.js +1 -0
- package/dist/web-ui/content/modules/5e5eabd9a9366a56/panel.js +1 -0
- package/dist/web-ui/content/modules/64c1b0217f5377aa/chunk.js +3 -0
- package/dist/web-ui/content/modules/{75316f04bc550b0c → 66f36cd2c38cfb07}/chunk.js +1 -1
- package/dist/web-ui/content/modules/6ed52e6604661678/chat-render.js +75 -0
- package/dist/web-ui/content/modules/801e7f686ce7eb28/chunk.js +1 -0
- package/dist/web-ui/content/modules/854b0f6dc887923b/chunk.js +1 -0
- package/dist/web-ui/content/modules/87379801875d1921/host.js +1 -0
- package/dist/web-ui/content/modules/89888da0972e5033/host.js +50 -0
- package/dist/web-ui/content/modules/{223edddafb366679 → 9984f6bb05ac55bb}/host.js +2 -2
- package/dist/web-ui/content/modules/a9f15bd03bc590e7/host.js +1 -0
- package/dist/web-ui/content/modules/{0fc6176e363fa071 → b33c567946044d83}/host.js +3 -3
- package/dist/web-ui/content/modules/c11165f843d818f4/host-bridge.js +1 -0
- package/dist/web-ui/content/modules/c1829693f91fd25c/host.js +1 -0
- package/dist/web-ui/content/modules/c41d8ffa27816766/host.js +1 -0
- package/dist/web-ui/content/modules/cdb882ddab12e325/host.js +1 -0
- package/dist/web-ui/content/modules/{c579975101257d79 → d30ac9af6dc7d555}/chunk.js +1 -1
- package/dist/web-ui/content/modules/de00ab5329b5e05a/chunk.js +1 -0
- package/dist/web-ui/content/modules/{0e667f1e0a0568c6 → e01e1e25b7fe37e9}/host.js +2 -2
- package/dist/web-ui/content/modules/e8f0cda6b9ac8a70/chunk.js +1 -0
- package/dist/web-ui/content/modules/{8eed1ac34220d07d → e9a01a25ec573294}/library-bridge.js +3 -3
- package/dist/web-ui/content/modules/ea67fd4e96c9f45a/workspace-window.js +1 -0
- package/dist/web-ui/content/modules/ee12d3c4421a5c12/host.js +1 -0
- package/dist/web-ui/content/modules/f5b3fb6fedd9b05d/chunk.js +20 -0
- package/dist/web-ui/content/modules/fd767282c29173b5/runtime.js +539 -0
- package/dist/web-ui/embedded-assets.d.ts +173 -167
- package/dist/web-ui/embedded-assets.js +11 -11
- package/dist/web-ui/index.js +7 -1
- package/dist/web-ui/login-credential.d.ts +2 -0
- package/dist/web-ui/login-credential.js +4 -0
- package/dist/web-ui/react/workspaces/types.d.ts +2 -0
- package/dist/ws-broadcast.js +0 -6
- package/package.json +3 -1
- package/skills/wand-system/SKILL.md +3 -1
- package/skills/wand-system/references/operations.md +13 -4
- package/skills/wand-system/scripts/web-login.mjs +22 -9
- package/browser-extension/icons/wand-128.png +0 -0
- package/browser-extension/icons/wand-16.png +0 -0
- package/browser-extension/icons/wand-32.png +0 -0
- package/browser-extension/icons/wand-48.png +0 -0
- package/dist/web-session-access.d.ts +0 -7
- package/dist/web-session-access.js +0 -21
- package/dist/web-ui/content/modules/031fd6b62eb91b3b/runtime.js +0 -583
- package/dist/web-ui/content/modules/066faea7b90f2114/host-bridge.js +0 -1
- package/dist/web-ui/content/modules/10b9a520636ec75f/host.js +0 -1
- package/dist/web-ui/content/modules/167ef46bca671291/host.js +0 -1
- package/dist/web-ui/content/modules/1898cb03cc5120f3/host.js +0 -1
- package/dist/web-ui/content/modules/2861e5534b5ae6ff/employee-card.js +0 -1
- package/dist/web-ui/content/modules/2fd5edf0b4cfa7b9/panel.js +0 -1
- package/dist/web-ui/content/modules/3302a337cb81794f/host.js +0 -1
- package/dist/web-ui/content/modules/56e326c7f428a6a7/chunk.js +0 -1
- package/dist/web-ui/content/modules/5de4594380459c38/chunk.js +0 -1
- package/dist/web-ui/content/modules/6d331698bd3da433/page.js +0 -6
- package/dist/web-ui/content/modules/6f6c773bb93791d4/host.js +0 -1
- package/dist/web-ui/content/modules/710a6252e7706be0/workspace-window.js +0 -1
- package/dist/web-ui/content/modules/82b154630921d35d/host.js +0 -5
- package/dist/web-ui/content/modules/892b93f49607f5d3/task-board-host.js +0 -2
- package/dist/web-ui/content/modules/99b1d1672bcc84f0/host.js +0 -1
- package/dist/web-ui/content/modules/ae01d465dedd7fea/chunk.js +0 -1
- package/dist/web-ui/content/modules/b83de6a326c35862/chunk.js +0 -1
- package/dist/web-ui/content/modules/baec0f694303f08d/chunk.js +0 -3
- package/dist/web-ui/content/modules/c2a4fada737ab3ec/chunk.js +0 -1
- package/dist/web-ui/content/modules/c54550a744c53791/host.js +0 -1
- package/dist/web-ui/content/modules/c61f9b60c61baa5d/chat-render.js +0 -25
- package/dist/web-ui/content/modules/d73ed3d868862912/host.js +0 -1
- package/dist/web-ui/content/modules/e494c4ed3913fc59/chunk.js +0 -1
- package/dist/web-ui/content/modules/ea5d23ebc906ff78/host.js +0 -18
- package/dist/web-ui/content/modules/f90a70d43e17334d/markdown.js +0 -1
package/README.md
CHANGED
|
@@ -187,6 +187,8 @@ New system employees default to OpenCode's **Free group**, a saved Wand-managed
|
|
|
187
187
|
|
|
188
188
|
Set `WAND_DISABLE_OPENCODE_FALLBACK=1` on the server to disable automatic preparation and the free-group execution service. Free upstream models can be rate-limited, retired, or have different data-retention/training policies; system text sent to this fallback is processed by that provider. See [OpenCode model policies](https://opencode.ai/docs/zen).
|
|
189
189
|
|
|
190
|
+
Employee onboarding is available in the Web console: **Settings menu → 员工与通讯录 → 管理员工与团队模板 → 员工 → 招聘员工**. Use manual configuration or review HR's draft before confirming onboarding. See the [employee onboarding guide](docs/employee-onboarding.md); `employee:list` only lists definitions, it does not create employees.
|
|
191
|
+
|
|
190
192
|
## 中文
|
|
191
193
|
|
|
192
194
|
### 概览
|
|
@@ -232,6 +234,10 @@ npm install -g opencode-ai@latest
|
|
|
232
234
|
|
|
233
235
|
服务环境设置 `WAND_DISABLE_OPENCODE_FALLBACK=1` 可关闭自动准备和免费分组执行服务。免费上游可能限流、退役,数据保留/训练政策也各不相同;使用兜底时系统文本会发往对应提供商,详见 [OpenCode 模型政策](https://opencode.ai/docs/zen)。
|
|
234
236
|
|
|
237
|
+
### 创建硅基员工
|
|
238
|
+
|
|
239
|
+
正式入口:**左侧设置菜单 → 员工与通讯录 → 管理员工与团队模板 → 员工 → 招聘员工**。可手动填写资料,或请 HR 嘟嘟拟定后核对并确认入职;起草不会自动创建员工。详见[招聘与入职指南](docs/employee-onboarding.md)。CLI `employee:list` 仅查询定义,创建请使用控制台,无需绕过界面调用内部接口。
|
|
240
|
+
|
|
235
241
|
### 功能
|
|
236
242
|
|
|
237
243
|
#### 核心
|
package/dist/ai-text-runner.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type EmployeeTextDeps } from "./employee-text.js";
|
|
2
|
-
import { type AiCallTraceFactory, type SystemAiPurpose } from "./system-ai-call.js";
|
|
2
|
+
import { type AiCallTrace, type AiCallTraceFactory, type SystemAiPurpose } from "./system-ai-call.js";
|
|
3
3
|
import type { OpenCodeTextRuntime, OpenCodeFreePool } from "./opencode-system-fallback.js";
|
|
4
4
|
import type { AiTextRequest, SessionProvider, SessionSnapshot } from "./types.js";
|
|
5
5
|
export { QuickCommitError } from "./command-error.js";
|
|
6
|
-
/** Neutral one-shot text execution. Tool-running Git fallback
|
|
6
|
+
/** Neutral one-shot text execution. Tool-running Git fallback uses the same employee chain. */
|
|
7
7
|
export declare function defaultProvider(provider: SessionProvider | undefined): SessionProvider;
|
|
8
8
|
export interface QuickCommitAiOptions {
|
|
9
9
|
modelGroups?: import("./model-groups.js").ModelGroup[];
|
|
@@ -53,6 +53,13 @@ export declare function runCliText(command: string, args: string[], prompt: stri
|
|
|
53
53
|
export declare function callCliAiText(request: AiTextRequest, cwd: string, language: string, opts: CliAiTextOptions): Promise<string>;
|
|
54
54
|
/** 角色设定走系统提示前缀;任务自己的输出格式与约束必须排在后面。 */
|
|
55
55
|
export declare function withOpsPersona(request: AiTextRequest, persona?: string): AiTextRequest;
|
|
56
|
+
export interface ExpandedAiCandidates {
|
|
57
|
+
chain: import("./types.js").AiCliCandidate[];
|
|
58
|
+
freeLeaves: Set<import("./types.js").AiCliCandidate>;
|
|
59
|
+
deadline: number;
|
|
60
|
+
}
|
|
61
|
+
/** Expand the system employee's chain once: groups, free leaves, and duplicates. */
|
|
62
|
+
export declare function expandEmployeeCandidates(opts: QuickCommitAiOptions, trace?: AiCallTrace): Promise<ExpandedAiCandidates>;
|
|
56
63
|
/**
|
|
57
64
|
* Run a lightweight AI request through the system employee's configured chain.
|
|
58
65
|
* provider 与引擎由候选决定;SDK 与 CLI 均保留各自适配和身份。
|
package/dist/ai-text-runner.js
CHANGED
|
@@ -16,7 +16,7 @@ import { composeSystemFallback, systemPromptFlag, thinkingEffortToClaudeCliEffor
|
|
|
16
16
|
export { QuickCommitError } from "./command-error.js";
|
|
17
17
|
const CLAUDE_MESSAGE_TIMEOUT_MS = 60_000;
|
|
18
18
|
const CODEX_MESSAGE_TIMEOUT_MS = 60_000;
|
|
19
|
-
/** Neutral one-shot text execution. Tool-running Git fallback
|
|
19
|
+
/** Neutral one-shot text execution. Tool-running Git fallback uses the same employee chain. */
|
|
20
20
|
export function defaultProvider(provider) {
|
|
21
21
|
return isSessionProvider(provider) ? provider : "claude";
|
|
22
22
|
}
|
|
@@ -531,12 +531,8 @@ function singleCandidate(opts) {
|
|
|
531
531
|
thinkingEffort: opts.thinkingEffort ?? undefined,
|
|
532
532
|
};
|
|
533
533
|
}
|
|
534
|
-
/**
|
|
535
|
-
|
|
536
|
-
* 只有一条候选时保持原行为(错误原样抛出,不摘要)。
|
|
537
|
-
* 仅用于一次性文本生成;执行工具的任务不能使用这条无条件重试路径。
|
|
538
|
-
*/
|
|
539
|
-
async function callTextCandidates(request, cwd, language, opts, parseOutput, trace) {
|
|
534
|
+
/** Expand the system employee's chain once: groups, free leaves, and duplicates. */
|
|
535
|
+
export async function expandEmployeeCandidates(opts, trace) {
|
|
540
536
|
opts.signal?.throwIfAborted();
|
|
541
537
|
const { resolveModelGroupModels } = await import("./model-groups.js");
|
|
542
538
|
const modelGroups = opts.modelGroups ?? opts.employeeText?.config.modelGroups;
|
|
@@ -570,8 +566,7 @@ async function callTextCandidates(request, cwd, language, opts, parseOutput, tra
|
|
|
570
566
|
}
|
|
571
567
|
expanded.push(...resolveModelGroupModels(modelGroups, candidate.provider, candidate.model, { preferDefault: !candidate.model || candidate.model === "default" }).map(model => ({ ...candidate, model: model || undefined })));
|
|
572
568
|
}
|
|
573
|
-
const chain = expanded
|
|
574
|
-
.filter(candidate => {
|
|
569
|
+
const chain = expanded.filter(candidate => {
|
|
575
570
|
const key = JSON.stringify([candidate.provider, candidate.engine ?? "cli", candidate.model ?? "", candidate.thinkingEffort ?? opts.thinkingEffort]);
|
|
576
571
|
if (seen.has(key))
|
|
577
572
|
return false;
|
|
@@ -583,6 +578,16 @@ async function callTextCandidates(request, cwd, language, opts, parseOutput, tra
|
|
|
583
578
|
}
|
|
584
579
|
if (!chain.length)
|
|
585
580
|
throw new QuickCommitError("系统员工没有可用的执行候选;免费分组为空或尚未同步。", "AI_FALLBACK_FAILED");
|
|
581
|
+
return { chain, freeLeaves, deadline };
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* 按顺序尝试候选:跳过没安装的 CLI,协议与输出校验均成功才返回。
|
|
585
|
+
* 只有一条候选时保持原行为(错误原样抛出,不摘要)。
|
|
586
|
+
* 文本生成可以无条件换下一条;有副作用的执行要先确认没有产生结果再换。
|
|
587
|
+
*/
|
|
588
|
+
async function callTextCandidates(request, cwd, language, opts, parseOutput, trace) {
|
|
589
|
+
const modelGroups = opts.modelGroups ?? opts.employeeText?.config.modelGroups;
|
|
590
|
+
const { chain, freeLeaves, deadline } = await expandEmployeeCandidates(opts, trace);
|
|
586
591
|
const call = async (candidate) => {
|
|
587
592
|
const free = freeLeaves.has(candidate);
|
|
588
593
|
const candidateDeadline = free ? Math.min(deadline, Date.now() + 20_000) : deadline;
|
package/dist/auth.js
CHANGED
|
@@ -60,10 +60,8 @@ export class AuthService {
|
|
|
60
60
|
const expiresAt = Date.now() + this.sessionTtlMs;
|
|
61
61
|
const cached = { expiresAt, principal: clonePrincipal(principal) };
|
|
62
62
|
this.sessions.set(token, cached);
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
if (!cached.principal.webSession)
|
|
66
|
-
this.storage.saveAuthSession(token, expiresAt, cached.principal);
|
|
63
|
+
// 所有登录态(含票据登录)持久化,和密码登录一样跨服务重启有效。
|
|
64
|
+
this.storage.saveAuthSession(token, expiresAt, cached.principal);
|
|
67
65
|
return token;
|
|
68
66
|
}
|
|
69
67
|
authenticateSession(token) {
|
|
@@ -85,10 +83,10 @@ export class AuthService {
|
|
|
85
83
|
return null;
|
|
86
84
|
}
|
|
87
85
|
// Retire cookies minted by the early ticket prototype. Native App sessions
|
|
88
|
-
// use the established multi-scope principal; a sessions-only
|
|
89
|
-
//
|
|
86
|
+
// use the established multi-scope principal; a sessions-only connected-app
|
|
87
|
+
// cookie must never regain broad workbench access after upgrade.
|
|
90
88
|
if (cached.principal.kind === "connected-app" && cached.principal.scopes.length === 1
|
|
91
|
-
&& cached.principal.scopes[0] === "sessions"
|
|
89
|
+
&& cached.principal.scopes[0] === "sessions")
|
|
92
90
|
return null;
|
|
93
91
|
return clonePrincipal(cached.principal);
|
|
94
92
|
}
|
|
@@ -131,13 +129,10 @@ export class AuthService {
|
|
|
131
129
|
}
|
|
132
130
|
}
|
|
133
131
|
export function principalHasScope(principal, scope) {
|
|
134
|
-
if (principal.webSession)
|
|
135
|
-
return scope === "sessions" && principal.scopes.includes("sessions");
|
|
136
132
|
return principal.kind === "browser-admin"
|
|
137
133
|
|| principal.scopes.includes("admin")
|
|
138
134
|
|| principal.scopes.includes(scope);
|
|
139
135
|
}
|
|
140
136
|
function clonePrincipal(principal) {
|
|
141
|
-
return { kind: principal.kind, scopes: [...principal.scopes]
|
|
142
|
-
...(principal.webSession ? { webSession: { ...principal.webSession } } : {}) };
|
|
137
|
+
return { kind: principal.kind, scopes: [...principal.scopes] };
|
|
143
138
|
}
|
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-10-
|
|
4
|
-
"version": "4.
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "a09542822526896c8dcc2c5af5862441fdb58282",
|
|
3
|
+
"builtAt": "2026-10-17T13:42:08.826Z",
|
|
4
|
+
"version": "4.97.0",
|
|
5
|
+
"channel": "stable"
|
|
6
6
|
}
|
package/dist/cli.js
CHANGED
|
@@ -359,7 +359,7 @@ Agent runtime:
|
|
|
359
359
|
wand system:skills [--providers pi,claude,...] Install the managed wand-system skill
|
|
360
360
|
wand employee:list [--include-archived] List employee definitions (not private knowledge)
|
|
361
361
|
wand workspace:list List project directories
|
|
362
|
-
wand web-login:issue --stdin Mint a 60s, one-use
|
|
362
|
+
wand web-login:issue --stdin Mint a 60s, one-use Web admin ticket (employee execution proof + controlled pipe only)
|
|
363
363
|
wand web-login:cancel --stdin Cancel an unconsumed ticket by requestId
|
|
364
364
|
wand session:create --stdin Create an employee session from JSON
|
|
365
365
|
wand session:resume <id> --stdin Continue the exact structured session with JSON input
|
package/dist/decision-skill.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
+
import { type ManagedSkillInstallResult } from "./managed-skill.js";
|
|
1
2
|
import { type SessionProvider } from "./provider-catalog.js";
|
|
2
|
-
export declare function installDecisionSkill(configDir: string, providers?: readonly SessionProvider[], home?: string): Promise<
|
|
3
|
-
installed: string[];
|
|
4
|
-
providers: readonly SessionProvider[];
|
|
5
|
-
}>;
|
|
3
|
+
export declare function installDecisionSkill(configDir: string, providers?: readonly SessionProvider[], home?: string): Promise<ManagedSkillInstallResult>;
|
package/dist/git-quick-commit.js
CHANGED
|
@@ -2,8 +2,10 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { getGitErrorMessage, runGitAsync as runGitAsyncBase, runGitRawAsync as runGitRawAsyncBase, } from "./git-utils.js";
|
|
3
3
|
import { thinkingEffortToClaudeCliEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToOpenCodeVariant } from "./structured-provider-common.js";
|
|
4
4
|
/** provider 缺省值:无法识别时回落到 Claude。 */
|
|
5
|
-
import { buildGrokTextArgs, buildPiTextArgs, buildQoderTextArgs, callConfiguredAiText, contentWithSystemPrompt, defaultProvider, normalizeAiText, runCliText, systemPromptArgsFrom, withOpsPersona } from "./ai-text-runner.js";
|
|
5
|
+
import { buildGrokTextArgs, buildPiTextArgs, buildQoderTextArgs, callConfiguredAiText, contentWithSystemPrompt, defaultProvider, expandEmployeeCandidates, normalizeAiText, runCliText, systemPromptArgsFrom, withOpsPersona } from "./ai-text-runner.js";
|
|
6
6
|
import { QuickCommitError } from "./command-error.js";
|
|
7
|
+
import { candidateInstalled } from "./session-provider.js";
|
|
8
|
+
import { AiCallTraceError } from "./system-ai-call.js";
|
|
7
9
|
export { callCliAiText, callConfiguredAiText, withOpsPersona } from "./ai-text-runner.js";
|
|
8
10
|
export { QuickCommitError } from "./command-error.js";
|
|
9
11
|
const GIT_TIMEOUT_MS = 1500;
|
|
@@ -876,23 +878,57 @@ async function getLatestTagAtHead(cwd) {
|
|
|
876
878
|
}
|
|
877
879
|
async function runQuickCommitFallbackCli(opts, priorError) {
|
|
878
880
|
const trace = opts.traceFactory?.start("git-fallback", buildFallbackPrompt(opts, priorError), opts.cwd);
|
|
879
|
-
|
|
881
|
+
if (trace)
|
|
882
|
+
opts.onCallSessionCreated?.(trace.sessionId);
|
|
883
|
+
const beforeHead = await getHead(opts.cwd);
|
|
880
884
|
try {
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
885
|
+
const { chain, deadline } = await expandEmployeeCandidates(opts, trace);
|
|
886
|
+
const executable = chain.filter(candidate => candidate.engine !== "sdk" && candidateInstalled(candidate));
|
|
887
|
+
for (const candidate of chain) {
|
|
888
|
+
if (candidate.engine === "sdk")
|
|
889
|
+
trace?.candidateSkipped(candidate, "提交执行需要 CLI 工具,未把无工具文字候选当执行器。");
|
|
890
|
+
else if (!executable.includes(candidate))
|
|
891
|
+
trace?.candidateSkipped(candidate, "CLI 未安装,未提交输入。");
|
|
892
|
+
}
|
|
893
|
+
if (!executable.length)
|
|
894
|
+
throw new QuickCommitError("系统员工没有可执行 git 的 CLI 候选。", "AI_FALLBACK_FAILED");
|
|
895
|
+
const errors = [];
|
|
896
|
+
for (const candidate of executable) {
|
|
897
|
+
opts.signal?.throwIfAborted();
|
|
898
|
+
if (Date.now() >= deadline) {
|
|
899
|
+
errors.push("已超过等待上限");
|
|
900
|
+
break;
|
|
901
|
+
}
|
|
902
|
+
trace?.candidateStarted(candidate);
|
|
903
|
+
try {
|
|
904
|
+
const result = await runQuickCommitFallbackCliImpl({
|
|
905
|
+
...opts,
|
|
906
|
+
provider: candidate.provider,
|
|
907
|
+
model: candidate.model,
|
|
908
|
+
thinkingEffort: candidate.thinkingEffort ?? opts.thinkingEffort,
|
|
909
|
+
}, priorError, Math.max(1, Math.min(QUICK_COMMIT_CLI_TIMEOUT_MS, deadline - Date.now())));
|
|
910
|
+
trace?.applicationResult(result);
|
|
911
|
+
trace?.complete();
|
|
912
|
+
return result;
|
|
913
|
+
}
|
|
914
|
+
catch (error) {
|
|
915
|
+
if (error instanceof AiCallTraceError)
|
|
916
|
+
throw error;
|
|
917
|
+
trace?.candidateFailed(candidate, error, "execution");
|
|
918
|
+
const afterHead = await getHead(opts.cwd);
|
|
919
|
+
if (afterHead && afterHead !== beforeHead)
|
|
920
|
+
throw error;
|
|
921
|
+
errors.push(`${candidate.provider}: ${getGitErrorMessage(error)}`);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
throw new QuickCommitError(`所有执行候选均失败:${errors.join(";")}`, "AI_FALLBACK_FAILED");
|
|
888
925
|
}
|
|
889
926
|
catch (error) {
|
|
890
|
-
trace?.candidateFailed(candidate, error, "execution");
|
|
891
927
|
trace?.fail(error);
|
|
892
928
|
throw error;
|
|
893
929
|
}
|
|
894
930
|
}
|
|
895
|
-
async function runQuickCommitFallbackCliImpl(opts, priorError) {
|
|
931
|
+
async function runQuickCommitFallbackCliImpl(opts, priorError, timeoutMs = QUICK_COMMIT_CLI_TIMEOUT_MS) {
|
|
896
932
|
await assertGitWorkTreeAsync(opts.cwd);
|
|
897
933
|
const beforeHead = await getHead(opts.cwd);
|
|
898
934
|
const request = withOpsPersona(buildFallbackPrompt(opts, priorError), opts.opsPersona);
|
|
@@ -908,7 +944,7 @@ async function runQuickCommitFallbackCliImpl(opts, priorError) {
|
|
|
908
944
|
args.push("-");
|
|
909
945
|
await runCliText("codex", args, contentWithSystemPrompt("codex", request), {
|
|
910
946
|
cwd: opts.cwd,
|
|
911
|
-
timeoutMs
|
|
947
|
+
timeoutMs,
|
|
912
948
|
inheritEnv: opts.inheritEnv,
|
|
913
949
|
});
|
|
914
950
|
}
|
|
@@ -922,28 +958,28 @@ async function runQuickCommitFallbackCliImpl(opts, priorError) {
|
|
|
922
958
|
args.push("--variant", variant);
|
|
923
959
|
await runCliText("opencode", args, contentWithSystemPrompt("opencode", request), {
|
|
924
960
|
cwd: opts.cwd,
|
|
925
|
-
timeoutMs
|
|
961
|
+
timeoutMs,
|
|
926
962
|
inheritEnv: opts.inheritEnv,
|
|
927
963
|
});
|
|
928
964
|
}
|
|
929
965
|
else if (provider === "grok") {
|
|
930
966
|
await runCliText("grok", buildGrokTextArgs(request, opts, true), "", {
|
|
931
967
|
cwd: opts.cwd,
|
|
932
|
-
timeoutMs
|
|
968
|
+
timeoutMs,
|
|
933
969
|
inheritEnv: opts.inheritEnv,
|
|
934
970
|
});
|
|
935
971
|
}
|
|
936
972
|
else if (provider === "qoder") {
|
|
937
973
|
await runCliText("qodercli", buildQoderTextArgs(request, opts, true), "", {
|
|
938
974
|
cwd: opts.cwd,
|
|
939
|
-
timeoutMs
|
|
975
|
+
timeoutMs,
|
|
940
976
|
inheritEnv: opts.inheritEnv,
|
|
941
977
|
});
|
|
942
978
|
}
|
|
943
979
|
else if (provider === "pi") {
|
|
944
980
|
await runCliText("pi", buildPiTextArgs(request, opts, true), "", {
|
|
945
981
|
cwd: opts.cwd,
|
|
946
|
-
timeoutMs
|
|
982
|
+
timeoutMs,
|
|
947
983
|
inheritEnv: opts.inheritEnv,
|
|
948
984
|
});
|
|
949
985
|
}
|
|
@@ -967,7 +1003,7 @@ async function runQuickCommitFallbackCliImpl(opts, priorError) {
|
|
|
967
1003
|
args.push(...systemPromptArgsFrom("claude", request.system));
|
|
968
1004
|
await runCliText("claude", args, request.prompt, {
|
|
969
1005
|
cwd: opts.cwd,
|
|
970
|
-
timeoutMs
|
|
1006
|
+
timeoutMs,
|
|
971
1007
|
inheritEnv: opts.inheritEnv,
|
|
972
1008
|
});
|
|
973
1009
|
}
|
package/dist/managed-skill.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { type SessionProvider } from "./provider-catalog.js";
|
|
2
|
-
|
|
3
|
-
export declare function installManagedSkill(configDir: string, name: string, files: Record<string, string>, providers?: readonly SessionProvider[], home?: string): Promise<{
|
|
2
|
+
export interface ManagedSkillInstallResult {
|
|
4
3
|
installed: string[];
|
|
5
4
|
providers: readonly SessionProvider[];
|
|
6
|
-
|
|
5
|
+
/** Canonical files whose content was newly written (missing or refreshed from a prior managed version). */
|
|
6
|
+
changed: string[];
|
|
7
|
+
/** Provider discovery symlinks newly created by this install. */
|
|
8
|
+
linked: string[];
|
|
9
|
+
}
|
|
10
|
+
/** Install trusted bundled files without overwriting user content or changing tool permissions. */
|
|
11
|
+
export declare function installManagedSkill(configDir: string, name: string, files: Record<string, string>, providers?: readonly SessionProvider[], home?: string): Promise<ManagedSkillInstallResult>;
|
package/dist/managed-skill.js
CHANGED
|
@@ -67,15 +67,20 @@ export async function installManagedSkill(configDir, name, files, providers = SE
|
|
|
67
67
|
throw error;
|
|
68
68
|
}
|
|
69
69
|
const contents = Object.entries(files).map(([file, content]) => ({ file, content }));
|
|
70
|
+
const changed = [];
|
|
70
71
|
for (const { file, content } of contents) {
|
|
71
72
|
try {
|
|
72
73
|
const old = await readFile(path.join(canonical, file), "utf8");
|
|
73
|
-
if (old !== content
|
|
74
|
-
|
|
74
|
+
if (old !== content) {
|
|
75
|
+
if (sha(old) !== previous[file])
|
|
76
|
+
throw new Error(`技能已有用户修改,拒绝覆盖:${file}`);
|
|
77
|
+
changed.push(file);
|
|
78
|
+
}
|
|
75
79
|
}
|
|
76
80
|
catch (error) {
|
|
77
81
|
if (error.code !== "ENOENT")
|
|
78
82
|
throw error;
|
|
83
|
+
changed.push(file);
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
for (const { file, content } of contents) {
|
|
@@ -83,10 +88,12 @@ export async function installManagedSkill(configDir, name, files, providers = SE
|
|
|
83
88
|
await writeFile(path.join(canonical, file), content, { mode: 0o644 });
|
|
84
89
|
}
|
|
85
90
|
await writeFile(path.join(canonical, ".wand-managed.json"), JSON.stringify(Object.fromEntries(contents.map(({ file, content }) => [file, sha(content)]))), { mode: 0o600 });
|
|
91
|
+
const linked = [];
|
|
86
92
|
for (const target of targets) {
|
|
87
93
|
await mkdir(path.dirname(target), { recursive: true });
|
|
88
94
|
try {
|
|
89
95
|
await symlink(canonical, target, "dir");
|
|
96
|
+
linked.push(target);
|
|
90
97
|
}
|
|
91
98
|
catch (error) {
|
|
92
99
|
if (error.code !== "EEXIST")
|
|
@@ -95,5 +102,5 @@ export async function installManagedSkill(configDir, name, files, providers = SE
|
|
|
95
102
|
if (await realpath(target) !== await realpath(canonical))
|
|
96
103
|
throw new Error("技能链接目标校验失败。");
|
|
97
104
|
}
|
|
98
|
-
return { installed: [...targets], providers };
|
|
105
|
+
return { installed: [...targets], providers, changed, linked };
|
|
99
106
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ManagedSkillEnsureOutcome {
|
|
2
|
+
name: string;
|
|
3
|
+
status: "synced" | "unchanged" | "failed";
|
|
4
|
+
files: number;
|
|
5
|
+
links: number;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
/** 机器级技能发现链接只服务默认安装;隔离配置(开发/测试)各有自己的 canonical 目录,不互相抢占。 */
|
|
9
|
+
export declare function isDefaultConfigPath(configPath: string, home?: string): boolean;
|
|
10
|
+
/** 幂等同步包内托管技能:不抛异常、不覆盖用户改动,调用方只在有变化或失败时输出。 */
|
|
11
|
+
export declare function ensureManagedSkills(configPath: string, home?: string): Promise<ManagedSkillEnsureOutcome[]>;
|
|
12
|
+
export declare function formatManagedSkillsEnsureLog(outcomes: readonly ManagedSkillEnsureOutcome[]): string | null;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { installDecisionSkill } from "./decision-skill.js";
|
|
4
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
5
|
+
import { installSystemSkill } from "./system-skill.js";
|
|
6
|
+
/** 机器级技能发现链接只服务默认安装;隔离配置(开发/测试)各有自己的 canonical 目录,不互相抢占。 */
|
|
7
|
+
export function isDefaultConfigPath(configPath, home = homedir()) {
|
|
8
|
+
return path.resolve(path.dirname(configPath)) === path.join(home, ".wand");
|
|
9
|
+
}
|
|
10
|
+
/** 幂等同步包内托管技能:不抛异常、不覆盖用户改动,调用方只在有变化或失败时输出。 */
|
|
11
|
+
export async function ensureManagedSkills(configPath, home = homedir()) {
|
|
12
|
+
const installs = [
|
|
13
|
+
["wand-system", async () => installSystemSkill(configPath, undefined, home)],
|
|
14
|
+
["wand-decision", async () => installDecisionSkill(path.dirname(configPath), undefined, home)],
|
|
15
|
+
];
|
|
16
|
+
const outcomes = [];
|
|
17
|
+
for (const [name, install] of installs) {
|
|
18
|
+
try {
|
|
19
|
+
const { changed, linked } = await install();
|
|
20
|
+
outcomes.push({
|
|
21
|
+
name,
|
|
22
|
+
status: changed.length || linked.length ? "synced" : "unchanged",
|
|
23
|
+
files: changed.length,
|
|
24
|
+
links: linked.length,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
outcomes.push({ name, status: "failed", files: 0, links: 0, error: getErrorMessage(error) });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return outcomes;
|
|
32
|
+
}
|
|
33
|
+
export function formatManagedSkillsEnsureLog(outcomes) {
|
|
34
|
+
const synced = outcomes.filter((outcome) => outcome.status === "synced");
|
|
35
|
+
const failed = outcomes.filter((outcome) => outcome.status === "failed");
|
|
36
|
+
if (!synced.length && !failed.length)
|
|
37
|
+
return null;
|
|
38
|
+
const parts = [
|
|
39
|
+
...synced.map((outcome) => `${outcome.name}(${outcome.files} 个文件${outcome.links ? `、${outcome.links} 个新链接` : ""})`),
|
|
40
|
+
...failed.map((outcome) => `${outcome.name} 未同步:${outcome.error}`),
|
|
41
|
+
];
|
|
42
|
+
return `[wand] 托管技能${failed.length ? "部分未同步" : "已同步"}:${parts.join(";")}\n`;
|
|
43
|
+
}
|
package/dist/middleware/auth.js
CHANGED
|
@@ -2,7 +2,6 @@ import { BROWSER_ADMIN_PRINCIPAL, CONNECTED_APP_PRINCIPAL, principalHasScope, re
|
|
|
2
2
|
import { firstHeaderValue } from "../server-request.js";
|
|
3
3
|
import { verifyAppToken } from "../server-app-connect.js";
|
|
4
4
|
import { checkBearerRateLimit, recordFailedBearer } from "./rate-limit.js";
|
|
5
|
-
import { webSessionRequestAllowed } from "../web-session-access.js";
|
|
6
5
|
export const UNAUTHORIZED_ERROR = { error: "未授权,请先登录。", code: "unauthorized" };
|
|
7
6
|
export const FORBIDDEN_SCOPE_ERROR = { error: "当前连接没有执行此操作的权限。", code: "forbidden" };
|
|
8
7
|
export const PREFERENCE_ONLY_ERROR = { error: "当前连接只能修改新会话默认偏好。", code: "forbidden" };
|
|
@@ -82,18 +81,6 @@ export function createRequireAuth(deps) {
|
|
|
82
81
|
res.status(401).json(UNAUTHORIZED_ERROR);
|
|
83
82
|
return;
|
|
84
83
|
}
|
|
85
|
-
if (principal.webSession) {
|
|
86
|
-
const session = deps.storage.getSessionSlim(principal.webSession.sessionId);
|
|
87
|
-
if (!session || session.employeeId !== principal.webSession.employeeId) {
|
|
88
|
-
deps.authService.revokeSession(readSessionCookie(req, deps.useHttps));
|
|
89
|
-
res.status(401).json(UNAUTHORIZED_ERROR);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (!webSessionRequestAllowed(principal, req.method, `${req.baseUrl}${req.path}`)) {
|
|
93
|
-
res.status(403).json({ error: "此票据登录仅可查看签发时的当前员工会话。", code: "web_session_read_only" });
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
84
|
req.principal = principal;
|
|
98
85
|
next();
|
|
99
86
|
};
|
|
@@ -14,6 +14,7 @@ export function withModelGroups(runner, config) {
|
|
|
14
14
|
const grouped = !!group && models[0] !== OPENROUTER_FREE_SELECTOR;
|
|
15
15
|
let cancelled = false;
|
|
16
16
|
let current;
|
|
17
|
+
const exits = [];
|
|
17
18
|
const start = (model) => {
|
|
18
19
|
let active = true;
|
|
19
20
|
let output = false;
|
|
@@ -40,6 +41,7 @@ export function withModelGroups(runner, config) {
|
|
|
40
41
|
observer.onUpdate({ ...state, model: state.model ?? model });
|
|
41
42
|
},
|
|
42
43
|
});
|
|
44
|
+
exits.push(execution.exited ?? execution.completion);
|
|
43
45
|
return { ...execution, get args() { return execution.args; }, get pid() { return execution.pid; },
|
|
44
46
|
completion: execution.completion.then((result) => {
|
|
45
47
|
const failed = result.spawnError ? { ...result, exitCode: result.exitCode ?? 1,
|
|
@@ -61,7 +63,8 @@ export function withModelGroups(runner, config) {
|
|
|
61
63
|
current = start(models[index + 1]);
|
|
62
64
|
}
|
|
63
65
|
})();
|
|
64
|
-
|
|
66
|
+
const exited = completion.then(() => Promise.all(exits), () => Promise.all(exits)).then(() => { });
|
|
67
|
+
return { ...first, get args() { return first.args; }, get pid() { return first.pid; }, completion, exited, interrupt() {
|
|
65
68
|
cancelled = true;
|
|
66
69
|
try {
|
|
67
70
|
current.interrupt();
|
|
@@ -139,7 +139,9 @@ export function withAutomaticPiResources(runner, config, options) {
|
|
|
139
139
|
};
|
|
140
140
|
});
|
|
141
141
|
return { get args() { return execution?.args ?? []; }, get pid() { return execution?.pid ?? null; },
|
|
142
|
-
spawnedAt, completion,
|
|
142
|
+
spawnedAt, completion,
|
|
143
|
+
exited: completion.then(() => execution?.exited, () => execution?.exited).then(() => { }),
|
|
144
|
+
interrupt: () => {
|
|
143
145
|
if (interrupted)
|
|
144
146
|
return;
|
|
145
147
|
if (notice.status === "selecting") {
|
package/dist/pi-tmux-host.js
CHANGED
|
@@ -111,7 +111,7 @@ class NativeProcess {
|
|
|
111
111
|
}
|
|
112
112
|
catch (error) {
|
|
113
113
|
logInfo(`Pi native file observation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
|
-
this.ended = { exitCode: 1, signal: null };
|
|
114
|
+
this.ended = { exitCode: 1, signal: null, processExited: false };
|
|
115
115
|
void this.host.kill(this.job).catch(() => { });
|
|
116
116
|
}
|
|
117
117
|
if (this.timer)
|
|
@@ -141,6 +141,7 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
141
141
|
private readonly sessions;
|
|
142
142
|
/** Own the full start handshake, including model resolution and late host replies. */
|
|
143
143
|
private readonly startingSessions;
|
|
144
|
+
private readonly nativeCleanupBarriers;
|
|
144
145
|
private readonly logger;
|
|
145
146
|
private readonly providerHistory;
|
|
146
147
|
/** Quiet window for the non-Claude PTY turn tracker. */
|
|
@@ -290,6 +291,8 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
290
291
|
private finishProviderCli;
|
|
291
292
|
stop(id: string): SessionSnapshot;
|
|
292
293
|
private cleanupRecord;
|
|
294
|
+
/** Capture before delete drops the process handles. Never unlink a live CLI's history. */
|
|
295
|
+
nativeHistoryCleanupBarrier(id: string): Promise<void> | null;
|
|
293
296
|
delete(id: string): void;
|
|
294
297
|
private deleteClaudeCache;
|
|
295
298
|
runStartupCommands(): Promise<SessionSnapshot[]>;
|
package/dist/process-manager.js
CHANGED
|
@@ -14,6 +14,8 @@ import { SessionTimers } from "./process-manager-timers.js";
|
|
|
14
14
|
import { truncateMessagesForTransport } from "./message-truncator.js";
|
|
15
15
|
import { appendWindow, hasExplicitConfirmSyntax, hasPermissionActionContext, normalizePromptText, PTY_OUTPUT_MAX_SIZE } from "./pty-text-utils.js";
|
|
16
16
|
import { buildChildEnv, isRunningAsRoot, systemEnvValue } from "./env-utils.js";
|
|
17
|
+
import { coreHarnessAgentDir } from "./harness-engine.js";
|
|
18
|
+
import { waitForSessionProcessExit } from "./session-artifact-cleanup.js";
|
|
17
19
|
import { buildLanguageDirective, buildManagedAutonomyDirective } from "./language-prompt.js";
|
|
18
20
|
import { prepareSessionWorktree } from "./git-worktree.js";
|
|
19
21
|
import { getProviderCommandSessionId, getProviderResumeCommandSessionId } from "./resume-policy.js";
|
|
@@ -90,8 +92,9 @@ export class ProcessManager extends EventEmitter {
|
|
|
90
92
|
sessions = new Map();
|
|
91
93
|
/** Own the full start handshake, including model resolution and late host replies. */
|
|
92
94
|
startingSessions = new Map();
|
|
95
|
+
nativeCleanupBarriers = new Map();
|
|
93
96
|
logger;
|
|
94
|
-
providerHistory
|
|
97
|
+
providerHistory;
|
|
95
98
|
/** Quiet window for the non-Claude PTY turn tracker. */
|
|
96
99
|
ptyTurnIdleMs;
|
|
97
100
|
/** Foreground-process-group sampling interval. */
|
|
@@ -128,6 +131,12 @@ export class ProcessManager extends EventEmitter {
|
|
|
128
131
|
super();
|
|
129
132
|
this.config = config;
|
|
130
133
|
this.storage = storage;
|
|
134
|
+
const cliEnv = buildChildEnv(config.inheritEnv !== false);
|
|
135
|
+
this.providerHistory = new ProviderHistoryScanner({
|
|
136
|
+
claudeHome: cliEnv.CLAUDE_CONFIG_DIR?.trim() || path.join(os.homedir(), ".claude"),
|
|
137
|
+
codexHome: cliEnv.CODEX_HOME?.trim() || path.join(os.homedir(), ".codex"),
|
|
138
|
+
openCodeDatabasePath: path.join(cliEnv.XDG_DATA_HOME?.trim() || path.join(os.homedir(), ".local", "share"), "opencode", "opencode.db"),
|
|
139
|
+
});
|
|
131
140
|
this.ptyTurnIdleMs = options.ptyTurnIdleMs ?? PTY_TURN_IDLE_MS;
|
|
132
141
|
this.ptyForegroundSampleMs = options.ptyForegroundSampleMs ?? PTY_FOREGROUND_SAMPLE_MS;
|
|
133
142
|
this.probePtyForegrounds = options.samplePtyForegrounds ?? samplePtyForegrounds;
|
|
@@ -1127,7 +1136,16 @@ export class ProcessManager extends EventEmitter {
|
|
|
1127
1136
|
return this.providerHistory.deleteQoderHistoryFiles(sessionIds);
|
|
1128
1137
|
}
|
|
1129
1138
|
deletePiHistoryFiles(sessionIds) {
|
|
1130
|
-
|
|
1139
|
+
// Structured Pi uses harness.agentDir, which may differ from the CLI default.
|
|
1140
|
+
// PTY Pi uses the default root. Remove the exact IDs from both, never a root.
|
|
1141
|
+
const configured = path.join(coreHarnessAgentDir(this.config.harness), "sessions");
|
|
1142
|
+
const defaults = path.join(buildChildEnv(this.config.inheritEnv !== false).PI_CODING_AGENT_DIR?.trim()
|
|
1143
|
+
|| path.join(os.homedir(), ".pi", "agent"), "sessions");
|
|
1144
|
+
let deleted = new ProviderHistoryScanner({ piSessionsDir: configured }).deletePiHistoryFiles(sessionIds);
|
|
1145
|
+
if (path.resolve(defaults) !== path.resolve(configured)) {
|
|
1146
|
+
deleted += new ProviderHistoryScanner({ piSessionsDir: defaults }).deletePiHistoryFiles(sessionIds);
|
|
1147
|
+
}
|
|
1148
|
+
return deleted;
|
|
1131
1149
|
}
|
|
1132
1150
|
deleteGrokHistoryFiles(sessionIds) {
|
|
1133
1151
|
return this.providerHistory.deleteGrokHistoryFiles(sessionIds);
|
|
@@ -1548,6 +1566,42 @@ export class ProcessManager extends EventEmitter {
|
|
|
1548
1566
|
record.terminalState?.dispose();
|
|
1549
1567
|
record.terminalState = null;
|
|
1550
1568
|
}
|
|
1569
|
+
/** Capture before delete drops the process handles. Never unlink a live CLI's history. */
|
|
1570
|
+
nativeHistoryCleanupBarrier(id) {
|
|
1571
|
+
const existing = this.nativeCleanupBarriers.get(id);
|
|
1572
|
+
if (existing)
|
|
1573
|
+
return existing;
|
|
1574
|
+
const record = this.sessions.get(id);
|
|
1575
|
+
if (!record)
|
|
1576
|
+
return null;
|
|
1577
|
+
if (this.startingSessions.has(id)) {
|
|
1578
|
+
return Promise.reject(new Error("CLI startup is still pending; session artifacts were preserved."));
|
|
1579
|
+
}
|
|
1580
|
+
const waits = [];
|
|
1581
|
+
if (record.ptyProcess) {
|
|
1582
|
+
const terminal = record.ptyProcess;
|
|
1583
|
+
waits.push(waitForSessionProcessExit((done) => {
|
|
1584
|
+
const listener = terminal.onExit((event) => {
|
|
1585
|
+
// A missing remote inventory entry is reported as -1, not an OS exit.
|
|
1586
|
+
if (event.exitCode >= 0)
|
|
1587
|
+
done();
|
|
1588
|
+
});
|
|
1589
|
+
return () => listener.dispose();
|
|
1590
|
+
}));
|
|
1591
|
+
}
|
|
1592
|
+
const child = record.childProcess;
|
|
1593
|
+
if (child && child.exitCode === null && child.signalCode === null) {
|
|
1594
|
+
waits.push(waitForSessionProcessExit((done) => {
|
|
1595
|
+
child.once("close", done);
|
|
1596
|
+
return () => { child.off("close", done); };
|
|
1597
|
+
}));
|
|
1598
|
+
}
|
|
1599
|
+
if (!waits.length)
|
|
1600
|
+
return null;
|
|
1601
|
+
const barrier = Promise.all(waits).then(() => { });
|
|
1602
|
+
this.nativeCleanupBarriers.set(id, barrier);
|
|
1603
|
+
return barrier;
|
|
1604
|
+
}
|
|
1551
1605
|
delete(id) {
|
|
1552
1606
|
const record = this.mustGet(id);
|
|
1553
1607
|
const start = this.startingSessions.get(id);
|
|
@@ -1588,7 +1642,18 @@ export class ProcessManager extends EventEmitter {
|
|
|
1588
1642
|
}
|
|
1589
1643
|
record.terminalState?.dispose();
|
|
1590
1644
|
record.terminalState = null;
|
|
1591
|
-
this.
|
|
1645
|
+
const barrier = this.nativeCleanupBarriers.get(id);
|
|
1646
|
+
if (barrier) {
|
|
1647
|
+
const forget = () => {
|
|
1648
|
+
this.nativeCleanupBarriers.delete(id);
|
|
1649
|
+
if (!this.sessions.has(id))
|
|
1650
|
+
this.terminalHost.forget(id);
|
|
1651
|
+
};
|
|
1652
|
+
// Keep remote handles until their exit event has reached the barrier.
|
|
1653
|
+
void barrier.then(forget, forget);
|
|
1654
|
+
}
|
|
1655
|
+
else
|
|
1656
|
+
this.terminalHost.forget(id);
|
|
1592
1657
|
// Delete from persistent storage BEFORE removing from in-memory map,
|
|
1593
1658
|
// so a storage failure doesn't leave orphan records in the database.
|
|
1594
1659
|
this.storage.deleteSession(id);
|
|
@@ -45,6 +45,7 @@ export interface QoderHistorySession {
|
|
|
45
45
|
export interface ProviderHistoryScannerOptions {
|
|
46
46
|
claudeHome?: string;
|
|
47
47
|
codexSessionsDir?: string;
|
|
48
|
+
codexHome?: string;
|
|
48
49
|
openCodeDatabasePath?: string;
|
|
49
50
|
/** Qoder and Qoder CN use separate config roots; callers may override both for tests. */
|
|
50
51
|
qoderProjectsDirs?: string[];
|
|
@@ -59,6 +60,7 @@ export declare class ProviderHistoryScanner {
|
|
|
59
60
|
private readonly claudeHome;
|
|
60
61
|
private readonly claudeProjectsDir;
|
|
61
62
|
private readonly codexSessionsDir;
|
|
63
|
+
private readonly codexHome;
|
|
62
64
|
private readonly openCodeDatabasePath;
|
|
63
65
|
private readonly qoderProjectsDirs;
|
|
64
66
|
private readonly piSessionsDir;
|