poly-weaver 0.15.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/dist/agents/implementors/handler.d.ts.map +1 -1
  2. package/dist/agents/implementors/handler.js +7 -2
  3. package/dist/agents/implementors/handler.js.map +1 -1
  4. package/dist/agents/interviewers/handler.d.ts.map +1 -1
  5. package/dist/agents/interviewers/handler.js +3 -1
  6. package/dist/agents/interviewers/handler.js.map +1 -1
  7. package/dist/agents/planners/adapter.d.ts.map +1 -1
  8. package/dist/agents/planners/adapter.js +30 -11
  9. package/dist/agents/planners/adapter.js.map +1 -1
  10. package/dist/agents/planners/handler.d.ts.map +1 -1
  11. package/dist/agents/planners/handler.js +7 -2
  12. package/dist/agents/planners/handler.js.map +1 -1
  13. package/dist/agents/reviewers/adapter.d.ts.map +1 -1
  14. package/dist/agents/reviewers/adapter.js +1 -0
  15. package/dist/agents/reviewers/adapter.js.map +1 -1
  16. package/dist/agents/reviewers/handler.d.ts.map +1 -1
  17. package/dist/agents/reviewers/handler.js +23 -4
  18. package/dist/agents/reviewers/handler.js.map +1 -1
  19. package/dist/agents/reviewers/types.d.ts +2 -0
  20. package/dist/agents/reviewers/types.d.ts.map +1 -1
  21. package/dist/agents/runner.d.ts +18 -1
  22. package/dist/agents/runner.d.ts.map +1 -1
  23. package/dist/agents/runner.js +136 -20
  24. package/dist/agents/runner.js.map +1 -1
  25. package/dist/agents/simplifiers/handler.d.ts.map +1 -1
  26. package/dist/agents/simplifiers/handler.js +7 -2
  27. package/dist/agents/simplifiers/handler.js.map +1 -1
  28. package/dist/auto-update/detect-agents.d.ts +1 -0
  29. package/dist/auto-update/detect-agents.d.ts.map +1 -1
  30. package/dist/auto-update/detect-agents.js +8 -7
  31. package/dist/auto-update/detect-agents.js.map +1 -1
  32. package/dist/cli.js +16 -4
  33. package/dist/cli.js.map +1 -1
  34. package/dist/crosscheck/coordinator.d.ts +5 -0
  35. package/dist/crosscheck/coordinator.d.ts.map +1 -1
  36. package/dist/crosscheck/coordinator.js +216 -96
  37. package/dist/crosscheck/coordinator.js.map +1 -1
  38. package/dist/crosscheck/live-agent-session.d.ts +10 -1
  39. package/dist/crosscheck/live-agent-session.d.ts.map +1 -1
  40. package/dist/crosscheck/live-agent-session.js +68 -7
  41. package/dist/crosscheck/live-agent-session.js.map +1 -1
  42. package/dist/crosscheck/state.d.ts +3 -0
  43. package/dist/crosscheck/state.d.ts.map +1 -1
  44. package/dist/crosscheck/state.js +17 -2
  45. package/dist/crosscheck/state.js.map +1 -1
  46. package/dist/dump/collector.d.ts.map +1 -1
  47. package/dist/dump/collector.js +3 -0
  48. package/dist/dump/collector.js.map +1 -1
  49. package/dist/dump/service.d.ts.map +1 -1
  50. package/dist/dump/service.js +3 -0
  51. package/dist/dump/service.js.map +1 -1
  52. package/dist/dump/types.d.ts +3 -0
  53. package/dist/dump/types.d.ts.map +1 -1
  54. package/dist/dump/types.js.map +1 -1
  55. package/dist/flow/executor.d.ts.map +1 -1
  56. package/dist/flow/executor.js +5 -0
  57. package/dist/flow/executor.js.map +1 -1
  58. package/dist/flow/types.d.ts +5 -0
  59. package/dist/flow/types.d.ts.map +1 -1
  60. package/dist/flow/types.js.map +1 -1
  61. package/dist/offscreen/controller.d.ts +19 -5
  62. package/dist/offscreen/controller.d.ts.map +1 -1
  63. package/dist/offscreen/controller.js +336 -159
  64. package/dist/offscreen/controller.js.map +1 -1
  65. package/dist/offscreen/fork-launch.d.ts +2 -2
  66. package/dist/offscreen/fork-launch.d.ts.map +1 -1
  67. package/dist/offscreen/fork-launch.js +2 -2
  68. package/dist/offscreen/fork-launch.js.map +1 -1
  69. package/dist/offscreen/fork.d.ts +16 -2
  70. package/dist/offscreen/fork.d.ts.map +1 -1
  71. package/dist/offscreen/fork.js +79 -40
  72. package/dist/offscreen/fork.js.map +1 -1
  73. package/dist/offscreen/inquiry-pane.d.ts +2 -1
  74. package/dist/offscreen/inquiry-pane.d.ts.map +1 -1
  75. package/dist/offscreen/inquiry-pane.js +8 -2
  76. package/dist/offscreen/inquiry-pane.js.map +1 -1
  77. package/dist/offscreen/inquiry-sender.d.ts +1 -0
  78. package/dist/offscreen/inquiry-sender.d.ts.map +1 -1
  79. package/dist/offscreen/inquiry-sender.js +1 -0
  80. package/dist/offscreen/inquiry-sender.js.map +1 -1
  81. package/dist/offscreen/panes.d.ts +1 -0
  82. package/dist/offscreen/panes.d.ts.map +1 -1
  83. package/dist/offscreen/registry.d.ts +2 -2
  84. package/dist/offscreen/registry.d.ts.map +1 -1
  85. package/dist/offscreen/registry.js +5 -1
  86. package/dist/offscreen/registry.js.map +1 -1
  87. package/dist/offscreen/resolve.d.ts +2 -0
  88. package/dist/offscreen/resolve.d.ts.map +1 -1
  89. package/dist/offscreen/resolve.js +5 -1
  90. package/dist/offscreen/resolve.js.map +1 -1
  91. package/dist/offscreen/session-turn-monitor.d.ts +34 -33
  92. package/dist/offscreen/session-turn-monitor.d.ts.map +1 -1
  93. package/dist/offscreen/session-turn-monitor.js +327 -281
  94. package/dist/offscreen/session-turn-monitor.js.map +1 -1
  95. package/dist/offscreen/session-turn-receipt.d.ts +28 -0
  96. package/dist/offscreen/session-turn-receipt.d.ts.map +1 -0
  97. package/dist/offscreen/session-turn-receipt.js +53 -0
  98. package/dist/offscreen/session-turn-receipt.js.map +1 -0
  99. package/dist/offscreen/split-inquiry.d.ts.map +1 -1
  100. package/dist/offscreen/split-inquiry.js +5 -0
  101. package/dist/offscreen/split-inquiry.js.map +1 -1
  102. package/dist/offscreen/starting-pane.d.ts +13 -0
  103. package/dist/offscreen/starting-pane.d.ts.map +1 -0
  104. package/dist/offscreen/starting-pane.js +24 -0
  105. package/dist/offscreen/starting-pane.js.map +1 -0
  106. package/dist/offscreen/surface.d.ts +1 -0
  107. package/dist/offscreen/surface.d.ts.map +1 -1
  108. package/dist/offscreen/surface.js +5 -3
  109. package/dist/offscreen/surface.js.map +1 -1
  110. package/dist/orchestrator.d.ts.map +1 -1
  111. package/dist/orchestrator.js +7 -1
  112. package/dist/orchestrator.js.map +1 -1
  113. package/dist/providers/base-completion.d.ts +1 -0
  114. package/dist/providers/base-completion.d.ts.map +1 -1
  115. package/dist/providers/base-completion.js +4 -2
  116. package/dist/providers/base-completion.js.map +1 -1
  117. package/dist/providers/codex/completion.d.ts +4 -2
  118. package/dist/providers/codex/completion.d.ts.map +1 -1
  119. package/dist/providers/codex/completion.js +99 -14
  120. package/dist/providers/codex/completion.js.map +1 -1
  121. package/dist/providers/codex/strategy.d.ts.map +1 -1
  122. package/dist/providers/codex/strategy.js +3 -2
  123. package/dist/providers/codex/strategy.js.map +1 -1
  124. package/dist/providers/copilot/completion.d.ts +32 -21
  125. package/dist/providers/copilot/completion.d.ts.map +1 -1
  126. package/dist/providers/copilot/completion.js +110 -166
  127. package/dist/providers/copilot/completion.js.map +1 -1
  128. package/dist/providers/copilot/environment.d.ts +2 -0
  129. package/dist/providers/copilot/environment.d.ts.map +1 -0
  130. package/dist/providers/copilot/environment.js +8 -0
  131. package/dist/providers/copilot/environment.js.map +1 -0
  132. package/dist/providers/copilot/fork-state.d.ts +7 -0
  133. package/dist/providers/copilot/fork-state.d.ts.map +1 -0
  134. package/dist/providers/copilot/fork-state.js +150 -0
  135. package/dist/providers/copilot/fork-state.js.map +1 -0
  136. package/dist/providers/copilot/observation.d.ts +275 -0
  137. package/dist/providers/copilot/observation.d.ts.map +1 -0
  138. package/dist/providers/copilot/observation.js +2072 -0
  139. package/dist/providers/copilot/observation.js.map +1 -0
  140. package/dist/providers/copilot/observer-extension/com.github.copilot/extensions/poly-weaver-observer/extension.d.mts +2 -0
  141. package/dist/providers/copilot/observer-extension/com.github.copilot/extensions/poly-weaver-observer/extension.d.mts.map +1 -0
  142. package/dist/providers/copilot/observer-extension/com.github.copilot/extensions/poly-weaver-observer/extension.mjs +426 -0
  143. package/dist/providers/copilot/observer-extension/com.github.copilot/extensions/poly-weaver-observer/extension.mjs.map +1 -0
  144. package/dist/providers/copilot/observer-extension/plugin.json +6 -0
  145. package/dist/providers/copilot/record-properties.d.ts +4 -0
  146. package/dist/providers/copilot/record-properties.d.ts.map +1 -0
  147. package/dist/providers/copilot/record-properties.js +14 -0
  148. package/dist/providers/copilot/record-properties.js.map +1 -0
  149. package/dist/providers/copilot/session.d.ts +8 -33
  150. package/dist/providers/copilot/session.d.ts.map +1 -1
  151. package/dist/providers/copilot/session.js +10 -176
  152. package/dist/providers/copilot/session.js.map +1 -1
  153. package/dist/providers/copilot/strategy.d.ts +4 -5
  154. package/dist/providers/copilot/strategy.d.ts.map +1 -1
  155. package/dist/providers/copilot/strategy.js +73 -67
  156. package/dist/providers/copilot/strategy.js.map +1 -1
  157. package/dist/providers/fork-cleanup.d.ts +4 -0
  158. package/dist/providers/fork-cleanup.d.ts.map +1 -0
  159. package/dist/providers/fork-cleanup.js +21 -0
  160. package/dist/providers/fork-cleanup.js.map +1 -0
  161. package/dist/providers/fork.d.ts +24 -3
  162. package/dist/providers/fork.d.ts.map +1 -1
  163. package/dist/providers/fork.js.map +1 -1
  164. package/dist/providers/invoke.d.ts.map +1 -1
  165. package/dist/providers/invoke.js +85 -34
  166. package/dist/providers/invoke.js.map +1 -1
  167. package/dist/providers/live.d.ts.map +1 -1
  168. package/dist/providers/live.js +62 -17
  169. package/dist/providers/live.js.map +1 -1
  170. package/dist/providers/observer-run.d.ts +4 -0
  171. package/dist/providers/observer-run.d.ts.map +1 -0
  172. package/dist/providers/observer-run.js +23 -0
  173. package/dist/providers/observer-run.js.map +1 -0
  174. package/dist/providers/session-launch.d.ts +5 -1
  175. package/dist/providers/session-launch.d.ts.map +1 -1
  176. package/dist/providers/session-launch.js +42 -16
  177. package/dist/providers/session-launch.js.map +1 -1
  178. package/dist/providers/types.d.ts +156 -4
  179. package/dist/providers/types.d.ts.map +1 -1
  180. package/dist/providers/types.js.map +1 -1
  181. package/dist/session/resume.d.ts.map +1 -1
  182. package/dist/session/resume.js +16 -2
  183. package/dist/session/resume.js.map +1 -1
  184. package/dist/terminal/paste-submit.d.ts +1 -0
  185. package/dist/terminal/paste-submit.d.ts.map +1 -1
  186. package/dist/terminal/paste-submit.js +45 -32
  187. package/dist/terminal/paste-submit.js.map +1 -1
  188. package/dist/user/host-curate-prompt.d.ts.map +1 -1
  189. package/dist/user/host-curate-prompt.js +9 -6
  190. package/dist/user/host-curate-prompt.js.map +1 -1
  191. package/dist/user/host-prompt.d.ts.map +1 -1
  192. package/dist/user/host-prompt.js +9 -6
  193. package/dist/user/host-prompt.js.map +1 -1
  194. package/package.json +4 -2
