instar 1.3.90 → 1.3.92

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 (152) hide show
  1. package/.claude/skills/autonomous/hooks/autonomous-stop-hook.sh +17 -0
  2. package/dashboard/index.html +108 -0
  3. package/dist/commands/server.d.ts.map +1 -1
  4. package/dist/commands/server.js +214 -1
  5. package/dist/commands/server.js.map +1 -1
  6. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  7. package/dist/config/ConfigDefaults.js +30 -0
  8. package/dist/config/ConfigDefaults.js.map +1 -1
  9. package/dist/config/LiveConfig.d.ts +3 -1
  10. package/dist/config/LiveConfig.d.ts.map +1 -1
  11. package/dist/config/LiveConfig.js +4 -1
  12. package/dist/config/LiveConfig.js.map +1 -1
  13. package/dist/config/stageWriteGuard.d.ts +23 -0
  14. package/dist/config/stageWriteGuard.d.ts.map +1 -0
  15. package/dist/config/stageWriteGuard.js +33 -0
  16. package/dist/config/stageWriteGuard.js.map +1 -0
  17. package/dist/core/DeliverMessageHandler.d.ts +33 -0
  18. package/dist/core/DeliverMessageHandler.d.ts.map +1 -0
  19. package/dist/core/DeliverMessageHandler.js +34 -0
  20. package/dist/core/DeliverMessageHandler.js.map +1 -0
  21. package/dist/core/LeaseCoordinator.d.ts +39 -2
  22. package/dist/core/LeaseCoordinator.d.ts.map +1 -1
  23. package/dist/core/LeaseCoordinator.js +45 -9
  24. package/dist/core/LeaseCoordinator.js.map +1 -1
  25. package/dist/core/MachineIdentity.d.ts +28 -1
  26. package/dist/core/MachineIdentity.d.ts.map +1 -1
  27. package/dist/core/MachineIdentity.js +81 -1
  28. package/dist/core/MachineIdentity.js.map +1 -1
  29. package/dist/core/MachineLoadBroker.d.ts +94 -0
  30. package/dist/core/MachineLoadBroker.d.ts.map +1 -0
  31. package/dist/core/MachineLoadBroker.js +87 -0
  32. package/dist/core/MachineLoadBroker.js.map +1 -0
  33. package/dist/core/MachinePoolRegistry.d.ts +94 -0
  34. package/dist/core/MachinePoolRegistry.d.ts.map +1 -0
  35. package/dist/core/MachinePoolRegistry.js +150 -0
  36. package/dist/core/MachinePoolRegistry.js.map +1 -0
  37. package/dist/core/MeshRpc.d.ts +165 -0
  38. package/dist/core/MeshRpc.d.ts.map +1 -0
  39. package/dist/core/MeshRpc.js +156 -0
  40. package/dist/core/MeshRpc.js.map +1 -0
  41. package/dist/core/MeshRpcClient.d.ts +59 -0
  42. package/dist/core/MeshRpcClient.d.ts.map +1 -0
  43. package/dist/core/MeshRpcClient.js +58 -0
  44. package/dist/core/MeshRpcClient.js.map +1 -0
  45. package/dist/core/MultiMachineCoordinator.d.ts +11 -0
  46. package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
  47. package/dist/core/MultiMachineCoordinator.js +13 -0
  48. package/dist/core/MultiMachineCoordinator.js.map +1 -1
  49. package/dist/core/NicknameAssigner.d.ts +51 -0
  50. package/dist/core/NicknameAssigner.d.ts.map +1 -0
  51. package/dist/core/NicknameAssigner.js +91 -0
  52. package/dist/core/NicknameAssigner.js.map +1 -0
  53. package/dist/core/NicknameCommand.d.ts +28 -0
  54. package/dist/core/NicknameCommand.d.ts.map +1 -0
  55. package/dist/core/NicknameCommand.js +69 -0
  56. package/dist/core/NicknameCommand.js.map +1 -0
  57. package/dist/core/PlacementExecutor.d.ts +71 -0
  58. package/dist/core/PlacementExecutor.d.ts.map +1 -0
  59. package/dist/core/PlacementExecutor.js +156 -0
  60. package/dist/core/PlacementExecutor.js.map +1 -0
  61. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  62. package/dist/core/PostUpdateMigrator.js +21 -0
  63. package/dist/core/PostUpdateMigrator.js.map +1 -1
  64. package/dist/core/RebalancePlanner.d.ts +48 -0
  65. package/dist/core/RebalancePlanner.d.ts.map +1 -0
  66. package/dist/core/RebalancePlanner.js +71 -0
  67. package/dist/core/RebalancePlanner.js.map +1 -0
  68. package/dist/core/SessionManager.d.ts +20 -3
  69. package/dist/core/SessionManager.d.ts.map +1 -1
  70. package/dist/core/SessionManager.js +54 -6
  71. package/dist/core/SessionManager.js.map +1 -1
  72. package/dist/core/SessionOwnership.d.ts +103 -0
  73. package/dist/core/SessionOwnership.d.ts.map +1 -0
  74. package/dist/core/SessionOwnership.js +0 -0
  75. package/dist/core/SessionOwnership.js.map +1 -0
  76. package/dist/core/SessionOwnershipRegistry.d.ts +115 -0
  77. package/dist/core/SessionOwnershipRegistry.d.ts.map +1 -0
  78. package/dist/core/SessionOwnershipRegistry.js +128 -0
  79. package/dist/core/SessionOwnershipRegistry.js.map +1 -0
  80. package/dist/core/SessionPoolE2EResultStore.d.ts +45 -0
  81. package/dist/core/SessionPoolE2EResultStore.d.ts.map +1 -0
  82. package/dist/core/SessionPoolE2EResultStore.js +66 -0
  83. package/dist/core/SessionPoolE2EResultStore.js.map +1 -0
  84. package/dist/core/SessionRouter.d.ts +95 -0
  85. package/dist/core/SessionRouter.d.ts.map +1 -0
  86. package/dist/core/SessionRouter.js +151 -0
  87. package/dist/core/SessionRouter.js.map +1 -0
  88. package/dist/core/StageAdvancer.d.ts +47 -0
  89. package/dist/core/StageAdvancer.d.ts.map +1 -0
  90. package/dist/core/StageAdvancer.js +70 -0
  91. package/dist/core/StageAdvancer.js.map +1 -0
  92. package/dist/core/TransferByNickname.d.ts +51 -0
  93. package/dist/core/TransferByNickname.d.ts.map +1 -0
  94. package/dist/core/TransferByNickname.js +50 -0
  95. package/dist/core/TransferByNickname.js.map +1 -0
  96. package/dist/core/TransferOrchestrator.d.ts +113 -0
  97. package/dist/core/TransferOrchestrator.d.ts.map +1 -0
  98. package/dist/core/TransferOrchestrator.js +79 -0
  99. package/dist/core/TransferOrchestrator.js.map +1 -0
  100. package/dist/core/types.d.ts +177 -1
  101. package/dist/core/types.d.ts.map +1 -1
  102. package/dist/core/types.js.map +1 -1
  103. package/dist/monitoring/RateLimitSentinel.d.ts +24 -2
  104. package/dist/monitoring/RateLimitSentinel.d.ts.map +1 -1
  105. package/dist/monitoring/RateLimitSentinel.js +25 -14
  106. package/dist/monitoring/RateLimitSentinel.js.map +1 -1
  107. package/dist/scaffold/templates.d.ts.map +1 -1
  108. package/dist/scaffold/templates.js +5 -0
  109. package/dist/scaffold/templates.js.map +1 -1
  110. package/dist/server/AgentServer.d.ts +8 -0
  111. package/dist/server/AgentServer.d.ts.map +1 -1
  112. package/dist/server/AgentServer.js +4 -0
  113. package/dist/server/AgentServer.js.map +1 -1
  114. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  115. package/dist/server/CapabilityIndex.js +14 -0
  116. package/dist/server/CapabilityIndex.js.map +1 -1
  117. package/dist/server/routes.d.ts +12 -0
  118. package/dist/server/routes.d.ts.map +1 -1
  119. package/dist/server/routes.js +91 -0
  120. package/dist/server/routes.js.map +1 -1
  121. package/package.json +1 -1
  122. package/src/data/builtin-manifest.json +65 -65
  123. package/src/scaffold/templates.ts +5 -0
  124. package/upgrades/1.3.91.md +54 -0
  125. package/upgrades/1.3.92.md +63 -0
  126. package/upgrades/side-effects/session-error-nudge-rearm.md +17 -0
  127. package/upgrades/side-effects/session-pool-capability-classification.md +16 -0
  128. package/upgrades/side-effects/session-pool-review-fixes.md +23 -0
  129. package/upgrades/side-effects/session-pool-track-a-router-lease.md +29 -0
  130. package/upgrades/side-effects/session-pool-track-b-agent-awareness.md +29 -0
  131. package/upgrades/side-effects/session-pool-track-b-nicknames.md +35 -0
  132. package/upgrades/side-effects/session-pool-track-b-pool-api.md +36 -0
  133. package/upgrades/side-effects/session-pool-track-b-registry.md +31 -0
  134. package/upgrades/side-effects/session-pool-track-b-wiring.md +39 -0
  135. package/upgrades/side-effects/session-pool-track-c-dispatcher.md +24 -0
  136. package/upgrades/side-effects/session-pool-track-c-meshrpc.md +35 -0
  137. package/upgrades/side-effects/session-pool-track-c-route-wiring.md +31 -0
  138. package/upgrades/side-effects/session-pool-track-d-ownership-fsm.md +31 -0
  139. package/upgrades/side-effects/session-pool-track-d-registry.md +27 -0
  140. package/upgrades/side-effects/session-pool-track-d-wiring.md +30 -0
  141. package/upgrades/side-effects/session-pool-track-e-boot-wiring.md +20 -0
  142. package/upgrades/side-effects/session-pool-track-e-placement-core.md +17 -0
  143. package/upgrades/side-effects/session-pool-track-e-router.md +23 -0
  144. package/upgrades/side-effects/session-pool-track-f-config.md +12 -0
  145. package/upgrades/side-effects/session-pool-track-f-handoff-core.md +17 -0
  146. package/upgrades/side-effects/session-pool-track-g-load-broker.md +16 -0
  147. package/upgrades/side-effects/session-pool-track-h-gate-wiring.md +17 -0
  148. package/upgrades/side-effects/session-pool-track-h-mesh-client.md +15 -0
  149. package/upgrades/side-effects/session-pool-track-h-rebalance.md +13 -0
  150. package/upgrades/side-effects/session-pool-track-h-rollout-gate.md +17 -0
  151. package/upgrades/side-effects/session-pool-track-h-stage-guard.md +16 -0
  152. package/upgrades/side-effects/transient-api-error-recovery-generalization.md +18 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * StageAdvancer — the SOLE writer of `multiMachine.sessionPool.stage` and the
