@co0ontty/wand 4.28.0 → 4.29.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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "commit": "53c936da2a9d14e6927295c028a77c6d0abd562e",
3
- "builtAt": "2026-08-05T01:35:22.744Z",
4
- "version": "4.28.0",
2
+ "commit": "ff3c41b2ee6aba11d741c8f27c891c25d2e51a72",
3
+ "builtAt": "2026-08-06T00:33:38.658Z",
4
+ "version": "4.29.0",
5
5
  "channel": "stable"
6
6
  }
@@ -51,6 +51,7 @@ export declare class ProcessManager extends EventEmitter {
51
51
  thinkingEffort?: SessionSnapshot["thinkingEffort"];
52
52
  sessionSource?: SessionSource;
53
53
  automationId?: string;
54
+ interactiveShell?: boolean;
54
55
  }): SessionSnapshot;
55
56
  list(): SessionSnapshot[];
56
57
  /** Return lightweight snapshots for the session list (no output/messages). */
@@ -151,6 +152,14 @@ export declare class ProcessManager extends EventEmitter {
151
152
  */
152
153
  private handleBridgeEvent;
153
154
  private mustGet;
155
+ /** Start a bare interactive login shell without a provider CLI. */
156
+ startShell(cwd: string | undefined, mode: ExecutionMode, opts?: {
157
+ worktreeEnabled?: boolean;
158
+ cols?: number;
159
+ rows?: number;
160
+ sessionSource?: SessionSource;
161
+ automationId?: string;
162
+ }): SessionSnapshot;
154
163
  private buildShellArgs;
155
164
  private shouldAutoApprovePermissions;
156
165
  private processCommandForMode;
@@ -22,6 +22,8 @@ import { resolveSystemAiContext } from "./session-ai-context.js";
22
22
  import { resolveSessionCwd } from "./session-cwd.js";
23
23
  import { ProviderHistoryScanner, } from "./provider-history-scanner.js";
24
24
  function resolveProviderFromCommand(command) {
25
+ if (/^(?:claude|npx\s+claude|[^\s]+\/claude)(?:\s|$)/.test(command.trim()))
26
+ return "claude";
25
27
  if (/^codex\b/.test(command.trim()))
26
28
  return "codex";
27
29
  if (/^opencode\b/.test(command.trim()))
@@ -30,7 +32,7 @@ function resolveProviderFromCommand(command) {
30
32
  return "grok";
31
33
  if (/^pi\b/.test(command.trim()))
32
34
  return "pi";
33
- return /^qodercli\b/.test(command.trim()) ? "qoder" : "claude";
35
+ return /^qodercli\b/.test(command.trim()) ? "qoder" : undefined;
34
36
  }
35
37
  /**
36
38
  * Tokenize the restricted shell-command subset accepted by the command
@@ -550,7 +552,9 @@ export class ProcessManager extends EventEmitter {
550
552
  const isClaudeCmd = provider === "claude";
551
553
  const isCodexCmd = provider === "codex";
552
554
  const isOpenCodeCmd = provider === "opencode";
553
- const resumeCommandSessionId = getProviderCommandSessionId(provider, snapshot.command);
555
+ const resumeCommandSessionId = provider
556
+ ? getProviderCommandSessionId(provider, snapshot.command)
557
+ : null;
554
558
  const orphanEndedAt = snapshot.status === "running" ? new Date().toISOString() : null;
555
559
  const sessionIdFromHistory = isClaudeCmd
556
560
  ? recoverClaudeSessionIdFromHistory({
@@ -784,7 +788,8 @@ export class ProcessManager extends EventEmitter {
784
788
  start(command, cwd, mode, initialInput, opts) {
785
789
  if (this.disposed)
786
790
  throw new Error("ProcessManager has been disposed.");
787
- this.assertCommandAllowed(command);
791
+ if (!opts?.interactiveShell)
792
+ this.assertCommandAllowed(command);
788
793
  const baseCwd = resolveSessionCwd(cwd, this.config.defaultCwd);
789
794
  const id = opts?.reuseId || randomUUID();
790
795
  // When a session is being resumed under the same id, capture its prior
@@ -815,7 +820,9 @@ export class ProcessManager extends EventEmitter {
815
820
  ? prepareSessionWorktree({ cwd: baseCwd, sessionId: id })
816
821
  : null;
817
822
  const resolvedCwd = worktreeSetup?.cwd ?? baseCwd;
818
- const provider = opts?.provider ?? resolveProviderFromCommand(command);
823
+ const provider = opts?.interactiveShell
824
+ ? undefined
825
+ : opts?.provider ?? resolveProviderFromCommand(command);
819
826
  const effectiveMode = provider === "codex" ? "full-access" : mode;
820
827
  const isClaudeProvider = provider === "claude";
821
828
  const selectedModel = opts?.model?.trim() || undefined;
@@ -823,8 +830,10 @@ export class ProcessManager extends EventEmitter {
823
830
  let processedCommand = this.processCommandForMode(command, effectiveMode, provider, selectedModel, initialThinkingEffort);
824
831
  const isCodexProvider = provider === "codex";
825
832
  const isOpenCodeProvider = provider === "opencode";
826
- const existingProviderSessionId = getProviderCommandSessionId(provider, processedCommand)
827
- ?? getProviderCommandSessionId(provider, command);
833
+ const existingProviderSessionId = provider
834
+ ? getProviderCommandSessionId(provider, processedCommand)
835
+ ?? getProviderCommandSessionId(provider, command)
836
+ : null;
828
837
  // Grok and Qoder accept caller-selected IDs for new conversations. Assigning
829
838
  // one up front avoids depending on provider-specific TUI rendering to learn
830
839
  // the durable ID later.
@@ -918,7 +927,7 @@ export class ProcessManager extends EventEmitter {
918
927
  this.providerHistory.invalidate(provider);
919
928
  }
920
929
  this.cleanupOldSessions();
921
- const shellArgs = this.buildShellArgs(processedCommand);
930
+ const shellArgs = this.buildShellArgs(processedCommand, opts?.interactiveShell === true);
922
931
  // Self-heal node-pty's spawn-helper +x bit before every spawn: a self-update
923
932
  // can re-drop it after this server already ran its startup chmod, which would
924
933
  // otherwise make every PTY launch throw "posix_spawnp failed." until restart.
@@ -2118,7 +2127,17 @@ export class ProcessManager extends EventEmitter {
2118
2127
  }
2119
2128
  return record;
2120
2129
  }
2121
- buildShellArgs(command) {
2130
+ /** Start a bare interactive login shell without a provider CLI. */
2131
+ startShell(cwd, mode, opts) {
2132
+ return this.start(this.config.shell, cwd, mode, undefined, {
2133
+ ...opts,
2134
+ interactiveShell: true,
2135
+ });
2136
+ }
2137
+ buildShellArgs(command, interactiveShell = false) {
2138
+ if (interactiveShell) {
2139
+ return os.platform() === "win32" ? [] : ["-l"];
2140
+ }
2122
2141
  if (os.platform() === "win32") {
2123
2142
  return ["/d", "/s", "/c", command];
2124
2143
  }
@@ -2149,6 +2168,8 @@ export class ProcessManager extends EventEmitter {
2149
2168
  return false;
2150
2169
  }
2151
2170
  processCommandForMode(command, mode, provider, model, thinkingEffort) {
2171
+ if (!provider)
2172
+ return command;
2152
2173
  if (provider === "codex") {
2153
2174
  let result = command;
2154
2175
  const trimmedModel = model?.trim();
package/dist/server.js CHANGED
@@ -978,7 +978,8 @@ export async function startServer(config, configPath, options = {}) {
978
978
  // ── Session control ──
979
979
  app.post("/api/commands", (req, res) => {
980
980
  const body = req.body;
981
- if (!body.command?.trim()) {
981
+ const interactiveShell = body.shell === true;
982
+ if (!interactiveShell && !body.command?.trim()) {
982
983
  res.status(400).json({ error: "请输入要执行的命令。" });
983
984
  return;
984
985
  }
@@ -990,34 +991,46 @@ export async function startServer(config, configPath, options = {}) {
990
991
  try {
991
992
  const origin = parseSessionCreationOrigin(body);
992
993
  const rawModel = typeof body.model === "string" ? body.model.trim() : "";
993
- const provider = body.provider === "codex" || /^codex\b/.test(body.command.trim())
994
- ? "codex"
995
- : body.provider === "opencode" || /^opencode\b/.test(body.command.trim())
996
- ? "opencode"
997
- : body.provider === "grok" || /^grok\b/.test(body.command.trim())
998
- ? "grok"
999
- : body.provider === "qoder" || /^qodercli\b/.test(body.command.trim())
1000
- ? "qoder"
1001
- : body.provider === "pi" || /^pi\b/.test(body.command.trim())
1002
- ? "pi"
1003
- : "claude";
994
+ const rawCommand = body.command?.trim() ?? "";
995
+ const provider = interactiveShell
996
+ ? undefined
997
+ : body.provider === "codex" || /^codex\b/.test(rawCommand)
998
+ ? "codex"
999
+ : body.provider === "opencode" || /^opencode\b/.test(rawCommand)
1000
+ ? "opencode"
1001
+ : body.provider === "grok" || /^grok\b/.test(rawCommand)
1002
+ ? "grok"
1003
+ : body.provider === "qoder" || /^qodercli\b/.test(rawCommand)
1004
+ ? "qoder"
1005
+ : body.provider === "pi" || /^pi\b/.test(rawCommand)
1006
+ ? "pi"
1007
+ : "claude";
1004
1008
  // Older clients used the provider id as the PTY command. Qoder's executable
1005
1009
  // is named qodercli, so keep those clients working while preserving custom commands.
1006
- const command = provider === "qoder" && body.command.trim() === "qoder"
1010
+ const command = provider === "qoder" && rawCommand === "qoder"
1007
1011
  ? "qodercli"
1008
- : body.command;
1009
- const effectiveModel = rawModel || getDefaultModelForProvider(config, provider) || undefined;
1012
+ : rawCommand;
1013
+ const effectiveModel = provider
1014
+ ? rawModel || getDefaultModelForProvider(config, provider) || undefined
1015
+ : undefined;
1010
1016
  const reqCols = typeof body.cols === "number" && Number.isFinite(body.cols) ? body.cols : undefined;
1011
1017
  const reqRows = typeof body.rows === "number" && Number.isFinite(body.rows) ? body.rows : undefined;
1012
- const snapshot = processes.start(command, body.cwd, body.mode ?? config.defaultMode, initialInput || undefined, {
1013
- worktreeEnabled: body.worktreeEnabled === true,
1014
- provider,
1015
- model: effectiveModel,
1016
- cols: reqCols,
1017
- rows: reqRows,
1018
- thinkingEffort: body.thinkingEffort ?? config.defaultThinkingEffort,
1019
- ...origin,
1020
- });
1018
+ const snapshot = interactiveShell
1019
+ ? processes.startShell(body.cwd, body.mode ?? "default", {
1020
+ worktreeEnabled: body.worktreeEnabled === true,
1021
+ cols: reqCols,
1022
+ rows: reqRows,
1023
+ ...origin,
1024
+ })
1025
+ : processes.start(command, body.cwd, body.mode ?? config.defaultMode, initialInput || undefined, {
1026
+ worktreeEnabled: body.worktreeEnabled === true,
1027
+ provider,
1028
+ model: effectiveModel,
1029
+ cols: reqCols,
1030
+ rows: reqRows,
1031
+ thinkingEffort: body.thinkingEffort ?? config.defaultThinkingEffort,
1032
+ ...origin,
1033
+ });
1021
1034
  recordRecentPath(storage, snapshot.cwd);
1022
1035
  res.status(201).json(snapshot);
1023
1036
  }
package/dist/types.d.ts CHANGED
@@ -308,7 +308,9 @@ export interface PushResult {
308
308
  error?: string;
309
309
  }
310
310
  export interface CommandRequest {
311
- command: string;
311
+ command?: string;
312
+ /** Start the configured login shell without launching a provider CLI. */
313
+ shell?: boolean;
312
314
  provider?: SessionProvider;
313
315
  cwd?: string;
314
316
  mode?: ExecutionMode;