@co0ontty/wand 4.46.0 → 4.47.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 (34) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/distribution-manager.d.ts +17 -0
  3. package/dist/distribution-manager.js +80 -17
  4. package/dist/server-update-routes.d.ts +5 -0
  5. package/dist/server-update-routes.js +15 -0
  6. package/dist/server.js +8 -2
  7. package/dist/structured-claude-adapter.d.ts +3 -1
  8. package/dist/structured-claude-adapter.js +48 -93
  9. package/dist/structured-codex-adapter.d.ts +5 -0
  10. package/dist/structured-codex-adapter.js +40 -78
  11. package/dist/structured-exec-host.d.ts +76 -0
  12. package/dist/structured-exec-host.js +117 -0
  13. package/dist/structured-exec-pump.d.ts +37 -0
  14. package/dist/structured-exec-pump.js +127 -0
  15. package/dist/structured-grok-adapter.d.ts +3 -1
  16. package/dist/structured-grok-adapter.js +28 -67
  17. package/dist/structured-opencode-adapter.d.ts +3 -1
  18. package/dist/structured-opencode-adapter.js +37 -79
  19. package/dist/structured-pi-adapter.d.ts +3 -1
  20. package/dist/structured-pi-adapter.js +27 -39
  21. package/dist/structured-qoder-adapter.d.ts +3 -1
  22. package/dist/structured-qoder-adapter.js +49 -87
  23. package/dist/structured-session-manager.d.ts +9 -1
  24. package/dist/structured-session-manager.js +399 -12
  25. package/dist/terminal-daemon-client.d.ts +15 -1
  26. package/dist/terminal-daemon-client.js +261 -0
  27. package/dist/terminal-daemon-protocol.d.ts +10 -3
  28. package/dist/terminal-daemon-protocol.js +1 -1
  29. package/dist/terminal-daemon-server.js +178 -0
  30. package/dist/web-ui/content/scripts.js +57 -57
  31. package/dist/web-ui/content/styles.css +1 -1
  32. package/dist/web-ui/embedded-assets.d.ts +1 -1
  33. package/dist/web-ui/embedded-assets.js +3 -3
  34. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { startStructuredCli } from "./structured-exec-pump.js";
2
3
  import { thinkingEffortToPiLevel } from "./structured-provider-common.js";
