pi-subagents 0.80.0 → 0.82.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 (184) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/bundle/agents.js +18 -0
  3. package/bundle/background-work.js +16 -0
  4. package/bundle/capability-ceiling.js +22 -0
  5. package/bundle/child-tool-plan.js +11 -0
  6. package/bundle/chunk-43SCHIMD.js +57 -0
  7. package/bundle/chunk-4QFZZV3O.js +57 -0
  8. package/bundle/chunk-4SDU2BZX.js +1190 -0
  9. package/bundle/chunk-52UBALJL.js +753 -0
  10. package/bundle/chunk-5LFLVSTM.js +260 -0
  11. package/bundle/chunk-5TYMSZX3.js +235 -0
  12. package/bundle/chunk-62OCY2S4.js +14 -0
  13. package/bundle/chunk-7F6RKT6W.js +132 -0
  14. package/bundle/chunk-7IVSUMQQ.js +1337 -0
  15. package/bundle/chunk-7J4ZXMY7.js +18 -0
  16. package/bundle/chunk-7PCKZHR5.js +1234 -0
  17. package/bundle/chunk-7YMXOYEU.js +1373 -0
  18. package/bundle/chunk-B5EKXDHE.js +16870 -0
  19. package/bundle/chunk-CIZQXJ5Q.js +8619 -0
  20. package/bundle/chunk-CTPAE36Y.js +94 -0
  21. package/bundle/chunk-D4EHT5SJ.js +14 -0
  22. package/bundle/chunk-GC4DSPMH.js +946 -0
  23. package/bundle/chunk-GJECHSAR.js +179 -0
  24. package/bundle/chunk-M2CTFYT3.js +4164 -0
  25. package/bundle/chunk-MHZ4G23A.js +1296 -0
  26. package/bundle/chunk-N2BN7BF5.js +159 -0
  27. package/bundle/chunk-NF7IEKBH.js +490 -0
  28. package/bundle/chunk-NGGAFDL6.js +742 -0
  29. package/bundle/chunk-NIXWDZZV.js +9 -0
  30. package/bundle/chunk-NRQ75MGH.js +8327 -0
  31. package/bundle/chunk-OYF6AFDY.js +458 -0
  32. package/bundle/chunk-PLCCBLPY.js +526 -0
  33. package/bundle/chunk-RMQFMMAT.js +416 -0
  34. package/bundle/chunk-SIJ4S3EN.js +276 -0
  35. package/bundle/chunk-UKG4V45O.js +228 -0
  36. package/bundle/chunk-VH5OAWVG.js +4096 -0
  37. package/bundle/chunk-W2SZ22EM.js +5462 -0
  38. package/bundle/chunk-WVD4UEL4.js +9367 -0
  39. package/bundle/chunk-XSWOTIA3.js +175 -0
  40. package/bundle/chunk-YFCK7H2S.js +129 -0
  41. package/bundle/chunk-YKEWIB4M.js +380 -0
  42. package/bundle/chunk-YYREO77V.js +137 -0
  43. package/bundle/chunk-Z7KVHQHL.js +173 -0
  44. package/bundle/chunk-ZLINIYTP.js +8613 -0
  45. package/bundle/chunk-ZQGSDHL6.js +128 -0
  46. package/bundle/control-channel.js +11 -0
  47. package/bundle/delegation.js +14 -0
  48. package/bundle/external-job-provider.js +20 -0
  49. package/bundle/external-runs.js +21 -0
  50. package/bundle/index.js +30 -0
  51. package/bundle/inspectors.js +8 -0
  52. package/bundle/intercom-bridge.js +11 -0
  53. package/bundle/preflight.js +425 -0
  54. package/bundle/project-panes.js +28 -0
  55. package/bundle/required-child-extensions.js +6 -0
  56. package/bundle/shared-types.js +8 -0
  57. package/bundle/workflow-resources.js +7 -0
  58. package/docs/agents.md +31 -0
  59. package/docs/configuration.md +9 -1
  60. package/docs/extension-api.md +27 -0
  61. package/docs/observability.md +6 -2
  62. package/docs/tool-reference.md +3 -1
  63. package/docs/workflows.md +16 -0
  64. package/package.json +19 -19
  65. package/skills/pi-subagents/references/execution-controls.md +5 -4
  66. package/src/agents/agent-management.js +3 -3
  67. package/src/agents/agent-memory.js +3 -1
  68. package/src/agents/agents.js +6 -6
  69. package/src/agents/frontmatter.d.ts +4 -0
  70. package/src/agents/frontmatter.js +24 -3
  71. package/src/agents/skills.js +2 -1
  72. package/src/api/delegation.d.ts +20 -0
  73. package/src/extension/config.js +3 -0
  74. package/src/extension/herdr-pi-bridge.js +17 -10
  75. package/src/extension/index.js +109 -9
  76. package/src/extension/schemas.js +3 -3
  77. package/src/intercom/native-supervisor-channel.js +20 -0
  78. package/src/missions/lifecycle.js +7 -1
  79. package/src/runs/background/active-async-capacity.d.ts +1 -0
  80. package/src/runs/background/active-async-capacity.js +9 -3
  81. package/src/runs/background/async-execution.js +20 -10
  82. package/src/runs/background/async-job-tracker.d.ts +6 -1
  83. package/src/runs/background/async-job-tracker.js +9 -4
  84. package/src/runs/background/async-status.d.ts +2 -0
  85. package/src/runs/background/async-status.js +21 -2
  86. package/src/runs/background/await-async-run.js +3 -7
  87. package/src/runs/background/boot-id.d.ts +12 -0
  88. package/src/runs/background/boot-id.js +47 -0
  89. package/src/runs/background/interrupted-elsewhere.d.ts +22 -0
  90. package/src/runs/background/interrupted-elsewhere.js +68 -0
  91. package/src/runs/background/notify.d.ts +2 -0
  92. package/src/runs/background/notify.js +13 -0
  93. package/src/runs/background/result-delivery-ownership.d.ts +6 -0
  94. package/src/runs/background/result-delivery-ownership.js +19 -1
  95. package/src/runs/background/result-watcher.d.ts +3 -1
  96. package/src/runs/background/result-watcher.js +24 -8
  97. package/src/runs/background/run-child-session.d.ts +1 -6
  98. package/src/runs/background/run-child-session.js +19 -1
  99. package/src/runs/background/run-status.js +2 -1
  100. package/src/runs/background/stale-run-reconciler.d.ts +25 -0
  101. package/src/runs/background/stale-run-reconciler.js +54 -13
  102. package/src/runs/background/subagent-runner-bootstrap.js +3 -0
  103. package/src/runs/background/subagent-runner.d.ts +4 -0
  104. package/src/runs/background/subagent-runner.js +13 -5
  105. package/src/runs/background/subagent-wait.d.ts +1 -1
  106. package/src/runs/background/subagent-wait.js +6 -1
  107. package/src/runs/background/terminal-run-index.d.ts +1 -0
  108. package/src/runs/background/terminal-run-index.js +4 -2
  109. package/src/runs/foreground/async-stop-action.js +3 -1
  110. package/src/runs/foreground/execution.js +45 -5
  111. package/src/runs/foreground/foreground-history.d.ts +16 -2
  112. package/src/runs/foreground/foreground-history.js +133 -31
  113. package/src/runs/foreground/subagent-executor.d.ts +11 -0
  114. package/src/runs/foreground/subagent-executor.js +277 -74
  115. package/src/runs/shared/child-launch.d.ts +2 -2
  116. package/src/runs/shared/child-session.d.ts +3 -2
  117. package/src/runs/shared/child-session.js +51 -8
  118. package/src/runs/shared/child-tool-plan.d.ts +7 -0
  119. package/src/runs/shared/child-tool-plan.js +21 -6
  120. package/src/runs/shared/herdr-connection.d.ts +17 -8
  121. package/src/runs/shared/herdr-connection.js +19 -7
  122. package/src/runs/shared/herdr-external-adapters.d.ts +3 -2
  123. package/src/runs/shared/herdr-external-adapters.js +1 -1
  124. package/src/runs/shared/herdr-machine.d.ts +12 -2
  125. package/src/runs/shared/herdr-machine.js +55 -9
  126. package/src/runs/shared/herdr-pane-host.d.ts +88 -0
  127. package/src/runs/shared/herdr-pane-host.js +155 -0
  128. package/src/runs/shared/herdr-placed-run.d.ts +27 -150
  129. package/src/runs/shared/herdr-placed-run.js +48 -395
  130. package/src/runs/shared/local-machine-access.d.ts +5 -0
  131. package/src/runs/shared/local-machine-access.js +28 -0
  132. package/src/runs/shared/orca-progress-tabs.d.ts +0 -1
  133. package/src/runs/shared/orca-progress-tabs.js +1 -24
  134. package/src/runs/shared/parallel-utils.d.ts +1 -1
  135. package/src/runs/shared/placed-run-owner.d.ts +49 -0
  136. package/src/runs/shared/placed-run-owner.js +93 -0
  137. package/src/runs/shared/placement-host.d.ts +99 -0
  138. package/src/runs/shared/placement-host.js +157 -0
  139. package/src/runs/shared/rex-machine.d.ts +25 -0
  140. package/src/runs/shared/rex-machine.js +83 -0
  141. package/src/runs/shared/rex-pane-host.d.ts +62 -0
  142. package/src/runs/shared/rex-pane-host.js +327 -0
  143. package/src/runs/shared/structured-output.d.ts +1 -0
  144. package/src/runs/shared/structured-output.js +1 -0
  145. package/src/shared/accessible-dir.js +4 -1
  146. package/src/shared/completion-owner.d.ts +5 -0
  147. package/src/shared/completion-owner.js +5 -0
  148. package/src/shared/provider-diagnostics.d.ts +9 -0
  149. package/src/shared/provider-diagnostics.js +75 -0
  150. package/src/shared/session-identity.d.ts +2 -0
  151. package/src/shared/session-identity.js +26 -0
  152. package/src/shared/types.d.ts +48 -6
  153. package/src/slash/delegation-adapters.d.ts +3 -1
  154. package/src/slash/delegation-adapters.js +7 -0
  155. package/src/slash/prompt-template-bridge.js +3 -1
  156. package/src/slash/subagents-admin.d.ts +1 -0
  157. package/src/slash/subagents-admin.js +1 -1
  158. package/src/tui/fleet-layout.d.ts +20 -0
  159. package/src/tui/fleet-layout.js +17 -0
  160. package/src/tui/fleet-viewport.d.ts +12 -0
  161. package/src/tui/fleet-viewport.js +35 -0
  162. package/src/tui/fleet.d.ts +6 -6
  163. package/src/tui/fleet.js +93 -66
  164. package/src/tui/render.d.ts +6 -0
  165. package/src/tui/render.js +3 -3
  166. package/src/tui/subagent-messages.js +1 -0
  167. package/src/workflows/scripted-workflow.d.ts +1 -1
  168. package/src/workflows/structured-workflow-scripts.d.ts +1 -0
  169. package/src/workflows/structured-workflow-scripts.js +5 -4
  170. package/src/workflows/workflow-auto-resume.d.ts +32 -0
  171. package/src/workflows/workflow-auto-resume.js +102 -0
  172. package/src/workflows/workflow-launch-record.d.ts +27 -0
  173. package/src/workflows/workflow-launch-record.js +48 -0
  174. package/src/workflows/workflow-resources.js +2 -2
  175. package/src/workflows/workflow-resume-claim.d.ts +82 -0
  176. package/src/workflows/workflow-resume-claim.js +210 -0
  177. package/src/workflows/workflow-resume.d.ts +56 -0
  178. package/src/workflows/workflow-resume.js +106 -0
  179. package/src/workflows/workflow-reuse.d.ts +15 -9
  180. package/src/workflows/workflow-reuse.js +77 -14
  181. package/src/workflows/workflow-revival.d.ts +2 -0
  182. package/src/workflows/workflow-revival.js +2 -1
  183. package/src/workflows/workflow-stop-cause.d.ts +6 -0
  184. package/src/workflows/workflow-stop-cause.js +8 -0
