pilotswarm-sdk 0.5.63 → 0.5.65

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 (289) hide show
  1. package/api/src/admin-diagnostics.d.ts +12 -0
  2. package/api/src/admin-diagnostics.js +38 -1
  3. package/api/src/http-api-transport.js +26 -0
  4. package/api/src/protocol.js +15 -0
  5. package/dist/activity-routing.d.ts +21 -0
  6. package/dist/activity-routing.d.ts.map +1 -0
  7. package/dist/activity-routing.js +32 -0
  8. package/dist/activity-routing.js.map +1 -0
  9. package/dist/agent-discovery.d.ts +35 -0
  10. package/dist/agent-discovery.d.ts.map +1 -0
  11. package/dist/agent-discovery.js +107 -0
  12. package/dist/agent-discovery.js.map +1 -0
  13. package/dist/client.d.ts +9 -1
  14. package/dist/client.d.ts.map +1 -1
  15. package/dist/client.js +94 -12
  16. package/dist/client.js.map +1 -1
  17. package/dist/cms-migrations.d.ts.map +1 -1
  18. package/dist/cms-migrations.js +4 -0
  19. package/dist/cms-migrations.js.map +1 -1
  20. package/dist/cms.d.ts +3 -0
  21. package/dist/cms.d.ts.map +1 -1
  22. package/dist/cms.js +7 -0
  23. package/dist/cms.js.map +1 -1
  24. package/dist/feature-flag-cache.d.ts +40 -0
  25. package/dist/feature-flag-cache.d.ts.map +1 -0
  26. package/dist/feature-flag-cache.js +125 -0
  27. package/dist/feature-flag-cache.js.map +1 -0
  28. package/dist/feature-flags.d.ts +68 -0
  29. package/dist/feature-flags.d.ts.map +1 -0
  30. package/dist/feature-flags.js +45 -0
  31. package/dist/feature-flags.js.map +1 -0
  32. package/dist/feature-store.d.ts +55 -0
  33. package/dist/feature-store.d.ts.map +1 -0
  34. package/dist/feature-store.js +106 -0
  35. package/dist/feature-store.js.map +1 -0
  36. package/dist/feature-tools.d.ts +94 -0
  37. package/dist/feature-tools.d.ts.map +1 -0
  38. package/dist/feature-tools.js +60 -0
  39. package/dist/feature-tools.js.map +1 -0
  40. package/dist/index.d.ts +6 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +4 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/managed-session.d.ts +4 -0
  45. package/dist/managed-session.d.ts.map +1 -1
  46. package/dist/managed-session.js +120 -23
  47. package/dist/managed-session.js.map +1 -1
  48. package/dist/management-client.d.ts +18 -0
  49. package/dist/management-client.d.ts.map +1 -1
  50. package/dist/management-client.js +19 -0
  51. package/dist/management-client.js.map +1 -1
  52. package/dist/migrations/feature-flags-0077.d.ts +3 -0
  53. package/dist/migrations/feature-flags-0077.d.ts.map +1 -0
  54. package/dist/migrations/feature-flags-0077.js +271 -0
  55. package/dist/migrations/feature-flags-0077.js.map +1 -0
  56. package/dist/migrations/native-tasks-default-policy-0078.d.ts +3 -0
  57. package/dist/migrations/native-tasks-default-policy-0078.d.ts.map +1 -0
  58. package/dist/migrations/native-tasks-default-policy-0078.js +21 -0
  59. package/dist/migrations/native-tasks-default-policy-0078.js.map +1 -0
  60. package/dist/native-subagents.d.ts +25 -0
  61. package/dist/native-subagents.d.ts.map +1 -0
  62. package/dist/native-subagents.js +191 -0
  63. package/dist/native-subagents.js.map +1 -0
  64. package/dist/native-task-observer.d.ts +87 -0
  65. package/dist/native-task-observer.d.ts.map +1 -0
  66. package/dist/native-task-observer.js +268 -0
  67. package/dist/native-task-observer.js.map +1 -0
  68. package/dist/orchestration/agents.d.ts +8 -3
  69. package/dist/orchestration/agents.d.ts.map +1 -1
  70. package/dist/orchestration/agents.js +87 -36
  71. package/dist/orchestration/agents.js.map +1 -1
  72. package/dist/orchestration/index.d.ts +2 -2
  73. package/dist/orchestration/index.js +1 -1
  74. package/dist/orchestration/lifecycle.js +1 -1
  75. package/dist/orchestration/lifecycle.js.map +1 -1
  76. package/dist/orchestration/runtime.d.ts +1 -1
  77. package/dist/orchestration/runtime.d.ts.map +1 -1
  78. package/dist/orchestration/runtime.js +18 -4
  79. package/dist/orchestration/runtime.js.map +1 -1
  80. package/dist/orchestration/turn.d.ts.map +1 -1
  81. package/dist/orchestration/turn.js +3 -5
  82. package/dist/orchestration/turn.js.map +1 -1
  83. package/dist/orchestration-registry.d.ts.map +1 -1
  84. package/dist/orchestration-registry.js +12 -2
  85. package/dist/orchestration-registry.js.map +1 -1
  86. package/dist/orchestration-version.d.ts +1 -1
  87. package/dist/orchestration-version.js +1 -1
  88. package/dist/orchestration.d.ts +2 -2
  89. package/dist/orchestration.js +1 -1
  90. package/dist/orchestration_1_0_46.d.ts +1 -1
  91. package/dist/orchestration_1_0_47.d.ts +1 -1
  92. package/dist/orchestration_1_0_48.d.ts +1 -1
  93. package/dist/orchestration_1_0_49.d.ts +1 -1
  94. package/dist/orchestration_1_0_50.d.ts +1 -1
  95. package/dist/orchestration_1_0_73/agents.d.ts +61 -0
  96. package/dist/orchestration_1_0_73/agents.d.ts.map +1 -0
  97. package/dist/orchestration_1_0_73/agents.js +843 -0
  98. package/dist/orchestration_1_0_73/agents.js.map +1 -0
  99. package/dist/orchestration_1_0_73/index.d.ts +24 -0
  100. package/dist/orchestration_1_0_73/index.d.ts.map +1 -0
  101. package/dist/orchestration_1_0_73/index.js +13 -0
  102. package/dist/orchestration_1_0_73/index.js.map +1 -0
  103. package/dist/orchestration_1_0_73/lifecycle.d.ts +51 -0
  104. package/dist/orchestration_1_0_73/lifecycle.d.ts.map +1 -0
  105. package/dist/orchestration_1_0_73/lifecycle.js +981 -0
  106. package/dist/orchestration_1_0_73/lifecycle.js.map +1 -0
  107. package/dist/orchestration_1_0_73/queue.d.ts +20 -0
  108. package/dist/orchestration_1_0_73/queue.d.ts.map +1 -0
  109. package/dist/orchestration_1_0_73/queue.js +894 -0
  110. package/dist/orchestration_1_0_73/queue.js.map +1 -0
  111. package/dist/orchestration_1_0_73/runtime.d.ts +31 -0
  112. package/dist/orchestration_1_0_73/runtime.d.ts.map +1 -0
  113. package/dist/orchestration_1_0_73/runtime.js +230 -0
  114. package/dist/orchestration_1_0_73/runtime.js.map +1 -0
  115. package/dist/orchestration_1_0_73/state.d.ts +214 -0
  116. package/dist/orchestration_1_0_73/state.d.ts.map +1 -0
  117. package/dist/orchestration_1_0_73/state.js +178 -0
  118. package/dist/orchestration_1_0_73/state.js.map +1 -0
  119. package/dist/orchestration_1_0_73/turn.d.ts +34 -0
  120. package/dist/orchestration_1_0_73/turn.d.ts.map +1 -0
  121. package/dist/orchestration_1_0_73/turn.js +1435 -0
  122. package/dist/orchestration_1_0_73/turn.js.map +1 -0
  123. package/dist/orchestration_1_0_73/utils.d.ts +39 -0
  124. package/dist/orchestration_1_0_73/utils.d.ts.map +1 -0
  125. package/dist/orchestration_1_0_73/utils.js +322 -0
  126. package/dist/orchestration_1_0_73/utils.js.map +1 -0
  127. package/dist/orchestration_1_0_74/agents.d.ts +61 -0
  128. package/dist/orchestration_1_0_74/agents.d.ts.map +1 -0
  129. package/dist/orchestration_1_0_74/agents.js +889 -0
  130. package/dist/orchestration_1_0_74/agents.js.map +1 -0
  131. package/dist/orchestration_1_0_74/index.d.ts +24 -0
  132. package/dist/orchestration_1_0_74/index.d.ts.map +1 -0
  133. package/dist/orchestration_1_0_74/index.js +13 -0
  134. package/dist/orchestration_1_0_74/index.js.map +1 -0
  135. package/dist/orchestration_1_0_74/lifecycle.d.ts +51 -0
  136. package/dist/orchestration_1_0_74/lifecycle.d.ts.map +1 -0
  137. package/dist/orchestration_1_0_74/lifecycle.js +981 -0
  138. package/dist/orchestration_1_0_74/lifecycle.js.map +1 -0
  139. package/dist/orchestration_1_0_74/queue.d.ts +20 -0
  140. package/dist/orchestration_1_0_74/queue.d.ts.map +1 -0
  141. package/dist/orchestration_1_0_74/queue.js +894 -0
  142. package/dist/orchestration_1_0_74/queue.js.map +1 -0
  143. package/dist/orchestration_1_0_74/runtime.d.ts +31 -0
  144. package/dist/orchestration_1_0_74/runtime.d.ts.map +1 -0
  145. package/dist/orchestration_1_0_74/runtime.js +232 -0
  146. package/dist/orchestration_1_0_74/runtime.js.map +1 -0
  147. package/dist/orchestration_1_0_74/state.d.ts +214 -0
  148. package/dist/orchestration_1_0_74/state.d.ts.map +1 -0
  149. package/dist/orchestration_1_0_74/state.js +178 -0
  150. package/dist/orchestration_1_0_74/state.js.map +1 -0
  151. package/dist/orchestration_1_0_74/turn.d.ts +34 -0
  152. package/dist/orchestration_1_0_74/turn.d.ts.map +1 -0
  153. package/dist/orchestration_1_0_74/turn.js +1435 -0
  154. package/dist/orchestration_1_0_74/turn.js.map +1 -0
  155. package/dist/orchestration_1_0_74/utils.d.ts +39 -0
  156. package/dist/orchestration_1_0_74/utils.d.ts.map +1 -0
  157. package/dist/orchestration_1_0_74/utils.js +322 -0
  158. package/dist/orchestration_1_0_74/utils.js.map +1 -0
  159. package/dist/orchestration_1_0_75/agents.d.ts +61 -0
  160. package/dist/orchestration_1_0_75/agents.d.ts.map +1 -0
  161. package/dist/orchestration_1_0_75/agents.js +887 -0
  162. package/dist/orchestration_1_0_75/agents.js.map +1 -0
  163. package/dist/orchestration_1_0_75/index.d.ts +24 -0
  164. package/dist/orchestration_1_0_75/index.d.ts.map +1 -0
  165. package/dist/orchestration_1_0_75/index.js +13 -0
  166. package/dist/orchestration_1_0_75/index.js.map +1 -0
  167. package/dist/orchestration_1_0_75/lifecycle.d.ts +51 -0
  168. package/dist/orchestration_1_0_75/lifecycle.d.ts.map +1 -0
  169. package/dist/orchestration_1_0_75/lifecycle.js +981 -0
  170. package/dist/orchestration_1_0_75/lifecycle.js.map +1 -0
  171. package/dist/orchestration_1_0_75/queue.d.ts +20 -0
  172. package/dist/orchestration_1_0_75/queue.d.ts.map +1 -0
  173. package/dist/orchestration_1_0_75/queue.js +894 -0
  174. package/dist/orchestration_1_0_75/queue.js.map +1 -0
  175. package/dist/orchestration_1_0_75/runtime.d.ts +31 -0
  176. package/dist/orchestration_1_0_75/runtime.d.ts.map +1 -0
  177. package/dist/orchestration_1_0_75/runtime.js +239 -0
  178. package/dist/orchestration_1_0_75/runtime.js.map +1 -0
  179. package/dist/orchestration_1_0_75/state.d.ts +214 -0
  180. package/dist/orchestration_1_0_75/state.d.ts.map +1 -0
  181. package/dist/orchestration_1_0_75/state.js +178 -0
  182. package/dist/orchestration_1_0_75/state.js.map +1 -0
  183. package/dist/orchestration_1_0_75/turn.d.ts +34 -0
  184. package/dist/orchestration_1_0_75/turn.d.ts.map +1 -0
  185. package/dist/orchestration_1_0_75/turn.js +1435 -0
  186. package/dist/orchestration_1_0_75/turn.js.map +1 -0
  187. package/dist/orchestration_1_0_75/utils.d.ts +39 -0
  188. package/dist/orchestration_1_0_75/utils.d.ts.map +1 -0
  189. package/dist/orchestration_1_0_75/utils.js +322 -0
  190. package/dist/orchestration_1_0_75/utils.js.map +1 -0
  191. package/dist/orchestration_1_0_76/agents.d.ts +61 -0
  192. package/dist/orchestration_1_0_76/agents.d.ts.map +1 -0
  193. package/dist/orchestration_1_0_76/agents.js +865 -0
  194. package/dist/orchestration_1_0_76/agents.js.map +1 -0
  195. package/dist/orchestration_1_0_76/index.d.ts +24 -0
  196. package/dist/orchestration_1_0_76/index.d.ts.map +1 -0
  197. package/dist/orchestration_1_0_76/index.js +13 -0
  198. package/dist/orchestration_1_0_76/index.js.map +1 -0
  199. package/dist/orchestration_1_0_76/lifecycle.d.ts +51 -0
  200. package/dist/orchestration_1_0_76/lifecycle.d.ts.map +1 -0
  201. package/dist/orchestration_1_0_76/lifecycle.js +981 -0
  202. package/dist/orchestration_1_0_76/lifecycle.js.map +1 -0
  203. package/dist/orchestration_1_0_76/queue.d.ts +20 -0
  204. package/dist/orchestration_1_0_76/queue.d.ts.map +1 -0
  205. package/dist/orchestration_1_0_76/queue.js +894 -0
  206. package/dist/orchestration_1_0_76/queue.js.map +1 -0
  207. package/dist/orchestration_1_0_76/runtime.d.ts +31 -0
  208. package/dist/orchestration_1_0_76/runtime.d.ts.map +1 -0
  209. package/dist/orchestration_1_0_76/runtime.js +239 -0
  210. package/dist/orchestration_1_0_76/runtime.js.map +1 -0
  211. package/dist/orchestration_1_0_76/state.d.ts +214 -0
  212. package/dist/orchestration_1_0_76/state.d.ts.map +1 -0
  213. package/dist/orchestration_1_0_76/state.js +178 -0
  214. package/dist/orchestration_1_0_76/state.js.map +1 -0
  215. package/dist/orchestration_1_0_76/turn.d.ts +34 -0
  216. package/dist/orchestration_1_0_76/turn.d.ts.map +1 -0
  217. package/dist/orchestration_1_0_76/turn.js +1435 -0
  218. package/dist/orchestration_1_0_76/turn.js.map +1 -0
  219. package/dist/orchestration_1_0_76/utils.d.ts +39 -0
  220. package/dist/orchestration_1_0_76/utils.d.ts.map +1 -0
  221. package/dist/orchestration_1_0_76/utils.js +322 -0
  222. package/dist/orchestration_1_0_76/utils.js.map +1 -0
  223. package/dist/orchestration_1_0_77/agents.d.ts +66 -0
  224. package/dist/orchestration_1_0_77/agents.d.ts.map +1 -0
  225. package/dist/orchestration_1_0_77/agents.js +895 -0
  226. package/dist/orchestration_1_0_77/agents.js.map +1 -0
  227. package/dist/orchestration_1_0_77/index.d.ts +24 -0
  228. package/dist/orchestration_1_0_77/index.d.ts.map +1 -0
  229. package/dist/orchestration_1_0_77/index.js +13 -0
  230. package/dist/orchestration_1_0_77/index.js.map +1 -0
  231. package/dist/orchestration_1_0_77/lifecycle.d.ts +51 -0
  232. package/dist/orchestration_1_0_77/lifecycle.d.ts.map +1 -0
  233. package/dist/orchestration_1_0_77/lifecycle.js +981 -0
  234. package/dist/orchestration_1_0_77/lifecycle.js.map +1 -0
  235. package/dist/orchestration_1_0_77/queue.d.ts +20 -0
  236. package/dist/orchestration_1_0_77/queue.d.ts.map +1 -0
  237. package/dist/orchestration_1_0_77/queue.js +894 -0
  238. package/dist/orchestration_1_0_77/queue.js.map +1 -0
  239. package/dist/orchestration_1_0_77/runtime.d.ts +31 -0
  240. package/dist/orchestration_1_0_77/runtime.d.ts.map +1 -0
  241. package/dist/orchestration_1_0_77/runtime.js +239 -0
  242. package/dist/orchestration_1_0_77/runtime.js.map +1 -0
  243. package/dist/orchestration_1_0_77/state.d.ts +214 -0
  244. package/dist/orchestration_1_0_77/state.d.ts.map +1 -0
  245. package/dist/orchestration_1_0_77/state.js +178 -0
  246. package/dist/orchestration_1_0_77/state.js.map +1 -0
  247. package/dist/orchestration_1_0_77/turn.d.ts +34 -0
  248. package/dist/orchestration_1_0_77/turn.d.ts.map +1 -0
  249. package/dist/orchestration_1_0_77/turn.js +1433 -0
  250. package/dist/orchestration_1_0_77/turn.js.map +1 -0
  251. package/dist/orchestration_1_0_77/utils.d.ts +39 -0
  252. package/dist/orchestration_1_0_77/utils.d.ts.map +1 -0
  253. package/dist/orchestration_1_0_77/utils.js +322 -0
  254. package/dist/orchestration_1_0_77/utils.js.map +1 -0
  255. package/dist/reserved-tool-names.d.ts +5 -0
  256. package/dist/reserved-tool-names.d.ts.map +1 -0
  257. package/dist/reserved-tool-names.js +102 -0
  258. package/dist/reserved-tool-names.js.map +1 -0
  259. package/dist/session-manager.d.ts +31 -1
  260. package/dist/session-manager.d.ts.map +1 -1
  261. package/dist/session-manager.js +343 -102
  262. package/dist/session-manager.js.map +1 -1
  263. package/dist/session-proxy.d.ts +28 -3
  264. package/dist/session-proxy.d.ts.map +1 -1
  265. package/dist/session-proxy.js +192 -71
  266. package/dist/session-proxy.js.map +1 -1
  267. package/dist/session-store.d.ts +2 -0
  268. package/dist/session-store.d.ts.map +1 -1
  269. package/dist/session-store.js +18 -2
  270. package/dist/session-store.js.map +1 -1
  271. package/dist/types.d.ts +25 -3
  272. package/dist/types.d.ts.map +1 -1
  273. package/dist/types.js.map +1 -1
  274. package/dist/web/generated-op-methods.d.ts +96 -0
  275. package/dist/web/generated-op-methods.d.ts.map +1 -1
  276. package/dist/web/generated-op-methods.js +24 -0
  277. package/dist/web/generated-op-methods.js.map +1 -1
  278. package/dist/web/web-management-client.d.ts +13 -0
  279. package/dist/web/web-management-client.d.ts.map +1 -1
  280. package/dist/web/web-management-client.js +12 -0
  281. package/dist/web/web-management-client.js.map +1 -1
  282. package/dist/worker.d.ts +13 -3
  283. package/dist/worker.d.ts.map +1 -1
  284. package/dist/worker.js +150 -110
  285. package/dist/worker.js.map +1 -1
  286. package/package.json +4 -3
  287. package/plugins/mgmt/agents/pilotswarm.agent.md +18 -1
  288. package/plugins/mgmt/agents/resourcemgr.agent.md +18 -1
  289. package/plugins/system/agents/default.agent.md +16 -7