3
4
  function record(value) {
4
5
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -86,50 +87,37 @@ export function applyPiEvent(state, event) {
86
87
  }
87
88
  export class PiRunner {
88
89
  spawnProcess;
89
- constructor(spawnProcess = spawn) {
90
+ execHost;
91
+ constructor(spawnProcess = spawn, execHost) {
90
92
  this.spawnProcess = spawnProcess;
93
+ this.execHost = execHost;
91
94
  }
92
95
  start(context, observer) {
93
96
  const args = buildPiArgs(context.session, context.prompt);
94
- const spawnedAt = new Date().toISOString();
95
- const child = this.spawnProcess("pi", args, { cwd: context.session.cwd, env: context.env, stdio: ["ignore", "pipe", "pipe"] });
96
97
  const state = { blocks: [], result: "", sessionId: context.session.claudeSessionId, model: context.session.selectedModel ?? undefined };
97
- let lineBuffer = "", stderr = "", primaryError = null, settled = false;
98
- const result = (exitCode, signal, spawnError) => ({ state, exitCode, signal, stderr, primaryError, ...(spawnError ? { spawnError } : {}) });
99
- const processLine = (line) => {
100
- if (!observer.isActive() || !line.trim())
101
- return;
102
- try {
103
- const event = JSON.parse(line);
104
- observer.onEvent?.(event);
105
- primaryError = applyPiEvent(state, event) ?? primaryError;
106
- observer.onUpdate(state);
107
- }
108
- catch { /* Pi stdout is NDJSON; ignore non-protocol noise. */ }
109
- };
110
- const completion = new Promise((resolve) => {
111
- child.stdout?.on("data", (chunk) => {
112
- const text = chunk.toString();
113
- observer.onStdout?.(text);
114
- lineBuffer += text;
115
- const lines = lineBuffer.split("\n");
116
- lineBuffer = lines.pop() ?? "";
117
- lines.forEach(processLine);
118
- });
119
- child.stderr?.on("data", (chunk) => { const text = chunk.toString(); observer.onStderr?.(text); stderr += text; });
120
- child.on("error", (error) => { if (!settled) {
121
- settled = true;
122
- resolve(result(null, null, error));
123
- } });
124
- child.on("close", (code, signal) => { if (!settled) {
125
- settled = true;
126
- processLine(lineBuffer);
127
- resolve(result(code, signal));
128
- } });
98
+ let primaryError = null;
99
+ return startStructuredCli({
100
+ sessionId: context.session.id,
101
+ file: "pi",
102
+ args,
103
+ cwd: context.session.cwd,
104
+ env: context.env,
105
+ observer,
106
+ execHost: this.execHost,
107
+ spawnProcess: this.spawnProcess,
108
+ createState: () => state,
109
+ processLine: (line) => {
110
+ if (!observer.isActive() || !line.trim())
111
+ return;
112
+ try {
113
+ const event = JSON.parse(line);
114
+ observer.onEvent?.(event);
115
+ primaryError = applyPiEvent(state, event) ?? primaryError;
116
+ observer.onUpdate(state);
117
+ }
118
+ catch { /* Pi stdout is NDJSON; ignore non-protocol noise. */ }
119
+ },
120
+ finalize: (ctx, exitCode, signal, spawnError) => ({ state, exitCode, signal, stderr: ctx.stderr, primaryError, ...(spawnError ? { spawnError } : {}) }),
129
121
  });
130
- return { args, spawnedAt, pid: child.pid ?? null, completion, interrupt: () => { try {
131
- child.kill("SIGTERM");
132
- }
133
- catch { /* best effort */ } } };
134
122
  }
135
123
  }
@@ -1,10 +1,12 @@
1
1
  import { spawn } from "node:child_process";
2
+ import type { StructuredExecHost } from "./structured-exec-host.js";
2
3
  import type { StructuredRunnerAdapter, StructuredRunnerContext, StructuredRunnerExecution, StructuredRunnerObserver } from "./structured-runner.js";
3
4
  import type { SessionSnapshot } from "./types.js";
4
5
  export declare function buildQoderArgs(session: SessionSnapshot, prompt: string): string[];
5
6
  /** Owns the official Qoder CLI print process and its stream-json protocol. */
6
7
  export declare class QoderRunner implements StructuredRunnerAdapter {
7
8
  private readonly spawnProcess;
8
- constructor(spawnProcess?: typeof spawn);
9
+ private readonly execHost?;
10
+ constructor(spawnProcess?: typeof spawn, execHost?: StructuredExecHost | undefined);
9
11
  start(context: StructuredRunnerContext, observer: StructuredRunnerObserver): StructuredRunnerExecution;
10
12
  }
@@ -1,5 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { ClaudeCliProtocolReducer } from "./structured-claude-protocol.js";
3
+ import { startStructuredCli } from "./structured-exec-pump.js";
3
4
  export function buildQoderArgs(session, prompt) {
4
5
  const args = ["-p", prompt, "--output-format", "stream-json"];
5
6
  const model = session.selectedModel?.trim();
@@ -20,105 +21,66 @@ export function buildQoderArgs(session, prompt) {
20
21
  /** Owns the official Qoder CLI print process and its stream-json protocol. */
21
22
  export class QoderRunner {
22
23
  spawnProcess;
23
- constructor(spawnProcess = spawn) {
24
+ execHost;
25
+ constructor(spawnProcess = spawn, execHost) {
24
26
  this.spawnProcess = spawnProcess;
27
+ this.execHost = execHost;
25
28
  }
26
29
  start(context, observer) {
27
30
  const args = buildQoderArgs(context.session, context.prompt);
28
- const spawnedAt = new Date().toISOString();
29
- const child = this.spawnProcess("qodercli", args, {
30
- cwd: context.session.cwd,
31
- env: context.env,
32
- stdio: ["ignore", "pipe", "pipe"],
33
- });
34
31
  const reducer = new ClaudeCliProtocolReducer(context.session);
35
- let lineBuffer = "";
36
- let stderr = "";
37
32
  let stdoutTail = "";
38
33
  let primaryError = null;
39
- let settled = false;
40
- const result = (exitCode, signal, spawnError) => ({
41
- state: reducer.state,
42
- exitCode,
43
- signal,
44
- stderr,
45
- stdoutTail,
46
- primaryError,
47
- ...(spawnError ? { spawnError } : {}),
48
- });
49
- const processLine = (line) => {
50
- if (!observer.isActive())
51
- return;
52
- const trimmed = line.trim();
53
- if (!trimmed)
54
- return;
55
- let event;
56
- try {
57
- event = JSON.parse(trimmed);
58
- }
59
- catch {
60
- return;
61
- }
62
- if (event && typeof event === "object" && !Array.isArray(event)) {
63
- const record = event;
64
- observer.onEvent?.(record);
65
- if (record.type === "result" && record.subtype !== "success") {
66
- const errors = Array.isArray(record.errors)
67
- ? record.errors.filter((item) => typeof item === "string")
68
- : [];
69
- primaryError = errors.join("\n") || "Qoder CLI execution failed";
70
- }
71
- }
72
- if (reducer.apply(event, context.session.mode === "managed"))
73
- observer.onUpdate(reducer.state);
74
- };
75
- const completion = new Promise((resolve) => {
76
- child.stdout?.on("data", (chunk) => {
34
+ return startStructuredCli({
35
+ sessionId: context.session.id,
36
+ file: "qodercli",
37
+ args,
38
+ cwd: context.session.cwd,
39
+ env: context.env,
40
+ observer,
41
+ execHost: this.execHost,
42
+ spawnProcess: this.spawnProcess,
43
+ createState: () => reducer.state,
44
+ processLine: (line) => {
77
45
  if (!observer.isActive())
78
46
  return;
79
- const text = chunk.toString();
80
- observer.onStdout?.(text);
47
+ const trimmed = line.trim();
48
+ if (!trimmed)
49
+ return;
50
+ let event;
51
+ try {
52
+ event = JSON.parse(trimmed);
53
+ }
54
+ catch {
55
+ return;
56
+ }
57
+ if (event && typeof event === "object" && !Array.isArray(event)) {
58
+ const record = event;
59
+ observer.onEvent?.(record);
60
+ if (record.type === "result" && record.subtype !== "success") {
61
+ const errors = Array.isArray(record.errors)
62
+ ? record.errors.filter((item) => typeof item === "string")
63
+ : [];
64
+ primaryError = errors.join("\n") || "Qoder CLI execution failed";
65
+ }
66
+ }
67
+ if (reducer.apply(event, context.session.mode === "managed"))
68
+ observer.onUpdate(reducer.state);
69
+ },
70
+ onStdoutText: (text) => {
81
71
  const trimmed = text.trim();
82
72
  if (trimmed)
83
73
  stdoutTail = trimmed.slice(-1024);
84
- lineBuffer += text;
85
- const lines = lineBuffer.split("\n");
86
- lineBuffer = lines.pop() ?? "";
87
- for (const line of lines)
88
- processLine(line);
89
- });
90
- child.stderr?.on("data", (chunk) => {
91
- if (!observer.isActive())
92
- return;
93
- const text = chunk.toString();
94
- observer.onStderr?.(text);
95
- stderr += text;
96
- });
97
- child.on("error", (error) => {
98
- if (settled)
99
- return;
100
- settled = true;
101
- resolve(result(null, null, error));
102
- });
103
- child.on("close", (exitCode, signal) => {
104
- if (settled)
105
- return;
106
- settled = true;
107
- if (lineBuffer.trim())
108
- processLine(lineBuffer);
109
- lineBuffer = "";
110
- resolve(result(exitCode, signal));
111
- });
74
+ },
75
+ finalize: (ctx, exitCode, signal, spawnError) => ({
76
+ state: reducer.state,
77
+ exitCode,
78
+ signal,
79
+ stderr: ctx.stderr,
80
+ stdoutTail,
81
+ primaryError,
82
+ ...(spawnError ? { spawnError } : {}),
83
+ }),
112
84
  });
113
- return {
114
- args,
115
- spawnedAt,
116
- pid: child.pid ?? null,
117
- completion,
118
- interrupt: () => { try {
119
- child.kill("SIGTERM");
120
- }
121
- catch { /* best effort */ } },
122
- };
123
85
  }
124
86
  }
@@ -3,6 +3,7 @@ import { type WorktreeSetupSpec } from "./git-worktree.js";
3
3
  import { SessionLogger } from "./session-logger.js";
4
4
  import { WandStorage } from "./storage.js";
5
5
  import { ExecutionMode, ProcessEvent, SessionProvider, SessionRunner, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
6
+ import { type StructuredExecHost } from "./structured-exec-host.js";
6
7
  import type { StructuredRunnerAdapter } from "./structured-runner.js";
7
8
  export interface StructuredSessionManagerRunners {
8
9
  claudeCli?: StructuredRunnerAdapter;
@@ -52,6 +53,7 @@ export declare class StructuredSessionManager {
52
53
  private readonly config;
53
54
  private readonly logger;
54
55
  private readonly sdkQueryFactory;
56
+ private readonly execHost?;
55
57
  private readonly sessions;
56
58
  private readonly pendingRunnerExecutions;
57
59
  private readonly pendingSdkAbort;
@@ -93,12 +95,18 @@ export declare class StructuredSessionManager {
93
95
  private readonly grokRunner;
94
96
  private readonly qoderRunner;
95
97
  private readonly piRunner;
98
+ /** Structured CLI runs that were mid-flight when the previous web process died. */
99
+ private pendingRecoveryIds;
96
100
  private disposed;
97
- constructor(storage: WandStorage, config: WandConfig, logger?: SessionLogger | null, sdkQueryFactory?: typeof sdkQuery, runners?: StructuredSessionManagerRunners);
101
+ constructor(storage: WandStorage, config: WandConfig, logger?: SessionLogger | null, sdkQueryFactory?: typeof sdkQuery, runners?: StructuredSessionManagerRunners, execHost?: StructuredExecHost | undefined);
98
102
  private archiveExpiredSessions;
99
103
  setEventEmitter(emitEvent: (event: ProcessEvent) => void): void;
100
104
  /** Stop every runner and flush terminal state before storage is closed. */
101
105
  dispose(): void;
106
+ /** Called once after startup wiring; safe to skip when no host or candidates. */
107
+ recoverDetachedRuns(): Promise<void>;
108
+ private resumeDetachedRun;
109
+ private finalizeRecoveredRun;
102
110
  private trackStreamEmitTimer;
103
111
  private clearStreamEmitTimer;
104
112
  /** Mark streaming payload dirty and enforce both leading and trailing checkpoints. */