pilotswarm-sdk 0.5.19 → 0.5.21

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 (201) hide show
  1. package/api/src/http-api-transport.js +4 -0
  2. package/api/src/protocol.js +2 -0
  3. package/dist/blob-store.d.ts +42 -11
  4. package/dist/blob-store.d.ts.map +1 -1
  5. package/dist/blob-store.js +163 -53
  6. package/dist/blob-store.js.map +1 -1
  7. package/dist/cms-migrations.d.ts.map +1 -1
  8. package/dist/cms-migrations.js +231 -0
  9. package/dist/cms-migrations.js.map +1 -1
  10. package/dist/cms.d.ts +62 -0
  11. package/dist/cms.d.ts.map +1 -1
  12. package/dist/cms.js +81 -4
  13. package/dist/cms.js.map +1 -1
  14. package/dist/distiller-tools.d.ts +9 -0
  15. package/dist/distiller-tools.d.ts.map +1 -0
  16. package/dist/distiller-tools.js +102 -0
  17. package/dist/distiller-tools.js.map +1 -0
  18. package/dist/footprint.d.ts +129 -0
  19. package/dist/footprint.d.ts.map +1 -0
  20. package/dist/footprint.js +254 -0
  21. package/dist/footprint.js.map +1 -0
  22. package/dist/inspect-tools.d.ts.map +1 -1
  23. package/dist/inspect-tools.js +63 -2
  24. package/dist/inspect-tools.js.map +1 -1
  25. package/dist/managed-session.d.ts.map +1 -1
  26. package/dist/managed-session.js +140 -14
  27. package/dist/managed-session.js.map +1 -1
  28. package/dist/management-client.d.ts +32 -0
  29. package/dist/management-client.d.ts.map +1 -1
  30. package/dist/management-client.js +93 -0
  31. package/dist/management-client.js.map +1 -1
  32. package/dist/orchestration/agents.d.ts +18 -0
  33. package/dist/orchestration/agents.d.ts.map +1 -1
  34. package/dist/orchestration/agents.js +63 -3
  35. package/dist/orchestration/agents.js.map +1 -1
  36. package/dist/orchestration/index.d.ts +2 -2
  37. package/dist/orchestration/index.js +1 -1
  38. package/dist/orchestration/lifecycle.d.ts +1 -0
  39. package/dist/orchestration/lifecycle.d.ts.map +1 -1
  40. package/dist/orchestration/lifecycle.js +349 -1
  41. package/dist/orchestration/lifecycle.js.map +1 -1
  42. package/dist/orchestration/queue.d.ts.map +1 -1
  43. package/dist/orchestration/queue.js +61 -2
  44. package/dist/orchestration/queue.js.map +1 -1
  45. package/dist/orchestration/runtime.d.ts +1 -1
  46. package/dist/orchestration/runtime.d.ts.map +1 -1
  47. package/dist/orchestration/runtime.js +31 -1
  48. package/dist/orchestration/runtime.js.map +1 -1
  49. package/dist/orchestration/state.d.ts +29 -0
  50. package/dist/orchestration/state.d.ts.map +1 -1
  51. package/dist/orchestration/state.js +11 -0
  52. package/dist/orchestration/state.js.map +1 -1
  53. package/dist/orchestration/turn.d.ts.map +1 -1
  54. package/dist/orchestration/turn.js +68 -1
  55. package/dist/orchestration/turn.js.map +1 -1
  56. package/dist/orchestration-registry.d.ts.map +1 -1
  57. package/dist/orchestration-registry.js +8 -2
  58. package/dist/orchestration-registry.js.map +1 -1
  59. package/dist/orchestration-version.d.ts +1 -1
  60. package/dist/orchestration-version.js +1 -1
  61. package/dist/orchestration.d.ts +2 -2
  62. package/dist/orchestration.js +1 -1
  63. package/dist/orchestration_1_0_46.d.ts +1 -1
  64. package/dist/orchestration_1_0_47.d.ts +1 -1
  65. package/dist/orchestration_1_0_48.d.ts +1 -1
  66. package/dist/orchestration_1_0_49.d.ts +1 -1
  67. package/dist/orchestration_1_0_50.d.ts +1 -1
  68. package/dist/orchestration_1_0_65/agents.d.ts +43 -0
  69. package/dist/orchestration_1_0_65/agents.d.ts.map +1 -0
  70. package/dist/orchestration_1_0_65/agents.js +780 -0
  71. package/dist/orchestration_1_0_65/agents.js.map +1 -0
  72. package/dist/orchestration_1_0_65/index.d.ts +24 -0
  73. package/dist/orchestration_1_0_65/index.d.ts.map +1 -0
  74. package/dist/orchestration_1_0_65/index.js +13 -0
  75. package/dist/orchestration_1_0_65/index.js.map +1 -0
  76. package/dist/orchestration_1_0_65/lifecycle.d.ts +48 -0
  77. package/dist/orchestration_1_0_65/lifecycle.d.ts.map +1 -0
  78. package/dist/orchestration_1_0_65/lifecycle.js +572 -0
  79. package/dist/orchestration_1_0_65/lifecycle.js.map +1 -0
  80. package/dist/orchestration_1_0_65/queue.d.ts +7 -0
  81. package/dist/orchestration_1_0_65/queue.d.ts.map +1 -0
  82. package/dist/orchestration_1_0_65/queue.js +745 -0
  83. package/dist/orchestration_1_0_65/queue.js.map +1 -0
  84. package/dist/orchestration_1_0_65/runtime.d.ts +29 -0
  85. package/dist/orchestration_1_0_65/runtime.d.ts.map +1 -0
  86. package/dist/orchestration_1_0_65/runtime.js +195 -0
  87. package/dist/orchestration_1_0_65/runtime.js.map +1 -0
  88. package/dist/orchestration_1_0_65/state.d.ts +147 -0
  89. package/dist/orchestration_1_0_65/state.d.ts.map +1 -0
  90. package/dist/orchestration_1_0_65/state.js +147 -0
  91. package/dist/orchestration_1_0_65/state.js.map +1 -0
  92. package/dist/orchestration_1_0_65/turn.d.ts +34 -0
  93. package/dist/orchestration_1_0_65/turn.d.ts.map +1 -0
  94. package/dist/orchestration_1_0_65/turn.js +1144 -0
  95. package/dist/orchestration_1_0_65/turn.js.map +1 -0
  96. package/dist/orchestration_1_0_65/utils.d.ts +39 -0
  97. package/dist/orchestration_1_0_65/utils.d.ts.map +1 -0
  98. package/dist/orchestration_1_0_65/utils.js +316 -0
  99. package/dist/orchestration_1_0_65/utils.js.map +1 -0
  100. package/dist/orchestration_1_0_66/agents.d.ts +52 -0
  101. package/dist/orchestration_1_0_66/agents.d.ts.map +1 -0
  102. package/dist/orchestration_1_0_66/agents.js +825 -0
  103. package/dist/orchestration_1_0_66/agents.js.map +1 -0
  104. package/dist/orchestration_1_0_66/index.d.ts +24 -0
  105. package/dist/orchestration_1_0_66/index.d.ts.map +1 -0
  106. package/dist/orchestration_1_0_66/index.js +13 -0
  107. package/dist/orchestration_1_0_66/index.js.map +1 -0
  108. package/dist/orchestration_1_0_66/lifecycle.d.ts +48 -0
  109. package/dist/orchestration_1_0_66/lifecycle.d.ts.map +1 -0
  110. package/dist/orchestration_1_0_66/lifecycle.js +573 -0
  111. package/dist/orchestration_1_0_66/lifecycle.js.map +1 -0
  112. package/dist/orchestration_1_0_66/queue.d.ts +7 -0
  113. package/dist/orchestration_1_0_66/queue.d.ts.map +1 -0
  114. package/dist/orchestration_1_0_66/queue.js +776 -0
  115. package/dist/orchestration_1_0_66/queue.js.map +1 -0
  116. package/dist/orchestration_1_0_66/runtime.d.ts +29 -0
  117. package/dist/orchestration_1_0_66/runtime.d.ts.map +1 -0
  118. package/dist/orchestration_1_0_66/runtime.js +198 -0
  119. package/dist/orchestration_1_0_66/runtime.js.map +1 -0
  120. package/dist/orchestration_1_0_66/state.d.ts +149 -0
  121. package/dist/orchestration_1_0_66/state.d.ts.map +1 -0
  122. package/dist/orchestration_1_0_66/state.js +148 -0
  123. package/dist/orchestration_1_0_66/state.js.map +1 -0
  124. package/dist/orchestration_1_0_66/turn.d.ts +34 -0
  125. package/dist/orchestration_1_0_66/turn.d.ts.map +1 -0
  126. package/dist/orchestration_1_0_66/turn.js +1169 -0
  127. package/dist/orchestration_1_0_66/turn.js.map +1 -0
  128. package/dist/orchestration_1_0_66/utils.d.ts +39 -0
  129. package/dist/orchestration_1_0_66/utils.d.ts.map +1 -0
  130. package/dist/orchestration_1_0_66/utils.js +316 -0
  131. package/dist/orchestration_1_0_66/utils.js.map +1 -0
  132. package/dist/orchestration_1_0_67/agents.d.ts +52 -0
  133. package/dist/orchestration_1_0_67/agents.d.ts.map +1 -0
  134. package/dist/orchestration_1_0_67/agents.js +825 -0
  135. package/dist/orchestration_1_0_67/agents.js.map +1 -0
  136. package/dist/orchestration_1_0_67/index.d.ts +28 -0
  137. package/dist/orchestration_1_0_67/index.d.ts.map +1 -0
  138. package/dist/orchestration_1_0_67/index.js +13 -0
  139. package/dist/orchestration_1_0_67/index.js.map +1 -0
  140. package/dist/orchestration_1_0_67/lifecycle.d.ts +49 -0
  141. package/dist/orchestration_1_0_67/lifecycle.d.ts.map +1 -0
  142. package/dist/orchestration_1_0_67/lifecycle.js +807 -0
  143. package/dist/orchestration_1_0_67/lifecycle.js.map +1 -0
  144. package/dist/orchestration_1_0_67/queue.d.ts +7 -0
  145. package/dist/orchestration_1_0_67/queue.d.ts.map +1 -0
  146. package/dist/orchestration_1_0_67/queue.js +804 -0
  147. package/dist/orchestration_1_0_67/queue.js.map +1 -0
  148. package/dist/orchestration_1_0_67/runtime.d.ts +29 -0
  149. package/dist/orchestration_1_0_67/runtime.d.ts.map +1 -0
  150. package/dist/orchestration_1_0_67/runtime.js +228 -0
  151. package/dist/orchestration_1_0_67/runtime.js.map +1 -0
  152. package/dist/orchestration_1_0_67/state.d.ts +176 -0
  153. package/dist/orchestration_1_0_67/state.d.ts.map +1 -0
  154. package/dist/orchestration_1_0_67/state.js +158 -0
  155. package/dist/orchestration_1_0_67/state.js.map +1 -0
  156. package/dist/orchestration_1_0_67/turn.d.ts +34 -0
  157. package/dist/orchestration_1_0_67/turn.d.ts.map +1 -0
  158. package/dist/orchestration_1_0_67/turn.js +1208 -0
  159. package/dist/orchestration_1_0_67/turn.js.map +1 -0
  160. package/dist/orchestration_1_0_67/utils.d.ts +39 -0
  161. package/dist/orchestration_1_0_67/utils.d.ts.map +1 -0
  162. package/dist/orchestration_1_0_67/utils.js +316 -0
  163. package/dist/orchestration_1_0_67/utils.js.map +1 -0
  164. package/dist/regen-worker.d.ts +167 -0
  165. package/dist/regen-worker.d.ts.map +1 -0
  166. package/dist/regen-worker.js +512 -0
  167. package/dist/regen-worker.js.map +1 -0
  168. package/dist/resourcemgr-tools.d.ts.map +1 -1
  169. package/dist/resourcemgr-tools.js +33 -2
  170. package/dist/resourcemgr-tools.js.map +1 -1
  171. package/dist/session-lifecycle.d.ts +8 -0
  172. package/dist/session-lifecycle.d.ts.map +1 -1
  173. package/dist/session-lifecycle.js +35 -17
  174. package/dist/session-lifecycle.js.map +1 -1
  175. package/dist/session-manager.d.ts +21 -0
  176. package/dist/session-manager.d.ts.map +1 -1
  177. package/dist/session-manager.js +104 -11
  178. package/dist/session-manager.js.map +1 -1
  179. package/dist/session-proxy.d.ts +34 -0
  180. package/dist/session-proxy.d.ts.map +1 -1
  181. package/dist/session-proxy.js +407 -5
  182. package/dist/session-proxy.js.map +1 -1
  183. package/dist/session-store.d.ts +50 -15
  184. package/dist/session-store.d.ts.map +1 -1
  185. package/dist/session-store.js +143 -30
  186. package/dist/session-store.js.map +1 -1
  187. package/dist/snapshot-protocol.d.ts +34 -3
  188. package/dist/snapshot-protocol.d.ts.map +1 -1
  189. package/dist/snapshot-protocol.js +25 -0
  190. package/dist/snapshot-protocol.js.map +1 -1
  191. package/dist/types.d.ts +101 -1
  192. package/dist/types.d.ts.map +1 -1
  193. package/dist/types.js.map +1 -1
  194. package/dist/web/web-management-client.d.ts +11 -0
  195. package/dist/web/web-management-client.d.ts.map +1 -1
  196. package/dist/web/web-management-client.js +8 -0
  197. package/dist/web/web-management-client.js.map +1 -1
  198. package/dist/worker.d.ts.map +1 -1
  199. package/dist/worker.js +7 -0
  200. package/dist/worker.js.map +1 -1
  201. package/package.json +1 -1
