@co0ontty/wand 4.27.1 → 4.28.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 (49) hide show
  1. package/README.md +6 -6
  2. package/dist/build-info.json +3 -3
  3. package/dist/cli-api.d.ts +11 -0
  4. package/dist/cli-api.js +76 -0
  5. package/dist/cli.js +97 -0
  6. package/dist/config.d.ts +4 -3
  7. package/dist/config.js +20 -3
  8. package/dist/git-quick-commit.js +47 -1
  9. package/dist/git-worktree.d.ts +11 -0
  10. package/dist/git-worktree.js +72 -3
  11. package/dist/mission-diff.d.ts +8 -0
  12. package/dist/mission-diff.js +65 -0
  13. package/dist/mission-types.d.ts +90 -0
  14. package/dist/mission-types.js +1 -0
  15. package/dist/missions.d.ts +32 -0
  16. package/dist/missions.js +344 -0
  17. package/dist/models.d.ts +1 -0
  18. package/dist/models.js +10 -1
  19. package/dist/path-repair.js +1 -1
  20. package/dist/process-manager.js +14 -1
  21. package/dist/prompt-optimizer.d.ts +2 -2
  22. package/dist/prompt-optimizer.js +11 -32
  23. package/dist/provider-cli-updater.d.ts +1 -1
  24. package/dist/provider-cli-updater.js +8 -0
  25. package/dist/resume-policy.js +5 -3
  26. package/dist/server-mission-routes.d.ts +3 -0
  27. package/dist/server-mission-routes.js +81 -0
  28. package/dist/server-session-routes.js +5 -3
  29. package/dist/server-settings-routes.js +5 -0
  30. package/dist/server-update-routes.js +1 -1
  31. package/dist/server.js +31 -5
  32. package/dist/session-ai-context.d.ts +3 -3
  33. package/dist/session-ai-context.js +14 -18
  34. package/dist/storage.d.ts +15 -0
  35. package/dist/storage.js +221 -1
  36. package/dist/structured-pi-adapter.d.ts +11 -0
  37. package/dist/structured-pi-adapter.js +135 -0
  38. package/dist/structured-provider-common.d.ts +1 -0
  39. package/dist/structured-provider-common.js +16 -1
  40. package/dist/structured-session-manager.d.ts +4 -0
  41. package/dist/structured-session-manager.js +21 -7
  42. package/dist/system-ai.js +1 -1
  43. package/dist/types.d.ts +9 -4
  44. package/dist/web-ui/content/scripts.js +146 -69
  45. package/dist/web-ui/embedded-assets.d.ts +1 -1
  46. package/dist/web-ui/embedded-assets.js +2 -2
  47. package/dist/web-ui/provider-identity.d.ts +2 -1
  48. package/dist/web-ui/provider-identity.js +9 -1
  49. package/package.json +1 -1
@@ -1,6 +1,4 @@
1
- import { ClaudeRunError, runClaudePrint } from "./claude-sdk-runner.js";
2
- import { callSystemAiTextWithFallback } from "./system-ai.js";
3
- const CLAUDE_TIMEOUT_MS = 60_000;
1
+ import { callConfiguredAiText } from "./git-quick-commit.js";
4
2
  const MAX_INPUT_LENGTH = 8000;
5
3
  export class PromptOptimizeError extends Error {
6
4
  code;
@@ -10,24 +8,6 @@ export class PromptOptimizeError extends Error {
10
8
  this.name = "PromptOptimizeError";
11
9
  }
12
10
  }
