@byok-sdk/client 0.12.0 → 0.14.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 (48) hide show
  1. package/README.md +93 -0
  2. package/dist/adapters/claude/process-client.d.ts +24 -0
  3. package/dist/adapters/codex/process-runner.d.ts +46 -1
  4. package/dist/adapters/index.js +433 -33
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/adapters/pi/events.d.ts +1 -1
  7. package/dist/adapters/pi/pi-adapter.d.ts +3 -0
  8. package/dist/adapters/pi/rpc-client.d.ts +47 -1
  9. package/dist/adapters/pi/subagents-policy-extension.js +1 -1
  10. package/dist/adapters/pi/subagents-policy-extension.js.map +1 -1
  11. package/dist/adapters/pi/team-interaction-extension.d.ts +24 -0
  12. package/dist/adapters/pi/team-interaction-extension.js +81 -0
  13. package/dist/adapters/pi/team-interaction-extension.js.map +1 -0
  14. package/dist/adapters/process-tree.d.ts +74 -4
  15. package/dist/adapters/provider-credential-environment.d.ts +1 -1
  16. package/dist/adapters/win32-job-object.d.ts +101 -0
  17. package/dist/agent-home.d.ts +74 -0
  18. package/dist/agent-memory/index.d.ts +1 -1
  19. package/dist/agent-memory/index.js.map +1 -1
  20. package/dist/bin/byok-agent-memory-mcp.js.map +1 -1
  21. package/dist/bin/byok-agent-message-mcp.js.map +1 -1
  22. package/dist/bin/byok-agent-team-mcp.js.map +1 -1
  23. package/dist/bin/byok-agent.js +9571 -8272
  24. package/dist/bin/byok-agent.js.map +1 -1
  25. package/dist/bin/byok-approval-mcp.js.map +1 -1
  26. package/dist/bin/commands/team-pi-relay.d.ts +24 -0
  27. package/dist/bin/commands/team-relay.d.ts +11 -0
  28. package/dist/bin/team-codex-relay.d.ts +32 -0
  29. package/dist/bin/team-notification-relay.d.ts +40 -0
  30. package/dist/bin/team-pi-session.d.ts +59 -0
  31. package/dist/daemon/agent-egress-policy.d.ts +8 -0
  32. package/dist/daemon/connection-manager.d.ts +66 -224
  33. package/dist/daemon/control-protocol.d.ts +10 -1
  34. package/dist/daemon/create-daemon.d.ts +58 -13
  35. package/dist/daemon/event-spill.d.ts +90 -0
  36. package/dist/daemon/journal/journal.d.ts +15 -3
  37. package/dist/daemon/journal/sqlite-journal.d.ts +7 -2
  38. package/dist/daemon/long-poll-transport.d.ts +28 -70
  39. package/dist/daemon/observer.d.ts +1 -1
  40. package/dist/daemon/task-runner.d.ts +31 -0
  41. package/dist/daemon/team-workspace.d.ts +9 -0
  42. package/dist/daemon/url.d.ts +10 -12
  43. package/dist/diagnostics/support-bundle.d.ts +1 -1
  44. package/dist/index.d.ts +2 -2
  45. package/dist/index.js +1334 -696
  46. package/dist/index.js.map +1 -1
  47. package/package.json +9 -8
  48. package/dist/daemon/ws-transport.d.ts +0 -139
@@ -1,7 +1,7 @@
1
1
  import type { AgentEvent } from '@byok-sdk/protocol';
2
2
  import type { PiRpcMessage } from './rpc-client';
3
3
  /**
4
- * Map pi 0.84.2 RPC frames into BYOK's runtime-neutral event contract.
4
+ * Map pi 0.85.1 RPC frames into BYOK's runtime-neutral event contract.
5
5
  *
6
6
  * `agent_settled` is the only whole-task completion authority. `agent_end`
7
7
  * ends one low-level agent run, but pi may still perform retry/compaction or
@@ -1,3 +1,4 @@
1
+ import type { ProviderProfileBinding } from '@byok-sdk/protocol';
1
2
  import { type RuntimeAdapter, type RuntimeDetectResult, type RuntimeAdapterPrepareInput, type RuntimeAdapterPrepareResult } from '../../types';
2
3
  import { type ResolvedBin } from './resolve-bin';
3
4
  import { type ResolvedPiExtensions } from './resolve-extensions';
@@ -22,6 +23,8 @@ export interface PiAdapterOptions {
22
23
  * and transparently proxies the pinned Pi RPC process.
23
24
  */
24
25
  byokLauncher?: PiByokLauncherConfig;
26
+ /** Admission-time exact local profile check. Tests may replace the process boundary. */
27
+ validateProviderProfileBinding?: (binding: ProviderProfileBinding, launcher: PiByokLauncherConfig) => Promise<void>;
25
28
  }