@@ -10,12 +10,14 @@ import { mergePromptSections } from "./prompt-layering.js";
10
10
  import { approvePermissionForSession } from "./permissions.js";
11
11
  import { formatSessionOwnerLabel, getSessionOwnerKind, matchesSessionOwnerFilters } from "./session-owner-utils.js";
12
12
  import { cmsRetryBestEffort, cmsRetryCritical } from "./cms-retry.js";
13
+ import { archiveName, artifactExists, assembleRegenClosure, buildMapReduceSeedPrompt, deterministicPackage, DISTILLER_SYSTEM_MESSAGE, distillInputName, distillOutputName, packageName, parseDistillerResponse, renderBootstrap, runRegenArchive, runRegenDistill, } from "./regen-worker.js";
14
+ import { REGEN_DISTILLER_SERVICE_KIND } from "./distiller-tools.js";
13
15
  import { computeCronAtNextFire } from "./cron-at.js";
14
16
  import { SpanStatusCode, trace as otelTrace } from "@opentelemetry/api";
15
17
  import os from "node:os";
16
18
  import fs from "node:fs";
17
19
  import path from "node:path";
18
- import { randomUUID } from "node:crypto";
20
+ import { createHash, randomUUID } from "node:crypto";
19
21
  import { attemptStoreRecovery, runTurnCommit, runTurnPreamble } from "./session-lifecycle.js";
