@wowyuarm/dsh-agent-team 0.1.8 → 0.1.10

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 (81) hide show
  1. package/README.md +4 -11
  2. package/README.zh.md +4 -11
  3. package/package.json +54 -44
  4. package/packages/agent-team/README.md +7 -5
  5. package/packages/agent-team/README.zh.md +7 -4
  6. package/packages/agent-team/lib/attachments.js +14 -4
  7. package/packages/agent-team/lib/context-management.js +382 -0
  8. package/packages/agent-team/lib/context-projection.js +578 -0
  9. package/packages/agent-team/lib/context-source.js +212 -0
  10. package/packages/agent-team/lib/index.js +1272 -76
  11. package/packages/agent-team/lib/ledger.js +333 -44
  12. package/packages/agent-team/lib/member-context.js +6 -2
  13. package/packages/agent-team/lib/member-runtime.js +159 -4
  14. package/packages/agent-team/lib/member-time-context.js +126 -0
  15. package/packages/agent-team/lib/pressure-policy.js +199 -0
  16. package/packages/agent-team/lib/progress-nudge.js +12 -4
  17. package/packages/agent-team/lib/session-remediation.js +481 -0
  18. package/packages/agent-team/lib/spec.js +21 -2
  19. package/packages/agent-team/lib/time-format.js +56 -0
  20. package/packages/agent-team/lib/typert.host.js +168 -54
  21. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  22. package/packages/agent-team/lib/typert.remote-client.js +59 -25
  23. package/packages/agent-team/lib/types/attachments.d.ts +1 -1
  24. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
  25. package/packages/agent-team/lib/types/context-management.d.ts +147 -0
  26. package/packages/agent-team/lib/types/context-management.d.ts.map +1 -0
  27. package/packages/agent-team/lib/types/context-projection.d.ts +219 -0
  28. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -0
  29. package/packages/agent-team/lib/types/context-source.d.ts +117 -0
  30. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -0
  31. package/packages/agent-team/lib/types/index.d.ts +300 -2
  32. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  33. package/packages/agent-team/lib/types/ledger.d.ts +119 -6
  34. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  35. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  36. package/packages/agent-team/lib/types/member-runtime.d.ts +30 -2
  37. package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
  38. package/packages/agent-team/lib/types/member-time-context.d.ts +83 -0
  39. package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -0
  40. package/packages/agent-team/lib/types/pressure-policy.d.ts +106 -0
  41. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -0
  42. package/packages/agent-team/lib/types/progress-nudge.d.ts.map +1 -1
  43. package/packages/agent-team/lib/types/session-remediation.d.ts +149 -0
  44. package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
  45. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  46. package/packages/agent-team/lib/types/time-format.d.ts +28 -0
  47. package/packages/agent-team/lib/types/time-format.d.ts.map +1 -0
  48. package/packages/agent-team/lib/types/types/entities.d.ts +22 -1
  49. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  50. package/packages/agent-team/lib/types/types/operations.d.ts +38 -3
  51. package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
  52. package/packages/agent-team/lib/types/types/requests-results.d.ts +58 -1
  53. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  54. package/packages/agent-team/preset/team-member/agent.cordis.yml +27 -4
  55. package/packages/client-agent-team/lib/client.js +369 -352
  56. package/packages/client-agent-team/lib/client.js.map +1 -1
  57. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +1 -2
  58. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  59. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +61 -41
  60. package/packages/client-agent-team/lib/types/client/TeamMessage.js +2 -2
  61. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +4 -4
  62. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  63. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  64. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -2
  65. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/index.js +44 -16
  67. package/packages/client-agent-team/lib/types/client/locales.d.ts +0 -14
  68. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  69. package/packages/client-agent-team/lib/types/client/locales.js +0 -14
  70. package/packages/client-agent-team/lib/types/client/slots.d.ts +6 -1
  71. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  72. package/packages/tool-agent-team/README.md +11 -6
  73. package/packages/tool-agent-team/README.zh.md +11 -6
  74. package/packages/tool-agent-team/lib/context-tools.js +179 -0
  75. package/packages/tool-agent-team/lib/index.js +369 -58
  76. package/packages/tool-agent-team/lib/types/context-tools.d.ts +16 -0
  77. package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -0
  78. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  79. package/packages/agent-team/lib/auto-compaction.js +0 -201
  80. package/packages/agent-team/lib/types/auto-compaction.d.ts +0 -47
  81. package/packages/agent-team/lib/types/auto-compaction.d.ts.map +0 -1
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Context-management coordinator: the one deep module that turns a Member's
3
+ * successful `context_rollover` tool result into its next private context
4
+ * generation.
5
+ *
6
+ * Authority split (see docs/architecture.md):
7
+ * - the Team ledger owns the Member→Session binding and rollover audit;
8
+ * - the Session log projection owns intent, checkpoints, and delivery state;
9
+ * - this coordinator owns only process locks and is always reconstructible.
10
+ *
11
+ * The coordinator reacts exclusively after the successful `tool/result` is
12
+ * durably appended — never inside a tool body — so a render/finalize failure
13
+ * can never outrun result durability. The actual swap waits for the
14
+ * containing turn to end and the Agent to be idle, captures later input so no
15
+ * old-generation model request opens, and then reuses the Member lifecycle:
16
+ * commit rollover operation → dispose old Agent → archive old Session →
17
+ * create/activate the new generation → deliver the handoff first.
18
+ * @module @wowyuarm/dsh-agent-team/context-management
19
+ */
20
+ import { SessionId as SessionIdBrand } from '@deepseek-ai/dsh-session';
21
+ import { createHash } from 'node:crypto';
22
+ import { AGENT_TEAM_PLUGIN_ID, createCheckpointContinuationMessage, createHandoffMessage, isAgentTeamContextSource } from "./context-source.js";
23
+ import { CONTEXT_CHECKPOINT_TOOL_NAME, CONTEXT_ROLLOVER_TOOL_NAME, continuationDelivered, foldContextProjection, } from "./context-projection.js";
24
+ /** Stable summary of the one-shot rollover pressure notice (ticket 03 wires delivery). */
25
+ export const CONTEXT_PRESSURE_NOTICE_SUMMARY = 'Context pressure: prepare a handoff';
26
+ export class ContextManagementCoordinator {
27
+ options;
28
+ members = new Map();
29
+ capturedInput = new Map();
30
+ /** Per-member latch for the in-process scheduling→delivery window. */
31
+ scheduledContinuations = new Set();
32
+ disposed = false;
33
+ constructor(options) {
34
+ this.options = options;
35
+ }
36
+ /** Whether one Member has a pending or in-flight rollover; tools use this to reject. */
37
+ isTransitioning(memberId) {
38
+ return this.members.has(memberId);
39
+ }
40
+ /**
41
+ * Root `session/event` observer for Member Sessions. The store's dispatch
42
+ * carrier is untagged, so the Host maps session ids to Members and calls
43
+ * this for every Member event. All reactions are gated on the projection
44
+ * state, which itself only records successful durable pairs.
45
+ */
46
+ onSessionEvent(memberId, agent, event) {
47
+ if (this.disposed)
48
+ return;
49
+ if (event.type === 'tool/result') {
50
+ this.onToolResult(memberId, agent, event);
51
+ return;
52
+ }
53
+ if (event.type === 'user/message') {
54
+ this.onUserMessage(memberId, agent, event);
55
+ return;
56
+ }
57
+ if (event.type === 'turn/end') {
58
+ this.onTurnEnd(memberId, agent);
59
+ }
60
+ }
61
+ /** Build the first handoff message of one generation; the lifecycle delivers it. */
62
+ handoffMessageFor(plan) {
63
+ return createHandoffMessage({
64
+ handoff: plan.handoff,
65
+ previousSessionId: plan.previousSessionId,
66
+ newSessionId: plan.newSessionId,
67
+ trigger: plan.trigger,
68
+ handoffEventSeq: plan.handoffEventSeq,
69
+ ...(plan.checkpointRef === undefined ? {} : { checkpointRef: plan.checkpointRef }),
70
+ ...(plan.relatedFiles.length === 0 ? {} : { relatedFiles: plan.relatedFiles }),
71
+ });
72
+ }
73
+ /**
74
+ * Whether one Agent's pending transition requires the admission gate: a
75
+ * pending rollover must stop old-generation turns from admitting queued
76
+ * input. The gate arms only for the old-generation Agent instance — the
77
+ * new generation activates mid-swap and must be free to consume the
78
+ * handoff and carried input immediately.
79
+ */
80
+ needsAdmissionGate(agent) {
81
+ const memberId = this.options.memberForAgent(agent)?.memberId;
82
+ if (memberId === undefined)
83
+ return false;
84
+ return this.members.get(memberId)?.agent === agent;
85
+ }
86
+ /**
87
+ * Capture the inbox messages queued for an old generation at its turn-stop
88
+ * boundary: non-Team input is preserved verbatim for delivery after the
89
+ * handoff; stale Team notices are dropped because the new generation
90
+ * rederives the Inbox from ledger facts. Removing them from the inbox lets
91
+ * the turn close cleanly instead of admitting another old-generation step.
92
+ */
93
+ captureQueuedInput(agent) {
94
+ const memberId = this.options.memberForAgent(agent)?.memberId;
95
+ if (memberId === undefined)
96
+ return [];
97
+ if (this.members.get(memberId)?.agent !== agent)
98
+ return [];
99
+ const removed = [...agent.inbox.nextStep, ...agent.inbox.nextTurn];
100
+ for (const message of removed)
101
+ agent.inbox.remove(message.id);
102
+ return this.captureInput(agent, removed);
103
+ }
104
+ /**
105
+ * Capture messages a racing pre-step already claimed from the inbox before
106
+ * rejecting that old-generation step. A rejected step's claimed message is
107
+ * otherwise neither discarded nor re-emitted, so the gate preserves it here.
108
+ */
109
+ captureClaimedInput(agent, messages) {
110
+ return this.captureInput(agent, messages);
111
+ }
112
+ captureInput(agent, messages) {
113
+ const memberId = this.options.memberForAgent(agent)?.memberId;
114
+ if (memberId === undefined)
115
+ return [];
116
+ if (this.members.get(memberId)?.agent !== agent)
117
+ return [];
118
+ const preserved = [];
119
+ for (const message of messages) {
120
+ if (this.isTeamNotice(message))
121
+ continue;
122
+ preserved.push(message);
123
+ }
124
+ if (preserved.length > 0)
125
+ this.capturedInput.set(memberId, [...(this.capturedInput.get(memberId) ?? []), ...preserved]);
126
+ return preserved;
127
+ }
128
+ /** Drain the captured input of one Member for delivery after the handoff. */
129
+ drainCapturedInput(memberId) {
130
+ const captured = this.capturedInput.get(memberId) ?? [];
131
+ this.capturedInput.delete(memberId);
132
+ return captured;
133
+ }
134
+ /**
135
+ * Whether one queued message is a Team-owned notice the rederived Inbox
136
+ * replaces. Handoff and continuation envelopes carry the same plugin
137
+ * attribution but are ordinary delivered context the new generation keeps, so
138
+ * they are excluded rather than dropped.
139
+ */
140
+ isTeamNotice(message) {
141
+ const source = message.source;
142
+ return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID && !isAgentTeamContextSource(message);
143
+ }
144
+ /** Drop one Member's bookkeeping; the Host calls this on dispose/removal. */
145
+ stopTracking(memberId) {
146
+ this.members.delete(memberId);
147
+ this.capturedInput.delete(memberId);
148
+ }
149
+ dispose() {
150
+ this.disposed = true;
151
+ this.members.clear();
152
+ this.capturedInput.clear();
153
+ }
154
+ onToolResult(memberId, agent, event) {
155
+ if (event.data.error !== undefined)
156
+ return;
157
+ const member = this.options.memberForAgent(agent);
158
+ if (member === undefined)
159
+ return;
160
+ const state = this.options.projectionForMember(memberId, member.sessionId);
161
+ if (state?.pending === null || state === undefined)
162
+ return;
163
+ const pending = state.pending;
164
+ // React only to the intent's own result landing durably, and only once.
165
+ if (pending.resultSeq !== event.seq)
166
+ return;
167
+ if (this.members.has(memberId))
168
+ return;
169
+ this.members.set(memberId, {
170
+ agent,
171
+ intent: {
172
+ toolCallId: pending.toolCallId,
173
+ resultSeq: pending.resultSeq,
174
+ turn: pending.turn,
175
+ handoff: pending.handoff,
176
+ ...(pending.checkpointRef === undefined ? {} : { checkpointRef: pending.checkpointRef }),
177
+ relatedFiles: pending.relatedFiles,
178
+ },
179
+ turnEnded: false,
180
+ });
181
+ }
182
+ onUserMessage(memberId, _agent, event) {
183
+ // Quiet-continuation delivery bookkeeping is projection-owned; the
184
+ // coordinator only repairs a crash gap after restart, which the Host
185
+ // performs through repairContinuations during activation.
186
+ void memberId;
187
+ void event;
188
+ }
189
+ onTurnEnd(memberId, agent) {
190
+ // Checkpoint continuations first: a turn that resolved checkpoints owes
191
+ // each of them exactly one quiet next-turn follow-up. The projection's
192
+ // continuations state deduplicates across restarts; this in-memory latch
193
+ // closes the same-process window between scheduling and the delivery
194
+ // event landing in the log.
195
+ this.scheduleCheckpointContinuations(memberId, agent);
196
+ const transition = this.members.get(memberId);
197
+ if (transition === undefined || transition.turnEnded)
198
+ return;
199
+ const member = this.options.memberForAgent(agent);
200
+ if (member === undefined || transition.agent !== agent)
201
+ return;
202
+ transition.turnEnded = true;
203
+ // Wait for true idle (the turn-end event fires before the driver fully
204
+ // converges), then perform the swap off the session-event dispatch path.
205
+ void agent.whenIdle().then(() => {
206
+ if (this.disposed)
207
+ return;
208
+ const current = this.members.get(memberId);
209
+ if (current === undefined || current !== transition)
210
+ return;
211
+ if (this.options.agentForMember(memberId) !== agent)
212
+ return;
213
+ void this.performTransition(memberId, member, transition);
214
+ }, error => {
215
+ this.options.log(`context rollover idle wait failed: ${error instanceof Error ? error.message : String(error)} (member ${memberId})`);
216
+ this.members.delete(memberId);
217
+ });
218
+ }
219
+ /**
220
+ * Quiet follow-ups for checkpoints resolved by the turn that just ended.
221
+ * A successful `context_checkpoint` result concludes its turn; the Host
222
+ * continues work in the next turn with one host-generated notice. The
223
+ * projection folds delivery (the continuation user/message event), so a
224
+ * restart repairs a missing follow-up through repairContinuations without
225
+ * duplicating a delivered one; this live path latches per checkpoint in
226
+ * memory for the scheduling window.
227
+ */
228
+ scheduleCheckpointContinuations(memberId, agent) {
229
+ const member = this.options.memberForAgent(agent);
230
+ if (member === undefined)
231
+ return;
232
+ const state = this.options.projectionForMember(memberId, member.sessionId);
233
+ if (state === undefined)
234
+ return;
235
+ for (const checkpoint of state.checkpoints) {
236
+ if (checkpoint.turnEndSeq === -1)
237
+ continue;
238
+ if (continuationDelivered(state, checkpoint.checkpointRef))
239
+ continue;
240
+ const latch = `${memberId}:${checkpoint.checkpointRef}`;
241
+ if (this.scheduledContinuations.has(latch))
242
+ continue;
243
+ this.scheduledContinuations.add(latch);
244
+ // The turn/end observer fires inside the session append publication
245
+ // (a synchronous followup would reenter the publishing append), and a
246
+ // next-turn message queued while the driver is still converging never
247
+ // latches a wake — the loop replays only maintenance/abort latches. So
248
+ // the continuation waits for true idle, then queues its own turn: the
249
+ // same discipline the rollover path uses.
250
+ void agent.whenIdle().then(() => {
251
+ if (this.disposed)
252
+ return;
253
+ try {
254
+ agent.followup(createCheckpointContinuationMessage(checkpoint.checkpointRef));
255
+ }
256
+ catch (error) {
257
+ this.scheduledContinuations.delete(latch);
258
+ this.options.log(`context continuation scheduling failed: ${error instanceof Error ? error.message : String(error)} (member ${memberId})`);
259
+ }
260
+ }, () => {
261
+ this.scheduledContinuations.delete(latch);
262
+ });
263
+ }
264
+ }
265
+ async performTransition(memberId, member, transition) {
266
+ // Stable rollover identity derives from the previous Session and the
267
+ // successful tool call: a result seq is session-local and would collide
268
+ // across generations. Both inputs are unconstrained strings (provider
269
+ // call ids carry arbitrary lengths and characters; Session ids are
270
+ // caller-branded), so the pair is JSON-encoded — no delimiter can alias
271
+ // across the two fields — and hashed to a fixed-length url-safe hex
272
+ // digest instead of being embedded verbatim: the derived Session id and
273
+ // request id stay bounded and collision-resistant.
274
+ const stableKey = createHash('sha256').update(JSON.stringify([member.sessionId, transition.intent.toolCallId])).digest('hex');
275
+ const newSessionId = SessionIdBrand(`agent-team-rollover-${stableKey}`);
276
+ const plan = {
277
+ previousSessionId: member.sessionId,
278
+ newSessionId,
279
+ handoff: transition.intent.handoff,
280
+ handoffEventSeq: transition.intent.resultSeq,
281
+ trigger: 'model',
282
+ relatedFiles: transition.intent.relatedFiles,
283
+ ...(transition.intent.checkpointRef === undefined ? {} : { checkpointRef: transition.intent.checkpointRef }),
284
+ requestId: `agent-team:rollover:${stableKey}`,
285
+ carriedInput: this.drainCapturedInput(memberId),
286
+ };
287
+ const swap = this.options.executeTransition(memberId, plan);
288
+ transition.swapping = swap;
289
+ try {
290
+ await swap;
291
+ this.members.delete(memberId);
292
+ }
293
+ catch (error) {
294
+ this.options.log(`context rollover failed, leaving the previous generation recoverable: ${error instanceof Error ? error.message : String(error)} (member ${memberId})`);
295
+ this.members.delete(memberId);
296
+ }
297
+ }
298
+ /**
299
+ * Crash-recovery hook the Host runs during Member activation: re-derive
300
+ * pending intent from the projection and finish a transition that a restart
301
+ * interrupted after the successful result was durable.
302
+ */
303
+ recoverPendingTransition(memberId, agent, sessionId) {
304
+ if (this.disposed || this.members.has(memberId))
305
+ return;
306
+ const state = this.options.projectionForMember(memberId, sessionId);
307
+ if (state?.pending === null || state === undefined)
308
+ return;
309
+ const pending = state.pending;
310
+ if (pending.turnEndSeq === -1) {
311
+ // The containing turn never ended durably; treat the intent as still
312
+ // waiting and observe the live events from here.
313
+ this.members.set(memberId, {
314
+ agent,
315
+ intent: {
316
+ toolCallId: pending.toolCallId,
317
+ resultSeq: pending.resultSeq,
318
+ turn: pending.turn,
319
+ handoff: pending.handoff,
320
+ ...(pending.checkpointRef === undefined ? {} : { checkpointRef: pending.checkpointRef }),
321
+ relatedFiles: pending.relatedFiles,
322
+ },
323
+ turnEnded: false,
324
+ });
325
+ return;
326
+ }
327
+ // The turn already ended before the crash; the Agent is idle at
328
+ // activation, so the swap can proceed directly.
329
+ const member = this.options.memberForAgent(agent);
330
+ if (member === undefined)
331
+ return;
332
+ const transition = {
333
+ agent,
334
+ intent: {
335
+ toolCallId: pending.toolCallId,
336
+ resultSeq: pending.resultSeq,
337
+ turn: pending.turn,
338
+ handoff: pending.handoff,
339
+ ...(pending.checkpointRef === undefined ? {} : { checkpointRef: pending.checkpointRef }),
340
+ relatedFiles: pending.relatedFiles,
341
+ },
342
+ turnEnded: true,
343
+ };
344
+ this.members.set(memberId, transition);
345
+ void this.performTransition(memberId, member, transition);
346
+ }
347
+ /**
348
+ * Crash-recovery for quiet continuations: schedule the follow-up for one
349
+ * resolved checkpoint exactly once when the result was durable but the
350
+ * delivery never landed. The projection's continuations state is the
351
+ * durable delivery record — a checkpoint whose delivery event exists in the
352
+ * log is never re-scheduled.
353
+ */
354
+ repairContinuations(agent, state) {
355
+ if (this.disposed)
356
+ return;
357
+ const memberId = this.options.memberForAgent(agent)?.memberId;
358
+ if (memberId === undefined)
359
+ return;
360
+ for (const checkpoint of state.checkpoints) {
361
+ if (checkpoint.turnEndSeq === -1)
362
+ continue;
363
+ if (continuationDelivered(state, checkpoint.checkpointRef))
364
+ continue;
365
+ const latch = `${memberId}:${checkpoint.checkpointRef}`;
366
+ if (this.scheduledContinuations.has(latch))
367
+ continue;
368
+ this.scheduledContinuations.add(latch);
369
+ try {
370
+ agent.followup(createCheckpointContinuationMessage(checkpoint.checkpointRef));
371
+ }
372
+ catch (error) {
373
+ this.scheduledContinuations.delete(latch);
374
+ this.options.log(`context continuation repair failed: ${error instanceof Error ? error.message : String(error)}`);
375
+ }
376
+ }
377
+ }
378
+ }
379
+ /** Tool names this module owns; the preset validation requires all of them. */
380
+ export const CONTEXT_TOOL_NAMES = Object.freeze([CONTEXT_CHECKPOINT_TOOL_NAME, CONTEXT_ROLLOVER_TOOL_NAME, 'context_timeline']);
381
+ /** Re-exported for Host wiring: cold-fold helper for archived ancestors. */
382
+ export { foldContextProjection };