@@ -1,3 +1,6 @@
1
+ import { NATIVE_BUILTIN_AGENTS, NATIVE_EXCLUDED_TOOLS, nativeSubagentGuidance, nativeSubagentDefinitions, nativeSubagentHooks, guardNativeExternalTools } from "./native-subagents.js";
2
+ import { createFeatureTools, FEATURE_OPERATION_SPECS } from "./feature-tools.js";
3
+ import { FeatureFlagError } from "./feature-flags.js";
1
4
  import { BYOK_CLIENT_PREFIX, createCopilotClient, needsByokRequestCompatibility } from "./copilot-client.js";
2
5
  import { ManagedSession } from "./managed-session.js";
3
6
  import { SESSION_STATE_MISSING_PREFIX } from "./types.js";
@@ -35,6 +38,7 @@ const DEHYDRATE_STORE_MAX_RETRIES = 1;
35
38
  const DEHYDRATE_STORE_RETRY_BASE_DELAY_MS = 0;
36
39
  const SESSION_LOCK_BACKOFF_MS = [5_000, 10_000, 20_000];
37
40
  const SESSION_LOCK_MAX_WAIT_MS = 120_000;
41
+ const COPILOT_CLIENT_SHUTDOWN_TIMEOUT_MS = 10_000;
38
42
  export const SESSION_LOCK_ACQUIRE_TIMEOUT_CODE = "PILOTSWARM_SESSION_LOCK_ACQUIRE_TIMEOUT";
