@henryqw/pi-herdr-btw 1.1.5 → 1.1.7

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/extensions/btw.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
3
- import { createHerdrClient, hasHerdrErrorCode } from "@henryqw/pi-herdr";
3
+ import { createHerdrClient, hasHerdrErrorCode, startPiAgent } from "@henryqw/pi-herdr";
4
4
  import {
5
5
  resolveConfiguredTaskRoutes,
6
6
  type ResolvedTaskRoute,
@@ -53,8 +53,6 @@ const BTW_TASK = "pi-herdr-btw/btw";
53
53
  const CHILD_HEARTBEAT_INTERVAL_MS = 5 * 60 * 1_000;
54
54
  const LITERAL_DRAFT_PREFIX = "\u200b";
55
55
  const MERGE_POLL_INTERVAL_MS = 3_000;
56
- const AGENT_START_BUSY_RETRIES = 5;
57
- const AGENT_START_RETRY_DELAY_MS = 250;
58
56
 
59
57
  function childPayloadPathFromArgv(): string | undefined {
60
58
  const index = process.argv.indexOf(CHILD_PAYLOAD_ARG);
@@ -703,20 +701,16 @@ export async function registerBtwExtension(
703
701
  }
704
702
 
705
703
  // Step 2: adopt pi into the new pane; herdr waits for readiness.
706
- const agentStartArgs = buildAgentStartArgs(launchOptions, paneId);
704
+ const agentStartArgs = buildAgentStartArgs(launchOptions);
707
705
  let result;
708
706
  try {
709
- result = await herdr.exec(agentStartArgs, { timeout: 45_000 });
710
- for (let attempt = 1; attempt < AGENT_START_BUSY_RETRIES; attempt += 1) {
711
- if (
712
- launchGeneration !== sessionGeneration ||
713
- result.killed ||
714
- !hasHerdrErrorCode(result, "agent_pane_busy")
715
- ) break;
716
- await new Promise((resolve) => setTimeout(resolve, AGENT_START_RETRY_DELAY_MS));
717
- if (launchGeneration !== sessionGeneration) break;
718
- result = await herdr.exec(agentStartArgs, { timeout: 45_000 });
719
- }
707
+ result = await startPiAgent(herdr, {
708
+ name: launchOptions.paneName,
709
+ pane: paneId,
710
+ args: agentStartArgs,
711
+ options: { timeout: 45_000 },
712
+ shouldRetry: (candidate) => !candidate.killed && launchGeneration === sessionGeneration,
713
+ });
720
714
  } catch (error) {
721
715
  const message = error instanceof Error ? error.message : String(error);
722
716
  await reportKnownPaneFailure(`/btw failed: ${message.slice(0, 500)}`, paneId, payloadPath);
@@ -1,4 +1,4 @@
1
- import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
1
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
4
4
  import { lock } from "proper-lockfile";
@@ -138,8 +138,6 @@ export class ConfigStore {
138
138
  }
139
139
 
140
140
  private async saveUnlocked(validated: BtwConfig): Promise<void> {
141
- const directory = dirname(this.path);
142
- await mkdir(directory, { recursive: true, mode: 0o700 });
143
141
  const temporaryPath = `${this.path}.${process.pid}.${Date.now()}.tmp`;
144
142
  try {
145
143
  await writeFile(temporaryPath, `${JSON.stringify(validated, null, 2)}\n`, {
@@ -147,7 +145,6 @@ export class ConfigStore {
147
145
  flag: "wx",
148
146
  mode: 0o600,
149
147
  });
150
- await chmod(temporaryPath, 0o600);
151
148
  await rename(temporaryPath, this.path);
152
149
  } catch (error) {
153
150
  await rm(temporaryPath, { force: true }).catch(() => undefined);
@@ -78,7 +78,6 @@ export class ContextStore {
78
78
  flag: "wx",
79
79
  mode: 0o600,
80
80
  });
81
- await chmod(payloadPath, 0o600);
82
81
  return payloadPath;
83
82
  } catch (error) {
84
83
  await rm(launchDir, { recursive: true, force: true }).catch(() => undefined);
@@ -88,7 +87,6 @@ export class ContextStore {
88
87
 
89
88
  async read(payloadPath: string): Promise<BtwPayload> {
90
89
  const canonicalPath = await this.validateLaunchFile(payloadPath, PAYLOAD_FILE);
91
- if (!canonicalPath) throw new Error(`Missing /btw context payload: ${payloadPath}`);
92
90
  const parsed: unknown = JSON.parse(await readFile(canonicalPath, "utf8"));
93
91
  if (!isBtwPayload(parsed)) {
94
92
  throw new Error("Invalid or unsupported /btw context payload");
@@ -204,7 +202,6 @@ export class ContextStore {
204
202
  flag: "wx",
205
203
  mode: 0o600,
206
204
  });
207
- await chmod(temporaryPath, 0o600);
208
205
  await rename(temporaryPath, targetPath);
209
206
  } catch (error) {
210
207
  await rm(temporaryPath, { force: true }).catch(() => undefined);
@@ -218,6 +215,8 @@ export class ContextStore {
218
215
  return JSON.parse(await readFile(canonicalPath, "utf8")) as unknown;
219
216
  }
220
217
 
218
+ private validateLaunchFile(payloadPath: string, fileName: typeof PAYLOAD_FILE): Promise<string>;
219
+ private validateLaunchFile(payloadPath: string, fileName: string): Promise<string | undefined>;
221
220
  private async validateLaunchFile(
222
221
  payloadPath: string,
223
222
  fileName: string,
package/internal/core.ts CHANGED
@@ -249,13 +249,13 @@ export function buildParentContextMessage(contextDocument: string): AgentMessage
249
249
  * here, after the reusable parent prefix, so the system prompt and parent
250
250
  * messages stay byte-identical to the parent's own requests.
251
251
  */
252
- export function buildNativeBridgeMessage(instructions: string, draftHint?: string): AgentMessage {
252
+ export function buildNativeBridgeMessage(instructions: string): AgentMessage {
253
253
  return {
254
254
  role: "user",
255
255
  content: [
256
256
  {
257
257
  type: "text",
258
- text: `The conversation above is a read-only snapshot of the parent session, replayed as reference context for this side conversation. It is not new work to continue.\n\n${instructions}${draftHint ? `\n\n${draftHint}` : ""}`,
258
+ text: `The conversation above is a read-only snapshot of the parent session, replayed as reference context for this side conversation. It is not new work to continue.\n\n${instructions}`,
259
259
  },
260
260
  ],
261
261
  timestamp: 0,
@@ -335,20 +335,9 @@ export function isAgentStartReady(
335
335
  }
336
336
  }
337
337
 
338
- /**
339
- * Step 2 of the launch: start pi in the freshly split pane. Herdr prepends
340
- * the canonical executable for `--kind pi`, so only pi's own args follow `--`.
341
- */
342
- export function buildAgentStartArgs(options: HerdrLaunchOptions, paneId: string): string[] {
338
+ /** The child Pi arguments for the Herdr agent-start boundary. */
339
+ export function buildAgentStartArgs(options: HerdrLaunchOptions): string[] {
343
340
  return [
344
- "agent",
345
- "start",
346
- options.paneName,
347
- "--kind",
348
- "pi",
349
- "--pane",
350
- paneId,
351
- "--",
352
341
  "--no-session",
353
342
  "--model",
354
343
  options.model,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-herdr-btw",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "Open and merge tool-enabled Pi side threads in Herdr panes.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -46,7 +46,7 @@
46
46
  ]
47
47
  },
48
48
  "dependencies": {
49
- "@henryqw/pi-herdr": "^0.3.0",
49
+ "@henryqw/pi-herdr": "^0.4.0",
50
50
  "@henryqw/pi-task-models": "^1.0.0",
51
51
  "proper-lockfile": "^4.1.2"
52
52
  },