@@ -2,7 +2,7 @@ import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
2
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import type { ChildRuntimeConfig } from "./child-runtime-config.ts";
4
4
  import type { RequiredChildExtensionSnapshot } from "../../shared/required-child-extensions.ts";
5
- import type { HerdrMachineReference, HerdrRemoteGitStatus } from "../../shared/types.ts";
5
+ import type { PlacementMachineReference, HerdrRemoteGitStatus } from "../../shared/types.ts";
6
6
  export interface ChildSessionEvent {
7
7
  type: string;
8
8
  [key: string]: unknown;
@@ -32,7 +32,7 @@ export type ChildSessionStorage = {
32
32
  export interface ChildSessionLaunch {
33
33
  cwd: string;
34
34
  /** Resolved pane-native placement. Local launches omit this field. */
35
- machine?: HerdrMachineReference;
35
+ machine?: PlacementMachineReference;
36
36
  /** The launching session's project trust; undefined keeps Pi's default for hosts without trust. */
37
37
  projectTrusted?: boolean;
38
38
  /** Process-local provider source owned by the invoking foreground parent. */
@@ -107,6 +107,7 @@ export interface ChildSession {
107
107
  readonly virtualModelId?: string;
108
108
  readonly contextWindow?: number;
109
109
  readonly machineEvidence?: {
110
+ provider: PlacementMachineReference["provider"];
110
111
  machineId: string;
111
112
  initial?: HerdrRemoteGitStatus;
112
113
  final?: HerdrRemoteGitStatus;
@@ -76,6 +76,34 @@ function inheritParentProviders(modelRuntime, parentProviders, claimedProviderId
76
76
  }
77
77
  const CHILD_PROMPT_RUNTIME_EXTENSION_PATH = "<inline:pi-subagents:prompt-runtime>";
78
78
  const CHILD_PROMPT_BOUNDARY_EXTENSION_PATH = "<inline:pi-subagents:prompt-boundary>";
79
+ /** Same class as UNICODE_SPACES in pi-coding-agent dist/utils/paths.js, which resolvePath(..., { normalizeUnicodeSpaces: true }) replaces with a space. */
80
+ const PI_UNICODE_SPACES = new RegExp("[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]", "g");
81
+ function childExtensionFileIdentity(extensionPath) {
82
+ if (!path.isAbsolute(extensionPath))
83
+ return undefined;
84
+ try {
85
+ const stat = fs.statSync(extensionPath, { bigint: true });
86
+ return stat.isFile() ? `${stat.dev}:${stat.ino}` : undefined;
87
+ }
88
+ catch {
89
+ return undefined;
90
+ }
91
+ }
92
+ function createRequiredChildExtensionIdentity(requiredPaths) {
93
+ const aliases = new Map([...requiredPaths].map((extensionPath) => [extensionPath, childExtensionFileIdentity(extensionPath) ?? extensionPath]));
94
+ const identities = new Set(aliases.values());
95
+ return (extensionPath) => {
96
+ const known = aliases.get(extensionPath);
97
+ if (known !== undefined)
98
+ return known;
99
+ const identity = childExtensionFileIdentity(extensionPath);
100
+ if (identity === undefined || !identities.has(identity))
101
+ return undefined;
102
+ // Retain the alias even if the extension removes its file before reporting a failure.
103
+ aliases.set(extensionPath, identity);
104
+ return identity;
105
+ };
106
+ }
79
107
  /** The prompt runtime filters parent-only context before ambient extensions inspect
80
108
  * the child prompt, and the boundary hook appends the child boundary after every
81
109
  * extension has added its prompt sections. Every other extension keeps its order. */
@@ -229,7 +257,7 @@ function missingBuiltinMcpTools(session, names) {
229
257
  const direct = new Set(session.getAllTools().filter((tool) => tool.exposure === "direct").map(({ name }) => name));
230
258
  return names.filter((name) => !active.has(name) || !direct.has(name));
231
259
  }
232
- function flushQueuedProviderRegistrations(loader, modelRuntime, onError, requiredPaths) {
260
+ function flushQueuedProviderRegistrations(loader, modelRuntime, onError, isRequiredSource) {
233
261
  const claimedProviderIds = new Set();
234
262
  if (!("getExtensions" in loader) || typeof loader.getExtensions !== "function")
235
263
  return { claimedProviderIds, registered: false };
@@ -243,7 +271,7 @@ function flushQueuedProviderRegistrations(loader, modelRuntime, onError, require
243
271
  }
244
272
  catch (error) {
245
273
  onError?.({ extensionPath, event: "register_provider", error });
246
- if (requiredPaths.has(extensionPath))
274
+ if (isRequiredSource(extensionPath))
247
275
  throw new Error(`Required child extension provider registration failed for '${extensionPath}': ${error instanceof Error ? error.message : String(error)}`);
248
276
  }
249
277
  }
@@ -257,7 +285,7 @@ function flushQueuedProviderRegistrations(loader, modelRuntime, onError, require
257
285
  }
258
286
  catch (error) {
259
287
  onError?.({ extensionPath, event: "register_provider", error });
260
- if (requiredPaths.has(extensionPath))
288
+ if (isRequiredSource(extensionPath))
261
289
  throw new Error(`Required child extension provider registration failed for '${extensionPath}': ${error instanceof Error ? error.message : String(error)}`);
262
290
  }
263
291
  }
@@ -280,7 +308,7 @@ function flushQueuedProviderRegistrations(loader, modelRuntime, onError, require
280
308
  }
281
309
  catch (error) {
282
310
  onError?.({ extensionPath, event: "register_virtual_model", error });
283
- if (requiredPaths.has(extensionPath))
311
+ if (isRequiredSource(extensionPath))
284
312
  throw new Error(`Required child extension virtual model registration failed for '${extensionPath}': ${error instanceof Error ? error.message : String(error)}`);
285
313
  }
286
314
  }
@@ -394,6 +422,13 @@ export function createDefaultChildSessionFactory(options = {}) {
394
422
  },
395
423
  })) }]
