@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.
Files changed (147) hide show
  1. package/README.md +6 -0
  2. package/dist/ai-text-runner.d.ts +9 -2
  3. package/dist/ai-text-runner.js +14 -9
  4. package/dist/auth.js +6 -11
  5. package/dist/build-info.json +4 -4
  6. package/dist/cli.js +1 -1
  7. package/dist/decision-skill.d.ts +2 -4
  8. package/dist/git-quick-commit.js +53 -17
  9. package/dist/managed-skill.d.ts +8 -3
  10. package/dist/managed-skill.js +10 -3
  11. package/dist/managed-skills-ensure.d.ts +12 -0
  12. package/dist/managed-skills-ensure.js +43 -0
  13. package/dist/middleware/auth.js +0 -13
  14. package/dist/model-group-runner.js +4 -1
  15. package/dist/pi-auto-resources.js +3 -1
  16. package/dist/pi-tmux-host.js +1 -1
  17. package/dist/process-manager.d.ts +3 -0
  18. package/dist/process-manager.js +68 -3
  19. package/dist/provider-history-scanner.d.ts +2 -0
  20. package/dist/provider-history-scanner.js +62 -48
  21. package/dist/render-structured-client.js +1 -1
  22. package/dist/scheduled-task-calendar.js +2 -3
  23. package/dist/scheduled-task-time.d.ts +2 -1
  24. package/dist/scheduled-task-time.js +27 -2
  25. package/dist/scheduled-task-types.d.ts +5 -0
  26. package/dist/server-session-list-routes.js +11 -4
  27. package/dist/server-task-routes.js +36 -8
  28. package/dist/server-update-routes.js +2 -1
  29. package/dist/server-workspace-routes.js +31 -3
  30. package/dist/server.js +9 -1
  31. package/dist/session-artifact-cleanup.d.ts +7 -0
  32. package/dist/session-artifact-cleanup.js +45 -0
  33. package/dist/session-registry.d.ts +9 -1
  34. package/dist/session-registry.js +59 -10
  35. package/dist/storage/internal.d.ts +3 -7
  36. package/dist/storage/internal.js +1 -1
  37. package/dist/storage/migrations.js +5 -0
  38. package/dist/storage/secrets-repository.js +0 -2
  39. package/dist/storage/tasks-repository.js +26 -4
  40. package/dist/storage-schema.js +3 -0
  41. package/dist/structured-claude-protocol.d.ts +2 -0
  42. package/dist/structured-claude-protocol.js +67 -2
  43. package/dist/structured-exec-host.d.ts +2 -0
  44. package/dist/structured-exec-pump.js +11 -2
  45. package/dist/structured-pi-adapter.js +1 -0
  46. package/dist/structured-runner.d.ts +2 -0
  47. package/dist/structured-session-manager.d.ts +4 -0
  48. package/dist/structured-session-manager.js +56 -2
  49. package/dist/system-cli.js +1 -1
  50. package/dist/system-skill.d.ts +1 -4
  51. package/dist/task-agent-parsing.d.ts +1 -1
  52. package/dist/task-agent-parsing.js +6 -11
  53. package/dist/task-dispatch-receipt.d.ts +4 -0
  54. package/dist/task-dispatch-receipt.js +59 -0
  55. package/dist/task-hierarchy.d.ts +15 -0
  56. package/dist/task-hierarchy.js +67 -0
  57. package/dist/task-read-model.d.ts +6 -0
  58. package/dist/task-read-model.js +12 -0
  59. package/dist/task-title-organize.js +23 -6
  60. package/dist/task-title-service.js +4 -0
  61. package/dist/task-types.d.ts +9 -0
  62. package/dist/terminal-daemon-client.js +2 -1
  63. package/dist/web-login-tickets.d.ts +1 -1
  64. package/dist/web-login-tickets.js +6 -6
  65. package/dist/web-ui/content/module-manifest.json +1 -1
  66. package/dist/web-ui/content/modules/0c96341a022aa360/host.js +1 -0
  67. package/dist/web-ui/content/modules/15e32e85e086f6b4/page.js +6 -0
  68. package/dist/web-ui/content/modules/1e43259bddbc6805/markdown.js +1 -0
  69. package/dist/web-ui/content/modules/271d488d262909c2/host.js +30 -0
  70. package/dist/web-ui/content/modules/{7762ec45d7726c43 → 2bce1137f402bd70}/chunk.js +1 -1
  71. package/dist/web-ui/content/modules/3086df78444998b3/host.js +1 -0
  72. package/dist/web-ui/content/modules/3172fe0b46d51f8d/chunk.js +1 -0
  73. package/dist/web-ui/content/modules/{f2aaa9ad807f9425 → 3338b55c7767bacb}/chunk.js +2 -2
  74. package/dist/web-ui/content/modules/{ed7ed2b0eeab084c → 349ee129eef5a182}/chunk.js +1 -1
  75. package/dist/web-ui/content/modules/3ddcbd8505bfcdf4/chunk.js +1 -0
  76. package/dist/web-ui/content/modules/{f8e0987f1a44bfb7 → 42ae495da5903b07}/sha256.js +1 -1
  77. package/dist/web-ui/content/modules/43746e7237d61da9/task-board-host.js +17 -0
  78. package/dist/web-ui/content/modules/4485e6c1f0459aa8/chunk.js +1 -0
  79. package/dist/web-ui/content/modules/{dcc8b270d7fb7336 → 5903104a2e0e3888}/host.js +2 -2
  80. package/dist/web-ui/content/modules/5b541e0f296a3592/employee-card.js +1 -0
  81. package/dist/web-ui/content/modules/5e5eabd9a9366a56/panel.js +1 -0
  82. package/dist/web-ui/content/modules/64c1b0217f5377aa/chunk.js +3 -0
  83. package/dist/web-ui/content/modules/{75316f04bc550b0c → 66f36cd2c38cfb07}/chunk.js +1 -1
  84. package/dist/web-ui/content/modules/6ed52e6604661678/chat-render.js +75 -0
  85. package/dist/web-ui/content/modules/801e7f686ce7eb28/chunk.js +1 -0
  86. package/dist/web-ui/content/modules/854b0f6dc887923b/chunk.js +1 -0
  87. package/dist/web-ui/content/modules/87379801875d1921/host.js +1 -0
  88. package/dist/web-ui/content/modules/89888da0972e5033/host.js +50 -0
  89. package/dist/web-ui/content/modules/{223edddafb366679 → 9984f6bb05ac55bb}/host.js +2 -2
  90. package/dist/web-ui/content/modules/a9f15bd03bc590e7/host.js +1 -0
  91. package/dist/web-ui/content/modules/{0fc6176e363fa071 → b33c567946044d83}/host.js +3 -3
  92. package/dist/web-ui/content/modules/c11165f843d818f4/host-bridge.js +1 -0
  93. package/dist/web-ui/content/modules/c1829693f91fd25c/host.js +1 -0
  94. package/dist/web-ui/content/modules/c41d8ffa27816766/host.js +1 -0
  95. package/dist/web-ui/content/modules/cdb882ddab12e325/host.js +1 -0
  96. package/dist/web-ui/content/modules/{c579975101257d79 → d30ac9af6dc7d555}/chunk.js +1 -1
  97. package/dist/web-ui/content/modules/de00ab5329b5e05a/chunk.js +1 -0
  98. package/dist/web-ui/content/modules/{0e667f1e0a0568c6 → e01e1e25b7fe37e9}/host.js +2 -2
  99. package/dist/web-ui/content/modules/e8f0cda6b9ac8a70/chunk.js +1 -0
  100. package/dist/web-ui/content/modules/{8eed1ac34220d07d → e9a01a25ec573294}/library-bridge.js +3 -3
  101. package/dist/web-ui/content/modules/ea67fd4e96c9f45a/workspace-window.js +1 -0
  102. package/dist/web-ui/content/modules/ee12d3c4421a5c12/host.js +1 -0
  103. package/dist/web-ui/content/modules/f5b3fb6fedd9b05d/chunk.js +20 -0
  104. package/dist/web-ui/content/modules/fd767282c29173b5/runtime.js +539 -0
  105. package/dist/web-ui/embedded-assets.d.ts +173 -167
  106. package/dist/web-ui/embedded-assets.js +11 -11
  107. package/dist/web-ui/index.js +7 -1
  108. package/dist/web-ui/login-credential.d.ts +2 -0
  109. package/dist/web-ui/login-credential.js +4 -0
  110. package/dist/web-ui/react/workspaces/types.d.ts +2 -0
  111. package/dist/ws-broadcast.js +0 -6
  112. package/package.json +3 -1
  113. package/skills/wand-system/SKILL.md +3 -1
  114. package/skills/wand-system/references/operations.md +13 -4
  115. package/skills/wand-system/scripts/web-login.mjs +22 -9
  116. package/browser-extension/icons/wand-128.png +0 -0
  117. package/browser-extension/icons/wand-16.png +0 -0
  118. package/browser-extension/icons/wand-32.png +0 -0
  119. package/browser-extension/icons/wand-48.png +0 -0
  120. package/dist/web-session-access.d.ts +0 -7
  121. package/dist/web-session-access.js +0 -21
  122. package/dist/web-ui/content/modules/031fd6b62eb91b3b/runtime.js +0 -583
  123. package/dist/web-ui/content/modules/066faea7b90f2114/host-bridge.js +0 -1
  124. package/dist/web-ui/content/modules/10b9a520636ec75f/host.js +0 -1
  125. package/dist/web-ui/content/modules/167ef46bca671291/host.js +0 -1
  126. package/dist/web-ui/content/modules/1898cb03cc5120f3/host.js +0 -1
  127. package/dist/web-ui/content/modules/2861e5534b5ae6ff/employee-card.js +0 -1
  128. package/dist/web-ui/content/modules/2fd5edf0b4cfa7b9/panel.js +0 -1
  129. package/dist/web-ui/content/modules/3302a337cb81794f/host.js +0 -1
  130. package/dist/web-ui/content/modules/56e326c7f428a6a7/chunk.js +0 -1
  131. package/dist/web-ui/content/modules/5de4594380459c38/chunk.js +0 -1
  132. package/dist/web-ui/content/modules/6d331698bd3da433/page.js +0 -6
  133. package/dist/web-ui/content/modules/6f6c773bb93791d4/host.js +0 -1
  134. package/dist/web-ui/content/modules/710a6252e7706be0/workspace-window.js +0 -1
  135. package/dist/web-ui/content/modules/82b154630921d35d/host.js +0 -5
  136. package/dist/web-ui/content/modules/892b93f49607f5d3/task-board-host.js +0 -2
  137. package/dist/web-ui/content/modules/99b1d1672bcc84f0/host.js +0 -1
  138. package/dist/web-ui/content/modules/ae01d465dedd7fea/chunk.js +0 -1
  139. package/dist/web-ui/content/modules/b83de6a326c35862/chunk.js +0 -1
  140. package/dist/web-ui/content/modules/baec0f694303f08d/chunk.js +0 -3
  141. package/dist/web-ui/content/modules/c2a4fada737ab3ec/chunk.js +0 -1
  142. package/dist/web-ui/content/modules/c54550a744c53791/host.js +0 -1
  143. package/dist/web-ui/content/modules/c61f9b60c61baa5d/chat-render.js +0 -25
  144. package/dist/web-ui/content/modules/d73ed3d868862912/host.js +0 -1
  145. package/dist/web-ui/content/modules/e494c4ed3913fc59/chunk.js +0 -1
  146. package/dist/web-ui/content/modules/ea5d23ebc906ff78/host.js +0 -18
  147. 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
  #### 核心