26
29
  export interface PiByokLauncherConfig {
27
30
  command: string;
@@ -18,6 +18,24 @@ export interface PiRpcClientOptions {
18
18
  cwd: string;
19
19
  env: NodeJS.ProcessEnv;
20
20
  spawnFn?: SpawnFn;
21
+ /** Explicit GUI-owned interaction lane; omission retains unattended cancellation. */
22
+ extensionUi?: {
23
+ mode: 'hold';
24
+ onRequest: (request: PiRpcMessage) => void;
25
+ };
26
+ /** Synchronous observation before command receipts resolve, for an owned relay. */
27
+ onFrame?: (frame: PiRpcMessage) => void;
28
+ /**
29
+ * DI seam scoped to ADOPTION only (`../process-tree.ts`'s
30
+ * `adoptOwnedProcessTree`), so the win32 job-object branch is exercisable
31
+ * from POSIX. Disposal keeps `process.platform` as its own authority — this
32
+ * must never silently reroute the taskkill sweep on a real host.
33
+ */
34
+ platform?: NodeJS.Platform;
35
+ /** DI seam for the win32 job-object backstop; see `../win32-job-object.ts`. */
36
+ jobObject?: {
37
+ assign(pid: number): Promise<void>;
38
+ };
21
39
  }
22
40
  /**
23
41
  * JSONL request/response + event-stream client for `pi --mode rpc`.
@@ -32,6 +50,7 @@ export interface PiRpcClientOptions {
32
50
  * response can overtake a slower in-flight command's response).
33
51
  */
34
52
  export declare class PiRpcClient {
53
+ private readonly options;
35
54
  private readonly child;
36
55
  private buffer;
37
56
  private nextId;
@@ -42,12 +61,23 @@ export declare class PiRpcClient {
42
61
  private readonly closedPromise;
43
62
  private resolveClosed;
44
63
  private disposalAttempt;
64
+ /** Resolves once this tree is backstopped (see `adoptOwnedProcessTree`); rejects with the adoption failure, having already terminated the tree. */
65
+ private readonly adopted;
66
+ /** Set before the fail-closed termination starts, so it — not the exit code of the kill we ourselves requested — becomes this client's exit error. */
67
+ private adoptionFailure;
45
68
  /** Bounded tail of recent stderr lines — pi discarded this entirely before (nothing ever read `child.stderr`), which is exactly why finding #1 (`Error: Unknown option: --session-id`, exit 1) had to be root-caused by hand instead of reading it off a thrown error. See `buildExitError`. */
46
69
  private readonly stderrRing;
47
70
  /** Count of pi RPC message types `PiSession` (pi-adapter.ts) has told us have no `AgentEvent` mapping and aren't routine bookkeeping — see `recordUnmappedFrame`. */
48
71
  private readonly unmappedFrameCounts;
49
72
  constructor(options: PiRpcClientOptions);
50
- /** Send a command, resolved with its correlated `response` message. */
73
+ /**
74
+ * Send a command, resolved with its correlated `response` message.
75
+ *
76
+ * Adoption is awaited FIRST — this is `PiAdapter.start()`'s first awaited
77
+ * operation, so an unbackstopped tree never receives the initial prompt and
78
+ * never yields a session. The wait costs one settled-promise turn once the
79
+ * tree is adopted; every later command sees it already resolved.
80
+ */
51
81
  send(command: Record<string, unknown> & {
52
82
  type: string;
53
83
  id?: string;
@@ -77,8 +107,24 @@ export declare class PiRpcClient {
77
107
  waitClosed(): Promise<void>;
78
108
  dispose(): Promise<void>;
79
109
  private processTreeOptions;
110
+ /**
111
+ * Backstop this tree, or tear it down. Adoption failure is a start-time
112
+ * precondition, not a degraded mode: the child is terminated through the one
113
+ * disposal authority and the failure is re-thrown, which is what makes the
114
+ * first `send()` — and therefore `PiAdapter.start()` — fail before any
115
+ * session is published. Both cleanup attempts are best-effort because the
116
+ * adoption failure, not a terminator's own complaint, is the reason to report.
117
+ */
118
+ private adoptOwnedTree;
80
119
  private onData;
81
120
  private onLine;
121
+ /** Write an explicit host-owned response; completion is a transport receipt only. */
122
+ respondExtensionUi(response: {
123
+ id: string;
124
+ cancelled?: true;
125
+ confirmed?: boolean;
126
+ value?: string;
127
+ }): Promise<void>;
82
128
  /**
83
129
  * Answer pi's extension-UI blocking protocol headlessly (rpc.md's
84
130
  * "Extension UI Protocol"). Fail-closed policy, stated explicitly because
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'buffer';
2
2
 
3
- // ../../node_modules/.bun/pi-subagents@0.60.0+ba57dcb6e433cb73/node_modules/pi-subagents/src/runs/shared/capability-ceiling.ts
3
+ // ../../node_modules/.bun/pi-subagents@0.60.0+5c5e823a15db0d8e/node_modules/pi-subagents/src/runs/shared/capability-ceiling.ts
4
4
  var SUBAGENT_CAPABILITY_CEILING_VERSION = 1;
5
5
  var SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY = "pi-subagents.capability-ceiling.v1";
6
6
  function registry() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../node_modules/.bun/pi-subagents@0.60.0+ba57dcb6e433cb73/node_modules/pi-subagents/src/runs/shared/capability-ceiling.ts","../../../src/adapters/pi/subagents-policy-config.ts","../../../src/adapters/pi/subagents-policy-extension.ts"],"names":[],"mappings":"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","file":"subagents-policy-extension.js","sourcesContent":["import { Buffer } from \"node:buffer\";\n\nexport const SUBAGENT_CAPABILITY_CEILING_VERSION = 1 as const;\nexport const SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY = \"pi-subagents.capability-ceiling.v1\";\nexport const SUBAGENT_CAPABILITY_CEILING_ENV = \"PI_SUBAGENT_CAPABILITY_CEILING_V1\";\n\nexport type SubagentCapabilityCeiling =\n\t| { allowedTools: readonly string[]; allowedAgents?: readonly string[]; denyExtensions?: boolean }\n\t| { allowedTools?: readonly string[]; allowedAgents?: readonly string[]; denyExtensions: boolean }\n\t| { allowedTools?: readonly string[]; allowedAgents: readonly string[]; denyExtensions?: boolean };\n\nexport interface ResolvedSubagentCapabilityCeiling {\n\tversion: typeof SUBAGENT_CAPABILITY_CEILING_VERSION;\n\tallowedTools?: string[];\n\tallowedAgents?: string[];\n\tdenyExtensions: boolean;\n\tsources: string[];\n}\n\nexport interface SubagentCapabilityAudit {\n\tceiling: ResolvedSubagentCapabilityCeiling;\n\trequestedTools?: string[];\n\teffectiveTools: string[];\n\tremovedTools: string[];\n\tinternalTools: string[];\n\textensionsDenied: boolean;\n\tremovedExtensionCount: number;\n\trequestedMcpToolCount: number;\n\teffectiveMcpTools: string[];\n\tagentAllowed: boolean;\n\tagentRestrictionSources?: string[];\n}\n\nexport interface RegisterSubagentCapabilityCeilingOptions {\n\tsessionId: string;\n\tsource: string;\n\tceiling: SubagentCapabilityCeiling;\n}\n\nexport interface SubagentCapabilityCeilingHandle {\n\tupdate(ceiling: SubagentCapabilityCeiling): void;\n\tdispose(): void;\n}\n\ntype Registration = { source: string; ceiling: ResolvedSubagentCapabilityCeiling };\ntype Registry = Map<string, Map<symbol, Registration>>;\n\nfunction registry(): Registry {\n\tconst key = Symbol.for(SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY);\n\tconst store = globalThis as typeof globalThis & { [key: symbol]: unknown };\n\tconst existing = store[key];\n\tif (existing instanceof Map) return existing as Registry;\n\tconst created: Registry = new Map();\n\tstore[key] = created;\n\treturn created;\n}\n\nfunction validateText(value: unknown, field: string): string {\n\tif (typeof value !== \"string\" || !value.trim() || /[\\u0000-\\u001f\\u007f]/u.test(value) || Buffer.byteLength(value.trim(), \"utf8\") > 256) {\n\t\tthrow new Error(`Invalid capability ceiling ${field}; expected a non-empty string without control characters (max 256 UTF-8 bytes).`);\n\t}\n\treturn value.trim();\n}\n\nfunction normalizeCeiling(ceiling: SubagentCapabilityCeiling): ResolvedSubagentCapabilityCeiling {\n\tif (!ceiling || typeof ceiling !== \"object\" || Array.isArray(ceiling)) throw new Error(\"Invalid capability ceiling; expected an object.\");\n\tconst hasAllowedTools = Object.hasOwn(ceiling, \"allowedTools\");\n\tconst hasAllowedAgents = Object.hasOwn(ceiling, \"allowedAgents\");\n\tconst hasDenyExtensions = Object.hasOwn(ceiling, \"denyExtensions\");\n\tif (!hasAllowedTools && !hasAllowedAgents && !hasDenyExtensions) throw new Error(\"Invalid capability ceiling; expected allowedTools, allowedAgents, or denyExtensions.\");\n\tif (hasDenyExtensions && typeof ceiling.denyExtensions !== \"boolean\") throw new Error(\"Invalid capability ceiling denyExtensions; expected a boolean.\");\n\tconst normalizeList = (field: \"allowedTools\" | \"allowedAgents\", pattern: RegExp): string[] | undefined => {\n\t\tif (!Object.hasOwn(ceiling, field)) return undefined;\n\t\tconst values = ceiling[field];\n\t\tif (!Array.isArray(values)) throw new Error(`Invalid capability ceiling ${field}; expected an array.`);\n\t\tif (values.length > 256) throw new Error(`Invalid capability ceiling ${field}; expected at most 256 names.`);\n\t\treturn [...new Set(values.map((entry) => {\n\t\t\tconst name = validateText(entry, `${field} entry`);\n\t\t\tif (!pattern.test(name)) throw new Error(`Invalid capability ceiling ${field} entry '${name}'.`);\n\t\t\tif (Buffer.byteLength(name, \"utf8\") > 128) throw new Error(`Invalid capability ceiling ${field} entry '${name}'; max 128 UTF-8 bytes.`);\n\t\t\treturn name;\n\t\t}))].sort();\n\t};\n\tconst allowedTools = normalizeList(\"allowedTools\", /^[A-Za-z0-9_.:-]+$/u);\n\tconst allowedAgents = normalizeList(\"allowedAgents\", /^[A-Za-z0-9_.:-]+$/u);\n\treturn {\n\t\tversion: SUBAGENT_CAPABILITY_CEILING_VERSION,\n\t\t...(allowedTools !== undefined ? { allowedTools } : {}),\n\t\t...(allowedAgents !== undefined ? { allowedAgents } : {}),\n\t\tdenyExtensions: ceiling.denyExtensions === true,\n\t\tsources: [],\n\t};\n}\n\nexport function parseSubagentCapabilityCeiling(value: unknown, field = \"capability ceiling\"): ResolvedSubagentCapabilityCeiling {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw new Error(`Invalid ${field}; expected an object.`);\n\tconst record = value as Record<string, unknown>;\n\tif (record.version !== SUBAGENT_CAPABILITY_CEILING_VERSION) throw new Error(`Invalid ${field} version.`);\n\tconst normalized = normalizeCeiling(record as SubagentCapabilityCeiling);\n\tconst sources = record.sources;\n\tif (!Array.isArray(sources) || sources.some((source) => typeof source !== \"string\")) throw new Error(`Invalid ${field} sources; expected an array of strings.`);\n\tnormalized.sources = [...new Set(sources.map((source) => validateText(source, `${field} source`)))].sort();\n\treturn normalized;\n}\n\nexport function registerSubagentCapabilityCeiling(options: RegisterSubagentCapabilityCeilingOptions): SubagentCapabilityCeilingHandle {\n\tconst sessionId = validateText(options.sessionId, \"sessionId\");\n\tconst source = validateText(options.source, \"source\");\n\tlet normalized = normalizeCeiling(options.ceiling);\n\tconst token = Symbol(source);\n\tconst store = registry();\n\tlet session = store.get(sessionId);\n\tif (!session) {\n\t\tsession = new Map();\n\t\tstore.set(sessionId, session);\n\t}\n\tconst setRegistration = () => {\n\t\tnormalized = normalizeCeiling(normalized);\n\t\tnormalized.sources = [source];\n\t\tsession!.set(token, { source, ceiling: normalized });\n\t};\n\tsetRegistration();\n\tlet disposed = false;\n\treturn {\n\t\tupdate(ceiling) {\n\t\t\tif (disposed) throw new Error(\"Cannot update a disposed capability ceiling handle.\");\n\t\t\tnormalized = normalizeCeiling(ceiling);\n\t\t\tnormalized.sources = [source];\n\t\t\tsession!.set(token, { source, ceiling: normalized });\n\t\t},\n\t\tdispose() {\n\t\t\tif (disposed) return;\n\t\t\tdisposed = true;\n\t\t\tsession!.delete(token);\n\t\t\tif (session!.size === 0) store.delete(sessionId);\n\t\t},\n\t};\n}\n\nexport function intersectSubagentCapabilityCeilings(...ceilings: Array<ResolvedSubagentCapabilityCeiling | undefined>): ResolvedSubagentCapabilityCeiling | undefined {\n\tconst active = ceilings.filter((ceiling): ceiling is ResolvedSubagentCapabilityCeiling => ceiling !== undefined);\n\tif (active.length === 0) return undefined;\n\tconst intersectLists = (field: \"allowedTools\" | \"allowedAgents\"): string[] | undefined => {\n\t\tconst definedLists = active.filter((ceiling) => ceiling[field] !== undefined).map((ceiling) => new Set(ceiling[field]));\n\t\tif (definedLists.length === 0) return undefined;\n\t\treturn [...definedLists[0]!].filter((entry) => definedLists.every((list) => list.has(entry))).sort();\n\t};\n\tconst allowedTools = intersectLists(\"allowedTools\");\n\tconst allowedAgents = intersectLists(\"allowedAgents\");\n\treturn {\n\t\tversion: SUBAGENT_CAPABILITY_CEILING_VERSION,\n\t\t...(allowedTools !== undefined ? { allowedTools } : {}),\n\t\t...(allowedAgents !== undefined ? { allowedAgents } : {}),\n\t\tdenyExtensions: active.some((ceiling) => ceiling.denyExtensions),\n\t\tsources: [...new Set(active.flatMap((ceiling) => ceiling.sources))].sort(),\n\t};\n}\n\nexport function resolveSubagentCapabilityCeiling(sessionId: string | undefined, inherited?: ResolvedSubagentCapabilityCeiling): ResolvedSubagentCapabilityCeiling | undefined {\n\tconst active: ResolvedSubagentCapabilityCeiling[] = [];\n\tif (sessionId) {\n\t\tconst registrations = registry().get(sessionId);\n\t\tif (registrations) active.push(...Array.from(registrations.values(), ({ ceiling }) => ceiling));\n\t}\n\treturn intersectSubagentCapabilityCeilings(inherited, ...active);\n}\n\nexport function resolveCurrentSubagentCapabilityCeiling(sessionId: string | undefined): ResolvedSubagentCapabilityCeiling | undefined {\n\treturn resolveSubagentCapabilityCeiling(sessionId, decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]));\n}\n\nexport function isAgentAllowedByCapabilityCeiling(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): boolean {\n\treturn ceiling?.allowedAgents === undefined || ceiling.allowedAgents.includes(agentName);\n}\n\nexport function capabilityCeilingAgentRestrictionMessage(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): string | undefined {\n\tif (isAgentAllowedByCapabilityCeiling(agentName, ceiling)) return undefined;\n\tconst sources = ceiling?.sources.length ? ceiling.sources.join(\", \") : \"unknown source\";\n\tconst allowed = ceiling?.allowedAgents?.length ? ceiling.allowedAgents.join(\", \") : \"(none)\";\n\treturn `Capability ceiling from ${sources} does not allow agent '${agentName}'. Allowed agents: ${allowed}.`;\n}\n\nexport function assertAgentAllowedByCapabilityCeiling(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): void {\n\tconst message = capabilityCeilingAgentRestrictionMessage(agentName, ceiling);\n\tif (message) throw new Error(message);\n}\n\nexport function capabilityCeilingAgentRestrictionSources(ceiling: ResolvedSubagentCapabilityCeiling | undefined): string[] | undefined {\n\treturn ceiling?.allowedAgents === undefined ? undefined : [...ceiling.sources];\n}\n\nexport function encodeSubagentCapabilityCeiling(ceiling: ResolvedSubagentCapabilityCeiling | undefined): string | undefined {\n\tif (!ceiling) return undefined;\n\treturn Buffer.from(JSON.stringify(ceiling), \"utf8\").toString(\"base64url\");\n}\n\nexport function decodeSubagentCapabilityCeiling(value: string | undefined): ResolvedSubagentCapabilityCeiling | undefined {\n\tif (value === undefined || value === \"\") return undefined;\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(Buffer.from(value, \"base64url\").toString(\"utf8\"));\n\t} catch (error) {\n\t\tthrow new Error(`Invalid inherited capability ceiling: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed) || (parsed as { version?: unknown }).version !== SUBAGENT_CAPABILITY_CEILING_VERSION) {\n\t\tthrow new Error(\"Invalid inherited capability ceiling version.\");\n\t}\n\treturn parseSubagentCapabilityCeiling(parsed, \"inherited capability ceiling\");\n}\n","export const BYOK_PI_PERMISSION_MODE = 'BYOK_PI_PERMISSION_MODE';\n\n/** SDK-owned extension tools that do not mutate the task workspace. */\nexport const BYOK_PI_READONLY_PARENT_TOOLS = ['subagent', 'todo'] as const;\n\n/** Child tools retained when a readonly parent delegates through pi-subagents. */\nexport const BYOK_PI_READONLY_SUBAGENT_TOOLS = ['read', 'grep', 'find', 'ls'] as const;\n\n/** Package-provided read-only roles; writer and ambient custom roles stay unavailable. */\nexport const BYOK_PI_READONLY_SUBAGENT_AGENTS = [\n 'reviewer',\n 'oracle',\n 'codex-exec',\n 'claude-code',\n 'cursor-agent',\n] as const;\n","import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport {\n registerSubagentCapabilityCeiling,\n type SubagentCapabilityCeilingHandle,\n} from 'pi-subagents/capability-ceiling';\nimport {\n BYOK_PI_PERMISSION_MODE,\n BYOK_PI_READONLY_SUBAGENT_AGENTS,\n BYOK_PI_READONLY_SUBAGENT_TOOLS,\n} from './subagents-policy-config';\n\nfunction permissionMode(): 'auto' | 'readonly' {\n const mode = process.env[BYOK_PI_PERMISSION_MODE];\n if (mode === 'auto' || mode === 'readonly') return mode;\n throw new Error(`${BYOK_PI_PERMISSION_MODE} must be \"auto\" or \"readonly\"`);\n}\n\n/**\n * Keep pi-subagents available in every Pi session without letting a readonly\n * parent widen its task contract through a child process.\n */\nexport default function registerByokSubagentsPolicy(pi: ExtensionAPI): void {\n const mode = permissionMode();\n let ceiling: SubagentCapabilityCeilingHandle | undefined;\n\n pi.on('session_start', (_event, ctx) => {\n ceiling?.dispose();\n ceiling = undefined;\n if (mode === 'auto') return;\n\n const sessionId = ctx.sessionManager.getSessionId();\n if (!sessionId) throw new Error('readonly Pi subagent policy requires an authoritative session id');\n ceiling = registerSubagentCapabilityCeiling({\n sessionId,\n source: 'byok-sdk-readonly',\n ceiling: {\n allowedTools: BYOK_PI_READONLY_SUBAGENT_TOOLS,\n allowedAgents: BYOK_PI_READONLY_SUBAGENT_AGENTS,\n denyExtensions: true,\n },\n });\n });\n\n pi.on('tool_call', (event) => {\n if (mode === 'readonly' && event.toolName === 'subagent' && ceiling === undefined) {\n return {\n block: true,\n terminate: true,\n reason: 'readonly Pi subagent capability ceiling is unavailable',\n };\n }\n });\n\n pi.on('session_shutdown', () => {\n ceiling?.dispose();\n ceiling = undefined;\n });\n}\n"]}
1
+ {"version":3,"sources":["../../../../../node_modules/.bun/pi-subagents@0.60.0+5c5e823a15db0d8e/node_modules/pi-subagents/src/runs/shared/capability-ceiling.ts","../../../src/adapters/pi/subagents-policy-config.ts","../../../src/adapters/pi/subagents-policy-extension.ts"],"names":[],"mappings":"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","file":"subagents-policy-extension.js","sourcesContent":["import { Buffer } from \"node:buffer\";\n\nexport const SUBAGENT_CAPABILITY_CEILING_VERSION = 1 as const;\nexport const SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY = \"pi-subagents.capability-ceiling.v1\";\nexport const SUBAGENT_CAPABILITY_CEILING_ENV = \"PI_SUBAGENT_CAPABILITY_CEILING_V1\";\n\nexport type SubagentCapabilityCeiling =\n\t| { allowedTools: readonly string[]; allowedAgents?: readonly string[]; denyExtensions?: boolean }\n\t| { allowedTools?: readonly string[]; allowedAgents?: readonly string[]; denyExtensions: boolean }\n\t| { allowedTools?: readonly string[]; allowedAgents: readonly string[]; denyExtensions?: boolean };\n\nexport interface ResolvedSubagentCapabilityCeiling {\n\tversion: typeof SUBAGENT_CAPABILITY_CEILING_VERSION;\n\tallowedTools?: string[];\n\tallowedAgents?: string[];\n\tdenyExtensions: boolean;\n\tsources: string[];\n}\n\nexport interface SubagentCapabilityAudit {\n\tceiling: ResolvedSubagentCapabilityCeiling;\n\trequestedTools?: string[];\n\teffectiveTools: string[];\n\tremovedTools: string[];\n\tinternalTools: string[];\n\textensionsDenied: boolean;\n\tremovedExtensionCount: number;\n\trequestedMcpToolCount: number;\n\teffectiveMcpTools: string[];\n\tagentAllowed: boolean;\n\tagentRestrictionSources?: string[];\n}\n\nexport interface RegisterSubagentCapabilityCeilingOptions {\n\tsessionId: string;\n\tsource: string;\n\tceiling: SubagentCapabilityCeiling;\n}\n\nexport interface SubagentCapabilityCeilingHandle {\n\tupdate(ceiling: SubagentCapabilityCeiling): void;\n\tdispose(): void;\n}\n\ntype Registration = { source: string; ceiling: ResolvedSubagentCapabilityCeiling };\ntype Registry = Map<string, Map<symbol, Registration>>;\n\nfunction registry(): Registry {\n\tconst key = Symbol.for(SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY);\n\tconst store = globalThis as typeof globalThis & { [key: symbol]: unknown };\n\tconst existing = store[key];\n\tif (existing instanceof Map) return existing as Registry;\n\tconst created: Registry = new Map();\n\tstore[key] = created;\n\treturn created;\n}\n\nfunction validateText(value: unknown, field: string): string {\n\tif (typeof value !== \"string\" || !value.trim() || /[\\u0000-\\u001f\\u007f]/u.test(value) || Buffer.byteLength(value.trim(), \"utf8\") > 256) {\n\t\tthrow new Error(`Invalid capability ceiling ${field}; expected a non-empty string without control characters (max 256 UTF-8 bytes).`);\n\t}\n\treturn value.trim();\n}\n\nfunction normalizeCeiling(ceiling: SubagentCapabilityCeiling): ResolvedSubagentCapabilityCeiling {\n\tif (!ceiling || typeof ceiling !== \"object\" || Array.isArray(ceiling)) throw new Error(\"Invalid capability ceiling; expected an object.\");\n\tconst hasAllowedTools = Object.hasOwn(ceiling, \"allowedTools\");\n\tconst hasAllowedAgents = Object.hasOwn(ceiling, \"allowedAgents\");\n\tconst hasDenyExtensions = Object.hasOwn(ceiling, \"denyExtensions\");\n\tif (!hasAllowedTools && !hasAllowedAgents && !hasDenyExtensions) throw new Error(\"Invalid capability ceiling; expected allowedTools, allowedAgents, or denyExtensions.\");\n\tif (hasDenyExtensions && typeof ceiling.denyExtensions !== \"boolean\") throw new Error(\"Invalid capability ceiling denyExtensions; expected a boolean.\");\n\tconst normalizeList = (field: \"allowedTools\" | \"allowedAgents\", pattern: RegExp): string[] | undefined => {\n\t\tif (!Object.hasOwn(ceiling, field)) return undefined;\n\t\tconst values = ceiling[field];\n\t\tif (!Array.isArray(values)) throw new Error(`Invalid capability ceiling ${field}; expected an array.`);\n\t\tif (values.length > 256) throw new Error(`Invalid capability ceiling ${field}; expected at most 256 names.`);\n\t\treturn [...new Set(values.map((entry) => {\n\t\t\tconst name = validateText(entry, `${field} entry`);\n\t\t\tif (!pattern.test(name)) throw new Error(`Invalid capability ceiling ${field} entry '${name}'.`);\n\t\t\tif (Buffer.byteLength(name, \"utf8\") > 128) throw new Error(`Invalid capability ceiling ${field} entry '${name}'; max 128 UTF-8 bytes.`);\n\t\t\treturn name;\n\t\t}))].sort();\n\t};\n\tconst allowedTools = normalizeList(\"allowedTools\", /^[A-Za-z0-9_.:-]+$/u);\n\tconst allowedAgents = normalizeList(\"allowedAgents\", /^[A-Za-z0-9_.:-]+$/u);\n\treturn {\n\t\tversion: SUBAGENT_CAPABILITY_CEILING_VERSION,\n\t\t...(allowedTools !== undefined ? { allowedTools } : {}),\n\t\t...(allowedAgents !== undefined ? { allowedAgents } : {}),\n\t\tdenyExtensions: ceiling.denyExtensions === true,\n\t\tsources: [],\n\t};\n}\n\nexport function parseSubagentCapabilityCeiling(value: unknown, field = \"capability ceiling\"): ResolvedSubagentCapabilityCeiling {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw new Error(`Invalid ${field}; expected an object.`);\n\tconst record = value as Record<string, unknown>;\n\tif (record.version !== SUBAGENT_CAPABILITY_CEILING_VERSION) throw new Error(`Invalid ${field} version.`);\n\tconst normalized = normalizeCeiling(record as SubagentCapabilityCeiling);\n\tconst sources = record.sources;\n\tif (!Array.isArray(sources) || sources.some((source) => typeof source !== \"string\")) throw new Error(`Invalid ${field} sources; expected an array of strings.`);\n\tnormalized.sources = [...new Set(sources.map((source) => validateText(source, `${field} source`)))].sort();\n\treturn normalized;\n}\n\nexport function registerSubagentCapabilityCeiling(options: RegisterSubagentCapabilityCeilingOptions): SubagentCapabilityCeilingHandle {\n\tconst sessionId = validateText(options.sessionId, \"sessionId\");\n\tconst source = validateText(options.source, \"source\");\n\tlet normalized = normalizeCeiling(options.ceiling);\n\tconst token = Symbol(source);\n\tconst store = registry();\n\tlet session = store.get(sessionId);\n\tif (!session) {\n\t\tsession = new Map();\n\t\tstore.set(sessionId, session);\n\t}\n\tconst setRegistration = () => {\n\t\tnormalized = normalizeCeiling(normalized);\n\t\tnormalized.sources = [source];\n\t\tsession!.set(token, { source, ceiling: normalized });\n\t};\n\tsetRegistration();\n\tlet disposed = false;\n\treturn {\n\t\tupdate(ceiling) {\n\t\t\tif (disposed) throw new Error(\"Cannot update a disposed capability ceiling handle.\");\n\t\t\tnormalized = normalizeCeiling(ceiling);\n\t\t\tnormalized.sources = [source];\n\t\t\tsession!.set(token, { source, ceiling: normalized });\n\t\t},\n\t\tdispose() {\n\t\t\tif (disposed) return;\n\t\t\tdisposed = true;\n\t\t\tsession!.delete(token);\n\t\t\tif (session!.size === 0) store.delete(sessionId);\n\t\t},\n\t};\n}\n\nexport function intersectSubagentCapabilityCeilings(...ceilings: Array<ResolvedSubagentCapabilityCeiling | undefined>): ResolvedSubagentCapabilityCeiling | undefined {\n\tconst active = ceilings.filter((ceiling): ceiling is ResolvedSubagentCapabilityCeiling => ceiling !== undefined);\n\tif (active.length === 0) return undefined;\n\tconst intersectLists = (field: \"allowedTools\" | \"allowedAgents\"): string[] | undefined => {\n\t\tconst definedLists = active.filter((ceiling) => ceiling[field] !== undefined).map((ceiling) => new Set(ceiling[field]));\n\t\tif (definedLists.length === 0) return undefined;\n\t\treturn [...definedLists[0]!].filter((entry) => definedLists.every((list) => list.has(entry))).sort();\n\t};\n\tconst allowedTools = intersectLists(\"allowedTools\");\n\tconst allowedAgents = intersectLists(\"allowedAgents\");\n\treturn {\n\t\tversion: SUBAGENT_CAPABILITY_CEILING_VERSION,\n\t\t...(allowedTools !== undefined ? { allowedTools } : {}),\n\t\t...(allowedAgents !== undefined ? { allowedAgents } : {}),\n\t\tdenyExtensions: active.some((ceiling) => ceiling.denyExtensions),\n\t\tsources: [...new Set(active.flatMap((ceiling) => ceiling.sources))].sort(),\n\t};\n}\n\nexport function resolveSubagentCapabilityCeiling(sessionId: string | undefined, inherited?: ResolvedSubagentCapabilityCeiling): ResolvedSubagentCapabilityCeiling | undefined {\n\tconst active: ResolvedSubagentCapabilityCeiling[] = [];\n\tif (sessionId) {\n\t\tconst registrations = registry().get(sessionId);\n\t\tif (registrations) active.push(...Array.from(registrations.values(), ({ ceiling }) => ceiling));\n\t}\n\treturn intersectSubagentCapabilityCeilings(inherited, ...active);\n}\n\nexport function resolveCurrentSubagentCapabilityCeiling(sessionId: string | undefined): ResolvedSubagentCapabilityCeiling | undefined {\n\treturn resolveSubagentCapabilityCeiling(sessionId, decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]));\n}\n\nexport function isAgentAllowedByCapabilityCeiling(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): boolean {\n\treturn ceiling?.allowedAgents === undefined || ceiling.allowedAgents.includes(agentName);\n}\n\nexport function capabilityCeilingAgentRestrictionMessage(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): string | undefined {\n\tif (isAgentAllowedByCapabilityCeiling(agentName, ceiling)) return undefined;\n\tconst sources = ceiling?.sources.length ? ceiling.sources.join(\", \") : \"unknown source\";\n\tconst allowed = ceiling?.allowedAgents?.length ? ceiling.allowedAgents.join(\", \") : \"(none)\";\n\treturn `Capability ceiling from ${sources} does not allow agent '${agentName}'. Allowed agents: ${allowed}.`;\n}\n\nexport function assertAgentAllowedByCapabilityCeiling(agentName: string, ceiling: ResolvedSubagentCapabilityCeiling | undefined): void {\n\tconst message = capabilityCeilingAgentRestrictionMessage(agentName, ceiling);\n\tif (message) throw new Error(message);\n}\n\nexport function capabilityCeilingAgentRestrictionSources(ceiling: ResolvedSubagentCapabilityCeiling | undefined): string[] | undefined {\n\treturn ceiling?.allowedAgents === undefined ? undefined : [...ceiling.sources];\n}\n\nexport function encodeSubagentCapabilityCeiling(ceiling: ResolvedSubagentCapabilityCeiling | undefined): string | undefined {\n\tif (!ceiling) return undefined;\n\treturn Buffer.from(JSON.stringify(ceiling), \"utf8\").toString(\"base64url\");\n}\n\nexport function decodeSubagentCapabilityCeiling(value: string | undefined): ResolvedSubagentCapabilityCeiling | undefined {\n\tif (value === undefined || value === \"\") return undefined;\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(Buffer.from(value, \"base64url\").toString(\"utf8\"));\n\t} catch (error) {\n\t\tthrow new Error(`Invalid inherited capability ceiling: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed) || (parsed as { version?: unknown }).version !== SUBAGENT_CAPABILITY_CEILING_VERSION) {\n\t\tthrow new Error(\"Invalid inherited capability ceiling version.\");\n\t}\n\treturn parseSubagentCapabilityCeiling(parsed, \"inherited capability ceiling\");\n}\n","export const BYOK_PI_PERMISSION_MODE = 'BYOK_PI_PERMISSION_MODE';\n\n/** SDK-owned extension tools that do not mutate the task workspace. */\nexport const BYOK_PI_READONLY_PARENT_TOOLS = ['subagent', 'todo'] as const;\n\n/** Child tools retained when a readonly parent delegates through pi-subagents. */\nexport const BYOK_PI_READONLY_SUBAGENT_TOOLS = ['read', 'grep', 'find', 'ls'] as const;\n\n/** Package-provided read-only roles; writer and ambient custom roles stay unavailable. */\nexport const BYOK_PI_READONLY_SUBAGENT_AGENTS = [\n 'reviewer',\n 'oracle',\n 'codex-exec',\n 'claude-code',\n 'cursor-agent',\n] as const;\n","import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport {\n registerSubagentCapabilityCeiling,\n type SubagentCapabilityCeilingHandle,\n} from 'pi-subagents/capability-ceiling';\nimport {\n BYOK_PI_PERMISSION_MODE,\n BYOK_PI_READONLY_SUBAGENT_AGENTS,\n BYOK_PI_READONLY_SUBAGENT_TOOLS,\n} from './subagents-policy-config';\n\nfunction permissionMode(): 'auto' | 'readonly' {\n const mode = process.env[BYOK_PI_PERMISSION_MODE];\n if (mode === 'auto' || mode === 'readonly') return mode;\n throw new Error(`${BYOK_PI_PERMISSION_MODE} must be \"auto\" or \"readonly\"`);\n}\n\n/**\n * Keep pi-subagents available in every Pi session without letting a readonly\n * parent widen its task contract through a child process.\n */\nexport default function registerByokSubagentsPolicy(pi: ExtensionAPI): void {\n const mode = permissionMode();\n let ceiling: SubagentCapabilityCeilingHandle | undefined;\n\n pi.on('session_start', (_event, ctx) => {\n ceiling?.dispose();\n ceiling = undefined;\n if (mode === 'auto') return;\n\n const sessionId = ctx.sessionManager.getSessionId();\n if (!sessionId) throw new Error('readonly Pi subagent policy requires an authoritative session id');\n ceiling = registerSubagentCapabilityCeiling({\n sessionId,\n source: 'byok-sdk-readonly',\n ceiling: {\n allowedTools: BYOK_PI_READONLY_SUBAGENT_TOOLS,\n allowedAgents: BYOK_PI_READONLY_SUBAGENT_AGENTS,\n denyExtensions: true,\n },\n });\n });\n\n pi.on('tool_call', (event) => {\n if (mode === 'readonly' && event.toolName === 'subagent' && ceiling === undefined) {\n return {\n block: true,\n terminate: true,\n reason: 'readonly Pi subagent capability ceiling is unavailable',\n };\n }\n });\n\n pi.on('session_shutdown', () => {\n ceiling?.dispose();\n ceiling = undefined;\n });\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
+ /** Tracks the public coalesced Pi UI span, never infers state from timers or isIdle. */
3
+ export declare class PiInteractionGate {
4
+ private readonly emit;
5
+ private phase;
6
+ private sessionId;
7
+ private revision;
8
+ private readonly waiters;
9
+ constructor(emit: (event: {
10
+ type: 'byok_team_gate';
11
+ sessionId?: string;
12
+ revision: number;
13
+ phase: string;
14
+ }) => void);
15
+ start(sessionId: string): void;
16
+ openPrompt(): void;
17
+ closePrompt(): void;
18
+ fail(): void;
19
+ close(): void;
20
+ private report;
21
+ wait(signal?: AbortSignal): Promise<void>;
22
+ }
23
+ /** Load first, RPC only. The GUI host owns the process and exact-ID UI responses. */
24
+ export default function teamInteractionExtension(pi: ExtensionAPI): void;
@@ -0,0 +1,81 @@
1
+ // src/adapters/pi/team-interaction-extension.ts
2
+ var PiInteractionGate = class {
3
+ constructor(emit) {
4
+ this.emit = emit;
5
+ }
6
+ emit;
7
+ phase = "starting";
8
+ sessionId;
9
+ revision = 0;
10
+ waiters = /* @__PURE__ */ new Set();
11
+ start(sessionId) {
12
+ if (this.phase !== "starting" || !sessionId) return this.fail();
13
+ this.sessionId = sessionId;
14
+ this.phase = "open";
15
+ this.report();
16
+ }
17
+ openPrompt() {
18
+ if (this.phase !== "open") return this.fail();
19
+ this.phase = "waiting";
20
+ this.report();
21
+ }
22
+ closePrompt() {
23
+ if (this.phase !== "waiting") return this.fail();
24
+ this.phase = "open";
25
+ this.report();
26
+ for (const resolve of this.waiters) resolve();
27
+ this.waiters.clear();
28
+ }
29
+ fail() {
30
+ this.phase = "failed";
31
+ this.report();
32
+ }
33
+ close() {
34
+ this.phase = "closed";
35
+ this.report();
36
+ }
37
+ report() {
38
+ this.emit({ type: "byok_team_gate", ...this.sessionId ? { sessionId: this.sessionId } : {}, revision: ++this.revision, phase: this.phase });
39
+ }
40
+ async wait(signal) {
41
+ await Promise.resolve();
42
+ while (this.phase !== "open") {
43
+ if (signal?.aborted && this.phase === "waiting") return;
44
+ await new Promise((resolve) => {
45
+ const done = () => {
46
+ this.waiters.delete(done);
47
+ signal?.removeEventListener("abort", aborted);
48
+ resolve();
49
+ };
50
+ const aborted = () => {
51
+ if (this.phase === "waiting") done();
52
+ };
53
+ this.waiters.add(done);
54
+ signal?.addEventListener("abort", aborted, { once: true });
55
+ });
56
+ await Promise.resolve();
57
+ }
58
+ }
59
+ };
60
+ function teamInteractionExtension(pi) {
61
+ let ui;
62
+ const gate = new PiInteractionGate((event) => ui.setStatus("byok_team_gate", JSON.stringify(event)));
63
+ pi.on("session_start", (_event, ctx) => {
64
+ ui = ctx.ui;
65
+ gate.start(ctx.sessionManager.getSessionId());
66
+ });
67
+ pi.on("ui_prompt_start", () => gate.openPrompt());
68
+ pi.on("ui_prompt_end", () => gate.closePrompt());
69
+ pi.on("input", async () => {
70
+ await gate.wait();
71
+ return { action: "continue" };
72
+ });
73
+ pi.on("before_provider_request", async (_event, ctx) => {
74
+ await gate.wait(ctx.signal);
75
+ });
76
+ pi.on("session_shutdown", () => gate.close());
77
+ }
78
+
79
+ export { PiInteractionGate, teamInteractionExtension as default };
80
+ //# sourceMappingURL=team-interaction-extension.js.map
81
+ //# sourceMappingURL=team-interaction-extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/adapters/pi/team-interaction-extension.ts"],"names":[],"mappings":"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","file":"team-interaction-extension.js","sourcesContent":["import type { ExtensionAPI, ExtensionUIContext } from '@earendil-works/pi-coding-agent';\n\n/** Tracks the public coalesced Pi UI span, never infers state from timers or isIdle. */\nexport class PiInteractionGate {\n private phase: 'starting' | 'open' | 'waiting' | 'failed' | 'closed' = 'starting';\n private sessionId: string | undefined;\n private revision = 0;\n private readonly waiters = new Set<() => void>();\n constructor(private readonly emit: (event: { type: 'byok_team_gate'; sessionId?: string; revision: number; phase: string }) => void) {}\n start(sessionId: string): void {\n if (this.phase !== 'starting' || !sessionId) return this.fail();\n this.sessionId = sessionId; this.phase = 'open'; this.report();\n }\n openPrompt(): void {\n if (this.phase !== 'open') return this.fail();\n this.phase = 'waiting'; this.report();\n }\n closePrompt(): void {\n if (this.phase !== 'waiting') return this.fail();\n this.phase = 'open'; this.report();\n for (const resolve of this.waiters) resolve(); this.waiters.clear();\n }\n fail(): void { this.phase = 'failed'; this.report(); }\n close(): void { this.phase = 'closed'; this.report(); }\n private report(): void { this.emit({ type: 'byok_team_gate', ...(this.sessionId ? { sessionId: this.sessionId } : {}), revision: ++this.revision, phase: this.phase }); }\n async wait(signal?: AbortSignal): Promise<void> {\n // Pi publishes span lifecycle through queueMicrotask. Drain events already\n // scheduled before checking the admission state in this process.\n await Promise.resolve();\n while (this.phase !== 'open') {\n // Unknown state never throws into Pi's best-effort extension dispatcher,\n // which would swallow the exception and continue. Host tears down on fatal.\n if (signal?.aborted && this.phase === 'waiting') return;\n await new Promise<void>(resolve => {\n const done = () => { this.waiters.delete(done); signal?.removeEventListener('abort', aborted); resolve(); };\n const aborted = () => { if (this.phase === 'waiting') done(); };\n this.waiters.add(done); signal?.addEventListener('abort', aborted, { once: true });\n });\n // A continuation may open another dialog before its start event runs.\n await Promise.resolve();\n }\n }\n}\n\n/** Load first, RPC only. The GUI host owns the process and exact-ID UI responses. */\nexport default function teamInteractionExtension(pi: ExtensionAPI): void {\n let ui: ExtensionUIContext;\n const gate = new PiInteractionGate(event => ui.setStatus('byok_team_gate', JSON.stringify(event)));\n pi.on('session_start', (_event, ctx) => { ui = ctx.ui; gate.start(ctx.sessionManager.getSessionId()); });\n pi.on('ui_prompt_start', () => gate.openPrompt());\n pi.on('ui_prompt_end', () => gate.closePrompt());\n pi.on('input', async () => { await gate.wait(); return { action: 'continue' }; });\n pi.on('before_provider_request', async (_event, ctx) => { await gate.wait(ctx.signal); });\n pi.on('session_shutdown', () => gate.close());\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { spawn, type ChildProcess, type SpawnOptions } from 'node:child_process';
2
- type KillFn = (pid: number, signal: NodeJS.Signals | number) => void;
1
+ import { spawn, spawnSync, type ChildProcess, type SpawnOptions } from 'node:child_process';
2
+ export type KillFn = (pid: number, signal: NodeJS.Signals | number) => void;
3
3
  export interface OwnedProcessTreeOptions {
4
4
  child: ChildProcess;
5
5
  waitClosed: () => Promise<void>;
@@ -20,6 +20,68 @@ export interface OwnedProcessTreeOptions {
20
20
  * the runtime outlive the daemon. Windows uses taskkill's `/T` tree authority.
21
21
  */
22
22
  export declare function withOwnedProcessTree<T extends SpawnOptions>(options: T): T;
23
+ /**
24
+ * The `exit` listener itself. Three constraints, all load-bearing:
25
+ * it never awaits (Node runs nothing asynchronous after `exit` begins), it
26
+ * never throws (a throw here would replace the host's own exit reason), and
27
+ * each target is swept in its own try/catch so one unreachable tree cannot
28
+ * strand the rest. POSIX kills the owned process GROUP — `withOwnedProcessTree`
29
+ * made the root a group leader precisely so this one signal reaches the whole
30
+ * tree. win32 has no process groups to signal, so it pays for a synchronous
31
+ * `taskkill /T /F` per target.
32
+ */
33
+ declare function terminateOwnedTreesForHostExit(): void;
34
+ /**
35
+ * Test-only view of the host-exit backstop. Not part of any package entry
36
+ * point (`process-tree.ts` is internal to `@byok-sdk/client`); it exists so
37
+ * the registry contract can be asserted without a second production seam.
38
+ */
39
+ export declare const __hostExitBackstopForTests: {
40
+ /** The exact function Node invokes on `exit`. */
41
+ run: typeof terminateOwnedTreesForHostExit;
42
+ registeredPids(): number[];
43
+ listenerCount(): number;
44
+ };
45
+ export interface AdoptOwnedProcessTreeOptions {
46
+ child: ChildProcess;
47
+ label: string;
48
+ /** DI seam — defaults to `process.platform`, mirroring {@link OwnedProcessTreeOptions}. */
49
+ platform?: NodeJS.Platform;
50
+ /** DI seam for the win32 job-object backstop — defaults to the lazily imported `./win32-job-object`. */
51
+ jobObject?: {
52
+ assign(pid: number): Promise<void>;
53
+ };
54
+ /** DI seam for the POSIX host-exit sweep — defaults to `process.kill`. */
55
+ killFn?: KillFn;
56
+ /** DI seam for the win32 host-exit sweep — defaults to `node:child_process`'s `spawnSync`. */
57
+ spawnSyncFn?: typeof spawnSync;
58
+ }
59
+ /**
60
+ * Take ownership of a just-spawned runtime tree, beyond what disposal covers.
61
+ *
62
+ * Two backstops with different coverage, both kernel- or runtime-owned rather
63
+ * than sweep-based:
64
+ *
65
+ * 1. Every platform: the tree joins a registry a once-installed, synchronous
66
+ * `process` `exit` listener sweeps. This covers an ordinary daemon exit —
67
+ * the case where disposal simply never ran.
68
+ * 2. win32 only: the root is assigned to the daemon-wide
69
+ * `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` job (see `./win32-job-object.ts`).
70
+ * That one is the kernel's, so it also covers the daemon deaths no listener
71
+ * can observe.
72
+ *
73
+ * Fail closed: a rejected assignment propagates, and the caller must terminate
74
+ * the child and refuse to publish a run handle rather than run a tree nothing
75
+ * owns. The registration happens FIRST so a child that fails assignment is
76
+ * still swept if the daemon exits during the caller's cleanup.
77
+ *
78
+ * Residual boundary, stated rather than hidden: assignment happens after
79
+ * `spawn` returns, so a grandchild forked inside that window is outside the
80
+ * job; and neither backstop can run when the daemon itself is SIGKILLed or
81
+ * OOM-killed on POSIX, where only the job object's kernel guarantee — which
82
+ * POSIX does not have — would help.
83
+ */
84
+ export declare function adoptOwnedProcessTree(options: AdoptOwnedProcessTreeOptions): Promise<void>;
23
85
  /**
24
86
  * Immediate termination request used by interrupt paths; close remains the
25
87
  * receipt. On win32 the request also RECORDS the pid set taskkill walked,
@@ -53,8 +115,16 @@ export declare function requestOwnedProcessTreeTermination(options: OwnedProcess
53
115
  * window means a recycled pid could read as alive. And if taskkill's output is
54
116
  * empty or unparseable, the walked set collapses to `{root}`: disposal then
55
117
  * measures the root alone and reports quiescence on that basis, which is a
56
- * narrower claim than the tree, not a false one. Preventing orphans left behind
57
- * by a DAEMON crash is a separate job-object concern, explicitly out of scope.
118
+ * narrower claim than the tree, not a false one.
119
+ *
120
+ * Orphans left behind by a DAEMON death are no longer out of scope, but they
121
+ * are not THIS function's job either — nothing here runs when the daemon is
122
+ * gone. {@link adoptOwnedProcessTree} owns that case with two backstops
123
+ * installed at spawn time: a synchronous host-`exit` sweep on every platform,
124
+ * and on win32 a kernel-owned kill-on-close Job Object. Their own residuals
125
+ * remain: the tiny window between `spawn` returning and the job assignment,
126
+ * and a daemon SIGKILL/OOM on POSIX, where no in-process listener can run and
127
+ * there is no job object to fall back on.
58
128
  */
59
129
  export declare function disposeOwnedProcessTree(options: OwnedProcessTreeOptions): Promise<void>;
60
130
  export {};
@@ -13,6 +13,6 @@ export declare const PROVIDER_CREDENTIAL_ENV_NAMES: readonly ['ANTHROPIC_API_KEY
13
13
  * denying a credential is safe, while allowing every cloud credential Pi
14
14
  * could consume would regress the daemon's ambient-environment isolation.
15
15
  */
16
- export declare const PROVIDER_CREDENTIAL_ENV_DENY_NAMES: readonly ["ANTHROPIC_API_KEY", "ANTHROPIC_OAUTH_TOKEN", "OPENAI_API_KEY", "GEMINI_API_KEY", "AZURE_OPENAI_API_KEY", "DEEPSEEK_API_KEY", "GROQ_API_KEY", "MISTRAL_API_KEY", "OPENROUTER_API_KEY", "XAI_API_KEY", "ZAI_API_KEY", "ANT_LING_API_KEY", "NVIDIA_API_KEY", "CEREBRAS_API_KEY", "CLOUDFLARE_API_KEY", "AI_GATEWAY_API_KEY", "ZAI_CODING_CN_API_KEY", "OPENCODE_API_KEY", "RADIUS_API_KEY", "FIREWORKS_API_KEY", "TOGETHER_API_KEY", "BASETEN_API_KEY", "KIMI_API_KEY", "MINIMAX_API_KEY", "MINIMAX_CN_API_KEY", "QWEN_TOKEN_PLAN_API_KEY", "QWEN_TOKEN_PLAN_CN_API_KEY", "XIAOMI_API_KEY", "XIAOMI_TOKEN_PLAN_CN_API_KEY", "XIAOMI_TOKEN_PLAN_AMS_API_KEY", "XIAOMI_TOKEN_PLAN_SGP_API_KEY", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "GOOGLE_APPLICATION_CREDENTIALS", "PI_PROVIDER_API_KEY"];
16
+ export declare const PROVIDER_CREDENTIAL_ENV_DENY_NAMES: readonly ["ANTHROPIC_API_KEY", "ANTHROPIC_OAUTH_TOKEN", "OPENAI_API_KEY", "GEMINI_API_KEY", "AZURE_OPENAI_API_KEY", "DEEPSEEK_API_KEY", "GROQ_API_KEY", "MISTRAL_API_KEY", "OPENROUTER_API_KEY", "XAI_API_KEY", "ZAI_API_KEY", "ANT_LING_API_KEY", "NVIDIA_API_KEY", "CEREBRAS_API_KEY", "CLOUDFLARE_API_KEY", "AI_GATEWAY_API_KEY", "ZAI_CODING_CN_API_KEY", "OPENCODE_API_KEY", "RADIUS_API_KEY", "FIREWORKS_API_KEY", "TOGETHER_API_KEY", "BASETEN_API_KEY", "KIMI_API_KEY", "HF_TOKEN", "MOONSHOT_API_KEY", "MINIMAX_API_KEY", "MINIMAX_CN_API_KEY", "QWEN_TOKEN_PLAN_API_KEY", "QWEN_TOKEN_PLAN_CN_API_KEY", "XIAOMI_API_KEY", "XIAOMI_TOKEN_PLAN_CN_API_KEY", "XIAOMI_TOKEN_PLAN_AMS_API_KEY", "XIAOMI_TOKEN_PLAN_SGP_API_KEY", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "GOOGLE_APPLICATION_CREDENTIALS", "PI_PROVIDER_API_KEY"];
17
17
  /** Return a copy that cannot pass ambient provider credentials to a child. */
18
18
  export declare function withoutProviderCredentials(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Windows kill-on-close Job Object ownership for owned runtime process trees.
3
+ *
4
+ * `adapters/process-tree.ts` can terminate a tree the daemon still controls.
5
+ * It cannot terminate one the daemon is no longer around to sweep: Windows
6
+ * never re-parents orphans, so descendants of a daemon that died (crash,
7
+ * SIGKILL, OOM) are unreachable to any later `taskkill /T`. A Job Object with
8
+ * `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` moves that guarantee into the kernel —
9
+ * ONE job is created per daemon process, every owned runtime root is assigned
10
+ * to it right after spawn, and the job handle is deliberately never closed.
11
+ * When the daemon's last handle to it goes away — for ANY reason, including a
12
+ * kill the daemon cannot observe — Windows terminates every process in the
13
+ * job.
14
+ *
15
+ * Fail-closed, no degraded path. `koffi` is an `optionalDependencies` entry of
16
+ * this package precisely because npm has no other per-platform install
17
+ * mechanism; the runtime rule, not the manifest field, carries the hardness.
18
+ * A missing module, a NULL handle, or a zero return from any of the four Win32
19
+ * calls throws {@link Win32JobObjectFailure} carrying the `GetLastError()`
20
+ * code, and the caller refuses to publish a run handle. Nothing here retries
21
+ * and nothing degrades to an unbacked tree.
22
+ *
23
+ * POSIX never loads this module: `process-tree.ts` reaches it through a
24
+ * dynamic `import()` taken only on the win32 branch, and `koffi` itself is
25
+ * imported only inside {@link loadBindings}. `__tests__/win32-job-object.test.ts`
26
+ * asserts that isolation from a real non-win32 subprocess.
27
+ *
28
+ * Signatures follow kernel32's documented ABI and mirror the binding table in
29
+ * deepseek-harness `sandbox-windows-acl` (`src/ffi.ts`), whose layout constants
30
+ * were verified against the real Windows headers by its own ABI probe.
31
+ */
32
+ /**
33
+ * A Win32 HANDLE as koffi 3 hands it back: a BigInt address, or null/0n for
34
+ * failure. Deliberately opaque — nothing here does pointer arithmetic, and a
35
+ * concrete numeric type would invite it.
36
+ */
37
+ export type Win32Handle = unknown;
38
+ /** The steps a job assignment can fail at, in the order they are attempted. */
39
+ export type Win32JobObjectStep = 'loadKoffi' | 'CreateJobObjectW' | 'SetInformationJobObject' | 'OpenProcess' | 'AssignProcessToJobObject';
40
+ export interface Win32JobObjectFailureInput {
41
+ step: Win32JobObjectStep;
42
+ reason: string;
43
+ /** The `GetLastError()` code read immediately after the failing call. Absent only when the failure happened before any Win32 call (module load). */
44
+ win32Code?: number;
45
+ }
46
+ /**
47
+ * Start-time precondition failure of the win32 job-object backstop.
48
+ *
49
+ * Module-local by design. `RuntimeExecutionFailure` is the adapter boundary's
50
+ * terminal control value and is frozen, so it cannot carry `win32Code`; this
51
+ * failure is a transport-level precondition that each client folds into its
52
+ * OWN existing exit-error channel, which is what the adapter boundary already
53
+ * classifies. Nothing new is exported from the package index.
54
+ */
55
+ export declare class Win32JobObjectFailure extends Error {
56
+ readonly step: Win32JobObjectStep;
57
+ readonly win32Code: number | undefined;
58
+ constructor(input: Win32JobObjectFailureInput, options?: ErrorOptions);
59
+ }
60
+ /** Narrowing guard usable across the two independently bundled package entries. */
61
+ export declare function isWin32JobObjectFailure(value: unknown): value is Win32JobObjectFailure;
62
+ /**
63
+ * The kernel32 calls the backstop needs. An interface, not a hard-wired koffi
64
+ * table, so the branch is exercisable from POSIX with a recording fake — the
65
+ * same DI convention `process-tree.ts` uses for `platform`/`spawnFn`/`killFn`.
66
+ */
67
+ export interface JobObjectBindings {
68
+ /** `HANDLE CreateJobObjectW(LPSECURITY_ATTRIBUTES, LPCWSTR)` — both NULL for an unnamed, default-security job. */
69
+ createJobObjectW(attributes: Win32Handle, name: string | null): Win32Handle;
70
+ /** `BOOL SetInformationJobObject(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD)`. */
71
+ setInformationJobObject(job: Win32Handle, infoClass: number, info: Buffer, infoLength: number): number;
72
+ /** `HANDLE OpenProcess(DWORD, BOOL, DWORD)`. */
73
+ openProcess(desiredAccess: number, inheritHandle: number, pid: number): Win32Handle;
74
+ /** `BOOL AssignProcessToJobObject(HANDLE, HANDLE)`. */
75
+ assignProcessToJobObject(job: Win32Handle, process: Win32Handle): number;
76
+ /** `BOOL CloseHandle(HANDLE)`. */
77
+ closeHandle(handle: Win32Handle): number;
78
+ /** `DWORD GetLastError()`. */
79
+ getLastError(): number;
80
+ }
81
+ /**
82
+ * Resolve the kernel32 binding table, importing `koffi` on first use only.
83
+ *
84
+ * The import is dynamic and lives here alone so that merely importing
85
+ * `process-tree.ts` — which every adapter does, on every platform — never
86
+ * pulls a native addon into the process.
87
+ */
88
+ export declare function loadBindings(): Promise<JobObjectBindings>;
89
+ export interface AssignOwnedProcessToJobOptions {
90
+ /** DI seam — defaults to the lazily loaded real kernel32 table. */
91
+ bindings?: JobObjectBindings;
92
+ }
93
+ /**
94
+ * Assign one owned runtime root to the daemon-wide kill-on-close job.
95
+ *
96
+ * Every failure throws {@link Win32JobObjectFailure} naming the step and
97
+ * carrying the Win32 error code. The process handle is opened solely to make
98
+ * the assignment and is closed in a `finally`, including on the failure path —
99
+ * the JOB holds the process afterwards, not this handle.
100
+ */
101
+ export declare function assignOwnedProcessToJob(pid: number, options?: AssignOwnedProcessToJobOptions): Promise<void>;
@@ -93,6 +93,27 @@ export declare class AgentHomeLayout {
93
93
  private canonicalRoot?;
94
94
  constructor(hostStorageRoot: string);
95
95
  resolve(agentRefInput: AgentRef): Promise<AgentHomeResolution>;
96
+ /**
97
+ * Pure canonical-home derivation for read-only callers, such as the
98
+ * pre-admission single-writer count. It validates the AgentRef and joins
99
+ * exactly the same `<hostStorageRoot>/agents/<agentId>` segments
100
+ * {@link AgentHomeLayout.resolve} would, canonicalizing only the components
101
+ * that already exist.
102
+ *
103
+ * It deliberately creates no directory, takes no cross-process mutation
104
+ * gate and records no Agent binding, so an offer the host vetoes after the
105
+ * count leaves nothing behind on disk. `resolve()` stays the only path that
106
+ * may materialize a home or bind it to an Agent identity.
107
+ *
108
+ * An `agents` root or `agents/<agentId>` leaf that already exists but is a
109
+ * symlink (or any non-directory) is rejected here with the same error class
110
+ * and message `resolve()` raises for it, so an in-root `two -> one` link
111
+ * fails closed instead of silently keying the count of `one`. A leaf that
112
+ * does not exist yet is not an error: this derivation runs before the home
113
+ * is materialized. A home canonicalizing outside the `agents` root stays
114
+ * rejected as before.
115
+ */
116
+ canonicalHomePath(agentRefInput: AgentRef): Promise<string>;
96
117
  /**
97
118
  * Prove the canonical root is materializable and writable before the daemon
98
119
  * advertises Agent-home capability. No Agent identity or persistent Agent
@@ -120,10 +141,33 @@ export declare class AgentHomeLeaseManager {
120
141
  acquire(resolution: AgentHomeResolution): Promise<AgentHomeLease>;
121
142
  private openLeaseMarker;
122
143
  }
144
+ /**
145
+ * WP0: counts-only readback of the per-canonical-Agent-home Attempt cap and
146
+ * what one daemon currently holds against it. Deliberately carries no home
147
+ * path, agentId or taskId: it exists so an operator can see that a home is
148
+ * still busy — including the fail-closed case where a failed `Session.close()`
149
+ * keeps the slot held after the task itself is gone — not to enumerate Agents.
150
+ * Projected into both `Daemon.status()` and the authenticated local control
151
+ * status (`create-daemon.ts`).
152
+ */
153
+ export interface AgentHomeExecutionStatus {
154
+ /** Effective `DaemonConfig.maxConcurrentMutableSessionsPerAgentHome` for this daemon. */
155
+ maxConcurrentMutableSessionsPerAgentHome: number;
156
+ /** Canonical Agent homes this daemon currently holds at least one execution lease in. */
157
+ activeHomes: number;
158
+ /** Total Attempts holding an execution lease across those homes. */
159
+ activeAttempts: number;
160
+ }
123
161
  /**
124
162
  * Session-scoped execution leases share one process-owned home marker. The
125
163
  * marker remains until the final session exits, so relocation still sees the
126
164
  * Agent home as active, while different sessions no longer exclude each other.
165
+ *
166
+ * This layer counts; it does not cap. How many Attempts may be active in one
167
+ * canonical home is a daemon admission decision made once, before any side
168
+ * effect, by `TaskRunner.handleOffer`'s per-home busy gate reading
169
+ * {@link AgentHomeExecutionLeaseManager.activeAttemptCount} against
170
+ * `DaemonConfig.maxConcurrentMutableSessionsPerAgentHome` (default 1).
127
171
  */
128
172
  export declare class AgentHomeExecutionLeaseManager {
129
173
  private readonly manager;
@@ -134,6 +178,36 @@ export declare class AgentHomeExecutionLeaseManager {
134
178
  readonly taskId: string;
135
179
  readonly sessionRef?: string;
136
180
  }): Promise<AgentHomeExecutionLease>;
181
+ /**
182
+ * WP0: Attempts currently holding an execution lease on this exact
183
+ * canonical home, across every lane and every session. This is the number
184
+ * the daemon's admission gate reads before any side effect — see
185
+ * `TaskRunner.handleOffer`'s per-home busy gate.
186
+ *
187
+ * Derived from the one lease registry above rather than a second tally, so
188
+ * it inherits the lease lifecycle exactly: an entry appears at `acquire()`,
189
+ * survives `bindSession()` (which rekeys in place), and disappears only at
190
+ * `release()`, which the task runner calls after the attempt is terminal
191
+ * AND `Session.close()` resolved. A failed disposal never reaches
192
+ * `release()`, so the slot stays held — fail closed, the same posture as
193
+ * `runtime-disposal-failed`. Crash residue needs nothing extra here: a
194
+ * restarted daemon starts with an empty registry and reclaims the on-disk
195
+ * marker only under the same stable owner identity (`openLeaseMarker`).
196
+ *
197
+ * Counted regardless of which lease manager owns the group: the invariant
198
+ * being protected is the filesystem path (`MEMORY.md`, `notes/`, `.git`),
199
+ * not the owner identity.
200
+ */
201
+ activeAttemptCount(canonicalHome: string): number;
202
+ /**
203
+ * Counts-only readback for daemon/control status. Scoped to this manager's
204
+ * own leases, so the number describes this daemon rather than every home
205
+ * any manager in the process happens to hold. Never exposes a home path.
206
+ */
207
+ activeAttemptSummary(): {
208
+ readonly homes: number;
209
+ readonly attempts: number;
210
+ };
137
211
  mutate<T>(binding: AgentHomeExecutionBinding, operation: () => Promise<T>): Promise<T>;
138
212
  private exclusive;
139
213
  }