3
+ * mechanical rollout gate (Multi-Machine Session Pool §Rollout). A stage activates
4
+ * ONLY when the prior stage's Tier-3 E2E recorded `green` for the CURRENT commit;
5
+ * otherwise the advance is refused and audited. If a live stage later records `red`,
6
+ * the advancer mechanically REVERTS to the prior (safe) stage. Per "Structure >
7
+ * Willpower" the gate is code, not prose: no other path may flip the stage (enforced
8
+ * by a Config.ts write-guard that only honors writes carrying this module's token).
9
+ */
10
+ export const STAGES = ['dark', 'shadow', 'live-transfer', 'rebalance'];
11
+ export function stageIndex(stage) {
12
+ return STAGES.indexOf(stage);
13
+ }
14
+ export class StageAdvancer {
15
+ d;
16
+ constructor(d) {
17
+ this.d = d;
18
+ }
19
+ /**
20
+ * Advance to `targetStage`, gated on a matching `green` E2E for the PRIOR stage at
21
+ * the current commit. A missing/`red`/stale-commit/tampered prior result → refused.
22
+ */
23
+ advanceTo(targetStage) {
24
+ const targetIdx = stageIndex(targetStage);
25
+ if (targetIdx < 0)
26
+ return { ok: false, reason: 'invalid-stage', detail: String(targetStage) };
27
+ const current = this.d.readStage();
28
+ if (stageIndex(current) >= targetIdx) {
29
+ return { ok: false, reason: 'already-at-or-past', detail: `current=${current}` };
30
+ }
31
+ // 'dark' (index 0) is the floor — there is no prior stage to gate on; you never
32
+ // "advance to dark", you revert to it. So any real advance targets index ≥ 1.
33
+ const priorStageIdx = targetIdx - 1;
34
+ const prior = this.d.resultStore.getLatestForStage(priorStageIdx);
35
+ const sha = this.d.currentCommitSha();
36
+ const gateOk = !!prior && prior.result === 'green' && prior.commitSha === sha && this.d.resultStore.verify(prior);
37
+ if (!gateOk) {
38
+ const detail = !prior ? 'no-result' : prior.result !== 'green' ? `result=${prior.result}` : prior.commitSha !== sha ? 'stale-commit' : 'bad-signature';
39
+ this.d.audit?.('stage-advance-refused', { targetStage, priorStage: STAGES[priorStageIdx], reason: 'e2e-gate-not-passed', detail, commitSha: sha });
40
+ return { ok: false, reason: 'e2e-gate-not-passed', detail };
41
+ }
42
+ this.d.writeStageConfig(targetStage);
43
+ this.d.audit?.('stage-advanced', { from: current, to: targetStage, commitSha: sha, evidenceRef: prior.evidenceRef });
44
+ return { ok: true, stage: targetStage };
45
+ }
46
+ /**
47
+ * Reconcile on each cycle: if the CURRENT stage's latest E2E is `red` (regression),
48
+ * mechanically revert to the prior stage until a fresh `green` is recorded. Returns
49
+ * the stage after reconciliation.
50
+ */
51
+ reconcile() {
52
+ const current = this.d.readStage();
53
+ const idx = stageIndex(current);
54
+ if (idx <= 0)
55
+ return current; // 'dark' is the floor — nothing to revert to
56
+ const latest = this.d.resultStore.getLatestForStage(idx);
57
+ // Only revert on a red for the CURRENT commit — a stale red from a prior commit
58
+ // is not a regression of the running build and must not trigger a revert.
59
+ // (2026-05-29 pre-merge review.)
60
+ const regressed = !!latest && latest.result === 'red' && latest.commitSha === this.d.currentCommitSha() && this.d.resultStore.verify(latest);
61
+ if (regressed) {
62
+ const reverted = STAGES[idx - 1];
63
+ this.d.writeStageConfig(reverted);
64
+ this.d.audit?.('stage-reverted', { from: current, to: reverted, reason: 'e2e-red', evidenceRef: latest.evidenceRef });
65
+ return reverted;
66
+ }
67
+ return current;
68
+ }
69
+ }
70
+ //# sourceMappingURL=StageAdvancer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StageAdvancer.js","sourceRoot":"","sources":["../../src/core/StageAdvancer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * TransferByNickname — the pure planner behind the headline "move this to
3
+ * <nickname>" mid-conversation swap (Multi-Machine Session Pool §L4 "Topic
4
+ * Placement Updates" + §L5). It turns a recognized NicknameCommand into a gated,
5
+ * validated TransferPlan: resolve the nickname → machineId, enforce the rate limit,
6
+ * the confirmation gate (offline target / mid-reply), and the already-there no-op.
7
+ * Per "Structure > Willpower" a transfer is high-impact, so this is NOT free-text
8
+ * inference — it operates on an ALREADY-recognized command + the registry state,
9
+ * and an unknown/ambiguous nickname is REJECTED (listing valid nicknames), never
10
+ * silently mis-routed.
11
+ */
12
+ import type { NicknameCommand } from './NicknameCommand.js';
13
+ export interface TransferByNicknameState {
14
+ /** Resolve a nickname → machineId (case-insensitive exact match), or null if unknown. */
15
+ resolveNickname: (nickname: string) => string | null;
16
+ /** The full set of valid nicknames (returned on an unknown-nickname rejection). */
17
+ validNicknames: () => string[];
18
+ isOnline: (machineId: string) => boolean;
19
+ currentOwnerOf: (sessionKey: string) => string | null;
20
+ /** Is the session mid-reply (a turn in flight)? Confirmation is required to interrupt it. */
21
+ isMidReply: (sessionKey: string) => boolean;
22
+ /** When the topic last had a placement update (for the rate limit), or null. */
23
+ lastPlacementUpdateAt: (sessionKey: string) => number | null;
24
+ now: () => number;
25
+ /** Minimum ms between placement updates per topic. Default 10000. */
26
+ minUpdateIntervalMs?: number;
27
+ }
28
+ export type TransferPlanAction = 'transfer' | 'noop' | 'confirm-required' | 'reject';
29
+ export interface TransferPlan {
30
+ action: TransferPlanAction;
31
+ sessionKey: string;
32
+ /** Resolved target machine (present for transfer / confirm-required / noop). */
33
+ targetMachine?: string;
34
+ /** Set the topic's preferredMachine + pinned:true (a hard pin — §L4). */
35
+ setPin?: boolean;
36
+ /** Human-facing confirmation prompt (action === 'confirm-required'). */
37
+ confirmationPrompt?: string;
38
+ /** Rejection reason (action === 'reject'). */
39
+ rejectReason?: 'unknown-machine-nickname' | 'rate-limited';
40
+ /** On unknown-nickname: the valid nicknames to surface to the user. */
41
+ validNicknames?: string[];
42
+ detail?: string;
43
+ }
44
+ /**
45
+ * Plan a transfer-by-nickname. Pure over (command, state). Both "move this to
46
+ * <nick>" and "run this on <nick>" set a hard pin (pinned:true) per §L4; the only
47
+ * intent difference is cosmetic. Confirmation is required when the target is offline,
48
+ * or when it differs from the current owner AND the session is mid-reply (§L4 line 456).
49
+ */
50
+ export declare function planTransferByNickname(command: NicknameCommand, state: TransferByNicknameState, sessionKey: string): TransferPlan;
51
+ //# sourceMappingURL=TransferByNickname.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TransferByNickname.d.ts","sourceRoot":"","sources":["../../src/core/TransferByNickname.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,MAAM,WAAW,uBAAuB;IACtC,yFAAyF;IACzF,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACrD,mFAAmF;IACnF,cAAc,EAAE,MAAM,MAAM,EAAE,CAAC;IAC/B,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,cAAc,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACtD,6FAA6F;IAC7F,UAAU,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,gFAAgF;IAChF,qBAAqB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAC7D,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,qEAAqE;IACrE,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,MAAM,GAAG,kBAAkB,GAAG,QAAQ,CAAC;AAErF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,YAAY,CAAC,EAAE,0BAA0B,GAAG,cAAc,CAAC;IAC3D,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,GAAG,YAAY,CAmCjI"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * TransferByNickname — the pure planner behind the headline "move this to
3
+ * <nickname>" mid-conversation swap (Multi-Machine Session Pool §L4 "Topic
4
+ * Placement Updates" + §L5). It turns a recognized NicknameCommand into a gated,
5
+ * validated TransferPlan: resolve the nickname → machineId, enforce the rate limit,
6
+ * the confirmation gate (offline target / mid-reply), and the already-there no-op.
7
+ * Per "Structure > Willpower" a transfer is high-impact, so this is NOT free-text
8
+ * inference — it operates on an ALREADY-recognized command + the registry state,
9
+ * and an unknown/ambiguous nickname is REJECTED (listing valid nicknames), never
10
+ * silently mis-routed.
11
+ */
12
+ const DEFAULT_MIN_UPDATE_INTERVAL_MS = 10000;
13
+ /**
14
+ * Plan a transfer-by-nickname. Pure over (command, state). Both "move this to
15
+ * <nick>" and "run this on <nick>" set a hard pin (pinned:true) per §L4; the only
16
+ * intent difference is cosmetic. Confirmation is required when the target is offline,
17
+ * or when it differs from the current owner AND the session is mid-reply (§L4 line 456).
18
+ */
19
+ export function planTransferByNickname(command, state, sessionKey) {
20
+ const target = state.resolveNickname(command.nickname);
21
+ if (!target) {
22
+ return { action: 'reject', sessionKey, rejectReason: 'unknown-machine-nickname', validNicknames: state.validNicknames(), detail: command.nickname };
23
+ }
24
+ // Rate limit — at most one placement update per topic per interval (defeats rapid-fire transfers).
25
+ const minInterval = state.minUpdateIntervalMs ?? DEFAULT_MIN_UPDATE_INTERVAL_MS;
26
+ const last = state.lastPlacementUpdateAt(sessionKey);
27
+ if (last != null && state.now() - last < minInterval) {
28
+ return { action: 'reject', sessionKey, targetMachine: target, rejectReason: 'rate-limited' };
29
+ }
30
+ const owner = state.currentOwnerOf(sessionKey);
31
+ // Already on the target → no transfer needed (still applies the pin if requested).
32
+ if (owner === target) {
33
+ return { action: 'noop', sessionKey, targetMachine: target, setPin: true, detail: 'already-on-target' };
34
+ }
35
+ // Confirmation gate (§L4): offline target, OR a different owner while mid-reply.
36
+ if (!state.isOnline(target)) {
37
+ return {
38
+ action: 'confirm-required', sessionKey, targetMachine: target, setPin: true,
39
+ confirmationPrompt: `${command.nickname} is offline right now. Move this conversation there anyway? It'll pick up like a fresh-session catch-up once it's back.`,
40
+ };
41
+ }
42
+ if (owner != null && owner !== target && state.isMidReply(sessionKey)) {
43
+ return {
44
+ action: 'confirm-required', sessionKey, targetMachine: target, setPin: true,
45
+ confirmationPrompt: `Move this to ${command.nickname}? I'm mid-reply — it'll be like a fresh-session catch-up on the other machine.`,
46
+ };
47
+ }
48
+ return { action: 'transfer', sessionKey, targetMachine: target, setPin: true };
49
+ }
50
+ //# sourceMappingURL=TransferByNickname.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TransferByNickname.js","sourceRoot":"","sources":["../../src/core/TransferByNickname.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAsCH,MAAM,8BAA8B,GAAG,KAAK,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAwB,EAAE,KAA8B,EAAE,UAAkB;IACjH,MAAM,MAAM,GAAG,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,0BAA0B,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;IACtJ,CAAC;IAED,mGAAmG;IACnG,MAAM,WAAW,GAAG,KAAK,CAAC,mBAAmB,IAAI,8BAA8B,CAAC;IAChF,MAAM,IAAI,GAAG,KAAK,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC;IACrD,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,WAAW,EAAE,CAAC;QACrD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/F,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IAE/C,mFAAmF;IACnF,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACrB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC1G,CAAC;IAED,iFAAiF;IACjF,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5B,OAAO;YACL,MAAM,EAAE,kBAAkB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI;YAC3E,kBAAkB,EAAE,GAAG,OAAO,CAAC,QAAQ,yHAAyH;SACjK,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACtE,OAAO;YACL,MAAM,EAAE,kBAAkB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI;YAC3E,kBAAkB,EAAE,gBAAgB,OAAO,CAAC,QAAQ,gFAAgF;SACrI,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACjF,CAAC"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * TransferOrchestrator — drives a planned session handoff through the §L3/§L5
3
+ * ordered sequence: active(S) → transferring(e+1) → [drain, flush ledger, target
4
+ * pulls+verifies] → active(T, e+2) → S-release. Claim-before-release, fenced by
5
+ * (status, epoch): there is no double-run (only one machine is `active` at the top
6
+ * epoch) and no no-owner gap (`transferring` still names S as the draining owner).
7
+ *
8
+ * The orchestrator enforces the two §L5 timing contracts:
9
+ * - Drain bound (`transferDrainTimeoutMs`): a long reply/tool-call must NOT block a
10
+ * transfer; on timeout the source abandons its partial output (NOT emitted).
11
+ * - Output exclusion (`transferOutputCutoffMs`): T holds its CONTINUATION until the
12
+ * cutoff has elapsed since the `transferring` write, so S's drain window and T's
13
+ * emission window are disjoint by construction — exactly one continuation per turn.
14
+ *
15
+ * All I/O (CAS, drain, ledger flush, mesh transfer, claim, clock) is injected so the
16
+ * sequence + timing logic is deterministic and unit-testable; the real wiring (real
17
+ * SessionMigrator drain, real git ledger flush, real mesh client) lands in Track H.
18
+ */
19
+ export type LedgerEntryStatus = 'reply_committed' | 'cursor_advanced' | 'in_flight';
20
+ export interface LedgerSnapshot {
21
+ sessionKey: string;
22
+ generatedAt: number;
23
+ entries: Array<{
24
+ messageId: string;
25
+ status: LedgerEntryStatus;
26
+ replyMarker?: string;
27
+ updatedAt: number;
28
+ }>;
29
+ snapshotSha256: string;
30
+ }
31
+ /**
32
+ * Verify a ledger snapshot at the transfer handoff (§L5): the SHA256 must match the
33
+ * SyncManifest-covered value AND every entry must be terminal (nothing `in_flight`).
34
+ * An `in_flight` entry means a turn was still processing at flush time — the target
35
+ * must NOT resume from it (it waits for a terminal re-flush, or treats it as the
36
+ * interrupted-drain case on failover). Pure.
37
+ */
38
+ export declare function verifyLedgerSnapshot(snapshot: LedgerSnapshot, expectedSha256: string, computeSha256: (s: LedgerSnapshot) => string): {
39
+ ok: true;
40
+ } | {
41
+ ok: false;
42
+ reason: 'sha-mismatch' | 'in-flight-entry';
43
+ };
44
+ export interface TransferRequest {
45
+ sessionKey: string;
46
+ source: string;
47
+ target: string;
48
+ reason: 'pin' | 'rebalance' | 'failover';
49
+ /** Whether the source machine is alive (planned transfer) or gone (failover). */
50
+ sourceAlive: boolean;
51
+ /** The ownership epoch the orchestrator observed before starting. */
52
+ baseEpoch: number;
53
+ }
54
+ export interface TransferOrchestratorConfig {
55
+ transferDrainTimeoutMs: number;
56
+ transferOutputCutoffMs: number;
57
+ }
58
+ export declare const DEFAULT_TRANSFER_CONFIG: TransferOrchestratorConfig;
59
+ export interface TransferOrchestratorDeps {
60
+ /** CAS the record source→transferring(epoch+1). */
61
+ casToTransferring: (sessionKey: string, target: string, expectedEpoch: number) => {
62
+ ok: boolean;
63
+ epoch: number;
64
+ };
65
+ /** Quiesce + drain the in-flight reply on the source, bounded by timeoutMs. */
66
+ drain: (sessionKey: string, timeoutMs: number) => Promise<{
67
+ drained: boolean;
68
+ abandonedPartial: boolean;
69
+ }>;
70
+ /** Source flushes its ledger snapshot synchronously (returns the snapshot + its ref). */
71
+ flushLedger: (sessionKey: string) => Promise<{
72
+ snapshot: LedgerSnapshot;
73
+ ledgerSnapshotRef: string;
74
+ syncManifestRef: string;
75
+ }>;
76
+ /** Send the transfer MeshRpc to the target; the target pulls + verifies the snapshot. */
77
+ sendTransferRpc: (target: string, refs: {
78
+ sessionKey: string;
79
+ ledgerSnapshotRef: string;
80
+ syncManifestRef: string;
81
+ }) => Promise<{
82
+ ok: boolean;
83
+ verified: boolean;
84
+ reason?: string;
85
+ }>;
86
+ /** Target CASes itself to active(owner=T, epoch+2). */
87
+ targetClaim: (sessionKey: string, expectedEpoch: number) => {
88
+ ok: boolean;
89
+ epoch: number;
90
+ };
91
+ /** Cleanup: source releases (NOT a precondition for T's claim — runs after). */
92
+ releaseSource: (sessionKey: string) => void;
93
+ /** Raise a deduped Attention item on sync corruption / unrecoverable handoff. */
94
+ raiseAttention?: (title: string, body: string) => void;
95
+ now: () => number;
96
+ sleep: (ms: number) => Promise<void>;
97
+ log?: (line: string) => void;
98
+ }
99
+ export type TransferStatus = 'transferred' | 'aborted-cas-lost' | 'sync-corrupted' | 'target-claim-failed';
100
+ export interface TransferOutcome {
101
+ ok: boolean;
102
+ status: TransferStatus;
103
+ drainAbandoned?: boolean;
104
+ finalEpoch?: number;
105
+ detail?: string;
106
+ }
107
+ export declare class TransferOrchestrator {
108
+ private readonly deps;
109
+ private readonly cfg;
110
+ constructor(deps: TransferOrchestratorDeps, cfg?: TransferOrchestratorConfig);
111
+ transfer(req: TransferRequest): Promise<TransferOutcome>;
112
+ }
113
+ //# sourceMappingURL=TransferOrchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TransferOrchestrator.d.ts","sourceRoot":"","sources":["../../src/core/TransferOrchestrator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * TransferOrchestrator — drives a planned session handoff through the §L3/§L5
3
+ * ordered sequence: active(S) → transferring(e+1) → [drain, flush ledger, target
4
+ * pulls+verifies] → active(T, e+2) → S-release. Claim-before-release, fenced by
5
+ * (status, epoch): there is no double-run (only one machine is `active` at the top
6
+ * epoch) and no no-owner gap (`transferring` still names S as the draining owner).
7
+ *
8
+ * The orchestrator enforces the two §L5 timing contracts:
9
+ * - Drain bound (`transferDrainTimeoutMs`): a long reply/tool-call must NOT block a
10
+ * transfer; on timeout the source abandons its partial output (NOT emitted).
11
+ * - Output exclusion (`transferOutputCutoffMs`): T holds its CONTINUATION until the
12
+ * cutoff has elapsed since the `transferring` write, so S's drain window and T's
13
+ * emission window are disjoint by construction — exactly one continuation per turn.
14
+ *
15
+ * All I/O (CAS, drain, ledger flush, mesh transfer, claim, clock) is injected so the
16
+ * sequence + timing logic is deterministic and unit-testable; the real wiring (real
17
+ * SessionMigrator drain, real git ledger flush, real mesh client) lands in Track H.
18
+ */
19
+ /**
20
+ * Verify a ledger snapshot at the transfer handoff (§L5): the SHA256 must match the
21
+ * SyncManifest-covered value AND every entry must be terminal (nothing `in_flight`).
22
+ * An `in_flight` entry means a turn was still processing at flush time — the target
23
+ * must NOT resume from it (it waits for a terminal re-flush, or treats it as the
24
+ * interrupted-drain case on failover). Pure.
25
+ */
26
+ export function verifyLedgerSnapshot(snapshot, expectedSha256, computeSha256) {
27
+ if (computeSha256(snapshot) !== expectedSha256 || snapshot.snapshotSha256 !== expectedSha256) {
28
+ return { ok: false, reason: 'sha-mismatch' };
29
+ }
30
+ if (snapshot.entries.some((e) => e.status === 'in_flight')) {
31
+ return { ok: false, reason: 'in-flight-entry' };
32
+ }
33
+ return { ok: true };
34
+ }
35
+ export const DEFAULT_TRANSFER_CONFIG = {
36
+ transferDrainTimeoutMs: 30000,
37
+ transferOutputCutoffMs: 1000,
38
+ };
39
+ export class TransferOrchestrator {
40
+ deps;
41
+ cfg;
42
+ constructor(deps, cfg = DEFAULT_TRANSFER_CONFIG) {
43
+ this.deps = deps;
44
+ this.cfg = cfg;
45
+ }
46
+ async transfer(req) {
47
+ // 1. CAS source→transferring(e+1). Stamp the moment for the output-exclusion window.
48
+ const t1 = this.deps.casToTransferring(req.sessionKey, req.target, req.baseEpoch);
49
+ if (!t1.ok) {
50
+ return { ok: false, status: 'aborted-cas-lost', detail: 'could-not-enter-transferring' };
51
+ }
52
+ const transferringAt = this.deps.now();
53
+ // 2. Drain the in-flight reply on the source, bounded (a long reply must not block).
54
+ const drainRes = await this.deps.drain(req.sessionKey, this.cfg.transferDrainTimeoutMs);
55
+ // 3. Source flushes its ledger snapshot synchronously.
56
+ const flush = await this.deps.flushLedger(req.sessionKey);
57
+ // 4. Send transfer to the target; the target pulls + verifies (SHA + all-terminal).
58
+ const sent = await this.deps.sendTransferRpc(req.target, { sessionKey: req.sessionKey, ledgerSnapshotRef: flush.ledgerSnapshotRef, syncManifestRef: flush.syncManifestRef });
59
+ if (!sent.ok || !sent.verified) {
60
+ this.deps.raiseAttention?.('Session transfer sync corrupted', `${req.sessionKey} → ${req.target}: ${sent.reason ?? 'verify-failed'}`);
61
+ return { ok: false, status: 'sync-corrupted', drainAbandoned: drainRes.abandonedPartial, detail: sent.reason ?? 'verify-failed' };
62
+ }
63
+ // 5. Output exclusion: T must NOT emit until transferOutputCutoffMs has elapsed
64
+ // since the transferring write, so S's drain window + T's emission are disjoint.
65
+ const elapsed = this.deps.now() - transferringAt;
66
+ const remaining = this.cfg.transferOutputCutoffMs - elapsed;
67
+ if (remaining > 0)
68
+ await this.deps.sleep(remaining);
69
+ // 6. Target claims active(owner=T, e+2). Then the source releases (cleanup only).
70
+ const t2 = this.deps.targetClaim(req.sessionKey, t1.epoch);
71
+ if (!t2.ok) {
72
+ return { ok: false, status: 'target-claim-failed', drainAbandoned: drainRes.abandonedPartial, detail: 'target-cas-failed' };
73
+ }
74
+ this.deps.releaseSource(req.sessionKey);
75
+ this.deps.log?.(`transfer ${req.sessionKey} ${req.source}→${req.target} complete @ epoch ${t2.epoch}`);
76
+ return { ok: true, status: 'transferred', drainAbandoned: drainRes.abandonedPartial, finalEpoch: t2.epoch };
77
+ }
78
+ }
79
+ //# sourceMappingURL=TransferOrchestrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TransferOrchestrator.js","sourceRoot":"","sources":["../../src/core/TransferOrchestrator.ts"],"names":[],"mappings":"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"}
@@ -1347,8 +1347,23 @@ export type MachineRole = 'awake' | 'standby';
1347
1347
  */