@@ -1,10 +1,9 @@
1
1
  import { resolveProvider } from "../providers/registry.js";
2
2
  import { readSessionFileWithStatus, } from "../session/reader.js";
3
- const DEFAULT_ATTEMPT_TIMEOUT_MS = 5_000;
4
- /**
5
- * Tracks one provider session from a settled line baseline to the completion
6
- * of each subsequently submitted turn. Read failures fail closed as busy.
7
- */
3
+ import { SessionTurnReceipt } from "./session-turn-receipt.js";
4
+ export { SessionTurnReceipt } from "./session-turn-receipt.js";
5
+ export const DEFAULT_TURN_ATTEMPT_TIMEOUT_MS = 5_000;
6
+ /** Ordered request ownership; provider observations remain work even without a host receipt. */
8
7
  export class SessionTurnMonitor {
9
8
  driver;
10
9
  sessionFilePath;
@@ -12,136 +11,174 @@ export class SessionTurnMonitor {
12
11
  baseline;
13
12
  busy = false;
14
13
  timer;
15
- polling = false;
16
14
  activePoll;
17
15
  stopped = false;
18
- turnGeneration = 0;
19
- mappedGeneration = 0;
20
- coveredGeneration = 0;
21
- nextReservationSequence = 0;
22
- pendingConfirmedTurns = [];
23
- pendingTurnAttempts = [];
24
- unreservedObservedGenerations = [];
25
- pendingAttemptWaiters = new Set();
16
+ attachingBatch = false;
17
+ ownershipWarned = false;
18
+ entries = [];
19
+ attached = new Set();
20
+ attemptWaiters = new Set();
26
21
  observedSubmitCount;
27
22
  pollMs;
28
23
  attemptTimeoutMs;
29
24
  submitFloor;
30
25
  historySnapshot;
31
- mappedSubmitIdentities = [];
32
26
  constructor(driver, sessionFilePath, baseline, options = {}) {
33
27
  this.driver = driver;
34
28
  this.sessionFilePath = sessionFilePath;
35
29
  this.options = options;
36
30
  this.baseline = Math.max(0, baseline);
37
31
  this.pollMs = options.pollMs ?? 500;
38
- this.attemptTimeoutMs =
39
- options.attemptTimeoutMs ?? DEFAULT_ATTEMPT_TIMEOUT_MS;
32
+ this.attemptTimeoutMs = options.attemptTimeoutMs ?? DEFAULT_TURN_ATTEMPT_TIMEOUT_MS;
40
33
  this.submitFloor = options.submitFloor;
41
34
  this.historySnapshot = options.initialHistorySnapshot;
42
35
  }
43
- get isIdle() {
44
- return !this.busy;
45
- }
46
- get lineBaseline() {
47
- return this.baseline;
48
- }
49
- get currentSubmitFloor() {
50
- return this.submitFloor;
36
+ get isIdle() { return !this.busy; }
37
+ get lineBaseline() { return this.baseline; }
38
+ get currentSubmitFloor() { return this.submitFloor; }
39
+ get hasUnobservedExpiredAttempts() {
40
+ return this.entries.some((entry) => entry.ordinal === undefined
41
+ && entry.receipt?.settlement === "expired");
51
42
  }
52
43
  get supportsHistoryRegression() {
53
- return resolveProvider(this.driver).fork.sessionHistorySnapshot !== undefined;
44
+ const provider = resolveProvider(this.driver);
45
+ return provider.forkSubmitFloor !== undefined
46
+ && provider.fork.sessionHistorySnapshot !== undefined;
54
47
  }
55
- expectedSubmitCount(lines) {
56
- const snapshot = this.reconcileHistory(lines);
57
- this.observeSubmissions(lines, undefined, snapshot);
58
- return (this.observedSubmitCount ?? 0)
59
- + this.pendingConfirmedTurns.length
60
- + this.pendingTurnAttempts.length;
48
+ reserveTurn(boundary = {}) {
49
+ const receipt = new SessionTurnReceipt({
50
+ ...boundary,
51
+ anchorSubmitCount: boundary.anchorSubmitCount ?? this.observedSubmitCount ?? this.submitFloor,
52
+ });
53
+ this.attachTurn(receipt);
54
+ return receipt;
61
55
  }
62
- startHistoryPolling() {
63
- if (this.stopped || !this.supportsHistoryRegression)
64
- return;
65
- this.ensurePolling();
56
+ async reserveTurnFromSession() {
57
+ await this.expectedSubmitCountFromSession();
58
+ return this.reserveTurn();
66
59
  }
67
- waitForPendingTurnAttempts() {
68
- if (this.pendingTurnAttempts.length === 0 || this.stopped) {
69
- return Promise.resolve();
60
+ /** Attach the same pre-input receipt after discovery, never an anonymous later registration. */
61
+ attachTurns(receipts, initialLines) {
62
+ this.attachingBatch = true;
63
+ try {
64
+ for (const receipt of [...receipts].sort((a, b) => a.sequence - b.sequence))
65
+ this.attachTurn(receipt);
66
+ if (initialLines)
67
+ this.expectedSubmitCount(initialLines);
70
68
  }
71
- return new Promise((resolve) => {
72
- this.pendingAttemptWaiters.add(resolve);
73
- });
69
+ finally {
70
+ this.attachingBatch = false;
71
+ }
72
+ this.updateActivity(true);
73
+ if (this.busy || this.hasUnobservedExpiredAttempts)
74
+ this.ensurePolling();
74
75
  }
75
- beginTurn() {
76
- if (this.stopped)
76
+ attachTurn(receipt) {
77
+ if (this.stopped || this.attached.has(receipt.id))
77
78
  return;
78
- if (this.adoptUnreservedObservedSubmit())
79
+ this.attached.add(receipt.id);
80
+ if (receipt.settlement && receipt.settlement !== "expired")
79
81
  return;
80
- this.turnGeneration++;
81
- this.pendingConfirmedTurns.push({
82
- sequence: this.nextReservationSequence++,
83
- anchorSubmitCount: this.observedSubmitCount ?? this.submitFloor,
82
+ const anchor = receipt.anchorSubmitCount ?? this.observedSubmitCount ?? this.submitFloor;
83
+ const observed = anchor === undefined ? undefined : this.entries.find((entry) => !entry.receipt && entry.ordinal !== undefined && entry.ordinal > anchor);
84
+ const entry = observed ?? { anchor };
85
+ entry.receipt = receipt;
86
+ if (!observed)
87
+ this.entries.push(entry);
88
+ entry.unsubscribe = receipt.onChange(() => {
89
+ if (this.stopped)
90
+ return;
91
+ if (receipt.settlement === "released" && entry.ordinal === undefined) {
92
+ this.removeEntry(entry);
93
+ this.updateActivity();
94
+ }
84
95
  });
85
- if (!this.busy) {
86
- this.busy = true;
87
- this.options.onChange?.(false);
96
+ if (receipt.rawAttempt && !receipt.settlement && entry.ordinal === undefined) {
97
+ const remainingMs = receipt.startedAt + this.attemptTimeoutMs - Date.now();
98
+ if (remainingMs <= 0) {
99
+ receipt.settle("expired");
100
+ }
101
+ else {
102
+ entry.timer = setTimeout(() => {
103
+ if (this.stopped || entry.ordinal !== undefined)
104
+ return;
105
+ entry.timer = undefined;
106
+ // Expiry releases speculative busy state, not its place ahead of later input.
107
+ receipt.settle("expired");
108
+ this.updateActivity();
109
+ }, remainingMs);
110
+ }
88
111
  }
89
- this.ensurePolling();
112
+ this.updateActivity(receipt.settlement === "expired");
113
+ if (entry.settlement)
114
+ receipt.settle(entry.settlement);
115
+ if (!this.attachingBatch && (this.busy || this.hasUnobservedExpiredAttempts))
116
+ this.ensurePolling();
117
+ }
118
+ beginTurn(receipt = this.reserveTurn()) {
119
+ this.attachTurn(receipt);
120
+ receipt.confirm();
121
+ return receipt;
90
122
  }
91
- beginTurnAttempt(snapshot = {}) {
123
+ beginTurnAttempt(boundary = {}) {
124
+ const receipt = new SessionTurnReceipt({
125
+ ...boundary,
126
+ anchorSubmitCount: boundary.anchorSubmitCount ?? this.observedSubmitCount ?? this.submitFloor,
127
+ }, true);
128
+ receipt.markWritten();
129
+ this.attachTurn(receipt);
130
+ return receipt;
131
+ }
132
+ expectedSubmitCount(lines) {
92
133
  if (this.stopped)
93
- return;
94
- if (this.adoptUnreservedObservedSubmit())
95
- return;
96
- const startedAt = snapshot.startedAt ?? Date.now();
97
- const expired = Date.now() - startedAt >= this.attemptTimeoutMs;
98
- const attempt = {
99
- sequence: this.nextReservationSequence++,
100
- anchorSubmitCount: snapshot.anchorSubmitCount
101
- ?? this.observedSubmitCount
102
- ?? this.submitFloor,
103
- expired,
104
- };
105
- if (attempt.expired) {
106
- this.releaseIfSettled({ notifyIfAlreadyIdle: true });
107
- return;
108
- }
109
- attempt.timer = setTimeout(() => {
110
- if (this.stopped || !this.pendingTurnAttempts.includes(attempt))
111
- return;
112
- attempt.expired = true;
113
- this.discardExpiredAttempts();
114
- this.releaseIfSettled();
115
- }, Math.max(0, startedAt + this.attemptTimeoutMs - Date.now()));
116
- this.pendingTurnAttempts.push(attempt);
117
- if (!this.busy) {
118
- this.busy = true;
119
- this.options.onChange?.(false);
120
- }
121
- this.ensurePolling();
134
+ throw new Error("Session turn monitor is shut down");
135
+ const evidence = this.validateEvidence(lines);
136
+ const snapshot = this.reconcileHistory(lines);
137
+ this.observeSubmissions(lines, snapshot, evidence);
138
+ if (!this.attachingBatch && (this.busy || this.hasUnobservedExpiredAttempts))
139
+ this.ensurePolling();
140
+ return (this.observedSubmitCount ?? 0)
141
+ + this.entries.filter((entry) => entry.ordinal === undefined && !entry.receipt?.settlement).length;
142
+ }
143
+ startHistoryPolling() {
144
+ if (!this.stopped && this.supportsHistoryRegression)
145
+ this.ensurePolling();
146
+ }
147
+ waitForPendingTurnAttempts() {
148
+ if (!this.hasPendingAttempts() || this.stopped)
149
+ return Promise.resolve();
150
+ return new Promise((resolve) => this.attemptWaiters.add(resolve));
122
151
  }
123
152
  async refreshIdleBaseline() {
124
- const result = await this.readLineResult();
125
- if (result.malformed)
126
- throw new Error("Session log contains malformed JSONL");
127
- this.reconcileHistory(result.lines);
128
- this.baseline = result.lines.length;
153
+ const result = await this.readValidResult();
154
+ this.expectedSubmitCount(result.lines);
155
+ if (this.isIdle)
156
+ this.baseline = result.lines.length;
157
+ this.retireNonSubmittingAttempts(result);
129
158
  }
130
159
  async expectedSubmitCountFromSession() {
131
- const result = await this.readLineResult();
132
- if (result.malformed)
133
- throw new Error("Session log contains malformed JSONL");
134
- return this.expectedSubmitCount(result.lines);
160
+ const result = await this.readValidResult();
161
+ const count = this.expectedSubmitCount(result.lines);
162
+ this.retireNonSubmittingAttempts(result);
163
+ return count;
164
+ }
165
+ async refreshExpiredAttempts() {
166
+ if (!this.hasUnobservedExpiredAttempts)
167
+ return;
168
+ await this.activePoll;
169
+ if (this.hasUnobservedExpiredAttempts)
170
+ await this.pollNow();
135
171
  }
136
172
  dispose() {
137
173
  if (this.timer)
138
174
  clearInterval(this.timer);
139
175
  this.timer = undefined;
140
- for (const attempt of this.pendingTurnAttempts) {
141
- if (attempt.timer)
142
- clearTimeout(attempt.timer);
176
+ for (const entry of this.entries) {
177
+ if (entry.timer)
178
+ clearTimeout(entry.timer);
179
+ entry.unsubscribe?.();
143
180
  }
144
- this.resolvePendingAttemptWaiters();
181
+ this.resolveAttemptWaiters();
145
182
  }
146
183
  async shutdown() {
147
184
  this.stopped = true;
@@ -149,234 +186,245 @@ export class SessionTurnMonitor {
149
186
  await this.activePoll;
150
187
  }
151
188
  pollNow() {
152
- if ((!this.busy && !this.supportsHistoryRegression) || this.stopped) {
189
+ if (this.jsonlRuntimeDisabled())
190
+ return Promise.resolve();
191
+ if ((!this.busy && !this.supportsHistoryRegression && !this.hasUnobservedExpiredAttempts)
192
+ || this.stopped) {
153
193
  return Promise.resolve();
154
194
  }
155
- if (this.polling)
156
- return this.activePoll ?? Promise.resolve();
157
- this.polling = true;
158
- let active;
159
- active = this.pollOnce()
160
- .catch((err) => {
195
+ if (this.activePoll)
196
+ return this.activePoll;
197
+ const active = this.pollOnce()
198
+ .catch((error) => {
161
199
  if (!this.stopped)
162
- this.options.onReadFailure?.(err);
200
+ this.options.onReadFailure?.(error);
163
201
  })
164
202
  .finally(() => {
165
203
  if (this.activePoll === active)
166
204
  this.activePoll = undefined;
167
- this.polling = false;
168
205
  });
169
206
  this.activePoll = active;
170
207
  return active;
171
208
  }
172
209
  async pollOnce() {
173
- const result = await this.readLineResult();
174
- if (result.malformed)
175
- throw new Error("Session log contains malformed JSONL");
176
- const lines = result.lines;
210
+ const result = await this.readValidResult();
211
+ const { lines } = result;
177
212
  if (this.stopped)
178
213
  return;
179
- const provider = resolveProvider(this.driver);
180
- const snapshot = this.reconcileHistory(lines);
181
- const priorSubmitCount = this.submitFloor
182
- ?? this.countSubmits(lines.slice(0, this.baseline));
183
- const currentSubmitCount = this.observeSubmissions(lines, priorSubmitCount, snapshot);
184
- this.discardExpiredAttempts();
185
- const unsettledSubmits = currentSubmitCount - priorSubmitCount;
186
- if (unsettledSubmits > 0) {
187
- const tail = lines.slice(this.baseline);
188
- const latestSubmit = provider.fork.latestSubmitIndex(tail);
189
- if (latestSubmit >= 0
190
- && provider.fork.turnComplete(lines, this.baseline + latestSubmit)) {
191
- this.submitFloor = currentSubmitCount;
192
- this.coveredGeneration = Math.min(this.mappedGeneration, this.coveredGeneration + unsettledSubmits);
193
- this.baseline = lines.length;
214
+ const evidence = this.validateEvidence(lines, this.options.pendingEvidencePolicy === "wait");
215
+ if (evidence?.indeterminate) {
216
+ const { prior, assignments } = this.prepareObservation(lines);
217
+ if (this.inspectHistory(lines).regression) {
218
+ throw new Error("Session submission history regressed during pending evidence");
194
219
  }
220
+ // Check projected ownership without committing any part of an incomplete observation.
221
+ this.warnAmbiguousOwnership(this.entries.map((entry) => ({
222
+ ...entry,
223
+ anchor: entry.anchor ?? prior,
224
+ ordinal: entry.ordinal ?? assignments.get(entry),
225
+ })));
226
+ return;
195
227
  }
196
- this.releaseIfSettled();
197
- }
198
- observeSubmissions(lines, priorSubmitCount = this.submitFloor
199
- ?? this.countSubmits(lines.slice(0, this.baseline)), snapshot) {
200
- const currentSubmitCount = this.countSubmits(lines);
201
- const observedSubmitCount = this.observedSubmitCount ?? priorSubmitCount;
202
- for (const attempt of this.pendingTurnAttempts) {
203
- attempt.anchorSubmitCount ??= observedSubmitCount;
204
- }
205
- for (const turn of this.pendingConfirmedTurns) {
206
- turn.anchorSubmitCount ??= observedSubmitCount;
228
+ const snapshot = this.reconcileHistory(lines);
229
+ const current = this.observeSubmissions(lines, snapshot, evidence);
230
+ this.retireNonSubmittingAttempts(result);
231
+ let aggregateComplete = false;
232
+ if (!evidence) {
233
+ const fork = resolveProvider(this.driver).fork;
234
+ const latest = fork.latestSubmitIndex(lines);
235
+ aggregateComplete = latest >= this.baseline && fork.turnComplete(lines, latest);
207
236
  }
208
- this.mapNewSubmits(observedSubmitCount, currentSubmitCount, snapshot);
209
- this.observedSubmitCount = Math.max(observedSubmitCount, currentSubmitCount);
210
- return currentSubmitCount;
211
- }
212
- mapNewSubmits(observedSubmitCount, currentSubmitCount, snapshot) {
213
- for (let submitOrdinal = observedSubmitCount + 1; submitOrdinal <= currentSubmitCount; submitOrdinal++) {
214
- const turnIndex = this.pendingConfirmedTurns.findIndex((turn) => turn.anchorSubmitCount !== undefined
215
- && submitOrdinal > turn.anchorSubmitCount);
216
- const attemptIndex = this.pendingTurnAttempts.findIndex((attempt) => attempt.anchorSubmitCount !== undefined
217
- && submitOrdinal > attempt.anchorSubmitCount);
218
- const turn = turnIndex >= 0
219
- ? this.pendingConfirmedTurns[turnIndex]
220
- : undefined;
221
- const attempt = attemptIndex >= 0
222
- ? this.pendingTurnAttempts[attemptIndex]
223
- : undefined;
224
- if (turn && (!attempt || turn.sequence < attempt.sequence)) {
225
- this.pendingConfirmedTurns.splice(turnIndex, 1);
226
- this.mappedGeneration++;
227
- this.rememberMappedSubmitIdentity(snapshot, submitOrdinal);
237
+ const offset = evidence ? evidence.submissions.length - current : 0;
238
+ for (const entry of this.entries) {
239
+ if (entry.ordinal === undefined || entry.settlement)
228
240
  continue;
229
- }
230
- if (!attempt) {
231
- this.mapUnreservedObservedSubmit(snapshot, submitOrdinal);
232
- continue;
233
- }
234
- this.pendingTurnAttempts.splice(attemptIndex, 1);
235
- if (attempt.timer)
236
- clearTimeout(attempt.timer);
237
- this.turnGeneration++;
238
- this.mappedGeneration++;
239
- this.rememberMappedSubmitIdentity(snapshot, submitOrdinal);
240
- continue;
241
+ entry.settlement = evidence
242
+ ? evidence.submissions[entry.ordinal - 1 + offset]?.settlement
243
+ : aggregateComplete ? "completed" : undefined;
241
244
  }
242
- this.resolvePendingAttemptWaitersIfSettled();
243
- }
244
- mapUnreservedObservedSubmit(snapshot, submitOrdinal) {
245
- this.turnGeneration++;
246
- this.mappedGeneration++;
247
- this.unreservedObservedGenerations.push(this.mappedGeneration);
248
- this.rememberMappedSubmitIdentity(snapshot, submitOrdinal);
249
- if (!this.busy) {
250
- this.busy = true;
251
- this.options.onChange?.(false);
245
+ if (this.entries.some((entry) => entry.ordinal !== undefined)
246
+ && this.entries.every((entry) => entry.ordinal === undefined || entry.settlement)) {
247
+ this.submitFloor = current;
248
+ this.baseline = lines.length;
252
249
  }
253
- }
254
- adoptUnreservedObservedSubmit() {
255
- const generation = this.unreservedObservedGenerations.shift();
256
- if (generation === undefined)
257
- return false;
258
- if (generation > this.coveredGeneration && !this.busy) {
259
- this.busy = true;
260
- this.options.onChange?.(false);
250
+ this.updateActivity();
251
+ if (this.busy)
261
252
  this.ensurePolling();
262
- return true;
253
+ for (const entry of this.entries) {
254
+ if (this.stopped)
255
+ break;
256
+ if (entry.settlement)
257
+ entry.receipt?.settle(entry.settlement);
263
258
  }
264
- this.releaseIfSettled();
265
- return true;
266
259
  }
267
- rememberMappedSubmitIdentity(snapshot, submitOrdinal) {
268
- const identity = snapshot?.submitIdentities[submitOrdinal - 1];
269
- if (identity)
270
- this.mappedSubmitIdentities.push(identity);
260
+ jsonlRuntimeDisabled() {
261
+ return resolveProvider(this.driver).forkSubmitFloor === undefined;
271
262
  }
272
- reconcileHistory(lines) {
273
- const fork = resolveProvider(this.driver).fork;
274
- const buildSnapshot = fork.sessionHistorySnapshot;
275
- if (!buildSnapshot)
276
- return undefined;
277
- const current = buildSnapshot(lines);
278
- if (!current) {
279
- throw new Error("Session history snapshot is unbuildable");
280
- }
281
- const previous = this.historySnapshot;
282
- if (previous && fork.compareSessionHistory) {
283
- const regression = fork.compareSessionHistory(previous, current);
284
- if (regression) {
285
- this.applyHistoryRegression(regression, current);
286
- this.historySnapshot = current;
287
- this.options.onHistoryRegression?.(regression, current);
288
- return current;
289
- }
263
+ observeSubmissions(lines, snapshot, evidence) {
264
+ const { current, prior, assignments } = this.prepareObservation(lines);
265
+ for (const entry of this.entries)
266
+ entry.anchor ??= prior;
267
+ for (const [entry, ordinal] of assignments) {
268
+ entry.ordinal = ordinal;
269
+ entry.identity = evidence?.submissions[ordinal - 1 + evidence.submissions.length - current]?.identity
270
+ ?? snapshot?.submitIdentities[ordinal - 1 + snapshot.submitCount - current];
271
+ if (entry.timer)
272
+ clearTimeout(entry.timer);
273
+ if (!this.entries.includes(entry))
274
+ this.entries.push(entry);
290
275
  }
291
- const changed = !sameHistorySnapshot(previous, current);
292
- this.historySnapshot = current;
293
- if (changed)
294
- this.options.onHistorySnapshot?.(current);
276
+ this.observedSubmitCount = current;
277
+ this.updateActivity();
278
+ this.warnAmbiguousOwnership();
295
279
  return current;
296
280
  }
297
- applyHistoryRegression(regression, snapshot) {
298
- const currentIdentities = new Set(snapshot.submitIdentities);
299
- let removedMapped = 0;
300
- for (let index = this.mappedSubmitIdentities.length - 1; index >= 0; index--) {
301
- const identity = this.mappedSubmitIdentities[index];
302
- if (currentIdentities.has(identity))
303
- continue;
304
- this.mappedSubmitIdentities.splice(index, 1);
305
- removedMapped++;
306
- }
307
- if (removedMapped > 0) {
308
- this.mappedGeneration = Math.max(0, this.mappedGeneration - removedMapped);
309
- this.coveredGeneration = Math.min(this.mappedGeneration, Math.max(0, this.coveredGeneration - removedMapped));
310
- this.turnGeneration = Math.max(this.pendingTurnAttempts.length + this.pendingConfirmedTurns.length, this.turnGeneration - removedMapped);
281
+ prepareObservation(lines) {
282
+ const current = this.countSubmits(lines);
283
+ const prior = this.observedSubmitCount ?? this.submitFloor
284
+ ?? this.countSubmits(lines.slice(0, this.baseline));
285
+ if (current < prior || this.baseline > lines.length) {
286
+ throw new Error("Session submission history regressed without a provider history boundary");
311
287
  }
312
- this.baseline = 0;
313
- this.submitFloor = Math.min(this.submitFloor ?? regression.commonSubmitCount, regression.commonSubmitCount);
314
- this.observedSubmitCount = Math.min(this.observedSubmitCount ?? regression.commonSubmitCount, regression.commonSubmitCount);
315
- for (const turn of this.pendingConfirmedTurns) {
316
- if (turn.anchorSubmitCount !== undefined) {
317
- turn.anchorSubmitCount = Math.min(turn.anchorSubmitCount, regression.commonSubmitCount);
318
- }
288
+ const pending = this.entries
289
+ .filter((entry) => entry.ordinal === undefined)
290
+ .sort((a, b) => a.receipt.sequence - b.receipt.sequence);
291
+ const assignments = new Map();
292
+ for (let ordinal = prior + 1; ordinal <= current; ordinal++) {
293
+ const index = pending.findIndex((entry) => ordinal > (entry.anchor ?? prior));
294
+ const entry = index < 0 ? {} : pending.splice(index, 1)[0];
295
+ assignments.set(entry, ordinal);
319
296
  }
320
- for (const attempt of this.pendingTurnAttempts) {
321
- if (attempt.anchorSubmitCount !== undefined) {
322
- attempt.anchorSubmitCount = Math.min(attempt.anchorSubmitCount, regression.commonSubmitCount);
323
- }
297
+ return { current, prior, assignments };
298
+ }
299
+ warnAmbiguousOwnership(entries = this.entries) {
300
+ const ambiguous = entries.some(({ receipt, ordinal }) => {
301
+ if (receipt?.settlement !== "expired" || ordinal === undefined)
302
+ return false;
303
+ return entries.some((newer) => newer.ordinal === undefined && newer.receipt && !newer.receipt.settlement
304
+ && newer.receipt.sequence > receipt.sequence
305
+ && newer.anchor !== undefined && newer.anchor < ordinal);
306
+ });
307
+ if (ambiguous && !this.ownershipWarned && !this.stopped) {
308
+ this.options.onReadFailure?.(new Error("Session submission ownership is ambiguous after an expired input attempt"));
324
309
  }
325
- this.unreservedObservedGenerations.length = 0;
310
+ this.ownershipWarned = ambiguous;
326
311
  }
327
- discardExpiredAttempts() {
328
- for (let index = this.pendingTurnAttempts.length - 1; index >= 0; index--) {
329
- const attempt = this.pendingTurnAttempts[index];
330
- if (!attempt.expired)
312
+ retireNonSubmittingAttempts(read) {
313
+ if (this.stopped || read.registrationCount !== this.attached.size)
314
+ return;
315
+ let removed = false;
316
+ for (const entry of read.expiredAttempts) {
317
+ if (entry.ordinal !== undefined || !this.entries.includes(entry))
331
318
  continue;
332
- if (attempt.timer)
333
- clearTimeout(attempt.timer);
334
- this.pendingTurnAttempts.splice(index, 1);
319
+ this.removeEntry(entry);
320
+ removed = true;
321
+ }
322
+ if (removed)
323
+ this.updateActivity(true);
324
+ }
325
+ validateEvidence(lines, allowPending = false) {
326
+ const evidence = resolveProvider(this.driver).fork.submissionEvidence?.(lines);
327
+ if (evidence?.indeterminate && !(allowPending && evidence.indeterminateReason === "pending-submission")) {
328
+ throw new Error("Session submission evidence is indeterminate");
335
329
  }
336
- this.resolvePendingAttemptWaitersIfSettled();
330
+ return evidence;
331
+ }
332
+ inspectHistory(lines) {
333
+ const fork = resolveProvider(this.driver).fork;
334
+ if (!fork.sessionHistorySnapshot)
335
+ return {};
336
+ const current = fork.sessionHistorySnapshot(lines);
337
+ if (!current)
338
+ throw new Error("Session history snapshot is unbuildable");
339
+ const previous = this.historySnapshot;
340
+ const regression = previous && fork.compareSessionHistory?.(previous, current);
341
+ return { snapshot: current, regression };
337
342
  }
338
- resolvePendingAttemptWaitersIfSettled() {
339
- if (this.pendingTurnAttempts.length === 0) {
340
- this.resolvePendingAttemptWaiters();
343
+ reconcileHistory(lines) {
344
+ const { snapshot: current, regression } = this.inspectHistory(lines);
345
+ if (!current)
346
+ return undefined;
347
+ const previous = this.historySnapshot;
348
+ if (regression) {
349
+ const identities = new Set(current.submitIdentities);
350
+ for (const entry of [...this.entries]) {
351
+ if (entry.identity && !identities.has(entry.identity)) {
352
+ this.removeEntry(entry);
353
+ entry.receipt?.settle("removed");
354
+ }
355
+ else if (entry.ordinal === undefined && entry.anchor !== undefined) {
356
+ entry.anchor = Math.min(entry.anchor, regression.commonSubmitCount);
357
+ }
358
+ }
359
+ this.baseline = 0;
360
+ this.submitFloor = Math.min(this.submitFloor ?? regression.commonSubmitCount, regression.commonSubmitCount);
361
+ this.observedSubmitCount = Math.min(this.observedSubmitCount ?? regression.commonSubmitCount, regression.commonSubmitCount);
362
+ this.historySnapshot = current;
363
+ if (!this.stopped)
364
+ this.options.onHistoryRegression?.(regression, current);
365
+ }
366
+ else {
367
+ this.historySnapshot = current;
368
+ if (!this.stopped && !sameHistorySnapshot(previous, current))
369
+ this.options.onHistorySnapshot?.(current);
341
370
  }
371
+ return current;
372
+ }
373
+ removeEntry(entry) {
374
+ if (entry.timer)
375
+ clearTimeout(entry.timer);
376
+ entry.unsubscribe?.();
377
+ const index = this.entries.indexOf(entry);
378
+ if (index >= 0)
379
+ this.entries.splice(index, 1);
380
+ }
381
+ hasPendingAttempts() {
382
+ return this.entries.some((entry) => entry.receipt?.rawAttempt && !entry.receipt.settlement
383
+ && entry.ordinal === undefined);
342
384
  }
343
- resolvePendingAttemptWaiters() {
344
- for (const resolve of this.pendingAttemptWaiters)
385
+ resolveAttemptWaiters() {
386
+ for (const resolve of this.attemptWaiters)
345
387
  resolve();
346
- this.pendingAttemptWaiters.clear();
388
+ this.attemptWaiters.clear();
347
389
  }
348
- releaseIfSettled(options = {}) {
349
- if (this.coveredGeneration !== this.turnGeneration
350
- || this.pendingTurnAttempts.length > 0) {
390
+ updateActivity(notifyIfAlreadyIdle = false) {
391
+ if (this.attachingBatch)
351
392
  return;
393
+ if (!this.hasPendingAttempts())
394
+ this.resolveAttemptWaiters();
395
+ const busy = this.entries.some((entry) => entry.ordinal === undefined
396
+ ? !entry.receipt?.settlement : !entry.settlement);
397
+ const changed = busy !== this.busy;
398
+ this.busy = busy;
399
+ if (!busy && !this.supportsHistoryRegression && !this.hasUnobservedExpiredAttempts && this.timer) {
400
+ clearInterval(this.timer);
401
+ this.timer = undefined;
352
402
  }
353
- const wasBusy = this.busy;
354
- this.busy = false;
355
- if (!this.supportsHistoryRegression)
356
- this.dispose();
357
- if ((wasBusy || options.notifyIfAlreadyIdle === true) && !this.stopped) {
358
- this.options.onChange?.(true);
403
+ if (!this.stopped && (changed || (!busy && notifyIfAlreadyIdle))) {
404
+ this.options.onChange?.(!busy);
359
405
  }
360
406
  }
361
407
  ensurePolling() {
362
- if (this.timer)
408
+ if (this.stopped || this.timer)
363
409
  return;
364
- this.timer = setInterval(() => {
410
+ this.timer = setInterval(() => { void this.pollNow(); }, this.pollMs);
411
+ if (this.busy || this.supportsHistoryRegression)
365
412
  void this.pollNow();
366
- }, this.pollMs);
367
- void this.pollNow();
368
413
  }
369
- async readLineResult() {
370
- if (this.options.readResult) {
371
- return this.options.readResult(this.sessionFilePath);
372
- }
373
- if (this.options.readLines) {
374
- return {
375
- lines: await this.options.readLines(this.sessionFilePath),
376
- malformed: false,
377
- };
378
- }
379
- return readSessionFileWithStatus(this.sessionFilePath);
414
+ async readValidResult() {
415
+ // Only a post-expiry read, uninterrupted by newer input, can release ownership.
416
+ const expiredAttempts = this.entries.filter(({ receipt, ordinal }) => ordinal === undefined && receipt?.settlement === "expired"
417
+ && !this.entries.some((newer) => newer.receipt && !newer.receipt.settlement
418
+ && newer.receipt.sequence > receipt.sequence));
419
+ const registrationCount = this.attached.size;
420
+ const result = this.options.readResult
421
+ ? await this.options.readResult(this.sessionFilePath)
422
+ : this.options.readLines
423
+ ? { lines: await this.options.readLines(this.sessionFilePath), malformed: false }
424
+ : await readSessionFileWithStatus(this.sessionFilePath);
425
+ if (result.malformed)
426
+ throw new Error("Session log contains malformed JSONL");
427
+ return { ...result, expiredAttempts, registrationCount };
380
428
  }
381
429
  countSubmits(lines) {
382
430
  return this.options.submitCount
@@ -385,9 +433,7 @@ export class SessionTurnMonitor {
385
433
  }
386
434
  }
387
435
  function sameHistorySnapshot(left, right) {
388
- if (!left)
389
- return false;
390
- return left.lineCount === right.lineCount
436
+ return left !== undefined && left.lineCount === right.lineCount
391
437
  && left.submitCount === right.submitCount
392
438
  && left.submitIdentities.length === right.submitIdentities.length
393
439
  && left.submitIdentities.every((identity, index) => identity === right.submitIdentities[index]);