396
424
  : [];
425
+ const requiredPaths = new Set((launch.requiredExtensions ?? []).map(({ path }) => path));
426
+ const requiredExtensionIdentity = createRequiredChildExtensionIdentity(requiredPaths);
427
+ const resolvedExtensionPaths = new Map();
428
+ // Pi reports failures by source path, without resolvedPath. A source Pi never loaded is classified by the
429
+ // file it names, after Pi's Unicode-space normalization. A required extension that failed under an
430
+ // unmatched spelling is still rejected by the missing-identity check after loading.
431
+ const isRequiredSource = (extensionPath) => requiredPaths.size > 0 && requiredExtensionIdentity(resolvedExtensionPaths.get(extensionPath) ?? extensionPath.replace(PI_UNICODE_SPACES, " ")) !== undefined;
397
432
  const loader = new pi.DefaultResourceLoader({
398
433
  cwd: launch.cwd,
399
434
  agentDir,
@@ -435,16 +470,24 @@ export function createDefaultChildSessionFactory(options = {}) {
435
470
  };
436
471
  let inheritedProviders = new Set();
437
472
  const open = async () => {
438
- const requiredPaths = new Set((launch.requiredExtensions ?? []).map(({ path }) => path));
439
473
  applyProcessEnv(launch.processEnv);
440
474
  if (!resetExtensionCacheOnReload(loader) && (launch.ambientExtensions || launch.extensionPaths.length))
441
475
  launch.onExtensionError?.({ extensionPath: "<loader>", event: "load", error: new Error("pi's extension cache reset is unavailable; extensions loaded into this child share module state with other sessions in this process.") });
442
476
  await loader.reload();
477
+ if (requiredPaths.size > 0)
478
+ for (const extension of loader.getExtensions().extensions)
479
+ resolvedExtensionPaths.set(extension.path, extension.resolvedPath);
443
480
  const loadErrors = requiredPaths.size > 0
444
- ? loader.getExtensions().errors.filter(({ path }) => requiredPaths.has(path)) : [];
481
+ ? loader.getExtensions().errors.filter(({ path }) => isRequiredSource(path)) : [];
445
482
  if (loadErrors.length > 0)
446
483
  throw new Error(`Required child extension failed to load: ${loadErrors.map(({ path, error }) => `${path}: ${error}`).join("; ")}`);
447
- const queued = flushQueuedProviderRegistrations(loader, modelRuntime, launch.onExtensionError, requiredPaths);
484
+ const queued = flushQueuedProviderRegistrations(loader, modelRuntime, launch.onExtensionError, isRequiredSource);
485
+ if (requiredPaths.size > 0) {
486
+ const loadedIdentities = new Set(loader.getExtensions().extensions.map(({ resolvedPath }) => requiredExtensionIdentity(resolvedPath)));
487
+ const missing = [...requiredPaths].filter((extensionPath) => !loadedIdentities.has(requiredExtensionIdentity(extensionPath)));
488
+ if (missing.length)
489
+ throw new Error(`Required child extension failed to load: ${missing.join(", ")}`);
490
+ }
448
491
  if (launch.parentProviderRegistry)
449
492
  inheritedProviders = inheritParentProviders(modelRuntime, launch.parentProviderRegistry, queued.claimedProviderIds, launch.onExtensionError);
450
493
  if (queued.registered || inheritedProviders.size > 0) {
@@ -496,7 +539,7 @@ export function createDefaultChildSessionFactory(options = {}) {
496
539
  await session.bindExtensions({
497
540
  mode: "print",
498
541
  onError: (error) => {
499
- if (binding && requiredPaths.has(error.extensionPath))
542
+ if (binding && isRequiredSource(error.extensionPath))
500
543
  requiredStartupErrors.push(`${error.extensionPath} (${error.event}): ${error.error}`);
501
544
  launch.onExtensionError?.({ extensionPath: error.extensionPath, event: error.event, error: error.error });
502
545
  },
@@ -65,6 +65,13 @@ export interface PiLaunchToolPlan {
65
65
  * runtime snapshot therefore cannot work and fails the launch.
66
66
  */
67
67
  export declare function formatRuntimeSnapshotMcpServersError(agentName: string | undefined, serverNames: readonly string[]): string;
68
+ /**
69
+ * A child's `tools` list is a complete allowlist. Read as names, Pi's modifier
70
+ * entries would become required tools no child has, or mix with the names the
71
+ * plan adds into a list Pi rejects. The modifier rule is Pi's `isToolModifier`:
72
+ * a `+name` or `-name` entry edits Pi's default tool selection.
73
+ */
74
+ export declare function assertNoToolModifierEntries(agentName: string | undefined, tools: readonly string[]): void;
68
75
  export declare function projectLaunchResolvedChildExtensions(toolPlan: Pick<PiLaunchToolPlan, "runtimeExtensions" | "configuredExtensions" | "requiredExtensions" | "extensionArgs" | "disableAmbientExtensions">): LaunchResolvedChildExtensions;
69
76
  /**
70
77
  * Resolve the permission-system extension entry point when installed.
@@ -100,6 +100,23 @@ export function formatRuntimeSnapshotMcpServersError(agentName, serverNames) {
100
100
  const subject = agentName ? `Agent '${agentName}'` : "Subagent";
101
101
  return `${subject} selects MCP tools from servers that exist only in pi-mcp-adapter's runtime snapshot (${serverNames.join(", ")}). MCP servers from the runtime snapshot cannot be provided to in-process children; MCP tools must come from an ambient adapter extension in a background child (\`async: true\`), so add the server to the adapter's configuration file instead.`;
102
102
  }
103
+ /** Path-like entries load extensions instead of naming tools, so Pi never reads them as modifiers. */
104
+ function isToolExtensionPath(entry) {
105
+ return entry.includes("/") || entry.endsWith(".ts") || entry.endsWith(".js");
106
+ }
107
+ /**
108
+ * A child's `tools` list is a complete allowlist. Read as names, Pi's modifier
109
+ * entries would become required tools no child has, or mix with the names the
110
+ * plan adds into a list Pi rejects. The modifier rule is Pi's `isToolModifier`:
111
+ * a `+name` or `-name` entry edits Pi's default tool selection.
112
+ */
113
+ export function assertNoToolModifierEntries(agentName, tools) {
114
+ const entries = tools.filter((entry) => /^[+-]/u.test(entry) && !isToolExtensionPath(entry));
115
+ if (entries.length === 0)
116
+ return;
117
+ const subject = agentName ? `Agent '${agentName}'` : "Subagent";
118
+ throw new Error(`${subject} lists Pi tool modifier ${entries.length === 1 ? "entry" : "entries"} in \`tools\`: ${entries.join(", ")}. A child's \`tools\` list is the complete set of tools it gets, so +name and -name entries are not supported; name every tool the child needs, or omit \`tools\` and use \`excludeTools\` to drop one.`);
119
+ }
103
120
  export function projectLaunchResolvedChildExtensions(toolPlan) {
104
121
  const runtime = boundedExtensionIdentifiers(toolPlan.runtimeExtensions);
105
122
  const configured = boundedExtensionIdentifiers(toolPlan.configuredExtensions);
@@ -178,7 +195,8 @@ export function resolvePiLaunchToolPlan(input) {
178
195
  const allowedToolSet = capabilityCeiling?.allowedTools === undefined
179
196
  ? undefined
180
197
  : new Set(capabilityCeiling.allowedTools);
181
- const requestedBuiltinTools = input.tools?.filter((tool) => !(tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js"))) ?? [];
198
+ const requestedBuiltinTools = input.tools?.filter((tool) => !isToolExtensionPath(tool)) ?? [];
199
+ assertNoToolModifierEntries(input.agentName, input.tools ?? []);
182
200
  if (input.requireReadTool && allowedToolSet && !allowedToolSet.has("read")) {
183
201
  throw new Error(`Capability ceiling from ${capabilityCeiling?.sources.join(", ") || "unknown source"} excludes required tool 'read' for lazy skill loading.`);
184
202
  }
@@ -204,8 +222,7 @@ export function resolvePiLaunchToolPlan(input) {
204
222
  }
205
223
  const toolExtensionPaths = capabilityCeiling?.denyExtensions
206
224
  ? []
207
- : (input.tools ?? []).filter((tool) => !requestedBuiltinTools.includes(tool) &&
208
- (tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js")));
225
+ : (input.tools ?? []).filter(isToolExtensionPath);
209
226
  const mcpResolution = capabilityCeiling?.denyExtensions
210
227
  ? { selections: [], unresolvedSelectors: [] }
211
228
  : input.builtinMcpTools
@@ -318,9 +335,7 @@ export function resolvePiLaunchToolPlan(input) {
318
335
  removedExtensionCount: capabilityCeiling.denyExtensions
319
336
  ? (input.extensions?.length ?? 0) +
320
337
  (input.subagentOnlyExtensions?.length ?? 0) +
321
- (input.tools ?? []).filter((tool) => tool.includes("/") ||
322
- tool.endsWith(".ts") ||
323
- tool.endsWith(".js")).length
338
+ (input.tools ?? []).filter(isToolExtensionPath).length
324
339
  : 0,
325
340
  requestedMcpToolCount: input.mcpDirectTools?.length ?? 0,
326
341
  effectiveMcpTools,
@@ -1,4 +1,5 @@
1
1
  import type { HerdrMachineReference } from "../../shared/types.ts";
2
+ import type { PlacementCommandResult, PlacementMachineAccess } from "./placement-host.ts";
2
3
  export declare const HERDR_REMOTE_PATH = "$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
3
4
  export declare const HERDR_SSH_BASE: readonly ["-T", "-o", "BatchMode=yes", "-o", "NumberOfPasswordPrompts=0", "-o", "StrictHostKeyChecking=yes", "-o", "ConnectTimeout=10", "-o", "ConnectionAttempts=1", "-o", "ForwardAgent=no", "-o", "ExitOnForwardFailure=yes", "-o", "ServerAliveInterval=15", "-o", "ServerAliveCountMax=4", "-o", "SendEnv=-*", "-o", "SetEnv=PI_SUBAGENTS_SSH_GUARD="];
4
5
  export declare function hardenedSshEnv(source?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
@@ -33,26 +34,28 @@ export interface HerdrForwardedConnection {
33
34
  }>;
34
35
  close(): Promise<void>;
35
36
  }
37
+ type SshTarget = Pick<HerdrMachineReference, "target">;
36
38
  export declare function decodeHerdrJsonLine(line: Buffer): unknown;
37
39
  export declare function shellQuoteRemote(value: string): string;
38
40
  export declare function remoteShellCommand(script: string, args?: string[]): string;
39
- export declare function runHerdrRemoteCommand(machine: HerdrMachineReference, command: string, options?: {
41
+ export declare function runHerdrRemoteCommand(machine: SshTarget, command: string, options?: {
40
42
  sshBin?: string;
41
43
  env?: NodeJS.ProcessEnv;
42
44
  timeout?: number;
43
45
  maxBuffer?: number;
44
46
  }): import("child_process").SpawnSyncReturns<string>;
45
- export declare function runHerdrRemoteCommandAsync(machine: HerdrMachineReference, command: string, options?: {
47
+ export declare function runHerdrRemoteCommandAsync(machine: SshTarget, command: string, options?: {
46
48
  sshBin?: string;
47
49
  env?: NodeJS.ProcessEnv;
48
50
  timeout?: number;
49
51
  maxBuffer?: number;
50
- }): Promise<{
51
- status: number | null;
52
- stdout: string;
53
- stderr: string;
54
- error?: Error;
55
- }>;
52
+ }): Promise<PlacementCommandResult>;
53
+ /** Spawns one process with bounded output and a SIGTERM-then-SIGKILL timeout; never rejects. */
54
+ export declare function runBoundedCommand(file: string, args: string[], options: {
55
+ env: NodeJS.ProcessEnv;
56
+ timeout?: number;
57
+ maxBuffer?: number;
58
+ }): Promise<PlacementCommandResult>;
56
59
  export declare function parseHerdrEndpoint(value: string, expectedSession?: string): HerdrEndpoint;
57
60
  export declare function discoverHerdrEndpoint(machine: HerdrMachineReference, options?: {
58
61
  sshBin?: string;
@@ -64,7 +67,13 @@ export declare class SocketRpcClient implements HerdrRpcClient {
64
67
  call<T>(method: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<T>;
65
68
  subscribe(subscriptions: unknown[], listener: (event: unknown) => void, onDisconnect?: (error: Error) => void): Promise<() => void>;
66
69
  }
70
+ /** SSH machine access: hardened ssh commands and StreamLocal forwards that share one private local directory per socket scope. */
71
+ export declare function sshMachineAccess(machine: SshTarget, options?: {
72
+ sshBin?: string;
73
+ env?: NodeJS.ProcessEnv;
74
+ }): PlacementMachineAccess;
67
75
  export declare function connectHerdrMachine(machine: HerdrMachineReference, options?: {
68
76
  sshBin?: string;
69
77
  env?: NodeJS.ProcessEnv;
70
78
  }): Promise<HerdrForwardedConnection>;
79
+ export {};
@@ -30,8 +30,12 @@ function canonicalHerdrSession(session) { return session && session !== "default
30
30
  function sshEnvCommand(session, command) { const selected = canonicalHerdrSession(session), assignment = selected ? ` HERDR_SESSION=${shellQuoteRemote(selected)}` : ""; return `/usr/bin/env -u HERDR_SOCKET_PATH -u HERDR_SESSION${assignment} ${remoteShellCommand(command)}`; }
31
31
  export function runHerdrRemoteCommand(machine, command, options = {}) { return spawnSync(options.sshBin ?? "ssh", [...herdrSshArgs(options.env), machine.target, command], { encoding: "utf8", env: hardenedSshEnv(options.env), timeout: options.timeout ?? 15_000, maxBuffer: options.maxBuffer ?? MAX_DISCOVERY_BYTES, windowsHide: true }); }
32
32
  export function runHerdrRemoteCommandAsync(machine, command, options = {}) {
33
+ return runBoundedCommand(options.sshBin ?? "ssh", [...herdrSshArgs(options.env), machine.target, command], { env: hardenedSshEnv(options.env), timeout: options.timeout, maxBuffer: options.maxBuffer });
34
+ }
35
+ /** Spawns one process with bounded output and a SIGTERM-then-SIGKILL timeout; never rejects. */
36
+ export function runBoundedCommand(file, args, options) {
33
37
  return new Promise((resolve) => {
34
- const child = spawn(options.sshBin ?? "ssh", [...herdrSshArgs(options.env), machine.target, command], { env: hardenedSshEnv(options.env), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
38
+ const child = spawn(file, args, { env: options.env, stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
35
39
  const stdout = [], stderr = [], maxBuffer = options.maxBuffer ?? MAX_DISCOVERY_BYTES;
36
40
  let stdoutBytes = 0, stderrBytes = 0, error, settled = false, escalation;
37
41
  const finish = (status, spawnError = error) => { if (settled)
@@ -226,10 +230,7 @@ async function terminateChild(child) { const wait = (ms) => child.exitCode !== n
226
230
  child.kill("SIGKILL");
227
231
  await wait(1_000);
228
232
  } }
229
- export async function connectHerdrMachine(machine, options = {}) {
230
- if (process.platform === "win32")
231
- throw new Error("Pane-native Herdr placement requires OpenSSH StreamLocal forwarding and is not supported on Windows.");
232
- const endpoint = await discoverHerdrEndpoint(machine, options);
233
+ function openSshSockets(machine, options) {
233
234
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-herdr-"));
234
235
  fs.chmodSync(dir, 0o700);
235
236
  const children = new Set();
@@ -252,6 +253,17 @@ export async function connectHerdrMachine(machine, options = {}) {
252
253
  }
253
254
  return { socketPath: local, close: stop };
254
255
  };
255
- const primary = await forward(endpoint.socket, "herdr").catch((error) => { fs.rmSync(dir, { recursive: true, force: true }); throw error; });
256
- return { endpoint, socketPath: primary.socketPath, client: new SocketRpcClient(primary.socketPath), forwardRemoteSocket: forward, close: async () => { await Promise.all([...children].map(terminateChild)); children.clear(); fs.rmSync(dir, { recursive: true, force: true }); } };
256
+ return { forwardRemoteSocket: forward, close: async () => { await Promise.all([...children].map(terminateChild)); children.clear(); fs.rmSync(dir, { recursive: true, force: true }); } };
257
+ }
258
+ /** SSH machine access: hardened ssh commands and StreamLocal forwards that share one private local directory per socket scope. */
259
+ export function sshMachineAccess(machine, options = {}) {
260
+ return { shellCommand: remoteShellCommand, run: (command, limits = {}) => runHerdrRemoteCommand(machine, command, { ...options, ...limits }), runAsync: (command, limits = {}) => runHerdrRemoteCommandAsync(machine, command, { ...options, ...limits }), openSockets: () => openSshSockets(machine, options) };
261
+ }
262
+ export async function connectHerdrMachine(machine, options = {}) {
263
+ if (process.platform === "win32")
264
+ throw new Error("Pane-native Herdr placement requires OpenSSH StreamLocal forwarding and is not supported on Windows.");
265
+ const endpoint = await discoverHerdrEndpoint(machine, options);
266
+ const sockets = sshMachineAccess(machine, options).openSockets();
267
+ const primary = await sockets.forwardRemoteSocket(endpoint.socket, "herdr").catch(async (error) => { await sockets.close(); throw error; });
268
+ return { endpoint, socketPath: primary.socketPath, client: new SocketRpcClient(primary.socketPath), forwardRemoteSocket: sockets.forwardRemoteSocket, close: sockets.close };
257
269
  }
@@ -1,6 +1,7 @@
1
1
  import type { HerdrMachineReference, HerdrRemoteGitStatus } from "../../shared/types.ts";
2
2
  import { connectHerdrMachine, type HerdrForwardedConnection } from "./herdr-connection.ts";
3
- import { HerdrPlacedRunOwner, type HerdrReconnectCandidate, type HerdrRunIdentity } from "./herdr-placed-run.ts";
3
+ import { HerdrPlacedRunOwner, type HerdrReconnectCandidate } from "./herdr-pane-host.ts";
4
+ import type { HerdrRunIdentity } from "./herdr-placed-run.ts";
4
5
  export type HerdrExternalAdapterId = "claude-code" | "claude-code-writer" | "cursor-agent" | "cursor-agent-writer" | "codex-exec" | "codex-exec-writer";
5
6
  export type HerdrExternalKind = "claude" | "cursor" | "codex";
6
7
  export interface HerdrExternalCapabilities {
@@ -62,7 +63,7 @@ export interface HerdrExternalAdapter {
62
63
  }
63
64
  export declare function reconcileHerdrExternalRun(identity: HerdrRunIdentity, candidate: HerdrReconnectCandidate): {
64
65
  ok: true;
65
- state: import("./herdr-placed-run.ts").HerdrRunSnapshot["state"];
66
+ state: import("./placement-host.ts").PlacementRunState;
66
67
  paneId: string;
67
68
  cursor: number;
68
69
  } | {
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import * as path from "node:path";
3
3
  import { stripVTControlCharacters } from "node:util";
4
4
  import { connectHerdrMachine, HerdrRpcError, HerdrTransportError, remoteShellCommand, shellQuoteRemote } from "./herdr-connection.js";
5
- import { HerdrPlacedRunOwner, herdrStatusSubscriptions, ownsHerdrPane, parseHerdrAgent, reconcileHerdrPlacedRun } from "./herdr-placed-run.js";
5
+ import { HerdrPlacedRunOwner, herdrStatusSubscriptions, ownsHerdrPane, parseHerdrAgent, reconcileHerdrPlacedRun } from "./herdr-pane-host.js";
6
6
  const CAPABILITIES = { stop: true, steer: false, resume: false, supervisor: "unsupported" };
7
7
  const MAX_EVIDENCE_BYTES = 4 * 1024 * 1024, MAX_OUTPUT_BYTES = 1024 * 1024, MAX_PREFLIGHT_BYTES = 64 * 1024, MAX_GIT_BYTES = 2048;
8
8
  const CODEX_OUTPUT_BYTES = 64 * 1024, EXTERNAL_STARTUP_TIMEOUT_MS = 45_000, EXTERNAL_STARTUP_BACKOFF_AFTER_MS = 15_000, EXTERNAL_STARTUP_SLOW_POLL_MS = 2_500, CODEX_STARTUP_GRACE_MS = 15_000, CODEX_QUIET_MS = 8_000, CODEX_POLL_MS = 1_000, CODEX_DEFAULT_TIMEOUT_MS = 30 * 60_000;
@@ -1,4 +1,4 @@
1
- import type { ExternalProcessStatus, HerdrMachineReference } from "../../shared/types.ts";
1
+ import type { ExternalProcessStatus, HerdrMachineReference, PlacementMachineReference } from "../../shared/types.ts";
2
2
  import type { runExternalCli } from "./external-cli-runner.ts";
3
3
  export { shellQuoteRemote as shellQuote } from "./herdr-connection.ts";
4
4
  /** The local ssh process gets only what ssh itself needs; remote runs use the machine's own credentials. */
@@ -7,6 +7,10 @@ type RunExternalCliInput = Parameters<typeof runExternalCli>[0];
7
7
  interface MachineSettingsEntry {
8
8
  cwd?: string;
9
9
  env?: Record<string, string>;
10
+ /** User settings only: run this machine's children in Rex panes reached over SSH at `ssh`. */
11
+ rex?: {
12
+ ssh: string;
13
+ };
10
14
  }
11
15
  export interface ResolveHerdrMachinePlacementInput {
12
16
  /** Profile id or label as typed by the operator. */
@@ -22,8 +26,12 @@ export interface ResolveHerdrMachinePlacementInput {
22
26
  settings?: MachineSettingsEntry;
23
27
  herdrBin?: string;
24
28
  }
25
- export interface HerdrMachinePlacement {
29
+ export interface HerdrMachinePlacement extends MachinePlacement {
26
30
  machine: HerdrMachineReference;
31
+ }
32
+ /** A resolved `machine` value: a Herdr saved machine, or the reserved `local` (a Rex pane on this machine). */
33
+ export interface MachinePlacement {
34
+ machine: PlacementMachineReference;
27
35
  /** Opt-in `subagents.machines.<name>.env`, exported in front of the remote command. */
28
36
  env?: Record<string, string>;
29
37
  }
@@ -32,6 +40,8 @@ export interface PreparedHerdrMachineExternalCliRun {
32
40
  decorateProcess(process: ExternalProcessStatus): ExternalProcessStatus;
33
41
  }
34
42
  export declare function resolveHerdrMachinePlacement(input: ResolveHerdrMachinePlacementInput): HerdrMachinePlacement;
43
+ /** `local` resolves to a Rex pane on this machine; a name with a `rex` entry in user settings to a Rex pane on that SSH host; any other value to a Herdr saved machine. */
44
+ export declare function resolveMachinePlacement(input: ResolveHerdrMachinePlacementInput): MachinePlacement;
35
45
  export declare function formatHerdrMachineRunnerUnsupported(input: {
36
46
  machine?: string;
37
47
  agentName: string;
@@ -3,6 +3,7 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { getAgentDir, getProjectConfigDir } from "../../shared/utils.js";
5
5
  import { CODE_OWNED_EXTERNAL_CLI_ADAPTER_IDS } from "./external-cli-contract.js";
6
+ import { REX_LOCAL_MACHINE, resolveRexLocalPlacement } from "./rex-machine.js";
6
7
  export { shellQuoteRemote as shellQuote } from "./herdr-connection.js";
7
8
  /**
8
9
  * Herdr saved-machine placement for external CLI children.
@@ -149,18 +150,31 @@ function machineSettingsFrom(settings, keys, filePath) {
149
150
  throw new Error(`Subagent settings in '${filePath}' set 'machines.${key}.env'. Saved-machine runs use the remote Herdr/Pi environment; configure credentials and environment on '${key}' and remove the local env map.`);
150
151
  }
151
152
  }
153
+ if (record.rex !== undefined) {
154
+ const rex = record.rex, ssh = rex && typeof rex === "object" && !Array.isArray(rex) && Object.keys(rex).every((field) => field === "ssh") ? rex.ssh : undefined;
155
+ if (typeof ssh !== "string")
156
+ throw new Error(`Subagent settings in '${filePath}' have invalid 'machines.${key}.rex'; expected { "ssh": "<ssh target>" }.`);
157
+ entry.rex = { ssh: validateTarget(ssh, key) };
158
+ }
152
159
  return entry;
153
160
  }
154
- /** Project settings beat user settings, field by field; the entry may be keyed by label, id, or the typed selector. */
161
+ /** Project settings beat user settings, field by field; the entry may be keyed by label, id, or the typed selector. Only user settings may name a Rex SSH host, so a cloned repo never chooses where a task runs. */
155
162
  function readMachineSettings(cwd, keys) {
156
163
  const userPath = path.join(getAgentDir(), "settings.json");
157
164
  const projectPath = path.join(getProjectConfigDir(findProjectRootForSettings(cwd)), "settings.json");
158
165
  const user = machineSettingsFrom(readJsonObject(userPath), keys, userPath);
159
166
  const project = projectPath === userPath ? undefined : machineSettingsFrom(readJsonObject(projectPath), keys, projectPath);
167
+ if (project?.rex)
168
+ throw new Error(`Project settings '${projectPath}' set a Rex SSH host in 'machines'. Move the rex entry to your user settings '${userPath}'; only user settings choose which host a task runs on.`);
160
169
  if (!user && !project)
161
170
  return undefined;
162
171
  return { ...user, ...project };
163
172
  }
173
+ /** Whether user settings name `machine` as a Rex SSH host. */
174
+ function userRexMachine(machine) {
175
+ const userPath = path.join(getAgentDir(), "settings.json");
176
+ return machineSettingsFrom(readJsonObject(userPath), [machine], userPath)?.rex !== undefined;
177
+ }
164
178
  function findProjectRootForSettings(cwd) {
165
179
  let current = path.resolve(cwd);
166
180
  while (true) {
@@ -172,6 +186,15 @@ function findProjectRootForSettings(cwd) {
172
186
  current = parent;
173
187
  }
174
188
  }
189
+ /** The child's cwd on a remote machine: an absolute launch cwd, or the launch cwd under the machine's configured root. */
190
+ function remoteMachineCwd(stepCwd, root, name) {
191
+ const step = stepCwd?.trim();
192
+ if (step && isRemoteAbsolute(step))
193
+ return step;
194
+ if (!root)
195
+ throw new Error(`No root for ${name} in this repo. Set subagents.machines.${name}.cwd in .pi/settings.json or pass an absolute cwd on that machine.`);
196
+ return step ? path.posix.join(root, step) : root;
197
+ }
175
198
  export function resolveHerdrMachinePlacement(input) {
176
199
  const requested = validateMachineName(input.machine);
177
200
  const catalog = input.catalogJson !== undefined
@@ -184,14 +207,7 @@ export function resolveHerdrMachinePlacement(input) {
184
207
  if (settings?.env && Object.keys(settings.env).length > 0) {
185
208
  throw new Error(`Saved-machine environment for '${name}' must be configured remotely. Remove machines.${name}.env and configure the remote Herdr/Pi session instead.`);
186
209
  }
187
- const stepCwd = input.stepCwd?.trim();
188
- let cwd;
189
- if (stepCwd && isRemoteAbsolute(stepCwd))
190
- cwd = stepCwd;
191
- else if (settings?.cwd)
192
- cwd = stepCwd ? path.posix.join(settings.cwd, stepCwd) : settings.cwd;
193
- else
194
- throw new Error(`No root for ${name} in this repo. Set subagents.machines.${name}.cwd in .pi/settings.json or pass an absolute cwd on that machine.`);
210
+ const cwd = remoteMachineCwd(input.stepCwd, settings?.cwd, name);
195
211
  return {
196
212
  machine: {
197
213
  provider: "herdr",
@@ -204,9 +220,39 @@ export function resolveHerdrMachinePlacement(input) {
204
220
  ...(settings?.env ? { env: settings.env } : {}),
205
221
  };
206
222
  }
223
+ /** `local` resolves to a Rex pane on this machine; a name with a `rex` entry in user settings to a Rex pane on that SSH host; any other value to a Herdr saved machine. */
224
+ export function resolveMachinePlacement(input) {
225
+ const requested = validateMachineName(input.machine);
226
+ const settings = input.settings ?? readMachineSettings(input.cwd, [requested]);
227
+ if (requested === REX_LOCAL_MACHINE) {
228
+ if (settings?.rex)
229
+ throw new Error("Machine 'local' is reserved for a Rex pane on this machine; give the Rex SSH host in subagents.machines.local.rex another name.");
230
+ return { machine: resolveRexLocalPlacement({ cwd: input.cwd, stepCwd: input.stepCwd, env: input.env }) };
231
+ }
232
+ if (settings?.rex)
233
+ return { machine: { provider: "rex", id: requested, target: settings.rex.ssh, cwd: validateRemoteCwd(remoteMachineCwd(input.stepCwd, settings.cwd, requested), requested) } };
234
+ return resolveHerdrMachinePlacement(input);
235
+ }
207
236
  export function formatHerdrMachineRunnerUnsupported(input) {
208
237
  if (input.machine === undefined)
209
238
  return undefined;
239
+ const name = input.machine.trim();
240
+ let rex;
241
+ try {
242
+ rex = name === REX_LOCAL_MACHINE || userRexMachine(name);
243
+ }
244
+ catch (error) {
245
+ return error instanceof Error ? error.message : String(error);
246
+ }
247
+ if (rex) {
248
+ if (input.runnerType !== undefined && input.runnerType !== "pi")
249
+ return `Agent '${input.agentName}' requested machine '${name}', but Rex supports native Pi children only. Remove the machine or use a native Pi agent.`;
250
+ if (input.worktree === true)
251
+ return `Agent '${input.agentName}' requested machine '${name}', but managed worktrees cannot be combined with machine placement.`;
252
+ if (process.platform === "win32")
253
+ return `Machine '${name}' runs the child in a Rex pane, which is not supported on Windows.`;
254
+ return undefined;
255
+ }
210
256
  if (input.runnerType !== undefined && input.runnerType !== "pi" && input.runnerType !== "external-cli") {
211
257
  return `Agent '${input.agentName}' requested machine '${input.machine}', but this runner cannot use pane-native Herdr placement. Use native Pi or a built-in Claude, Codex, or Cursor profile.`;
212
258
  }
@@ -0,0 +1,88 @@
1
+ import type { HerdrMachineReference, PlacementMachineReference } from "../../shared/types.ts";
2
+ import { type HerdrForwardedConnection, type HerdrRpcClient } from "./herdr-connection.ts";
3
+ import type { HerdrRunIdentity } from "./herdr-placed-run.ts";
4
+ import { PlacedRunOwner, type PlacedBridgeManifest } from "./placed-run-owner.ts";
5
+ import { type PlacementAgentKind, type PlacementBridgeIdentity, type PlacementEvidenceProof, type PlacementOwnedPane, type PlacementReconnectCheck, type PlacementRunState, type PlacementStartOptions } from "./placement-host.ts";
6
+ export interface HerdrPaneCandidate {
7
+ endpoint: {
8
+ session: string | null;
9
+ protocol: number;
10
+ version: string;
11
+ };
12
+ agents: Array<{
13
+ terminal_id?: string;
14
+ pane_id?: string;
15
+ agent_status?: string;
16
+ }>;
17
+ }
18
+ export interface HerdrReconnectCandidate extends HerdrPaneCandidate {
19
+ bridge: PlacementBridgeIdentity;
20
+ }
21
+ export declare function herdrStatusSubscriptions(paneId: string): unknown[];
22
+ export declare function reconcileHerdrPlacedRun(identity: HerdrRunIdentity, candidate: HerdrPaneCandidate, proveEvidence: () => PlacementEvidenceProof): PlacementReconnectCheck;
23
+ export declare function parseHerdrCreated(value: unknown, type: "workspace_created" | "tab_created"): {
24
+ workspaceId?: string;
25
+ tabId: string;
26
+ paneId: string;
27
+ };
28
+ export declare function parseHerdrAgent(value: unknown, type: "agent_started" | "agent_info" | "agent_prompted"): Record<string, unknown>;
29
+ export declare function parseHerdrSessionSnapshot(value: unknown): Record<string, unknown>;
30
+ export declare function ownsHerdrPane(agent: Record<string, unknown>, terminalId: string, paneId: string): boolean;
31
+ export declare const HERDR_MAX_OWNED_PANES = 20;
32
+ export declare function provisionHerdrPane(client: HerdrRpcClient, cwd: string, runId: string, runtimeDir: string, environment?: Record<string, string>, allocationKey?: string): Promise<{
33
+ workspaceId: string;
34
+ tabId: string;
35
+ paneId: string;
36
+ }>;
37
+ declare function closeOwnedHerdrPane(connection: HerdrForwardedConnection, owned: PlacementOwnedPane, terminalId: string | undefined, agentName: string | undefined): Promise<void>;
38
+ /** Herdr pane host: Herdr RPC over the SSH-forwarded Herdr socket. Ownership is the Herdr terminal_id/pane_id pair; run state comes from agent_status. */
39
+ export declare const herdrPaneHost: {
40
+ connect(machine: PlacementMachineReference): Promise<HerdrForwardedConnection>;
41
+ sessionOf(connection: HerdrForwardedConnection): string | null;
42
+ launch(connection: HerdrForwardedConnection, machine: PlacementMachineReference, runId: string, runtimeDir: string, request: import("./placement-host.ts").PlacementLaunchRequest, onPane: (owned: PlacementOwnedPane) => Promise<void>, options: PlacementStartOptions | undefined): Promise<{
43
+ owned: PlacementOwnedPane;
44
+ terminalId: string;
45
+ }>;
46
+ prompt(connection: HerdrForwardedConnection, identity: HerdrRunIdentity, text: string): Promise<void>;
47
+ subscribe(connection: HerdrForwardedConnection, paneId: string, listener: (event: unknown) => void, onDisconnect: (error: Error) => void): Promise<() => void>;
48
+ paneEvent(event: unknown, fallback: PlacementRunState): {
49
+ terminalId: unknown;
50
+ paneId: unknown;
51
+ state: PlacementRunState;
52
+ } | undefined;
53
+ panes(connection: HerdrForwardedConnection): Promise<{
54
+ endpoint: import("./herdr-connection.ts").HerdrEndpoint;
55
+ agents: {
56
+ terminal_id?: string;
57
+ pane_id?: string;
58
+ agent_status?: string;
59
+ }[];
60
+ }>;
61
+ reconcile: typeof reconcileHerdrPlacedRun;
62
+ closeOwnedPane: typeof closeOwnedHerdrPane;
63
+ };
64
+ /** SSH entry points for the host-neutral runtime-dir and manifest helpers. */
65
+ export declare function discoverBridgeManifest(machine: HerdrMachineReference, runId: string, runtimeDir: string, options?: {
66
+ sshBin?: string;
67
+ env?: NodeJS.ProcessEnv;
68
+ }): Promise<PlacedBridgeManifest>;
69
+ export declare function createRemoteRuntimeDir(machine: HerdrMachineReference, runId: string, options?: {
70
+ sshBin?: string;
71
+ env?: NodeJS.ProcessEnv;
72
+ }): Promise<string>;
73
+ export declare function removeRemoteRuntimeDir(machine: HerdrMachineReference, runtimeDir: string, options?: {
74
+ sshBin?: string;
75
+ env?: NodeJS.ProcessEnv;
76
+ }): void;
77
+ /** The placed-run owner bound to Herdr and SSH access, plus the separate provision and start steps the external CLI adapters use. */
78
+ export declare class HerdrPlacedRunOwner extends PlacedRunOwner<HerdrForwardedConnection, HerdrMachineReference> {
79
+ private constructor();
80
+ static create(machine: HerdrMachineReference, runId: string, beforePane?: (owner: HerdrPlacedRunOwner) => void | Promise<void>): Promise<HerdrPlacedRunOwner>;
81
+ provision(environment?: Record<string, string>): Promise<PlacementOwnedPane>;
82
+ start(kind: PlacementAgentKind, name: string, args: string[], options?: PlacementStartOptions): Promise<{
83
+ terminalId: string;
84
+ paneId: string;
85
+ argv: string[];
86
+ }>;
87
+ }
88
+ export {};