1348
1348
  export type CoordinationMode = 'primary-standby' | 'independent';
1349
1349
  export interface MachineRegistryEntry {
1350
- /** Human-friendly machine name */
1350
+ /** Human-friendly machine name (auto-detected hostname at pairing; static). */
1351
1351
  name: string;
1352
+ /**
1353
+ * User-facing, EDITABLE nickname — the handle a user types in "run this on
1354
+ * <nickname>" / "move this to <nickname>" (Multi-Machine Session Pool §L2).
1355
+ * Auto-assigned at registration via NicknameAssigner (idempotent — kept if
1356
+ * already set), editable via PATCH /pool/machines/:id, unique within the pool.
1357
+ * Optional for backward-compat with registries written before §L2.
1358
+ */
1359
+ nickname?: string;
1360
+ /**
1361
+ * Self-attested static hardware properties (Session Pool §L2) — captured by
1362
+ * the machine from `os` and recorded into ITS OWN registry entry, then synced
1363
+ * to peers via the registry. Surfaced on the Machines dashboard tab + GET /pool.
1364
+ * Optional (absent until the machine records it).
1365
+ */
1366
+ hardware?: MachineHardware;
1352
1367
  /** Current trust status */
1353
1368
  status: MachineStatus;
1354
1369
  /** Current operational role */
@@ -1427,6 +1442,71 @@ export interface MachineRegistry {
1427
1442
  */
1428
1443
  lease?: LeaseRecord;
1429
1444
  }
1445
+ /**
1446
+ * Static hardware properties of a machine, captured once at registration (cheap,
1447
+ * stable `os` reads) and surfaced on the Machines dashboard tab + as placement
1448
+ * signal (§L2). Self-reported → advisory for display, never authority.
1449
+ */
1450
+ export interface MachineHardware {
1451
+ /** os.platform() — e.g. "darwin", "linux", "win32". */
1452
+ platform: string;
1453
+ /** os.arch() — e.g. "arm64", "x64". */
1454
+ arch: string;
1455
+ /** os.cpus()[0].model — e.g. "Apple M2". */
1456
+ cpuModel: string;
1457
+ /** os.cpus().length. */
1458
+ cpuCores: number;
1459
+ /** os.totalmem() in bytes. */
1460
+ totalMemBytes: number;
1461
+ /** os.hostname(). */
1462
+ hostname: string;
1463
+ /** The instar version this machine reported. */
1464
+ instarVersion?: string;
1465
+ }
1466
+ /**
1467
+ * Clock-skew quarantine state (§L2). An explicit three-value FSM so every
1468
+ * implementation + recovery path is identical. A SINGLE divergent heartbeat
1469
+ * never removes a machine; removal requires 2 consecutive divergent beats;
1470
+ * re-admission requires 2 consecutive in-tolerance beats.
1471
+ */
1472
+ export type ClockSkewStatus = 'ok' | 'divergence-detected-once' | 'suspect-clock-removed';
1473
+ /**
1474
+ * Live per-machine capacity record (§L2) — the input to placement and the
1475
+ * Machines dashboard tab. Assembled by MachinePoolRegistry from the machine
1476
+ * registry (nickname), MachineHeartbeat (liveness), `os` (hardware/load), and
1477
+ * SessionManager diagnostics (sessions/memPressure). Liveness + freshness key
1478
+ * on `routerReceivedAt` (the router's own clock), NEVER the machine's
1479
+ * self-reported timestamp (clock-skew safety).
1480
+ */
1481
+ export interface MachineCapacity {
1482
+ machineId: string;
1483
+ /** User-facing nickname (§L2), mirrored from the registry entry. */
1484
+ nickname?: string;
1485
+ /** Liveness, computed as (now(router) − routerReceivedAt) < failoverThreshold. */
1486
+ online: boolean;
1487
+ /** The machine's own last-heartbeat timestamp (ISO) — debugging only. */
1488
+ selfReportedLastSeen?: string;
1489
+ /** When the router last observed this machine, on the ROUTER's clock (ISO). */
1490
+ routerReceivedAt?: string;
1491
+ /** 1-minute OS load average from the machine (os.loadavg()[0]). */
1492
+ loadAvg?: number;
1493
+ /** Memory pressure bucket from SessionManager diagnostics. */
1494
+ memPressure?: 'low' | 'moderate' | 'high' | 'critical';
1495
+ /** Active session count on the machine. */
1496
+ activeSessionCount?: number;
1497
+ /** Configured max sessions. */
1498
+ maxSessions?: number;
1499
+ /** Capabilities (e.g. "gpu", "local-model:llama3", "fast-cpu"). */
1500
+ capabilities?: string[];
1501
+ /** Local models available. */
1502
+ modelsAvailable?: string[];
1503
+ /** Agents resident on the machine (multi-agent-per-machine, §L6). */
1504
+ agentsResident?: string[];
1505
+ /** Static hardware properties (§L2). */
1506
+ hardware?: MachineHardware;
1507
+ /** Clock-skew quarantine state (§L2 FSM). */
1508
+ clockSkewStatus: ClockSkewStatus;
1509
+ }
1430
1510
  export interface MultiMachineConfig {
1431
1511
  /** Whether multi-machine is enabled */
1432
1512
  enabled: boolean;
@@ -1484,6 +1564,102 @@ export interface MultiMachineConfig {
1484
1564
  * SEAMLESSNESS_PROTOCOL_VERSION applies); present so migrations/tests can pin it.
1485
1565
  */
1486
1566
  protocolVersion?: number;
1567
+ /**
1568
+ * Multi-Machine Session Pool (spec docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md).
1569
+ * Active-active per-session placement + transfer on top of the router lease.
1570
+ * Ships DARK: the entire layer is inert unless `enabled` is true AND the
1571
+ * graduated `stage` has been advanced past 'dark' (the stage is written ONLY
1572
+ * by StageAdvancer — Track H — gated on a green E2E result). Independent of
1573
+ * the single-awake seamlessness model above; a 1-machine agent is a no-op.
1574
+ */
1575
+ sessionPool?: SessionPoolConfig;
1576
+ }
1577
+ /**
1578
+ * Multi-Machine Session Pool config block. All optional with safe dark defaults
1579
+ * (see ConfigDefaults `SHARED_DEFAULTS.multiMachine.sessionPool`). Tunables for
1580
+ * later layers (placement, transfer, registry, clock-skew) are added to this
1581
+ * interface by their tracks as they land.
1582
+ */
1583
+ export interface SessionPoolConfig {
1584
+ /** Master switch. Default false — the entire session-pool layer is inert when false. */
1585
+ enabled?: boolean;
1586
+ /**
1587
+ * Graduated rollout stage (spec §Rollout). 'dark' (code shipped, placement
1588
+ * dry-run, always local) → 'shadow' (real placement + ownership, no transfer)
1589
+ * → 'live-transfer' (failover + pin transfers) → 'rebalance' (load-driven).
1590
+ * Written ONLY by StageAdvancer via a Config.ts write-guard (Track H);
1591
+ * a direct write is rejected. Default 'dark'.
1592
+ */
1593
+ stage?: 'dark' | 'shadow' | 'live-transfer' | 'rebalance';
1594
+ /**
1595
+ * When true, the placement engine LOGS the decision it would make but always
1596
+ * places locally (Stage-0 behavior). Default true.
1597
+ */
1598
+ dryRun?: boolean;
1599
+ /**
1600
+ * Clock-skew divergence tolerance (ms) — a machine whose self-reported vs
1601
+ * router-observed timestamps diverge beyond this on 2 consecutive heartbeats
1602
+ * is quarantined from placement (§L2). Default 300000 (5 min). Must be
1603
+ * ≥ 2× maxExpectedNtpDriftMs (validated at startup).
1604
+ */
1605
+ clockSkewToleranceMs?: number;
1606
+ /**
1607
+ * Max expected NTP drift (ms) on a healthy host. `clockSkewToleranceMs` must
1608
+ * be ≥ 2× this. Default 250.
1609
+ */
1610
+ maxExpectedNtpDriftMs?: number;
1611
+ /**
1612
+ * How long an offline machine's capacity record is retained before eviction
1613
+ * (ms). Default 86400000 (24h) — a briefly-offline machine keeps its
1614
+ * nickname/hardware/history for fast re-placement.
1615
+ */
1616
+ machineRecordEvictionMs?: number;
1617
+ /**
1618
+ * MeshRpc (§L0) command timestamp tolerance (ms) — a signed command whose
1619
+ * timestamp is outside |now - ts| is rejected `stale-timestamp`. Default 30000.
1620
+ */
1621
+ meshRpcClockToleranceMs?: number;
1622
+ /**
1623
+ * §L4 deliverMessage per-attempt timeout (ms) — the router treats a forward that
1624
+ * does not ACK within this as failed and retries. Default 5000.
1625
+ */
1626
+ deliverMessageTimeoutMs?: number;
1627
+ /**
1628
+ * §L4 max deliverMessage retries before the router falls back to owner-dead
1629
+ * re-placement. Default 3.
1630
+ */
1631
+ deliverMessageMaxRetries?: number;
1632
+ /**
1633
+ * §L4 placement stickiness margin — the current owner is kept unless another
1634
+ * machine is better by more than this score delta (prevents flapping). Default 0.15.
1635
+ */
1636
+ placementHysteresisDelta?: number;
1637
+ /**
1638
+ * §L3 max per-session ownership CAS retries on non-fast-forward contention before
1639
+ * the router queues the message. Default 5.
1640
+ */
1641
+ ownershipCasMaxRetries?: number;
1642
+ /**
1643
+ * §L5 max time (ms) the source drains an in-flight reply before cancelling and
1644
+ * proceeding with the transfer — a long reply/tool-call must not block. Default 30000.
1645
+ */
1646
+ transferDrainTimeoutMs?: number;
1647
+ /**
1648
+ * §L5 output-exclusion window (ms): the source emits no NEW output after entering
1649
+ * `transferring` and the target holds its CONTINUATION until this elapses, so the
1650
+ * two emission windows are disjoint (no double-send). Default 1000.
1651
+ */
1652
+ transferOutputCutoffMs?: number;
1653
+ /**
1654
+ * §L4 cool-down (ms) after a transfer before a session is eligible for re-placement
1655
+ * (a hard user-pin bypasses it). Default 300000.
1656
+ */
1657
+ placementCooldownMs?: number;
1658
+ /**
1659
+ * §L4 minimum interval (ms) between placement updates per topic — defeats rapid-fire
1660
+ * transfers. Default 10000.
1661
+ */
1662
+ topicPlacementUpdateMinIntervalMs?: number;
1487
1663
  }
1488
1664
  export type AgentAutonomyLevel = 'supervised' | 'collaborative' | 'autonomous';
1489
1665
  export type UserRegistrationPolicy = 'open' | 'invite-only' | 'admin-only';