39
43
  export class SessionLockAcquireTimeoutError extends Error {
40
44
  code = SESSION_LOCK_ACQUIRE_TIMEOUT_CODE;
@@ -76,6 +80,24 @@ function isMissingDehydrateSnapshotError(error) {
76
80
  const message = error instanceof Error ? error.message : String(error ?? "");
77
81
  return /Session state directory not ready during dehydrate/i.test(message);
78
82
  }
83
+ export class PackageToolBindingError extends Error {
84
+ toolName;
85
+ code = "PACKAGE_TOOL_REQUIRES_BOUND_AGENT";
86
+ constructor(toolName) {
87
+ super(`Package tool "${toolName}" requires its owning named-agent definition. Use ps_list_agents to find its named agent, then call spawn_agent with the exact agent_name and your assignment in task.`);
88
+ this.toolName = toolName;
89
+ this.name = "PackageToolBindingError";
90
+ }
91
+ }
92
+ export class BoundAgentPackageUnavailableError extends Error {
93
+ packageId;
94
+ code = "BOUND_AGENT_PACKAGE_UNAVAILABLE";
95
+ constructor(packageId, message = `The bound agent package "${packageId}" is no longer available to this session.`) {
96
+ super(message);
97
+ this.packageId = packageId;
98
+ this.name = "BoundAgentPackageUnavailableError";
99
+ }
100
+ }
79
101
  /** Key under which a package copy's per-agent config (MCP) is registered. */
80
102
  export function packageAgentKey(packageId, agentName) {
81
103
  return `pkg\u0001${packageId}\u0001${agentName}`;
@@ -120,7 +142,58 @@ export function pickAgentCopyForOwner(entry, ownerKey) {
120
142
  ?? copies.find((copy) => copy.packageScope === "shared")
121
143
  ?? undefined;
122
144
  }
123
- function buildEffectivePromptLayers(workerDefaults, config, sessionOwnerKey = null) {
145
+ /** Resolve an already-authorized exact package copy while rechecking owner visibility. */
146
+ export function pickAgentCopyByPackageIdForOwner(entry, packageId, ownerKey) {
147
+ if (!entry || !packageId)
148
+ return undefined;
149
+ const copies = entry.copies?.length ? entry.copies : [entry];
150
+ const copy = copies.find((candidate) => candidate.packageId === packageId);
151
+ if (!copy)
152
+ return undefined;
153
+ if (copy.packageScope !== "user")
154
+ return copy;
155
+ return agentOwnerKey(copy.packageOwner) === ownerKey && ownerKey !== null
156
+ ? copy
157
+ : undefined;
158
+ }
159
+ /** Resolve every part of a bound agent using the same authorized package copy. */
160
+ function resolveBoundAgentCopy(workerDefaults, config, sessionOwnerKey) {
161
+ const entry = config.boundAgentName
162
+ ? workerDefaults.agentPromptLookup?.[config.boundAgentName]
163
+ : undefined;
164
+ if (config.boundAgentSource === "deployment") {
165
+ const copies = entry?.copies?.length ? entry.copies : entry ? [entry] : [];
166
+ const deployment = copies.find(copy => !copy.packageId && copy.packageScope == null);
167
+ if (config.boundAgentPackageId || !deployment) {
168
+ throw new BoundAgentPackageUnavailableError(`deployment:${config.boundAgentName ?? ""}`, `The bound deployment agent "${config.boundAgentName ?? ""}" is no longer available or has a conflicting package binding.`);
169
+ }
170
+ return deployment;
171
+ }
172
+ const copy = config.boundAgentPackageId
173
+ ? pickAgentCopyByPackageIdForOwner(entry, config.boundAgentPackageId, sessionOwnerKey)
174
+ : pickAgentCopyForOwner(entry, sessionOwnerKey);
175
+ if (config.boundAgentPackageId && !copy) {
176
+ throw new BoundAgentPackageUnavailableError(config.boundAgentPackageId);
177
+ }
178
+ return copy;
179
+ }
180
+ /** Match the SDK wire declaration, including schemas supplied through Zod. */
181
+ function toolDeclarationForFingerprint(tool) {
182
+ const parameters = tool.parameters;
183
+ return {
184
+ name: tool.name,
185
+ description: tool.description,
186
+ parameters: parameters != null && typeof parameters === "object" && typeof parameters.toJSONSchema === "function"
187
+ ? parameters.toJSONSchema()
188
+ : parameters,
189
+ overridesBuiltInTool: tool.overridesBuiltInTool,
190
+ skipPermission: tool.skipPermission,
191
+ defer: tool.defer ?? "never",
192
+ metadata: tool.metadata,
193
+ isTerminal: tool.isTerminal,
194
+ };
195
+ }
196
+ function buildEffectivePromptLayers(workerDefaults, config, sessionOwnerKey = null, boundAgentCopy = resolveBoundAgentCopy(workerDefaults, config, sessionOwnerKey) ?? null) {
124
197
  const boundAgentName = config.boundAgentName;
125
198
  const layerKind = config.promptLayering?.kind ?? (boundAgentName ? "app-agent" : undefined);
126
199
  const isPilotSwarmSystemAgent = layerKind === "pilotswarm-system-agent";
@@ -130,9 +203,8 @@ function buildEffectivePromptLayers(workerDefaults, config, sessionOwnerKey = nu
130
203
  if (!isPilotSwarmSystemAgent && workerDefaults.appDefaultDescriptor)
131
204
  layers.push(workerDefaults.appDefaultDescriptor);
132
205
  if (boundAgentName) {
133
- const copy = pickAgentCopyForOwner(workerDefaults.agentPromptLookup?.[boundAgentName], sessionOwnerKey);
134
- if (copy?.descriptor)
135
- layers.push(copy.descriptor);
206
+ if (boundAgentCopy?.descriptor)
207
+ layers.push(boundAgentCopy.descriptor);
136
208
  }
137
209
  return layers;
138
210
  }
@@ -287,6 +359,8 @@ export class SessionManager {
287
359
  }
288
360
  }
289
361
  sessions = new Map();
362
+ featureFlags = null;
363
+ unsubscribeFeatureFlags = null;
290
364
  /** Live in-memory session count — worker-registry health reporting. */
291
365
  get activeSessionCount() {
292
366
  return this.sessions.size;
@@ -304,10 +378,18 @@ export class SessionManager {
304
378
  sessionStore = null;
305
379
  /** In-memory configs with non-serializable fields (tools, hooks). */
306
380
  sessionConfigs = new Map();
381
+ /** Only application-supplied tools; resolved registry/platform handlers are never cached as inputs. */
382
+ sessionApplicationTools = new Map();
383
+ /** One authorized agent snapshot per turn, also used by dynamic prompt callbacks. */
384
+ sessionAgentCopies = new Map();
385
+ /** CLI declarations and MCP grants require a new SDK handle when they change. */
386
+ sessionBindingFingerprints = new Map();
307
387
  /** Worker-level tool registry — shared reference from PilotSwarmWorker. */
308
388
  toolRegistry = new Map();
309
389
  /** Per-package tool maps, so a session prefers ITS package's handler on a name collision. */
310
390
  packageToolRegistry = null;
391
+ /** Package-owned names cannot be attached without the matching agent package binding. */
392
+ packageToolNames = new Set();
311
393
  /** Names registered by deployment code — a package tool must never shadow these. */
312
394
  staticToolNames = null;
313
395
  /** Worker-level defaults for building blocks. */
@@ -363,6 +445,10 @@ export class SessionManager {
363
445
  /** Store full config (with tools/hooks) for a session. Called by PilotSwarmClient. */
364
446
  setConfig(sessionId, config) {
365
447
  this.sessionConfigs.set(sessionId, config);
448
+ if (config.tools?.length)
449
+ this.sessionApplicationTools.set(sessionId, [...config.tools]);
450
+ else
451
+ this.sessionApplicationTools.delete(sessionId);
366
452
  }
367
453
  async _allowedModelProviderIds(sessionId) {
368
454
  const registry = this.workerDefaults.modelProviders;
@@ -596,6 +682,12 @@ export class SessionManager {
596
682
  this.toolRegistry = registry;
597
683
  this.packageToolRegistry = opts?.byPackage ?? null;
598
684
  this.staticToolNames = opts?.staticNames ?? null;
685
+ // Remember ownership after removal as well: a stale application-supplied
686
+ // handler must not become an ordinary tool when its package disappears.
687
+ for (const tools of this.packageToolRegistry?.values() ?? []) {
688
+ for (const name of tools.keys())
689
+ this.packageToolNames.add(name);
690
+ }
599
691
  }
600
692
  /** Set the cluster facts store for always-on facts tools. */
601
693
  setFactStore(factStore) {
@@ -609,6 +701,16 @@ export class SessionManager {
609
701
  setSessionCatalog(catalog) {
610
702
  this.sessionCatalog = catalog;
611
703
  }
704
+ setFeatureFlagCache(cache) {
705
+ this.unsubscribeFeatureFlags?.();
706
+ this.featureFlags = cache;
707
+ this.unsubscribeFeatureFlags = cache?.onChange(() => {
708
+ for (const managed of this.sessions.values())
709
+ managed.refreshNativeFeaturePolicy();
710
+ }) ?? null;
711
+ for (const managed of this.sessions.values())
712
+ managed.refreshNativeFeaturePolicy();
713
+ }
612
714
  /**
613
715
  * Hot-swap the model-provider registry after a config-file change on
614
716
  * disk (ConfigMap update). Applies to all subsequent model resolution;
@@ -830,7 +932,7 @@ export class SessionManager {
830
932
  await existing.destroy();
831
933
  }
832
934
  catch { }
833
- this.sessions.delete(sessionId);
935
+ this._forgetWarmSession(sessionId);
834
936
  }
835
937
  try {
836
938
  const client = await this._ensureClientForSession(sessionId);
@@ -866,7 +968,7 @@ export class SessionManager {
866
968
  await existing.destroy();
867
969
  }
868
970
  catch { }
869
- this.sessions.delete(sessionId);
971
+ this._forgetWarmSession(sessionId);
870
972
  }
871
973
  try {
872
974
  const client = await this._ensureClientForSession(sessionId);
@@ -983,7 +1085,7 @@ export class SessionManager {
983
1085
  await existing.destroy();
984
1086
  }
985
1087
  catch { }
986
- this.sessions.delete(sessionId);
1088
+ this._forgetWarmSession(sessionId);
987
1089
  }
988
1090
  if (!dirExists) {
989
1091
  this.sessionLastTouchedAt.delete(sessionId);
@@ -1128,7 +1230,7 @@ export class SessionManager {
1128
1230
  ...(this.workerDefaults.appDefaultToolNames ?? []),
1129
1231
  ...(serializableConfig.toolNames ?? []),
1130
1232
  ]));
1131
- const effectiveSerializableConfig = inheritedToolNames.length > 0
1233
+ let effectiveSerializableConfig = inheritedToolNames.length > 0
1132
1234
  ? { ...serializableConfig, toolNames: inheritedToolNames }
1133
1235
  : serializableConfig;
1134
1236
  // Which copy of the bound agent does this session get? Owner-aware:
@@ -1136,20 +1238,69 @@ export class SessionManager {
1136
1238
  // one, the same rule the registry resolver applies at create time.
1137
1239
  // Resolved once here; prompt, descriptor, MCP, and tool-handler picks
1138
1240
  // below all follow the same copy so a session can never mix copies.
1139
- const boundAgentEntry = effectiveSerializableConfig.boundAgentName
1140
- ? this.workerDefaults.agentPromptLookup?.[effectiveSerializableConfig.boundAgentName]
1141
- : undefined;
1142
- // A PACKAGE agent (as opposed to a deployment/inline agent) carries a
1143
- // packageId on its entry or on any of its copies. This is load-bearing
1144
- // for MCP resolution: a package agent must never fall back to the
1145
- // bare-name MCP key, which another copy of a shadowed name also wrote.
1146
1241
  const sessionOwnerKey = effectiveSerializableConfig.boundAgentName
1147
1242
  ? await this._sessionAgentOwnerKey(sessionId)
1148
1243
  : null;
1149
- const boundAgentCopy = pickAgentCopyForOwner(boundAgentEntry, sessionOwnerKey);
1150
- // Resolve tools: merge per-session (setConfig) + registry (toolNames)
1151
1244
  const storedConfig = this.sessionConfigs.get(sessionId);
1152
- const resolvedTools = this._resolveTools(storedConfig, effectiveSerializableConfig, boundAgentCopy?.packageId);
1245
+ let boundAgentCopy;
1246
+ let resolvedTools;
1247
+ try {
1248
+ boundAgentCopy = resolveBoundAgentCopy(this.workerDefaults, effectiveSerializableConfig, sessionOwnerKey);
1249
+ // Snapshot the selected prompt and descriptor. A registry refresh in the
1250
+ // middle of a turn must not change instructions while handlers still
1251
+ // belong to the previously selected version.
1252
+ if (boundAgentCopy)
1253
+ boundAgentCopy = {
1254
+ ...boundAgentCopy,
1255
+ ...(boundAgentCopy.toolNames ? { toolNames: [...boundAgentCopy.toolNames] } : {}),
1256
+ ...(boundAgentCopy.descriptor ? { descriptor: { ...boundAgentCopy.descriptor } } : {}),
1257
+ };
1258
+ if (boundAgentCopy?.toolNames && effectiveSerializableConfig.boundAgentName
1259
+ && (effectiveSerializableConfig.boundAgentPackageId || effectiveSerializableConfig.detachedPackageToolPolicy
1260
+ || effectiveSerializableConfig.namedAgentToolAdditions !== undefined)) {
1261
+ // Named definitions refresh as a unit. Roots may add ordinary
1262
+ // application tools; protected children receive only defaults and
1263
+ // their definition. Legacy package roots have no provenance, so
1264
+ // retain their ordinary names but never stale package capabilities.
1265
+ const additions = effectiveSerializableConfig.detachedPackageToolPolicy
1266
+ ? []
1267
+ : (effectiveSerializableConfig.namedAgentToolAdditions ?? effectiveSerializableConfig.toolNames ?? [])
1268
+ .filter(name => this.staticToolNames?.has(name) || !this.packageToolNames.has(name));
1269
+ effectiveSerializableConfig = {
1270
+ ...effectiveSerializableConfig,
1271
+ toolNames: [...new Set([
1272
+ ...(this.workerDefaults.frameworkBaseToolNames ?? []),
1273
+ ...(this.workerDefaults.appDefaultToolNames ?? []),
1274
+ ...boundAgentCopy.toolNames,
1275
+ ...additions,
1276
+ ])],
1277
+ };
1278
+ }
1279
+ if (effectiveSerializableConfig.boundAgentSource === "deployment") {
1280
+ // A deployment definition is bound, but owns no registry package.
1281
+ // It cannot borrow a shared/private package's exported handlers.
1282
+ effectiveSerializableConfig = { ...effectiveSerializableConfig, detachedPackageToolPolicy: "reject" };
1283
+ }
1284
+ resolvedTools = this._resolveTools({ tools: this.sessionApplicationTools.get(sessionId) }, effectiveSerializableConfig, boundAgentCopy?.packageId);
1285
+ }
1286
+ catch (error) {
1287
+ // Failed authorization/removal must not leave a reusable handle with
1288
+ // the previous package's handlers or MCP grants.
1289
+ await this.dropWarmSession(sessionId);
1290
+ this.sessionAgentCopies.delete(sessionId);
1291
+ throw error;
1292
+ }
1293
+ // Capture MCP grants with the same registry snapshot, before asynchronous
1294
+ // provider/catalog work below can allow a package reload to intervene.
1295
+ const boundAgentMcpServers = !effectiveSerializableConfig.boundAgentName || !boundAgentCopy
1296
+ ? undefined
1297
+ : boundAgentCopy.packageId
1298
+ ? this.workerDefaults.agentMcpServers?.[packageAgentKey(boundAgentCopy.packageId, effectiveSerializableConfig.boundAgentName)]
1299
+ : this.workerDefaults.agentMcpServers?.[effectiveSerializableConfig.boundAgentName];
1300
+ const effectiveMcpServers = {
1301
+ ...(this.workerDefaults.baseMcpServers ?? {}),
1302
+ ...(boundAgentMcpServers ?? {}),
1303
+ };
1153
1304
  const config = {
1154
1305
  ...storedConfig,
1155
1306
  ...effectiveSerializableConfig,
@@ -1274,7 +1425,7 @@ export class SessionManager {
1274
1425
  await existingWarm.destroy();
1275
1426
  }
1276
1427
  catch { }
1277
- this.sessions.delete(sessionId);
1428
+ this._forgetWarmSession(sessionId);
1278
1429
  }
1279
1430
  }
1280
1431
  const client = await this.ensureClient(userGithubToken, byokOpenAi);
@@ -1451,16 +1602,36 @@ export class SessionManager {
1451
1602
  // sub-agent, fact, inspect, or graph tools. Mirrors the per-turn gate
1452
1603
  // in managed-session.ts runTurn — hard exclusion beats instructions.
1453
1604
  const isServiceSession = effectiveSerializableConfig.agentIdentity === "regen-distiller";
1605
+ const featureTools = this.sessionCatalog?.features
1606
+ && ["resourcemgr", "pilotswarm", "agent-manager"].includes(effectiveSerializableConfig.agentIdentity ?? "")
1607
+ && (await this._resolveFeatureViewer(sessionId)).isAdmin
1608
+ ? createFeatureTools(this.sessionCatalog.features, () => this._resolveFeatureViewer(sessionId)) : [];
1609
+ // Handler refresh alone cannot change the CLI's tool declarations.
1610
+ // Role transitions must recreate the warm SDK handle at the next turn.
1611
+ config.featureToolFingerprint = createHash("sha256")
1612
+ .update(JSON.stringify(featureTools.map(({ name, description, parameters }) => ({ name, description, parameters }))))
1613
+ .digest("hex");
1614
+ const nativeOwner = catalogRow?.owner ?? null;
1615
+ // An unreadable owner must not bypass a user-level OFF by resolving
1616
+ // cluster policy. Ownerless resolution is only for standalone managers.
1617
+ const nativeOwnerKnown = !this.sessionCatalog || Boolean(nativeOwner?.provider && nativeOwner?.subject);
1618
+ config.nativeFeatureAllowed = () => nativeOwnerKnown
1619
+ && (this.featureFlags?.resolve("copilot.native_tasks", nativeOwner, { fallback: false }).enabled ?? false);
1620
+ const nativeEnabled = this.workerDefaults.nativeSubagents === "sync" && config.nativeFeatureAllowed()
1621
+ && !catalogRow?.isSystem && !isServiceSession && !isTunerSession
1622
+ && config.promptLayering?.kind !== "pilotswarm-system-agent";
1623
+ config.nativeSubagents = nativeEnabled ? "sync" : "off";
1454
1624
  const unlessService = (tools) => (isServiceSession ? [] : tools);
1455
1625
  const SYSTEM_TOOL_NAMES = new Set([
1456
1626
  ...systemTools, ...subAgentTools, ...factTools, ...inspectTools, ...graphTools,
1457
- ].map((t) => t.name));
1627
+ ].map((t) => t.name).concat(FEATURE_OPERATION_SPECS.map(spec => spec.name)));
1458
1628
  const persistentSessionTools = [
1459
1629
  ...userTools.filter((t) => !SYSTEM_TOOL_NAMES.has(t.name)),
1460
1630
  ...unlessService(factTools),
1461
1631
  ...unlessService(inspectTools),
1462
1632
  ...unlessService(graphTools),
1463
1633
  ...unlessService(providerTools),
1634
+ ...unlessService(featureTools),
1464
1635
  ];
1465
1636
  const allTools = [
1466
1637
  ...persistentSessionTools.filter((t) => !SYSTEM_TOOL_NAMES.has(t.name)),
@@ -1470,38 +1641,23 @@ export class SessionManager {
1470
1641
  ...unlessService(inspectTools),
1471
1642
  ...unlessService(graphTools),
1472
1643
  ...unlessService(providerTools),
1644
+ ...unlessService(featureTools),
1473
1645
  ];
1474
1646
  config.tools = persistentSessionTools;
1475
1647
  // Build system message: worker base + client override
1476
- const systemMessage = this._buildSystemMessage(sessionId, config, sessionOwnerKey);
1477
- // Per-agent MCP (capability-profiles Phase 1): a session gets the
1478
- // base map (base-agent opt-ins + direct worker-config servers) plus
1479
- // its bound agent's resolved server map — resolved worker-side at the
1480
- // same chokepoint as the agent prompt. The deployment catalog is
1481
- // never applied wholesale.
1482
- //
1483
- // Read the MCP map of the copy THIS session actually resolved to.
1484
- // The worker registers a package agent's MCP under a package-qualified
1485
- // key only, and reserves the bare name for deployment/inline agents —
1486
- // so the resolved copy's own packageId is the discriminator:
1487
- // • package copy resolved → its qualified key (never the bare name,
1488
- // which another copy of a shadowed name could have written);
1489
- // • deployment/inline copy → the bare name (its own MCP);
1490
- // • no copy resolved (a foreign-private-only name) → no grants.
1491
- // Keying off `boundAgentCopy.packageId` rather than "is any copy a
1492
- // package" is load-bearing: when a deployment agent and a package
1493
- // share a name and this session resolved the DEPLOYMENT copy, it must
1494
- // still get the deployment agent's bare-key MCP, not an empty
1495
- // qualified lookup.
1496
- const boundAgentMcpServers = !effectiveSerializableConfig.boundAgentName || !boundAgentCopy
1497
- ? undefined
1498
- : boundAgentCopy.packageId
1499
- ? this.workerDefaults.agentMcpServers?.[packageAgentKey(boundAgentCopy.packageId, effectiveSerializableConfig.boundAgentName)]
1500
- : this.workerDefaults.agentMcpServers?.[effectiveSerializableConfig.boundAgentName];
1501
- const effectiveMcpServers = {
1502
- ...(this.workerDefaults.baseMcpServers ?? {}),
1503
- ...(boundAgentMcpServers ?? {}),
1504
- };
1648
+ const systemMessage = this._buildSystemMessage(sessionId, config, sessionOwnerKey, boundAgentCopy ?? null);
1649
+ // Handler changes use updateConfig; declaration, MCP and authored prompt
1650
+ // changes need a fresh CLI handle at this turn boundary. Do not include
1651
+ // per-turn runtime context or newly allocated handler function identity.
1652
+ const bindingFingerprint = createHash("sha256").update(JSON.stringify({
1653
+ boundAgentName: config.boundAgentName,
1654
+ boundAgentSource: config.boundAgentSource,
1655
+ boundAgentCopy,
1656
+ mcpServers: effectiveMcpServers,
1657
+ tools: allTools.map(toolDeclarationForFingerprint),
1658
+ })).digest("hex");
1659
+ const bindingChanged = this.sessionBindingFingerprints.has(sessionId)
1660
+ && this.sessionBindingFingerprints.get(sessionId) !== bindingFingerprint;
1505
1661
  const sessionConfig = {
1506
1662
  sessionId,
1507
1663
  // Sole chokepoint where tool DECLARATIONS reach the CLI (create and
@@ -1509,7 +1665,7 @@ export class SessionManager {
1509
1665
  // refreshes the client-side handler map). Pinned so tool search
1510
1666
  // cannot defer PilotSwarm tools out of the prompt — see
1511
1667
  // tool-pinning.ts for the why and the phase-2 opt-out path.
1512
- tools: pinToolsNeverDefer(allTools),
1668
+ tools: pinToolsNeverDefer(nativeEnabled ? guardNativeExternalTools(allTools, sessionId) : allTools),
1513
1669
  model: sdkModelName,
1514
1670
  // Tell the runtime what a BYOK model can do.
1515
1671
  //
@@ -1542,7 +1698,7 @@ export class SessionManager {
1542
1698
  // state placement (verified against @github/copilot 1.0.36). State location is
1543
1699
  // controlled exclusively via COPILOT_HOME, set on the spawned CLI in ensureClient().
1544
1700
  workingDirectory: config.workingDirectory,
1545
- hooks: config.hooks,
1701
+ hooks: nativeEnabled ? nativeSubagentHooks(sdkModelName, config.hooks, () => this.sessions.get(sessionId)?.canAdmitNativeTask() ?? false) : config.hooks,
1546
1702
  onPermissionRequest: config.onPermissionRequest ?? approvePermissionForSession,
1547
1703
  infiniteSessions: { enabled: true },
1548
1704
  // Enable token-level streaming so the catch-all event handler in
@@ -1555,17 +1711,20 @@ export class SessionManager {
1555
1711
  // Suppress sub-agent streaming events — we never want the parent
1556
1712
  // session's event log polluted with grandchild deltas.
1557
1713
  includeSubAgentStreamingEvents: false,
1558
- // Exclude the Copilot SDK's built-in "task" tool — PilotSwarm provides
1559
- // its own durable sub-agent mechanism via spawn_agent / check_agents.
1560
- // The native "task" tool spawns in-process sub-agents that bypass the
1561
- // durable orchestration layer, causing the LLM to use the wrong mechanism.
1562
- excludedTools: ["task"],
1714
+ // Native workers are explicitly scoped: built-ins inherit external
1715
+ // tools, and loaded PilotSwarm agents expect durable child contracts.
1716
+ excludedTools: nativeEnabled ? NATIVE_EXCLUDED_TOOLS : ["task"],
1717
+ ...(nativeEnabled ? {
1718
+ customAgents: nativeSubagentDefinitions(sdkModelName),
1719
+ customAgentsLocalOnly: true,
1720
+ excludedBuiltinAgents: NATIVE_BUILTIN_AGENTS,
1721
+ } : {}),
1563
1722
  // Custom LLM provider — resolve from registry or legacy single provider
1564
1723
  ...resolvedProviderConfig,
1565
1724
  // Pass loaded skills and agents from worker defaults; MCP servers
1566
1725
  // are the bound agent's own resolved map (see above).
1567
1726
  ...(this.workerDefaults.skillDirectories?.length && { skillDirectories: this.workerDefaults.skillDirectories }),
1568
- ...(this.workerDefaults.customAgents?.length && { customAgents: this.workerDefaults.customAgents }),
1727
+ ...(!nativeEnabled && this.workerDefaults.customAgents?.length && { customAgents: this.workerDefaults.customAgents }),
1569
1728
  ...(Object.keys(effectiveMcpServers).length > 0 && { mcpServers: effectiveMcpServers }),
1570
1729
  };
1571
1730
  let copilotSession;
@@ -1582,15 +1741,16 @@ export class SessionManager {
1582
1741
  console.warn(`[SessionManager] epoch-start for ${sessionId} (epoch ${transcriptEpoch}); ` +
1583
1742
  `discarding the warm Copilot session of the previous epoch.`);
1584
1743
  await existing.destroy();
1585
- this.sessions.delete(sessionId);
1744
+ this._forgetWarmSession(sessionId);
1586
1745
  }
1587
- else if (existing.requiresModelRebind(config)) {
1588
- console.warn(`[SessionManager] model config changed for ${sessionId}; ` +
1589
- `disconnecting warm Copilot session so it can resume with the new model config.`);
1746
+ else if (bindingChanged || existing.requiresModelRebind(config)) {
1747
+ console.warn(`[SessionManager] model or agent configuration changed for ${sessionId}; ` +
1748
+ `disconnecting warm Copilot session so it can resume with the updated configuration.`);
1590
1749
  await existing.destroy();
1591
- this.sessions.delete(sessionId);
1750
+ this._forgetWarmSession(sessionId);
1592
1751
  }
1593
1752
  else {
1753
+ this.sessionAgentCopies.set(sessionId, boundAgentCopy);
1594
1754
  existing.updateConfig(config);
1595
1755
  return existing;
1596
1756
  }
@@ -1698,7 +1858,9 @@ export class SessionManager {
1698
1858
  agentId: effectiveSerializableConfig.agentIdentity ?? null,
1699
1859
  }));
1700
1860
  this.sessions.set(sessionId, managed);
1701
- const promptLayers = buildEffectivePromptLayers(this.workerDefaults, config);
1861
+ this.sessionAgentCopies.set(sessionId, boundAgentCopy);
1862
+ this.sessionBindingFingerprints.set(sessionId, bindingFingerprint);
1863
+ const promptLayers = buildEffectivePromptLayers(this.workerDefaults, config, sessionOwnerKey, boundAgentCopy ?? null);
1702
1864
  if (promptLayers.length > 0 && this.sessionCatalog) {
1703
1865
  void this.sessionCatalog.recordEvents(sessionId, [{
1704
1866
  eventType: "session.prompt_layers",
@@ -1715,6 +1877,12 @@ export class SessionManager {
1715
1877
  getSessionStateDir() {
1716
1878
  return this.sessionStateDir;
1717
1879
  }
1880
+ /** Release snapshots with their SDK handle; application tools survive ordinary hydration. */
1881
+ _forgetWarmSession(sessionId) {
1882
+ this.sessions.delete(sessionId);
1883
+ this.sessionAgentCopies.delete(sessionId);
1884
+ this.sessionBindingFingerprints.delete(sessionId);
1885
+ }
1718
1886
  /**
1719
1887
  * Destroy the in-memory ManagedSession only — disk state untouched.
1720
1888
  * Used by the lifecycle preamble before overwriting local files with a
@@ -1723,13 +1891,13 @@ export class SessionManager {
1723
1891
  */
1724
1892
  async dropWarmSession(sessionId) {
1725
1893
  const existing = this.sessions.get(sessionId);
1726
- if (!existing)
1727
- return;
1728
- try {
1729
- await existing.destroy();
1894
+ if (existing) {
1895
+ try {
1896
+ await existing.destroy();
1897
+ }
1898
+ catch { }
1730
1899
  }
1731
- catch { }
1732
- this.sessions.delete(sessionId);
1900
+ this._forgetWarmSession(sessionId);
1733
1901
  }
1734
1902
  /**
1735
1903
  * Dehydrate a session: snapshot to the session store, release in-memory state.
@@ -1785,13 +1953,13 @@ export class SessionManager {
1785
1953
  try {
1786
1954
  emitSessionManagerTrace(sessionId, `destroy attempt ${attempt}/${DESTROY_MAX_RETRIES}`, { trace });
1787
1955
  await session.destroy();
1788
- this.sessions.delete(sessionId);
1956
+ this._forgetWarmSession(sessionId);
1789
1957
  emitSessionManagerTrace(sessionId, `destroy complete on attempt ${attempt}/${DESTROY_MAX_RETRIES}`, { trace });
1790
1958
  break; // Success
1791
1959
  }
1792
1960
  catch (err) {
1793
1961
  lastDestroyError = normalizeError(err);
1794
- this.sessions.delete(sessionId); // Remove broken session from map
1962
+ this._forgetWarmSession(sessionId); // Remove broken session from map
1795
1963
  emitSessionManagerTrace(sessionId, `destroy failed on attempt ${attempt}/${DESTROY_MAX_RETRIES} error=${lastDestroyError.message}`, { trace, level: "warn" });
1796
1964
  if (attempt < DESTROY_MAX_RETRIES) {
1797
1965
  // Re-create the session from local files so we can try destroy again.
@@ -1952,10 +2120,11 @@ export class SessionManager {
1952
2120
  return this._withSessionLock(sessionId, "destroySession", () => this.destroySession(sessionId, { lockHeld: true }));
1953
2121
  }
1954
2122
  const session = this.sessions.get(sessionId);
1955
- if (session) {
2123
+ if (session)
1956
2124
  await session.destroy();
1957
- this.sessions.delete(sessionId);
1958
- }
2125
+ this._forgetWarmSession(sessionId);
2126
+ this.sessionApplicationTools.delete(sessionId);
2127
+ this.sessionConfigs.delete(sessionId);
1959
2128
  }
1960
2129
  /**
1961
2130
  * Drop the warm in-memory session handle without deleting any persisted
@@ -1973,7 +2142,7 @@ export class SessionManager {
1973
2142
  await session.destroy();
1974
2143
  }
1975
2144
  catch { }
1976
- this.sessions.delete(sessionId);
2145
+ this._forgetWarmSession(sessionId);
1977
2146
  }
1978
2147
  /**
1979
2148
  * Fully reset a session's live and persisted Copilot state.
@@ -2002,23 +2171,51 @@ export class SessionManager {
2002
2171
  activeSessionIds() {
2003
2172
  return [...this.sessions.keys()];
2004
2173
  }
2005
- /** Shutdown: destroy all sessions, stop CopilotClient. */
2174
+ /** Final worker cleanup, after the durable runtime has drained. */
2006
2175
  async shutdown() {
2007
- for (const [_, session] of this.sessions) {
2008
- try {
2009
- await session.destroy();
2010
- }
2011
- catch { }
2012
- }
2176
+ this.unsubscribeFeatureFlags?.();
2177
+ this.unsubscribeFeatureFlags = null;
2178
+ // CopilotClient.stop owns session detachment. Detaching each session
2179
+ // first can hang forever in the SDK's unbounded session.detach RPC.
2180
+ // Snapshot and release this pool before awaiting: late SDK cleanup
2181
+ // must never clear clients/configuration installed by a later start.
2182
+ const clients = [...new Set(this.clients.values())];
2183
+ this.clients.clear();
2013
2184
  this.sessions.clear();
2185
+ this.sessionAgentCopies.clear();
2186
+ this.sessionBindingFingerprints.clear();
2187
+ this.sessionApplicationTools.clear();
2188
+ this.sessionConfigs.clear();
2014
2189
  this.sessionClientKeys.clear();
2015
- for (const [, client] of this.clients) {
2190
+ // Each process gets the same deadline concurrently; one stuck client
2191
+ // cannot block another or multiply the worker's termination budget.
2192
+ // This is final cleanup only, not the individual-session eviction path.
2193
+ await Promise.all(clients.map(async (client) => {
2194
+ let timer;
2016
2195
  try {
2017
- await client.stop();
2196
+ const errors = await Promise.race([
2197
+ client.stop(),
2198
+ new Promise((_, reject) => {
2199
+ timer = setTimeout(() => reject(new Error("Copilot shutdown timed out")), COPILOT_CLIENT_SHUTDOWN_TIMEOUT_MS);
2200
+ }),
2201
+ ]);
2202
+ if (errors?.length)
2203
+ throw errors[0];
2018
2204
  }
2019
- catch { }
2020
- }
2021
- this.clients.clear();
2205
+ catch {
2206
+ console.warn("[SessionManager] Copilot did not shut down cleanly; forcing its local process to stop.");
2207
+ try {
2208
+ await client.forceStop();
2209
+ }
2210
+ catch {
2211
+ console.warn("[SessionManager] Copilot force-stop failed.");
2212
+ }
2213
+ }
2214
+ finally {
2215
+ if (timer)
2216
+ clearTimeout(timer);
2217
+ }
2218
+ }));
2022
2219
  }
2023
2220
  /**
2024
2221
  * Resolve tools from per-session config + worker-level registry.
@@ -2037,15 +2234,42 @@ export class SessionManager {
2037
2234
  : undefined;
2038
2235
  if (serializableConfig.toolNames?.length) {
2039
2236
  for (const name of serializableConfig.toolNames) {
2040
- const tool = (this.staticToolNames?.has(name) ? this.toolRegistry.get(name) : undefined)
2041
- ?? packageTools?.get(name)
2042
- ?? this.toolRegistry.get(name);
2237
+ const staticTool = this.staticToolNames?.has(name)
2238
+ ? this.toolRegistry.get(name)
2239
+ : undefined;
2240
+ const packageTool = packageTools?.get(name);
2241
+ if (!staticTool && !packageTool && this.packageToolNames.has(name)) {
2242
+ if (serializableConfig.detachedPackageToolPolicy === "drop")
2243
+ continue;
2244
+ if (serializableConfig.detachedPackageToolPolicy === "reject" || preferredPackageId) {
2245
+ throw new PackageToolBindingError(name);
2246
+ }
2247
+ }
2248
+ const tool = staticTool ?? packageTool ?? this.toolRegistry.get(name);
2043
2249
  if (tool)
2044
2250
  registryTools.push(tool);
2045
2251
  }
2046
2252
  }
2253
+ const storedTools = (storedConfig?.tools ?? []).filter((tool) => {
2254
+ const name = String(tool?.name || "");
2255
+ if (!name || this.staticToolNames?.has(name) || !this.packageToolNames.has(name))
2256
+ return true;
2257
+ // An explicitly supplied package handler follows the current package
2258
+ // copy, never a stale object retained by the caller across a reload.
2259
+ if (packageTools?.has(name) && serializableConfig.toolNames?.includes(name))
2260
+ return true;
2261
+ if (serializableConfig.detachedPackageToolPolicy === "drop")
2262
+ return false;
2263
+ if (serializableConfig.detachedPackageToolPolicy === "reject" || preferredPackageId) {
2264
+ throw new PackageToolBindingError(name);
2265
+ }
2266
+ return true;
2267
+ }).map((tool) => {
2268
+ const name = String(tool?.name || "");
2269
+ return this.staticToolNames?.has(name) ? tool : packageTools?.get(name) ?? tool;
2270
+ });
2047
2271
  const combined = [
2048
- ...(storedConfig?.tools ?? []),
2272
+ ...storedTools,
2049
2273
  ...registryTools,
2050
2274
  ];
2051
2275
  // Deduplicate by name — per-session tools take precedence
@@ -2177,18 +2401,17 @@ export class SessionManager {
2177
2401
  },
2178
2402
  };
2179
2403
  }
2180
- _buildLastInstructionsSection(sessionId, initialConfig) {
2404
+ _buildLastInstructionsSection(sessionId, initialConfig, initialAgentCopy) {
2181
2405
  return {
2182
2406
  action: async (currentContent) => {
2183
2407
  const latest = this.sessionConfigs.get(sessionId) ?? initialConfig;
2184
2408
  const runtimeContext = extractPromptContent(latest.systemMessage);
2185
- // Owner-aware copy pick, re-resolved here because this action
2186
- // runs per compose: with scope shadowing one agent name can be
2187
- // served by several packages, and this session must get the
2188
- // copy its OWNER resolves to — not whichever loaded last.
2189
- const activeAgentPrompt = latest.boundAgentName
2190
- ? pickAgentCopyForOwner(this.workerDefaults.agentPromptLookup?.[latest.boundAgentName], await this._sessionAgentOwnerKey(sessionId))?.prompt
2191
- : undefined;
2409
+ // Follow the same per-turn snapshot as tools/MCP/descriptor.
2410
+ // Re-selecting by bare name here could both defeat an explicit
2411
+ // shared-package pin and adopt refreshed instructions mid-turn.
2412
+ const activeAgentPrompt = (this.sessionAgentCopies.has(sessionId)
2413
+ ? this.sessionAgentCopies.get(sessionId)
2414
+ : initialAgentCopy)?.prompt;
2192
2415
  // Orchestration ≥1.0.71 delivers the per-turn note inside the
2193
2416
  // user turn (see prompt-system-context.ts) and flags it. For
2194
2417
  // those turns the note must NOT land here: this section is
@@ -2209,6 +2432,10 @@ export class SessionManager {
2209
2432
  runtimeContext,
2210
2433
  ownSkillsIndex,
2211
2434
  latest.systemContextInPrompt ? undefined : latest.turnSystemPrompt,
2435
+ // The SDK ignores sibling `content` when `action` is a
2436
+ // transform callback. Include worker guidance in the actual
2437
+ // rendered section, using the current session policy.
2438
+ latest.nativeSubagents === "sync" ? nativeSubagentGuidance() : undefined,
2212
2439
  ]);
2213
2440
  return this._notePromptSection(sessionId, "last_instructions", mergePromptSections([currentContent, overlay]) ?? currentContent);
2214
2441
  },
@@ -2365,6 +2592,20 @@ export class SessionManager {
2365
2592
  return false;
2366
2593
  }
2367
2594
  }
2595
+ async _resolveFeatureViewer(sessionId) {
2596
+ // Never use the inspect-viewer TTL for feature mutations: demotion must
2597
+ // apply on the next invocation, including an already-hydrated session.
2598
+ const row = await this.sessionCatalog?.getSession(sessionId);
2599
+ // CMS deliberately leaves worker-provisioned system sessions ownerless.
2600
+ // Their persisted service classification supplies the same identity used
2601
+ // by credential and inspect resolution; an agent name grants nothing.
2602
+ const principal = row?.owner ?? (row?.isSystem === true ? SYSTEM_USER_PRINCIPAL : null);
2603
+ if (!principal?.provider || !principal.subject)
2604
+ throw new FeatureFlagError("FEATURE_FORBIDDEN", "Feature tools require an authenticated session owner", 403);
2605
+ const system = row?.isSystem === true && principal.provider === SYSTEM_USER_PRINCIPAL.provider
2606
+ && principal.subject === SYSTEM_USER_PRINCIPAL.subject;
2607
+ return { principal, isAdmin: system || await this._resolveOwnerIsAdmin(principal) };
2608
+ }
2368
2609
  /**
2369
2610
  * A one-line-per-skill index of the private skills this session's owner
2370
2611
  * published, or undefined when there are none. Mirrors the wording of the
@@ -2425,17 +2666,17 @@ export class SessionManager {
2425
2666
  const names = new Set(own.map((s) => s.name));
2426
2667
  return [...own, ...shared.filter((s) => !names.has(s.name))];
2427
2668
  }
2428
- _buildSystemMessage(sessionId, config, sessionOwnerKey = null) {
2669
+ _buildSystemMessage(sessionId, config, sessionOwnerKey = null, boundAgentCopy = resolveBoundAgentCopy(this.workerDefaults, config, sessionOwnerKey) ?? null) {
2429
2670
  const frameworkBase = this.workerDefaults.frameworkBasePrompt ?? this.workerDefaults.systemMessage;
2430
2671
  const boundAgentName = config.boundAgentName;
2431
2672
  const layerKind = config.promptLayering?.kind ?? (boundAgentName ? "app-agent" : undefined);
2432
2673
  const knowledgeToolInstructions = this._buildKnowledgeToolInstructionsSection(sessionId, config.agentIdentity);
2433
- const lastInstructions = this._buildLastInstructionsSection(sessionId, config);
2674
+ const lastInstructions = this._buildLastInstructionsSection(sessionId, config, boundAgentCopy);
2434
2675
  const additionalSections = knowledgeToolInstructions
2435
2676
  ? { tool_instructions: knowledgeToolInstructions, last_instructions: lastInstructions }
2436
2677
  : { last_instructions: lastInstructions };
2437
2678
  const isPilotSwarmSystemAgent = layerKind === "pilotswarm-system-agent";
2438
- const layerManifest = buildEffectivePromptLayers(this.workerDefaults, config, sessionOwnerKey);
2679
+ const layerManifest = buildEffectivePromptLayers(this.workerDefaults, config, sessionOwnerKey, boundAgentCopy);
2439
2680
  return composeStructuredSystemMessage({
2440
2681
  frameworkBase,
2441
2682
  appDefault: isPilotSwarmSystemAgent