20
22
  import { supportsVersionedSnapshots, writeTurnSentinel } from "./snapshot-protocol.js";
21
23
  const SYSTEM_AGENT_IDS = new Set(["pilotswarm", "sweeper", "resourcemgr", "facts-manager", "agent-tuner"]);
@@ -439,7 +441,12 @@ async function tryReadSnapshotSizeBytes(sessionStore, sessionId) {
439
441
  export function createSessionProxy(ctx, sessionId, affinityKey, config) {
440
442
  return {
441
443
  runTurn(prompt, bootstrap, turnIndex, turnMeta) {
442
- return ctx.scheduleActivityOnSession("runTurn", {
444
+ return ctx.scheduleActivityOnSession(
445
+ // The epoch-start turn is a distinct activity name (runTurn2):
446
+ // pre-1.0.67 workers don't register it, so a rolling deploy can
447
+ // never hand the fresh-epoch create to a worker that would
448
+ // silently resume the dead transcript instead.
449
+ turnMeta?.epochStart ? "runTurn2" : "runTurn", {
443
450
  sessionId,
444
451
  prompt,
445
452
  config,
@@ -463,6 +470,8 @@ export function createSessionProxy(ctx, sessionId, affinityKey, config) {
463
470
  ...(turnMeta?.attachments && turnMeta.attachments.length > 0
464
471
  ? { attachments: turnMeta.attachments }
465
472
  : {}),
473
+ ...(turnMeta?.transcriptEpoch ? { transcriptEpoch: turnMeta.transcriptEpoch } : {}),
474
+ ...(turnMeta?.epochStart ? { epochStart: true } : {}),
466
475
  }, affinityKey);
467
476
  },
468
477
  dehydrate(reason, eventData) {
@@ -656,6 +665,40 @@ export function createSessionManagerProxy(ctx) {
656
665
  computeCronAtNextFire(schedule, afterUtcMs, lastOccurrenceKey) {
657
666
  return ctx.scheduleActivity("computeCronAtNextFire", { schedule, afterUtcMs, lastOccurrenceKey });
658
667
  },
668
+ // ── Session regeneration (1.0.67) ──────────────────────
669
+ /** ARCHIVE stage: transcript slice → attempt-scoped artifact. Idempotent per attempt. */
670
+ runRegenArchive(sessionId, epoch, attemptId) {
671
+ return ctx.scheduleActivity("runRegenArchive", { sessionId, epoch, attemptId });
672
+ },
673
+ /** DISTILL (deterministic): closure package in-activity → ResumePackage artifact + bootstrap. */
674
+ runRegenDistill(sessionId, epoch, attemptId, opts) {
675
+ return ctx.scheduleActivity("runRegenDistill", { sessionId, epoch, attemptId, ...(opts ?? {}) });
676
+ },
677
+ /** Post-flip boundary: epoch_committed event + transcript_epoch + regen_count, one CMS transaction. */
678
+ commitEpochBoundary(sessionId, commit) {
679
+ return ctx.scheduleActivity("commitEpochBoundary", { sessionId, commit });
680
+ },
681
+ /** Proven rebirth: session.regenerated event + last_regen_stats. */
682
+ recordRegenerated(sessionId, payload) {
683
+ return ctx.scheduleActivity("recordRegenerated", { sessionId, payload });
684
+ },
685
+ // ── Service-session distiller (1.0.68) ─────────────────
686
+ /** Spawn the regen-distiller service session under the tree root (idempotent per attempt). */
687
+ runRegenSpawnDistiller(sessionId, epoch, attemptId, opts) {
688
+ return ctx.scheduleActivity("runRegenSpawnDistiller", { sessionId, epoch, attemptId, ...(opts ?? {}) });
689
+ },
690
+ /** Poll the distiller service session: running | completed (with response) | failed. */
691
+ runRegenCheckDistiller(distillerSessionId) {
692
+ return ctx.scheduleActivity("runRegenCheckDistiller", { distillerSessionId });
693
+ },
694
+ /** Parse/validate the distiller's final message into the package (+dumps); deterministic fallback on junk. */
695
+ runRegenCollectDistiller(sessionId, epoch, attemptId, distillerSessionId, opts) {
696
+ return ctx.scheduleActivity("runRegenCollectDistiller", { sessionId, epoch, attemptId, distillerSessionId, ...(opts ?? {}) });
697
+ },
698
+ /** Best-effort cancel of a timed-out/failed distiller service session. */
699
+ runRegenCancelDistiller(distillerSessionId) {
700
+ return ctx.scheduleActivity("runRegenCancelDistiller", { distillerSessionId });
701
+ },
659
702
  };
660
703
  }
661
704
  // ─── Activity Registration ───────────────────────────────────────
@@ -695,7 +738,7 @@ artifactStore) {
695
738
  ...(clientConfig?.aadDbUser != null && { aadDbUser: clientConfig.aadDbUser }),
696
739
  });
697
740
  // ── runTurn ──────────────────────────────────────────────
698
- runtime.registerActivity("runTurn", async (activityCtx, input) => {
741
+ const runTurnHandler = async (activityCtx, input) => {
699
742
  // Attachment count is traced unconditionally: a 2026-07-21 incident
700
743
  // showed turns whose ActivityScheduled input carried refs executing
701
744
  // WITHOUT them (suspected work-item redelivery after eviction/lock
@@ -786,12 +829,17 @@ artifactStore) {
786
829
  // observability-only reads (lossy flag, snapshot_store_empty).
787
830
  expectedVersion: input.snapshot.expectedVersion ?? 0,
788
831
  turnKey: input.snapshot.turnKey,
832
+ // Session regeneration: epoch scopes every store call to
833
+ // the current chain and gates local-dir trust. Absent on
834
+ // pre-1.0.67 inputs → 0 (legacy chain, today's behavior).
835
+ transcriptEpoch: input.transcriptEpoch ?? 0,
789
836
  dropWarmSession: () => sessionManager.dropWarmSession(input.sessionId),
790
837
  trace,
791
838
  }
792
839
  : null;
793
840
  let lifecycleBaseVersion = 0;
794
841
  let lifecycleRehydrated = false;
842
+ let lifecyclePreambleFresh = false;
795
843
  // Protocol-native persistence stats: the legacy dehydrate/hydrate
796
844
  // activities no longer run, so the preamble/commit paths feed the
797
845
  // same CMS summary + events the Stats pane reads. Best-effort only.
@@ -819,6 +867,7 @@ artifactStore) {
819
867
  }
820
868
  lifecycleBaseVersion = pre.baseVersion;
821
869
  lifecycleRehydrated = pre.kind === "hydrated";
870
+ lifecyclePreambleFresh = pre.kind === "fresh";
822
871
  if (lifecycleRehydrated) {
823
872
  await recordLifecycleHydration(pre.baseVersion);
824
873
  }
@@ -837,7 +886,7 @@ artifactStore) {
837
886
  },
838
887
  }], workerNodeId), (msg) => activityCtx.traceInfo(msg));
