@groeponline/pi-wishcraft 1.4.9 → 1.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.4.10] - 2026-08-30
6
+
5
7
  ## [1.4.9] - 2026-08-29
6
8
 
7
9
  ## [1.4.8] - 2026-08-29
@@ -119,6 +119,8 @@ interface RunningCommand {
119
119
  buffer: string;
120
120
  /** Trailing partial escape sequence carried across chunks. */
121
121
  escapeTail: string;
122
+ /** Wrapper result observed on stdout; publication waits for child close. */
123
+ pendingResult: PtyRunResult | null;
122
124
  resolve: (result: PtyRunResult) => void;
123
125
  settled: boolean;
124
126
  }
@@ -179,6 +181,7 @@ export class PtyShellSession {
179
181
  done,
180
182
  buffer: "",
181
183
  escapeTail: "",
184
+ pendingResult: null,
182
185
  resolve: (result) => {
183
186
  if (running.settled) return;
184
187
  running.settled = true;
@@ -224,9 +227,8 @@ export class PtyShellSession {
224
227
  child.stderr.on("data", (chunk: string) => this.handleChunk(String(chunk)));
225
228
  child.on("error", (error) => {
226
229
  // spawn itself failed (script removed between probe and spawn, dead
227
- // SHELL path): surface the reason, then settle as a normal failure.
228
- // The first script-path failure also flips the probe cache so the
229
- // next command degrades to pipes instead of silently failing again.
230
+ // SHELL path): surface the reason, but do not publish completion until
231
+ // the matching child emits close. That preserves strict child ownership.
230
232
  console.warn(
231
233
  "[wishcraft] spawn failed:",
232
234
  error instanceof Error ? error.message : String(error),
@@ -234,20 +236,24 @@ export class PtyShellSession {
234
236
  if (usePty) {
235
237
  _resetScriptAvailableForTests();
236
238
  }
237
- running.resolve({ exitCode: 1, cwd: this.state.cwd });
239
+ running.pendingResult = { exitCode: 1, cwd: this.state.cwd };
238
240
  });
239
241
  child.on("close", (code, signal) => {
240
- this.child = null;
241
- // No sentinel observed. An operator interrupt never lets the command
242
- // complete, so it maps to 130 regardless of how `script` exits.
242
+ if (this.child === child) {
243
+ this.child = null;
244
+ }
245
+ // Completion is published only after this exact child closes, so a
246
+ // later run cannot have its child reference cleared by this callback.
243
247
  if (this.interrupted) {
244
248
  running.resolve({ exitCode: 130, cwd: this.state.cwd });
245
249
  return;
246
250
  }
247
- running.resolve({
248
- exitCode: getCloseExitCode(code, signal),
249
- cwd: this.state.cwd,
250
- });
251
+ running.resolve(
252
+ running.pendingResult ?? {
253
+ exitCode: getCloseExitCode(code, signal),
254
+ cwd: this.state.cwd,
255
+ },
256
+ );
251
257
  });
252
258
  });
253
259
  }
@@ -322,10 +328,10 @@ export class PtyShellSession {
322
328
  if (firstColon !== -1) {
323
329
  const exitCode = Number.parseInt(rest.slice(0, firstColon), 10);
324
330
  const cwd = rest.slice(firstColon + 1);
325
- running.resolve({
331
+ running.pendingResult = {
326
332
  exitCode: Number.isFinite(exitCode) ? exitCode : 1,
327
333
  cwd: cwd || this.state.cwd,
328
- });
334
+ };
329
335
  return;
330
336
  }
331
337
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groeponline/pi-wishcraft",
3
- "version": "1.4.9",
3
+ "version": "1.4.10",
4
4
  "description": "Operator cockpit for Pi: live powerline status, searchable skills, idea queue, sticky Bash, hooks, policy controls, and session UX.",
5
5
  "type": "module",
6
6
  "files": [