@@ -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 never uses the retry chain. */
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 均保留各自适配和身份。
@@ -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 never uses the retry chain. */
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
- * 按顺序尝试候选:跳过没安装的 CLI,协议与输出校验均成功才返回。
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
- // Scoped automation cookies deliberately die on server restart. Persisting only
64
- // kind/scopes would silently discard the actor boundary and broaden access.
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 cookie without
89
- // its actor boundary must never regain broad workbench access after upgrade.
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" && !cached.principal.webSession)
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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
- "commit": "1aed3bb45f06259c1e9b957b93983dda9ea6c8d8",
3
- "builtAt": "2026-10-16T09:10:20.913Z",
4
- "version": "4.96.1-beta.202610161706",
5
- "channel": "beta"
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, non-admin Web ticket (controlled pipe only)
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
@@ -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>;
@@ -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
- const candidate = { provider: defaultProvider(opts.provider), engine: "cli", model: opts.model ?? undefined, thinkingEffort: opts.thinkingEffort ?? undefined };
881
+ if (trace)
882
+ opts.onCallSessionCreated?.(trace.sessionId);
883
+ const beforeHead = await getHead(opts.cwd);
880
884
  try {
881
- if (trace)
882
- opts.onCallSessionCreated?.(trace.sessionId);
883
- trace?.candidateStarted(candidate);
884
- const result = await runQuickCommitFallbackCliImpl(opts, priorError);
885
- trace?.applicationResult(result);
886
- trace?.complete();
887
- return result;
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: QUICK_COMMIT_CLI_TIMEOUT_MS,
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: QUICK_COMMIT_CLI_TIMEOUT_MS,
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: QUICK_COMMIT_CLI_TIMEOUT_MS,
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: QUICK_COMMIT_CLI_TIMEOUT_MS,
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: QUICK_COMMIT_CLI_TIMEOUT_MS,
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: QUICK_COMMIT_CLI_TIMEOUT_MS,
1006
+ timeoutMs,
971
1007
  inheritEnv: opts.inheritEnv,
972
1008
  });
973
1009
  }
@@ -1,6 +1,11 @@
1
1
  import { type SessionProvider } from "./provider-catalog.js";
2
- /** Install trusted bundled files without overwriting user content or changing tool permissions. */
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>;
@@ -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 && sha(old) !== previous[file])
74
- throw new Error(`技能已有用户修改,拒绝覆盖:${file}`);
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
+ }
@@ -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
- return { ...first, get args() { return first.args; }, get pid() { return first.pid; }, completion, interrupt() {
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, interrupt: () => {
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") {
@@ -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[]>;
@@ -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 = new ProviderHistoryScanner();
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
- return this.providerHistory.deletePiHistoryFiles(sessionIds);
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.terminalHost.forget(id);
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;