839
888
  }
840
- if (pre.kind === "fresh" && pre.lossy && catalog) {
889
+ if (pre.kind === "fresh" && pre.lossy && !input.epochStart && catalog) {
841
890
  await cmsRetryBestEffort(`runTurn.recordEvent snapshot-store-empty session=${input.sessionId}`, () => catalog.recordEvents(input.sessionId, [{
842
891
  eventType: "session.snapshot_store_empty",
843
892
  data: {
@@ -850,9 +899,17 @@ artifactStore) {
850
899
  const executeTurnBody = async () => {
851
900
  let session = null;
852
901
  let effectivePrompt = input.prompt;
902
+ // Conditional epoch init (runTurn2): create a brand-new SDK session
903
+ // ONLY when the epoch's chain is empty (preamble "fresh"). A committed
904
+ // grounding turn recovers via already-committed above; a non-empty
905
+ // chain resumes — an unconditional reset would erase a committed
906
+ // grounding turn on activity retry.
907
+ const epochCreate = input.epochStart === true && (!lifecycle || lifecyclePreambleFresh);
853
908
  try {
854
909
  session = await sessionManager.getOrCreate(input.sessionId, runConfig, {
855
910
  turnIndex: input.turnIndex,
911
+ transcriptEpoch: input.transcriptEpoch ?? 0,
912
+ ...(epochCreate ? { epochStart: true } : {}),
856
913
  trace,
857
914
  lockHeld: true,
858
915
  });
@@ -1279,6 +1336,62 @@ artifactStore) {
1279
1336
  return `[SYSTEM: set_session_model failed: ${err?.message || String(err)}]`;
1280
1337
  }
1281
1338
  },
1339
+ regenerateContext: async (args) => {
1340
+ try {
1341
+ if (!storeUrl)
1342
+ return "[SYSTEM: regenerate_context failed: no storeUrl is configured.]";
1343
+ const sdkClient = new PilotSwarmClient(internalClientConfig());
1344
+ try {
1345
+ await sdkClient.start();
1346
+ await sdkClient.duroxideClient.enqueueEvent(`session-${input.sessionId}`, "messages", JSON.stringify({
1347
+ type: "cmd",
1348
+ cmd: "regenerate",
1349
+ id: `regenerate-tool-${randomUUID()}`,
1350
+ args: {
1351
+ ...(args.handoff ? { handoff: String(args.handoff).slice(0, 4_000) } : {}),
1352
+ ...(args.instructions ? { instructions: String(args.instructions).slice(0, 4_000) } : {}),
1353
+ source: "tool",
1354
+ },
1355
+ // Owner-gate evidence: the runTurn activity input's
1356
+ // authoritative sender, stamped server-side — the
1357
+ // cmd handler refuses non-owner turns on shared
1358
+ // sessions. Never an LLM-supplied value.
1359
+ ...(input.sender ? { sender: input.sender } : {}),
1360
+ }));
1361
+ }
1362
+ finally {
1363
+ await sdkClient.stop().catch(() => { });
1364
+ }
1365
+ return "[SYSTEM: Regeneration accepted. Finish this turn cleanly NOW — at the next boundary the runtime archives your transcript, distills a resume package, and rebuilds your context. Durable state (facts, artifacts, children, schedule) is untouched.]";
1366
+ }
1367
+ catch (err) {
1368
+ return `[SYSTEM: regenerate_context failed: ${err?.message || String(err)}]`;
1369
+ }
1370
+ },
1371
+ regenerateAgent: async (args) => {
1372
+ try {
1373
+ const child = await resolveManagedChild(args.agent_id);
1374
+ const sdkClient = await getInlineClient();
1375
+ await sdkClient._getDuroxideClient().enqueueEvent(child.orchId, "messages", JSON.stringify({
1376
+ type: "cmd",
1377
+ cmd: "regenerate",
1378
+ id: `regenerate-parent-${randomUUID()}`,
1379
+ args: {
1380
+ ...(args.handoff ? { handoff: String(args.handoff).slice(0, 4_000) } : {}),
1381
+ ...(args.instructions ? { instructions: String(args.instructions).slice(0, 4_000) } : {}),
1382
+ source: "parent",
1383
+ },
1384
+ // Parent-of gate evidence: the child's handler accepts
1385
+ // source "parent" only when this id equals its own
1386
+ // carried parentSessionId.
1387
+ requestedBy: input.sessionId,
1388
+ }));
1389
+ return `[SYSTEM: Regeneration requested for sub-agent ${child.orchId}. It applies at the child's next turn boundary; check_agents will reflect the rebirth. Continue your work in this SAME turn.]`;
1390
+ }
1391
+ catch (err) {
1392
+ return `[SYSTEM: regenerate_agent failed: ${err?.message || String(err)}]`;
1393
+ }
1394
+ },
1282
1395
  messageAgent: async (args) => {
1283
1396
  try {
1284
1397
  const child = await resolveManagedChild(args.agent_id);
@@ -2165,7 +2278,12 @@ artifactStore) {
2165
2278
  catch { }
2166
2279
  }
2167
2280
  }
2168
- });
2281
+ };
2282
+ runtime.registerActivity("runTurn", runTurnHandler);
2283
+ // Session regeneration: the epoch-start turn dispatches under a NEW
2284
+ // activity name so pre-1.0.67 workers — which would silently resume the
2285
+ // dead transcript — structurally cannot receive it (deployment gate §4).
2286
+ runtime.registerActivity("runTurn2", runTurnHandler);
2169
2287
  // ── abortTurn ────────────────────────────────────────────
2170
2288
  // Stop-turn fast-path interrupt. Routed on the session affinity key so it
2171
2289
  // lands on the worker owning the warm ManagedSession and runs CONCURRENTLY
@@ -2997,5 +3115,289 @@ artifactStore) {
2997
3115
  activityCtx.traceInfo(`[computeCronAtNextFire] tz=${input.schedule?.tz} after=${input.afterUtcMs}`);
2998
3116
  return computeCronAtNextFire(input.schedule, input.afterUtcMs, input.lastOccurrenceKey);
2999
3117
  });
3118
+ // ── Session regeneration activities (1.0.67) ──────────────
3119
+ const regenDeps = (activityCtx) => {
3120
+ if (!catalog)
3121
+ throw new Error("session regeneration requires the CMS catalog");
3122
+ return {
3123
+ catalog,
3124
+ artifactStore: artifactStore ?? null,
3125
+ resolveModelOptions: (ref) => sessionManager.resolveModelSessionOptions(ref),
3126
+ fallbackDistillerModel: process.env.PILOTSWARM_DISTILLER_FALLBACK_MODEL || undefined,
3127
+ trace: (message) => activityCtx.traceInfo(`[regen] ${message}`),
3128
+ };
3129
+ };
3130
+ runtime.registerActivity("runRegenArchive", async (activityCtx, input) => {
3131
+ activityCtx.traceInfo(`[runRegenArchive] session=${input.sessionId} epoch=${input.epoch} attempt=${input.attemptId}`);
3132
+ return runRegenArchive(regenDeps(activityCtx), input);
3133
+ });
3134
+ runtime.registerActivity("runRegenDistill", async (activityCtx, input) => {
3135
+ activityCtx.traceInfo(`[runRegenDistill] session=${input.sessionId} epoch=${input.epoch} attempt=${input.attemptId}`);
3136
+ return runRegenDistill(regenDeps(activityCtx), input);
3137
+ });
3138
+ runtime.registerActivity("commitEpochBoundary", async (activityCtx, input) => {
3139
+ if (!catalog)
3140
+ throw new Error("session regeneration requires the CMS catalog");
3141
+ activityCtx.traceInfo(`[commitEpochBoundary] session=${input.sessionId} toEpoch=${input.commit?.toEpoch}`);
3142
+ return catalog.recordEpochCommitted(input.sessionId, input.commit);
3143
+ });
3144
+ runtime.registerActivity("recordRegenerated", async (activityCtx, input) => {
3145
+ if (!catalog)
3146
+ throw new Error("session regeneration requires the CMS catalog");
3147
+ activityCtx.traceInfo(`[recordRegenerated] session=${input.sessionId} epoch=${input.payload?.epoch}`);
3148
+ return catalog.recordRegenerated(input.sessionId, input.payload);
3149
+ });
3150
+ // ── Service-session distiller activities (1.0.68) ──────────
3151
+ // The distiller is a REAL session (service_kind="regen-distiller") under
3152
+ // the served tree's ROOT: lifecycle events, metrics, and normal sweeper
3153
+ // cleanup apply because nothing is special-cased. Its id is deterministic
3154
+ // per attempt so activity retries reuse the same session.
3155
+ const distillerSessionIdFor = (sessionId, epoch, attemptId) => {
3156
+ const digest = createHash("sha1")
3157
+ .update(`ps-regen-distiller:${sessionId}:e${epoch}:${attemptId}`)
3158
+ .digest();
3159
+ const b = Buffer.from(digest.subarray(0, 16));
3160
+ b[6] = (b[6] & 0x0f) | 0x50; // UUIDv5 version bits
3161
+ b[8] = (b[8] & 0x3f) | 0x80; // RFC 4122 variant
3162
+ const h = b.toString("hex");
3163
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
3164
+ };
3165
+ const enqueueDistillerCmd = async (distillerSessionId, cmd, idPrefix) => {
3166
+ const sdkClient = new PilotSwarmClient(internalClientConfig());
3167
+ try {
3168
+ await sdkClient.start();
3169
+ await sdkClient.duroxideClient.enqueueEvent(`session-${distillerSessionId}`, "messages", JSON.stringify({ type: "cmd", cmd, id: `${idPrefix}-${randomUUID()}` }));
3170
+ }
3171
+ finally {
3172
+ await sdkClient.stop().catch(() => { });
3173
+ }
3174
+ };
3175
+ runtime.registerActivity("runRegenSpawnDistiller", async (activityCtx, input) => {
3176
+ if (!catalog)
3177
+ throw new Error("distiller spawn requires the CMS catalog");
3178
+ if (!artifactStore)
3179
+ throw new Error("distiller spawn requires an artifact store");
3180
+ if (!storeUrl)
3181
+ throw new Error("distiller spawn requires a storeUrl");
3182
+ // Deployment kill switch: force the deterministic package fleet-wide.
3183
+ // Read in the ACTIVITY (recorded in history) — never in the orchestration.
3184
+ if (process.env.PILOTSWARM_REGEN_DETERMINISTIC_ONLY === "1") {
3185
+ return { fallback: "deterministic-only" };
3186
+ }
3187
+ const deps = regenDeps(activityCtx);
3188
+ const distillerSessionId = distillerSessionIdFor(input.sessionId, input.epoch, input.attemptId);
3189
+ // Build the seed once — reused by the fresh spawn and by the re-seed
3190
+ // path below so a create-then-crash-before-send retry still delivers it.
3191
+ const buildSeed = async () => {
3192
+ const closure = await assembleRegenClosure({ catalog, artifactStore: artifactStore }, input.sessionId);
3193
+ const seed = buildMapReduceSeedPrompt({
3194
+ servedSessionId: input.sessionId,
3195
+ epoch: input.epoch,
3196
+ attemptId: input.attemptId,
3197
+ archiveArtifactId: input.archiveArtifactId || archiveName(input.epoch, input.attemptId),
3198
+ closure,
3199
+ ...(input.handoff ? { handoff: input.handoff } : {}),
3200
+ ...(input.instructions ? { instructions: input.instructions } : {}),
3201
+ });
3202
+ // Dump the EXACT distiller input (§9 dumps) on the served session.
3203
+ await artifactStore.uploadArtifact(input.sessionId, distillInputName(input.epoch, input.attemptId), Buffer.from(seed, "utf8"), "text/markdown");
3204
+ return seed;
3205
+ };
3206
+ const existing = await catalog.getSession(distillerSessionId).catch(() => null);
3207
+ if (existing) {
3208
+ // Only reuse a row that is ACTUALLY our distiller for THIS session —
3209
+ // re-verify the service columns before trusting its output as this
3210
+ // regen's package. The id is per-attempt and unguessable, so a
3211
+ // mismatch is not a real attack, but defense-in-depth: never collect
3212
+ // a foreign/mislabelled session as the distiller (adversarial-review
3213
+ // finding). A mismatch throws → the pipeline falls back deterministically.
3214
+ if (existing.serviceKind !== REGEN_DISTILLER_SERVICE_KIND || existing.serviceOf !== input.sessionId) {
3215
+ throw new Error(`distiller id collision: ${distillerSessionId} exists but is not this session's distiller `
3216
+ + `(serviceKind=${existing.serviceKind ?? "null"}, serviceOf=${existing.serviceOf ?? "null"})`);
3217
+ }
3218
+ // Re-seed if the seed never landed (activity retried after
3219
+ // createSession but before send): otherwise the distiller sits with
3220
+ // no orchestration/work and the regen burns the whole 5-min deadline
3221
+ // before falling back (adversarial-review finding). Idempotent —
3222
+ // _startTurn ensures the orchestration then enqueues the prompt.
3223
+ const seeded = (await catalog.getSessionEventsBefore(distillerSessionId, Number.MAX_SAFE_INTEGER, 1, ["user.message"]).catch(() => [])).length > 0;
3224
+ if (!seeded) {
3225
+ activityCtx.traceInfo(`[runRegenSpawnDistiller] ${distillerSessionId} exists but was never seeded — re-seeding`);
3226
+ const seed = await buildSeed();
3227
+ const reseedClient = new PilotSwarmClient(internalClientConfig());
3228
+ try {
3229
+ await reseedClient.start();
3230
+ await catalog.markSessionService(distillerSessionId, REGEN_DISTILLER_SERVICE_KIND, input.sessionId);
3231
+ await reseedClient._startTurn(distillerSessionId, seed, { bootstrap: true });
3232
+ }
3233
+ finally {
3234
+ await reseedClient.stop().catch(() => { });
3235
+ }
3236
+ }
3237
+ else {
3238
+ activityCtx.traceInfo(`[runRegenSpawnDistiller] reusing ${distillerSessionId} (attempt retry)`);
3239
+ }
3240
+ return { distillerSessionId, distillerModel: existing.model ?? "(existing)", reused: true };
3241
+ }
3242
+ // Model policy (§9): per-call override → CLUSTER DEFAULT → configured
3243
+ // fallback. The served session's model is deliberately not in the chain.
3244
+ const candidates = [input.distillerModel, undefined, deps.fallbackDistillerModel];
3245
+ let resolvedRef;
3246
+ let resolvedAny = false;
3247
+ for (const ref of candidates) {
3248
+ if (deps.resolveModelOptions(ref)) {
3249
+ resolvedRef = ref;
3250
+ resolvedAny = true;
3251
+ break;
3252
+ }
3253
+ }
3254
+ if (!resolvedAny) {
3255
+ activityCtx.traceInfo(`[runRegenSpawnDistiller] no distiller model resolvable — caller falls back deterministically`);
3256
+ return { fallback: "no-model" };
3257
+ }
3258
+ // Root ancestor: service sessions collect under the tree root so a
3259
+ // sub-agent's distiller is still visible at the top (§9.1).
3260
+ let rootId = input.sessionId;
3261
+ for (let hop = 0; hop < 16; hop++) {
3262
+ const row = await catalog.getSession(rootId).catch(() => null);
3263
+ if (!row?.parentSessionId)
3264
+ break;
3265
+ rootId = row.parentSessionId;
3266
+ }
3267
+ const seed = await buildSeed();
3268
+ const owner = await resolveEffectiveSpawnOwner((id) => catalog.getSession(id), rootId).catch(() => null);
3269
+ const sdkClient = new PilotSwarmClient(internalClientConfig());
3270
+ try {
3271
+ await sdkClient.start();
3272
+ const session = await sdkClient.createSession({
3273
+ sessionId: distillerSessionId,
3274
+ parentSessionId: rootId,
3275
+ nestingLevel: 1,
3276
+ agentId: REGEN_DISTILLER_SERVICE_KIND,
3277
+ ...(resolvedRef ? { model: resolvedRef } : {}),
3278
+ systemMessage: DISTILLER_SYSTEM_MESSAGE,
3279
+ toolNames: ["read_transcript_page"],
3280
+ ...(owner ? { owner } : {}),
3281
+ });
3282
+ await catalog.markSessionService(distillerSessionId, REGEN_DISTILLER_SERVICE_KIND, input.sessionId);
3283
+ await cmsRetryBestEffort(`runRegenSpawnDistiller.updateSession meta session=${distillerSessionId}`, () => catalog.updateSession(distillerSessionId, {
3284
+ title: `Regen Distiller — ${input.sessionId.slice(0, 8)} e${input.epoch}→e${input.epoch + 1}`,
3285
+ agentId: REGEN_DISTILLER_SERVICE_KIND,
3286
+ }), (msg) => activityCtx.traceInfo(msg));
3287
+ await session.send(seed, { bootstrap: true });
3288
+ }
3289
+ finally {
3290
+ await sdkClient.stop().catch(() => { });
3291
+ }
3292
+ activityCtx.traceInfo(`[runRegenSpawnDistiller] spawned ${distillerSessionId} under root ${rootId} model=${resolvedRef ?? "(default)"}`);
3293
+ return { distillerSessionId, distillerModel: resolvedRef ?? "(default)" };
3294
+ });
3295
+ runtime.registerActivity("runRegenCheckDistiller", async (_activityCtx, input) => {
3296
+ if (!catalog)
3297
+ throw new Error("distiller check requires the CMS catalog");
3298
+ const row = await catalog.getSession(input.distillerSessionId).catch(() => null);
3299
+ if (!row)
3300
+ return { status: "failed", reason: "missing" };
3301
+ const rows = await catalog.getSessionEventsBefore(input.distillerSessionId, Number.MAX_SAFE_INTEGER, 1, ["assistant.message"]);
3302
+ if (rows.length > 0)
3303
+ return { status: "completed" };
3304
+ if (row.state === "failed" || row.state === "cancelled" || row.state === "completed") {
3305
+ return { status: "failed", reason: row.state };
3306
+ }
3307
+ return { status: "running" };
3308
+ });
3309
+ runtime.registerActivity("runRegenCollectDistiller", async (activityCtx, input) => {
3310
+ if (!catalog)
3311
+ throw new Error("distiller collect requires the CMS catalog");
3312
+ if (!artifactStore)
3313
+ throw new Error("distiller collect requires an artifact store");
3314
+ const { sessionId, epoch, attemptId, distillerSessionId } = input;
3315
+ const filename = packageName(epoch, attemptId);
3316
+ const bootstrapMeta = {
3317
+ epoch,
3318
+ ...(input.archiveArtifactId ? { archiveArtifactId: input.archiveArtifactId } : {}),
3319
+ packageArtifactId: filename,
3320
+ };
3321
+ // Attempt idempotency: a retry after the package landed re-renders from it.
3322
+ if (await artifactExists(artifactStore, sessionId, filename)) {
3323
+ const stored = await artifactStore.downloadArtifact(sessionId, filename);
3324
+ const pkg = parseDistillerResponse(stored.body.toString("utf8"));
3325
+ return {
3326
+ packageArtifactId: filename,
3327
+ bootstrap: renderBootstrap(pkg, bootstrapMeta),
3328
+ distillerModel: "(reused)",
3329
+ distillMode: "llm",
3330
+ packageBytes: stored.body.length,
3331
+ };
3332
+ }
3333
+ // Scan the last few assistant messages (newest first), not just the
3334
+ // latest: a distiller that emits the JSON and THEN narrates ("done!")
3335
+ // would otherwise be misread as junk (adversarial-review finding). The
3336
+ // latest message is still dumped as the raw output of record.
3337
+ const rows = await catalog.getSessionEventsBefore(distillerSessionId, Number.MAX_SAFE_INTEGER, 5, ["assistant.message"]);
3338
+ const candidates = rows
3339
+ .slice()
3340
+ .sort((a, b) => Number(b.seq) - Number(a.seq))
3341
+ .map((r) => String(r.data?.content ?? ""))
3342
+ .filter((t) => t.trim());
3343
+ const responseText = candidates[0] ?? "";
3344
+ // Dump the RAW pre-parse output (§9 dumps) — junk that triggers the
3345
+ // fallback stays inspectable.
3346
+ await artifactStore.uploadArtifact(sessionId, distillOutputName(epoch, attemptId), Buffer.from(responseText, "utf8"), "text/plain");
3347
+ let pkg;
3348
+ let distillMode = "llm";
3349
+ let modelLabel = input.distillerModel ?? "(default)";
3350
+ try {
3351
+ if (candidates.length === 0)
3352
+ throw new Error("distiller produced no response");
3353
+ // Take the newest message that parses as a valid ResumePackage.
3354
+ let parsed;
3355
+ for (const text of candidates) {
3356
+ try {
3357
+ parsed = parseDistillerResponse(text);
3358
+ break;
3359
+ }
3360
+ catch { /* try older */ }
3361
+ }
3362
+ if (!parsed)
3363
+ throw new Error("no assistant message parsed as a ResumePackage");
3364
+ pkg = parsed;
3365
+ }
3366
+ catch (err) {
3367
+ activityCtx.traceInfo(`[runRegenCollectDistiller] parse failed (${err?.message || err}) — deterministic fallback`);
3368
+ const closure = await assembleRegenClosure({ catalog, artifactStore }, sessionId);
3369
+ pkg = deterministicPackage(closure, { ...(input.instructions ? { instructions: input.instructions } : {}) });
3370
+ distillMode = "deterministic";
3371
+ modelLabel = `(fallback:${modelLabel}:invalid)`;
3372
+ }
3373
+ const body = Buffer.from(JSON.stringify(pkg, null, 2), "utf8");
3374
+ await artifactStore.uploadArtifact(sessionId, filename, body, "application/json");
3375
+ // The distiller delivered — complete it so the sweeper's normal
3376
+ // stale-terminal scan reclaims it. Best-effort: an already-terminal or
3377
+ // unreachable distiller must never fail the collect.
3378
+ try {
3379
+ await enqueueDistillerCmd(distillerSessionId, "done", "distiller-complete");
3380
+ }
3381
+ catch (err) {
3382
+ activityCtx.traceInfo(`[runRegenCollectDistiller] complete enqueue failed (${err?.message || err})`);
3383
+ }
3384
+ return {
3385
+ packageArtifactId: filename,
3386
+ bootstrap: renderBootstrap(pkg, bootstrapMeta),
3387
+ distillerModel: modelLabel,
3388
+ distillMode,
3389
+ packageBytes: body.length,
3390
+ };
3391
+ });
3392
+ runtime.registerActivity("runRegenCancelDistiller", async (activityCtx, input) => {
3393
+ try {
3394
+ await enqueueDistillerCmd(input.distillerSessionId, "cancel", "distiller-cancel");
3395
+ return { ok: true };
3396
+ }
3397
+ catch (err) {
3398
+ activityCtx.traceInfo(`[runRegenCancelDistiller] cancel enqueue failed (${err?.message || err})`);
3399
+ return { ok: false };
3400
+ }
3401
+ });
3000
3402
  }
3001
3403
  //# sourceMappingURL=session-proxy.js.map