13
- async function callClaudeText(prompt, cwd, language) {
14
- try {
15
- return await runClaudePrint(prompt, { cwd, timeoutMs: CLAUDE_TIMEOUT_MS, language });
16
- }
17
- catch (error) {
18
- if (error instanceof ClaudeRunError) {
19
- // 翻译成 prompt-optimizer 自己的话术 + 错误码(与原文案保持一致)。
20
- if (error.code === "CLAUDE_TIMEOUT") {
21
- throw new PromptOptimizeError("Claude 优化超时,请稍后重试。", "CLAUDE_TIMEOUT");
22
- }
23
- if (error.code === "CLAUDE_EMPTY_RESULT") {
24
- throw new PromptOptimizeError("Claude 返回了空结果。", "EMPTY_RESULT");
25
- }
26
- throw new PromptOptimizeError(error.message, error.code);
27
- }
28
- throw error;
29
- }
30
- }
31
11
  function buildOptimizePrompt(userInput, language) {
32
12
  const lang = (language || "").trim() || "中文";
33
13
  return [
@@ -42,7 +22,7 @@ function buildOptimizePrompt(userInput, language) {
42
22
  userInput,
43
23
  ].join("\n");
44
24
  }
45
- export async function optimizePrompt(rawText, language, cwd, systemAi) {
25
+ export async function optimizePrompt(rawText, language, cwd, ai = {}) {
46
26
  const text = (rawText || "").trim();
47
27
  if (!text) {
48
28
  throw new PromptOptimizeError("请先输入要优化的内容。", "EMPTY_INPUT");
@@ -52,16 +32,15 @@ export async function optimizePrompt(rawText, language, cwd, systemAi) {
52
32
  }
53
33
  const prompt = buildOptimizePrompt(text, language);
54
34
  let raw;
55
- if (systemAi?.enabled) {
56
- try {
57
- raw = await callSystemAiTextWithFallback(prompt, systemAi);
58
- }
59
- catch {
60
- raw = await callClaudeText(prompt, cwd, language);
61
- }
35
+ try {
36
+ raw = await callConfiguredAiText(prompt, cwd ?? process.cwd(), language, ai);
62
37
  }
63
- else {
64
- raw = await callClaudeText(prompt, cwd, language);
38
+ catch (error) {
39
+ const message = error instanceof Error ? error.message : String(error);
40
+ const code = error && typeof error === "object" && "code" in error && typeof error.code === "string"
41
+ ? error.code
42
+ : "AI_OPTIMIZE_FAILED";
43
+ throw new PromptOptimizeError(message, code);
65
44
  }
66
45
  const cleaned = raw
67
46
  .replace(/^```[a-zA-Z]*\n?/, "")
@@ -69,7 +48,7 @@ export async function optimizePrompt(rawText, language, cwd, systemAi) {
69
48
  .replace(/^["'`]+|["'`]+$/g, "")
70
49
  .trim();
71
50
  if (!cleaned) {
72
- throw new PromptOptimizeError("Claude 返回了空结果。", "EMPTY_RESULT");
51
+ throw new PromptOptimizeError("AI 返回了空结果。", "EMPTY_RESULT");
73
52
  }
74
53
  return cleaned;
75
54
  }
@@ -1,4 +1,4 @@
1
- export type ProviderCliId = "claude" | "codex" | "opencode" | "qoder";
1
+ export type ProviderCliId = "claude" | "codex" | "opencode" | "qoder" | "pi";
2
2
  export interface ProviderCliUpdateStatus {
3
3
  id: ProviderCliId;
4
4
  label: string;
@@ -44,6 +44,14 @@ const PROVIDER_CLI_SPECS = [
44
44
  versionArgs: ["--version"],
45
45
  updateArgs: ["update"],
46
46
  },
47
+ {
48
+ id: "pi",
49
+ label: "Pi CLI",
50
+ command: "pi",
51
+ npmPackage: "@mariozechner/pi-coding-agent",
52
+ versionArgs: ["--version"],
53
+ updateArgs: ["update", "self"],
54
+ },
47
55
  ];
48
56
  function childEnv(options) {
49
57
  return options.env ?? buildChildEnv(options.inheritEnv !== false);
@@ -18,7 +18,7 @@ function resumeArgumentPattern(provider) {
18
18
  if (provider === "codex") {
19
19
  return new RegExp(`(?:^|\\s)resume\\s+${SAFE_PROVIDER_SESSION_ID_SOURCE}(?=\\s|$)`, "i");
20
20
  }
21
- if (provider === "opencode") {
21
+ if (provider === "opencode" || provider === "pi") {
22
22
  return new RegExp(`(?:^|\\s)(?:--session|-s)\\s+${SAFE_PROVIDER_SESSION_ID_SOURCE}(?=\\s|$)`, "i");
23
23
  }
24
24
  return new RegExp(`(?:^|\\s)(?:--resume|-r)\\s+${SAFE_PROVIDER_SESSION_ID_SOURCE}(?=\\s|$)`, "i");
@@ -34,13 +34,13 @@ export function getProviderCommandSessionId(provider, command) {
34
34
  const resumed = getProviderResumeCommandSessionId(provider, command);
35
35
  if (resumed)
36
36
  return resumed;
37
- if (provider === "codex" || provider === "opencode")
37
+ if (provider === "codex" || provider === "opencode" || provider === "pi")
38
38
  return null;
39
39
  return assignedSessionIdPattern().exec(command)?.[1] ?? null;
40
40
  }
41
41
  function stripProviderResumeArgument(provider, command) {
42
42
  const withoutResume = command.replace(resumeArgumentPattern(provider), " ");
43
- const withoutAssignedId = provider === "codex" || provider === "opencode"
43
+ const withoutAssignedId = provider === "codex" || provider === "opencode" || provider === "pi"
44
44
  ? withoutResume
45
45
  : withoutResume.replace(assignedSessionIdPattern(), " ");
46
46
  return withoutAssignedId.replace(/\s+/g, " ").trim();
@@ -55,5 +55,7 @@ export function buildProviderResumeCommand(provider, command, providerSessionId)
55
55
  return `${base} resume ${providerSessionId}`;
56
56
  if (provider === "opencode")
57
57
  return `${base} --session ${providerSessionId}`;
58
+ if (provider === "pi")
59
+ return `${base} --session ${providerSessionId}`;
58
60
  return `${base} --resume ${providerSessionId}`;
59
61
  }
@@ -0,0 +1,3 @@
1
+ import type { Express } from "express";
2
+ import type { Missions } from "./missions.js";
3
+ export declare function registerMissionRoutes(app: Express, missions: Missions): void;
@@ -0,0 +1,81 @@
1
+ import { getErrorMessage } from "./error-utils.js";
2
+ function sendMissionError(res, error) {
3
+ const message = getErrorMessage(error, "任务操作失败。");
4
+ const missing = /^(任务不存在|任务 attempt 不存在)|没有关联会话|当前不可用/.test(message);
5
+ res.status(missing ? 404 : 400).json({ error: message });
6
+ }
7
+ export function registerMissionRoutes(app, missions) {
8
+ app.get("/api/inbox", (_req, res) => {
9
+ res.json({ items: missions.inbox() });
10
+ });
11
+ app.post("/api/inbox/read", (req, res) => {
12
+ const sessionId = typeof req.body?.sessionId === "string" ? req.body.sessionId : undefined;
13
+ missions.markInboxRead(sessionId);
14
+ res.json({ ok: true });
15
+ });
16
+ app.get("/api/missions", (_req, res) => {
17
+ res.json({ missions: missions.list() });
18
+ });
19
+ app.post("/api/missions", (req, res) => {
20
+ try {
21
+ res.status(201).json(missions.create(req.body));
22
+ }
23
+ catch (error) {
24
+ sendMissionError(res, error);
25
+ }
26
+ });
27
+ app.get("/api/missions/:missionId", (req, res) => {
28
+ const mission = missions.get(req.params.missionId);
29
+ if (!mission) {
30
+ res.status(404).json({ error: "任务不存在。" });
31
+ return;
32
+ }
33
+ res.json(mission);
34
+ });
35
+ app.post("/api/missions/:missionId/archive", (req, res) => {
36
+ try {
37
+ res.json(missions.archive(req.params.missionId));
38
+ }
39
+ catch (error) {
40
+ sendMissionError(res, error);
41
+ }
42
+ });
43
+ app.get("/api/missions/:missionId/attempts/:attemptId/diff", (req, res) => {
44
+ try {
45
+ res.json(missions.diff(req.params.missionId, req.params.attemptId));
46
+ }
47
+ catch (error) {
48
+ sendMissionError(res, error);
49
+ }
50
+ });
51
+ app.post("/api/missions/:missionId/attempts/:attemptId/comments", (req, res) => {
52
+ try {
53
+ res.status(201).json(missions.addReviewComment(req.params.missionId, req.params.attemptId, req.body));
54
+ }
55
+ catch (error) {
56
+ sendMissionError(res, error);
57
+ }
58
+ });
59
+ app.post("/api/missions/:missionId/attempts/:attemptId/review/send", (req, res) => {
60
+ try {
61
+ const commentIds = Array.isArray(req.body?.commentIds)
62
+ ? req.body.commentIds.filter((id) => typeof id === "string")
63
+ : undefined;
64
+ res.status(202).json({ comments: missions.sendReview(req.params.missionId, req.params.attemptId, commentIds) });
65
+ }
66
+ catch (error) {
67
+ sendMissionError(res, error);
68
+ }
69
+ });
70
+ app.post("/api/missions/:missionId/attempts/:attemptId/review/resolve", (req, res) => {
71
+ try {
72
+ const commentIds = Array.isArray(req.body?.commentIds)
73
+ ? req.body.commentIds.filter((id) => typeof id === "string")
74
+ : [];
75
+ res.json({ comments: missions.resolveReview(req.params.missionId, req.params.attemptId, commentIds) });
76
+ }
77
+ catch (error) {
78
+ sendMissionError(res, error);
79
+ }
80
+ });
81
+ }
@@ -319,6 +319,8 @@ function resolvePtyResumeProvider(snapshot) {
319
319
  return "grok";
320
320
  if (/^qodercli\b/.test(command))
321
321
  return "qoder";
322
+ if (/^pi\b/.test(command))
323
+ return "pi";
322
324
  return "claude";
323
325
  }
324
326
  function isPtyProviderCommand(provider, command) {
@@ -375,7 +377,7 @@ function canAutoResumePtyForInput(snapshot, input) {
375
377
  return Boolean(snapshot
376
378
  && (snapshot.sessionKind ?? "pty") === "pty"
377
379
  && snapshot.status !== "running"
378
- && (snapshot.provider === "claude" || snapshot.provider === "codex" || snapshot.provider === "opencode" || snapshot.provider === "grok" || snapshot.provider === "qoder"
380
+ && (snapshot.provider === "claude" || snapshot.provider === "codex" || snapshot.provider === "opencode" || snapshot.provider === "grok" || snapshot.provider === "qoder" || snapshot.provider === "pi"
379
381
  || /^(?:claude|codex|opencode|grok|qodercli)\b/.test(snapshot.command.trim()))
380
382
  && snapshot.claudeSessionId
381
383
  && input);
@@ -418,11 +420,11 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
418
420
  app.post("/api/structured-sessions", asyncRoute(async (req, res) => {
419
421
  const body = req.body;
420
422
  try {
421
- if (body.provider && body.provider !== "claude" && body.provider !== "codex" && body.provider !== "opencode" && body.provider !== "grok" && body.provider !== "qoder") {
423
+ if (body.provider && body.provider !== "claude" && body.provider !== "codex" && body.provider !== "opencode" && body.provider !== "grok" && body.provider !== "qoder" && body.provider !== "pi") {
422
424
  res.status(400).json({ error: "结构化会话当前仅支持 Claude、Codex、OpenCode、Grok 或 Qoder provider。" });
423
425
  return;
424
426
  }
425
- const provider = body.provider === "codex" || body.provider === "opencode" || body.provider === "grok" || body.provider === "qoder" ? body.provider : "claude";
427
+ const provider = body.provider === "codex" || body.provider === "opencode" || body.provider === "grok" || body.provider === "qoder" || body.provider === "pi" ? body.provider : "claude";
426
428
  const rawModel = typeof body.model === "string" ? body.model.trim() : "";
427
429
  const origin = parseSessionCreationOrigin(body);
428
430
  const snapshot = structured.createSession({
@@ -25,6 +25,7 @@ function publicConfig(config) {
25
25
  defaultOpenCodeModel: defaultModels.opencode,
26
26
  defaultGrokModel: defaultModels.grok,
27
27
  defaultQoderModel: defaultModels.qoder,
28
+ defaultPiModel: defaultModels.pi,
28
29
  defaultModels,
29
30
  };
30
31
  }
@@ -283,6 +284,8 @@ export function registerSettingsRoutes(app, deps) {
283
284
  stagePreference("defaultGrokModel", body.defaultModels.grok);
284
285
  if (Object.hasOwn(body.defaultModels, "qoder"))
285
286
  stagePreference("defaultQoderModel", body.defaultModels.qoder);
287
+ if (Object.hasOwn(body.defaultModels, "pi"))
288
+ stagePreference("defaultPiModel", body.defaultModels.pi);
286
289
  }
287
290
  if (body.systemAi !== undefined) {
288
291
  if (!body.systemAi || typeof body.systemAi !== "object" || Array.isArray(body.systemAi)) {
@@ -397,6 +400,7 @@ export function registerSettingsRoutes(app, deps) {
397
400
  defaultOpenCodeModel: defaults.opencode,
398
401
  defaultGrokModel: defaults.grok,
399
402
  defaultQoderModel: defaults.qoder,
403
+ defaultPiModel: defaults.pi,
400
404
  defaultModels: defaults,
401
405
  });
402
406
  });
@@ -411,6 +415,7 @@ export function registerSettingsRoutes(app, deps) {
411
415
  defaultOpenCodeModel: defaults.opencode,
412
416
  defaultGrokModel: defaults.grok,
413
417
  defaultQoderModel: defaults.qoder,
418
+ defaultPiModel: defaults.pi,
414
419
  defaultModels: defaults,
415
420
  });
416
421
  }
@@ -126,7 +126,7 @@ export function registerAdminUpdateRoutes(app, deps) {
126
126
  return;
127
127
  }
128
128
  const rawIds = Array.isArray(req.body?.ids) ? req.body.ids : [];
129
- const ids = rawIds.filter((value) => value === "claude" || value === "codex" || value === "opencode" || value === "qoder");
129
+ const ids = rawIds.filter((value) => value === "claude" || value === "codex" || value === "opencode" || value === "qoder" || value === "pi");
130
130
  state.providerCliUpdateInFlight = true;
131
131
  try {
132
132
  const before = await refreshProviderCliUpdateState(state, config);
package/dist/server.js CHANGED
@@ -18,9 +18,12 @@ import { ModelCatalogService } from "./models.js";
18
18
  import { ProcessManager } from "./process-manager.js";
19
19
  import { SessionLogger } from "./session-logger.js";
20
20
  import { SessionRegistry } from "./session-registry.js";
21
+ import { resolveSessionAiContext, resolveSystemAiContext } from "./session-ai-context.js";
21
22
  import { StructuredSessionManager } from "./structured-session-manager.js";
22
23
  import { recordRecentPath, registerFileRoutes } from "./server-file-routes.js";
23
24
  import { registerSettingsRoutes } from "./server-settings-routes.js";
25
+ import { registerMissionRoutes } from "./server-mission-routes.js";
26
+ import { Missions } from "./missions.js";
24
27
  import { refreshProviderCliUpdateState, registerAdminUpdateRoutes, registerPublicUpdateRoutes, ServerUpdateState, } from "./server-update-routes.js";
25
28
  import { parseSessionCreationOrigin, registerClaudeHistoryRoutes, registerSessionRoutes } from "./server-session-routes.js";
26
29
  import { resolveSessionCwd } from "./session-cwd.js";
@@ -500,6 +503,7 @@ export async function startServer(config, configPath, options = {}) {
500
503
  const structuredLogger = new SessionLogger(configDir, config.shortcutLogMaxBytes);
501
504
  const structuredSessions = new StructuredSessionManager(storage, config, structuredLogger);
502
505
  const sessionRegistry = new SessionRegistry(processes, structuredSessions, storage);
506
+ const missions = new Missions(storage, structuredSessions, sessionRegistry);
503
507
  const updateState = new ServerUpdateState();
504
508
  const getUpdateChannel = () => normalizeUpdateChannel(storage.getConfigValue("updateChannel"));
505
509
  let disconnectAuthenticatedSockets = () => { };
@@ -700,6 +704,8 @@ export async function startServer(config, configPath, options = {}) {
700
704
  "/api/claude-sessions",
701
705
  "/api/codex-sessions",
702
706
  "/api/optimize-prompt",
707
+ "/api/inbox",
708
+ "/api/missions",
703
709
  ], requireSessions);
704
710
  app.use([
705
711
  "/api/directory",
@@ -929,18 +935,34 @@ export async function startServer(config, configPath, options = {}) {
929
935
  recordRecentPath(storage, cwd);
930
936
  });
931
937
  registerClaudeHistoryRoutes(app, processes, structuredSessions, storage, sessionRegistry);
938
+ registerMissionRoutes(app, missions);
932
939
  registerUploadRoutes(app, processes);
933
940
  app.post("/api/optimize-prompt", asyncRoute(async (req, res) => {
934
941
  const body = (req.body ?? {});
935
942
  const text = typeof body.text === "string" ? body.text : "";
936
943
  let cwd;
944
+ let ai;
937
945
  if (typeof body.sessionId === "string" && body.sessionId.length > 0) {
938
- const snap = storage.getSession(body.sessionId);
939
- if (snap?.cwd)
940
- cwd = snap.cwd;
946
+ const snapshot = sessionRegistry.getLatest(body.sessionId);
947
+ if (snapshot?.cwd)
948
+ cwd = snapshot.cwd;
949
+ if (snapshot)
950
+ ai = resolveSystemAiContext(snapshot, config);
951
+ }
952
+ if (!ai) {
953
+ const defaultSession = {
954
+ provider: config.defaultProvider,
955
+ structuredState: undefined,
956
+ runner: undefined,
957
+ command: config.defaultProvider === "qoder" ? "qodercli" : config.defaultProvider ?? "claude",
958
+ selectedModel: null,
959
+ thinkingEffort: config.defaultThinkingEffort,
960
+ };
961
+ const cli = resolveSessionAiContext(defaultSession, config);
962
+ ai = config.systemAi?.enabled ? { ...cli, systemAi: config.systemAi } : cli;
941
963
  }
942
964
  try {
943
- const optimized = await optimizePrompt(text, config.language ?? "", cwd, config.systemAi);
965
+ const optimized = await optimizePrompt(text, config.language ?? "", cwd, ai);
944
966
  res.json({ optimized });
945
967
  }
946
968
  catch (error) {
@@ -976,7 +998,9 @@ export async function startServer(config, configPath, options = {}) {
976
998
  ? "grok"
977
999
  : body.provider === "qoder" || /^qodercli\b/.test(body.command.trim())
978
1000
  ? "qoder"
979
- : "claude";
1001
+ : body.provider === "pi" || /^pi\b/.test(body.command.trim())
1002
+ ? "pi"
1003
+ : "claude";
980
1004
  // Older clients used the provider id as the PTY command. Qoder's executable
981
1005
  // is named qodercli, so keep those clients working while preserving custom commands.
982
1006
  const command = provider === "qoder" && body.command.trim() === "qoder"
@@ -1059,9 +1083,11 @@ export async function startServer(config, configPath, options = {}) {
1059
1083
  });
1060
1084
  // Wire process events to WebSocket broadcast
1061
1085
  processes.on("process", (event) => {
1086
+ missions.ingest(event);
1062
1087
  wsManager.emitEvent(event);
1063
1088
  });
1064
1089
  structuredSessions.setEventEmitter((event) => {
1090
+ missions.ingest(event);
1065
1091
  wsManager.emitEvent(event);
1066
1092
  });
1067
1093
  // ── Restart endpoint (needs server + wss in scope) ──
@@ -14,8 +14,8 @@ export interface SessionAiContext {
14
14
  */
15
15
  export declare function resolveSessionProvider(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command">): SessionProvider;
16
16
  /** Build the provider-specific settings used by session-adjacent AI actions. */
17
- export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultThinkingEffort" | "inheritEnv">): SessionAiContext;
17
+ export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultThinkingEffort" | "inheritEnv">): SessionAiContext;
18
18
  /** Build the source order for Wand-owned AI features such as titles. */
19
- export declare function resolveSystemAiContext(snapshot: Parameters<typeof resolveSessionAiContext>[0], config: Parameters<typeof resolveSessionAiContext>[1] & Pick<WandConfig, "systemAi" | "commitCli" | "commitModel">): SessionAiContext;
19
+ export declare function resolveSystemAiContext(snapshot: Parameters<typeof resolveSessionAiContext>[0], config: Parameters<typeof resolveSessionAiContext>[1] & Pick<WandConfig, "systemAi">): SessionAiContext;
20
20
  /** Build the AI context for quick-commit actions from their global preferences. */
21
- export declare function resolveCommitAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultThinkingEffort" | "inheritEnv" | "commitAiSource" | "systemAi">, discoverApis?: typeof discoverCliSystemAiConfigs): SessionAiContext;
21
+ export declare function resolveCommitAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultThinkingEffort" | "inheritEnv" | "commitAiSource" | "systemAi">, discoverApis?: typeof discoverCliSystemAiConfigs): SessionAiContext;
@@ -10,14 +10,16 @@ export function resolveSessionProvider(snapshot) {
10
10
  || snapshot.provider === "codex"
11
11
  || snapshot.provider === "opencode"
12
12
  || snapshot.provider === "grok"
13
- || snapshot.provider === "qoder") {
13
+ || snapshot.provider === "qoder"
14
+ || snapshot.provider === "pi") {
14
15
  return snapshot.provider;
15
16
  }
16
17
  if (snapshot.structuredState?.provider === "claude"
17
18
  || snapshot.structuredState?.provider === "codex"
18
19
  || snapshot.structuredState?.provider === "opencode"
19
20
  || snapshot.structuredState?.provider === "grok"
20
- || snapshot.structuredState?.provider === "qoder") {
21
+ || snapshot.structuredState?.provider === "qoder"
22
+ || snapshot.structuredState?.provider === "pi") {
21
23
  return snapshot.structuredState.provider;
22
24
  }
23
25
  const runner = snapshot.runner ?? snapshot.structuredState?.runner;
@@ -29,6 +31,8 @@ export function resolveSessionProvider(snapshot) {
29
31
  return "grok";
30
32
  if (runner === "qoder-cli-print")
31
33
  return "qoder";
34
+ if (runner === "pi-cli-json")
35
+ return "pi";
32
36
  if (runner === "claude-cli" || runner === "claude-cli-print" || runner === "claude-sdk")
33
37
  return "claude";
34
38
  if (/^codex\b/i.test(snapshot.command.trim()))
@@ -39,6 +43,8 @@ export function resolveSessionProvider(snapshot) {
39
43
  return "grok";
40
44
  if (/^qodercli\b/i.test(snapshot.command.trim()))
41
45
  return "qoder";
46
+ if (/^pi\b/i.test(snapshot.command.trim()))
47
+ return "pi";
42
48
  return "claude";
43
49
  }
44
50
  function normalizeModel(value) {
@@ -66,28 +72,18 @@ export function resolveSessionAiContext(snapshot, config) {
66
72
  export function resolveSystemAiContext(snapshot, config) {
67
73
  const sessionContext = resolveSessionAiContext(snapshot, config);
68
74
  const directApi = config.systemAi ? usableSystemAi(config.systemAi) : undefined;
69
- const cliContext = {
70
- ...sessionContext,
71
- provider: config.commitCli === "codex" || config.commitCli === "opencode" ? config.commitCli : "claude",
72
- model: normalizeModel(config.commitModel),
73
- };
74
- return directApi && config.systemAi?.enabled ? { ...cliContext, systemAi: directApi } : cliContext;
75
+ return directApi && config.systemAi?.enabled
76
+ ? { ...sessionContext, systemAi: directApi }
77
+ : sessionContext;
75
78
  }
76
79
  /** Build the AI context for quick-commit actions from their global preferences. */
77
80
  export function resolveCommitAiContext(snapshot, config, discoverApis = discoverCliSystemAiConfigs) {
78
81
  const sessionContext = resolveSessionAiContext(snapshot, config);
79
- const commitContext = {
80
- ...sessionContext,
81
- // Commit messages and tags are short classification/summarization tasks.
82
- // Keep them on the minimum explicit provider effort instead of inheriting
83
- // an expensive deep/max setting from the conversation.
84
- thinkingEffort: "standard",
85
- };
86
82
  if (config.commitAiSource !== "api")
87
- return commitContext;
88
- const directApi = mergeSystemAiConfigs(config.systemAi, discoverApis(commitContext.provider));
83
+ return sessionContext;
84
+ const directApi = mergeSystemAiConfigs(config.systemAi, discoverApis(sessionContext.provider));
89
85
  return {
90
- ...commitContext,
86
+ ...sessionContext,
91
87
  ...(directApi ? { systemAi: directApi } : {}),
92
88
  };
93
89
  }
package/dist/storage.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { SessionSnapshot, ConversationTurn, StructuredSessionState } from "./types.js";
2
+ import type { AgentActivityItem, Mission, MissionAttempt, MissionReviewComment, MissionReviewStatus, MissionStatus } from "./mission-types.js";
2
3
  import { type PasswordVault, type PasswordVaultItem, type PasswordVaultItemFilter, type PasswordVaultItemInput } from "./password-manager.js";
3
4
  export declare const DEFAULT_DB_FILE = "wand.db";
4
5
  export type AuthPrincipalKind = "browser-admin" | "connected-app";
@@ -60,6 +61,20 @@ export declare class WandStorage {
60
61
  deleteAuthSession(token: string): void;
61
62
  deleteAllAuthSessions(): void;
62
63
  deleteExpiredAuthSessions(now: number): void;
64
+ saveMission(mission: Mission): void;
65
+ getMission(id: string): Mission | null;
66
+ listMissions(includeArchived?: boolean): Mission[];
67
+ updateMissionStatus(id: string, status: MissionStatus, updatedAt?: string): void;
68
+ saveMissionAttempt(attempt: MissionAttempt): void;
69
+ getMissionAttempt(id: string): MissionAttempt | null;
70
+ getMissionAttemptBySession(sessionId: string): MissionAttempt | null;
71
+ listMissionAttempts(missionId: string): MissionAttempt[];
72
+ saveMissionReviewComment(comment: MissionReviewComment): void;
73
+ listMissionReviewComments(missionId: string, attemptId?: string): MissionReviewComment[];
74
+ updateMissionReviewStatus(ids: string[], status: MissionReviewStatus, at?: string): void;
75
+ upsertAgentActivity(item: AgentActivityItem): void;
76
+ listAgentActivity(): AgentActivityItem[];
77
+ markAgentActivityRead(sessionId?: string): void;
63
78
  saveSession(snapshot: SessionSnapshot): void;
64
79
  /** Update runtime/scalar fields without serializing or rewriting messages/output. */
65
80
  updateSessionRuntimeMetadata(snapshot: SessionSnapshot): void;