@wowyuarm/dsh-agent-team 0.1.14-alpha.2 → 0.1.14
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.
- package/README.md +5 -3
- package/README.zh.md +5 -3
- package/cordis.patch.yml +2 -185
- package/package.json +52 -46
- package/packages/agent-team/README.md +1 -1
- package/packages/agent-team/README.zh.md +1 -1
- package/packages/agent-team/core-skills/member-memory-manager/SKILL.md +30 -0
- package/packages/agent-team/lib/context-continuity-host.js +91 -0
- package/packages/agent-team/lib/context-projection.js +266 -489
- package/packages/agent-team/lib/context-source.js +17 -106
- package/packages/agent-team/lib/human-avatar.js +61 -0
- package/packages/agent-team/lib/human-profile.js +46 -0
- package/packages/agent-team/lib/human-update-check.js +119 -0
- package/packages/agent-team/lib/index.js +408 -264
- package/packages/agent-team/lib/ledger.js +120 -15
- package/packages/agent-team/lib/member-context.js +9 -8
- package/packages/agent-team/lib/member-runtime.js +1 -1
- package/packages/agent-team/lib/member-time-context.js +2 -5
- package/packages/agent-team/lib/preset-roster.js +15 -0
- package/packages/agent-team/lib/pressure-policy.js +9 -6
- package/packages/agent-team/lib/session-remediation.js +493 -0
- package/packages/agent-team/lib/stored-session-reader.js +2 -1
- package/packages/agent-team/lib/typert.host.js +605 -291
- package/packages/agent-team/lib/typert.remote-client.d.ts +15 -6
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +367 -244
- package/packages/agent-team/lib/types/context-continuity-host.d.ts +84 -0
- package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -0
- package/packages/agent-team/lib/types/context-projection.d.ts +130 -185
- package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-source.d.ts +17 -81
- package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
- package/packages/agent-team/lib/types/human-avatar.d.ts +29 -0
- package/packages/agent-team/lib/types/human-avatar.d.ts.map +1 -0
- package/packages/agent-team/lib/types/human-profile.d.ts +44 -0
- package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -0
- package/packages/agent-team/lib/types/human-update-check.d.ts +73 -0
- package/packages/agent-team/lib/types/human-update-check.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +112 -38
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +31 -2
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-context.d.ts +0 -14
- package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-time-context.d.ts +0 -15
- package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/preset-roster.d.ts +5 -0
- package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts +6 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
- package/packages/agent-team/lib/types/session-remediation.d.ts +172 -0
- package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
- package/packages/agent-team/lib/types/stored-session-reader.d.ts +2 -1
- package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/entities.d.ts +20 -3
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +63 -0
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +166 -0
- package/packages/client-agent-team/lib/client.js +1667 -516
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts +15 -0
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.js +115 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +12 -8
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +22 -1
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +25 -2
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +6 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +69 -31
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +4 -4
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +3 -3
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +12 -5
- package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +4 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +22 -7
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +11 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +69 -8
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +18 -3
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +213 -67
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +5 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +55 -29
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +3 -3
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceSelector.js +2 -2
- package/packages/client-agent-team/lib/types/client/avatar-image.d.ts +21 -0
- package/packages/client-agent-team/lib/types/client/avatar-image.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/avatar-image.js +9 -0
- package/packages/client-agent-team/lib/types/client/human-identity.d.ts +94 -0
- package/packages/client-agent-team/lib/types/client/human-identity.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/human-identity.js +117 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +112 -41
- package/packages/client-agent-team/lib/types/client/locales.d.ts +72 -2
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +78 -2
- package/packages/client-agent-team/lib/types/client/multi-menu-field.js +2 -2
- package/packages/client-agent-team/lib/types/client/refs.d.ts +71 -0
- package/packages/client-agent-team/lib/types/client/refs.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/refs.js +181 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +8 -2
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +57 -9
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.js +85 -17
- package/packages/tool-agent-team/README.md +1 -1
- package/packages/tool-agent-team/README.zh.md +1 -1
- package/packages/tool-agent-team/lib/context-tools.js +20 -6
- package/packages/tool-agent-team/lib/index.js +2 -2
- package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
- package/packages/agent-team/lib/context-management.js +0 -371
- package/packages/agent-team/lib/types/context-management.d.ts +0 -154
- package/packages/agent-team/lib/types/context-management.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts +0 -31
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/task-refs.js +0 -99
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Team's projection contribution to the context-continuity engine: the durable
|
|
3
|
+
* ref naming, the Team-notice rule, and the domain judgement that decides which
|
|
4
|
+
* events anchor a timeline boundary and what each boundary is attributable to.
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* successful `context_checkpoint` pair resolved by its containing `turn/end`),
|
|
10
|
-
* handoff/compaction boundaries, and quiet-continuation delivery state. The
|
|
11
|
-
* fold is the single authority for these facts — no second store, and callers
|
|
12
|
-
* never re-derive intent from raw events.
|
|
6
|
+
* The fold itself is the engine's (`createContextProjectionDefinition`): one
|
|
7
|
+
* registered unit per Host folds every Session's durable log and owns the
|
|
8
|
+
* universal structure — checkpoints, the pending rollover intent, quiet
|
|
9
|
+
* continuation delivery, carry candidates, open calls, turn cursors. This
|
|
10
|
+
* module supplies only what the engine deliberately refuses to know:
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
12
|
+
* - the durable refs Team's existing logs already carry
|
|
13
|
+
* (`context-checkpoint-<sha256>`, `team-boundary-<sha256>`), frozen because
|
|
14
|
+
* recorded history is read back through them;
|
|
15
|
+
* - which queued messages are Team-owned notices the successor rederives;
|
|
16
|
+
* - which successful Team calls anchor a boundary, and which Threads each
|
|
17
|
+
* boundary is attributable to. A claim mutation resolves its Task's Thread
|
|
18
|
+
* through the ledger; that binding is written once at Task creation, so a
|
|
19
|
+
* re-fold reproduces the same attribution, and a missing ledger or mapping
|
|
20
|
+
* yields no attribution instead of failing the fold.
|
|
21
|
+
*
|
|
22
|
+
* The remaining exports are read views over the engine's state that Team still
|
|
23
|
+
* owns: the anchor lookups the rollover guard revalidates a cited ref through,
|
|
24
|
+
* the accumulated Thread attribution that guard and the timeline both judge a
|
|
25
|
+
* boundary by, and carried-message resolution — the engine keys carry
|
|
26
|
+
* candidates by message id, and the durable log is where the message body
|
|
27
|
+
* lives.
|
|
19
28
|
* @module @wowyuarm/dsh-agent-team/context-projection
|
|
20
29
|
*/
|
|
21
30
|
import { createHash } from 'node:crypto';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
31
|
+
import { CONTEXT_CHECKPOINT_TOOL_NAME, CONTEXT_ROLLOVER_TOOL_NAME, createContextProjectionDefinition, foldContextProjection as foldEngineContextProjection, } from '@wowyuarm/dsh-context-continuity';
|
|
32
|
+
import { AGENT_TEAM_PLUGIN_ID, handoffOf, isAgentTeamContextSource } from "./context-source.js";
|
|
33
|
+
import { TEAM_CONTEXT_CODEC } from "./context-continuity-host.js";
|
|
24
34
|
/** Summary marker of the pre-compaction memory hint. */
|
|
25
35
|
const PRE_COMPACTION_NOTICE_SUMMARY = 'Compaction is imminent; consider persisting key conclusions.';
|
|
26
36
|
/** Team tool whose successful mutations are semantic timeline candidates. */
|
|
@@ -32,111 +42,42 @@ const TEAM_THREAD_TOOL_NAME = 'team_thread';
|
|
|
32
42
|
const TEAM_MESSAGE_BOUNDARY_LABEL = 'Team message';
|
|
33
43
|
const TEAM_ATTENTION_BOUNDARY_LABEL = 'Team attention change';
|
|
34
44
|
const TEAM_CLAIM_BOUNDARY_LABEL = 'Team task claim change';
|
|
45
|
+
/** The handoff boundary label; every generation opens with one. */
|
|
46
|
+
const HANDOFF_BOUNDARY_LABEL = 'context handoff';
|
|
47
|
+
/** The compaction boundary label. */
|
|
48
|
+
const COMPACTION_BOUNDARY_LABEL = 'compaction notice';
|
|
49
|
+
/**
|
|
50
|
+
* The anchor kinds Team contributes. The engine treats a kind as opaque, but
|
|
51
|
+
* Team's own read views map it back to the timeline source vocabulary, so the
|
|
52
|
+
* three are named once here instead of as bare strings at each contribution.
|
|
53
|
+
*/
|
|
54
|
+
const TEAM_BOUNDARY_KINDS = ['team-boundary', 'handoff', 'compaction'];
|
|
35
55
|
/**
|
|
36
56
|
* Notice summaries that are pure reminders, never semantic Team facts: a
|
|
37
57
|
* recovery instruction must not become a return anchor. `Progress visibility
|
|
38
58
|
* reminder` is a historical decoder — the progress-nudge system was removed,
|
|
39
59
|
* but session logs recorded before its removal still carry those notices, and
|
|
40
|
-
* the
|
|
41
|
-
*
|
|
60
|
+
* the fold keeps recognizing them (same pattern as the legacy `new_context`
|
|
61
|
+
* tool name below).
|
|
42
62
|
*/
|
|
43
63
|
const REMINDER_NOTICE_SUMMARIES = new Set(['Progress visibility reminder', 'Recovery: continue your interrupted work.']);
|
|
44
64
|
/** Whether one notice summary is a pure reminder (never a semantic Team fact). */
|
|
45
65
|
export function isReminderNoticeSummary(summary) {
|
|
46
66
|
return REMINDER_NOTICE_SUMMARIES.has(summary);
|
|
47
67
|
}
|
|
48
|
-
/** Stable tool names the
|
|
49
|
-
export
|
|
50
|
-
export const CONTEXT_ROLLOVER_TOOL_NAME = 'context_rollover';
|
|
68
|
+
/** Stable tool names the fold recognizes under their current names. */
|
|
69
|
+
export { CONTEXT_CHECKPOINT_TOOL_NAME, CONTEXT_ROLLOVER_TOOL_NAME };
|
|
51
70
|
/**
|
|
52
71
|
* Legacy decoder name: the rollover tool was renamed `new_context` →
|
|
53
72
|
* `context_rollover`, and Sessions recorded before the rename still carry
|
|
54
|
-
* durable `new_context` call/result pairs. The
|
|
55
|
-
*
|
|
73
|
+
* durable `new_context` call/result pairs. The fold keeps decoding them —
|
|
74
|
+
* pending rollovers and crash recovery of existing Members depend on old
|
|
56
75
|
* events still resolving intent — but this is a log decoder, not a tool
|
|
57
76
|
* alias: no new call can carry the old name.
|
|
58
77
|
*/
|
|
59
78
|
export const NEW_CONTEXT_TOOL_NAME = 'new_context';
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
return name === CONTEXT_ROLLOVER_TOOL_NAME || name === NEW_CONTEXT_TOOL_NAME;
|
|
63
|
-
}
|
|
64
|
-
/** Whether one tool name can produce a Team-effect boundary from a successful call. */
|
|
65
|
-
function isTeamEffectToolName(name) {
|
|
66
|
-
return name === TEAM_CLAIM_TOOL_NAME || name === TEAM_MESSAGE_TOOL_NAME || name === TEAM_THREAD_TOOL_NAME;
|
|
67
|
-
}
|
|
68
|
-
/** Whether one raw team_thread arguments string is an attention mutation (follow/unfollow). */
|
|
69
|
-
function argumentsAreAttentionMutation(raw) {
|
|
70
|
-
let parsed;
|
|
71
|
-
try {
|
|
72
|
-
parsed = JSON.parse(raw);
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
if (typeof parsed !== 'object' || parsed === null)
|
|
78
|
-
return false;
|
|
79
|
-
const { action } = parsed;
|
|
80
|
-
return action === 'follow' || action === 'unfollow';
|
|
81
|
-
}
|
|
82
|
-
/** Whether one raw team_message arguments string is a Thread-effect attempt (start/reply — dm is not a Thread fact). */
|
|
83
|
-
function argumentsAreMessageCommit(raw) {
|
|
84
|
-
let parsed;
|
|
85
|
-
try {
|
|
86
|
-
parsed = JSON.parse(raw);
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
if (typeof parsed !== 'object' || parsed === null)
|
|
92
|
-
return false;
|
|
93
|
-
const { action } = parsed;
|
|
94
|
-
return action === 'start' || action === 'reply';
|
|
95
|
-
}
|
|
96
|
-
const relatedFileSchema = z.object({ path: z.string().min(1), reason: z.string() }).strict();
|
|
97
|
-
const checkpointRefSchema = z.string().regex(/^(context-checkpoint-[0-9a-f]{64}|team-boundary-[0-9a-f]{64})$/).transform(value => value);
|
|
98
|
-
const openCallSchema = z.object({ callId: z.string().min(1), name: z.string(), arguments: z.string() }).strict();
|
|
99
|
-
const carriedCandidateSchema = z.object({
|
|
100
|
-
message: z.any(),
|
|
101
|
-
surfacedTurn: z.number().int(),
|
|
102
|
-
consumed: z.boolean(),
|
|
103
|
-
});
|
|
104
|
-
const boundarySchema = z.object({
|
|
105
|
-
key: z.string().min(1),
|
|
106
|
-
source: z.enum(['team-boundary', 'handoff', 'compaction']),
|
|
107
|
-
label: z.string(),
|
|
108
|
-
seq: z.number().int().nonnegative(),
|
|
109
|
-
turn: z.number().int().nonnegative(),
|
|
110
|
-
turnEndSeq: z.number().int(),
|
|
111
|
-
}).strict();
|
|
112
|
-
const stateSchema = z.object({
|
|
113
|
-
checkpoints: z.array(z.object({
|
|
114
|
-
checkpointRef: checkpointRefSchema,
|
|
115
|
-
name: z.string(),
|
|
116
|
-
resultSeq: z.number().int().nonnegative(),
|
|
117
|
-
turn: z.number().int().nonnegative(),
|
|
118
|
-
turnEndSeq: z.number().int(),
|
|
119
|
-
}).strict()),
|
|
120
|
-
pending: z.object({
|
|
121
|
-
toolCallId: z.string().min(1),
|
|
122
|
-
resultSeq: z.number().int().nonnegative(),
|
|
123
|
-
turn: z.number().int().nonnegative(),
|
|
124
|
-
handoff: z.string(),
|
|
125
|
-
checkpointRef: checkpointRefSchema.optional(),
|
|
126
|
-
relatedFiles: z.array(relatedFileSchema),
|
|
127
|
-
turnEndSeq: z.number().int(),
|
|
128
|
-
}).strict().nullable(),
|
|
129
|
-
continuations: z.array(z.object({
|
|
130
|
-
checkpointRef: checkpointRefSchema,
|
|
131
|
-
deliveredSeq: z.number().int(),
|
|
132
|
-
}).strict()),
|
|
133
|
-
carriedCandidates: z.array(carriedCandidateSchema),
|
|
134
|
-
lastTurn: z.number().int().nonnegative(),
|
|
135
|
-
openCalls: z.array(openCallSchema),
|
|
136
|
-
boundaries: z.array(boundarySchema),
|
|
137
|
-
seenThreads: z.array(z.string()),
|
|
138
|
-
lastTurnEndSeq: z.number().int(),
|
|
139
|
-
}).strict();
|
|
79
|
+
/** Every recorded tool name a rollover call may carry, current name first. */
|
|
80
|
+
export const TEAM_ROLLOVER_TOOL_NAMES = [CONTEXT_ROLLOVER_TOOL_NAME, NEW_CONTEXT_TOOL_NAME];
|
|
140
81
|
/**
|
|
141
82
|
* Deterministic checkpoint ref from the recording session and tool call
|
|
142
83
|
* identity: a bounded, collision-resistant opaque token. Provider call ids
|
|
@@ -150,8 +91,8 @@ export function checkpointRefFor(sessionId, callId) {
|
|
|
150
91
|
return `context-checkpoint-${createHash('sha256').update(JSON.stringify([sessionId, callId])).digest('hex')}`;
|
|
151
92
|
}
|
|
152
93
|
/**
|
|
153
|
-
* Deterministic
|
|
154
|
-
*
|
|
94
|
+
* Deterministic boundary ref for one delivered Team boundary: the same
|
|
95
|
+
* session-scoped hash shape as checkpoint refs, keyed on the boundary's
|
|
155
96
|
* anchoring event seq. Consecutive generations routinely repeat event seqs,
|
|
156
97
|
* so the Session identity must be part of the key or two generations'
|
|
157
98
|
* boundaries at the same seq collide — the timeline would silently drop the
|
|
@@ -160,129 +101,54 @@ export function checkpointRefFor(sessionId, callId) {
|
|
|
160
101
|
export function boundaryRefFor(sessionId, seq) {
|
|
161
102
|
return `team-boundary-${createHash('sha256').update(JSON.stringify([sessionId, seq])).digest('hex')}`;
|
|
162
103
|
}
|
|
163
|
-
function emptyState() {
|
|
164
|
-
return { checkpoints: [], pending: null, continuations: [], carriedCandidates: [], lastTurn: 0, openCalls: [], boundaries: [], seenThreads: [], lastTurnEndSeq: -1 };
|
|
165
|
-
}
|
|
166
104
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* provider call id in two different Sessions produces two distinct refs.
|
|
105
|
+
* Whether one queued message is a Team-owned notice the rederived Inbox
|
|
106
|
+
* replaces. Handoff and continuation envelopes carry the same plugin
|
|
107
|
+
* attribution but are ordinary delivered context the new generation keeps, so
|
|
108
|
+
* they are excluded rather than dropped.
|
|
172
109
|
*/
|
|
173
|
-
export function
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
for (const event of events) {
|
|
177
|
-
if (event.seq < inherited)
|
|
178
|
-
continue;
|
|
179
|
-
state = applyContextEvent(state, event, sessionId);
|
|
180
|
-
}
|
|
181
|
-
return state;
|
|
110
|
+
export function isTeamNotice(message) {
|
|
111
|
+
const source = message.source;
|
|
112
|
+
return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID && !isAgentTeamContextSource(message);
|
|
182
113
|
}
|
|
183
|
-
/**
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
* therefore share one transition function: a Session's projection cannot
|
|
187
|
-
* depend on which of the two read it.
|
|
188
|
-
*/
|
|
189
|
-
export const contextProjectionFold = (sessionId) => ({
|
|
190
|
-
start: emptyState(),
|
|
191
|
-
step: (state, event) => applyContextEvent(state, event, sessionId),
|
|
192
|
-
});
|
|
193
|
-
/**
|
|
194
|
-
* The host-only projection unit; no wire view is published. The definition is
|
|
195
|
-
* a factory: each Session folds with its own identity so checkpoint refs
|
|
196
|
-
* derive from that Session's exact `(sessionId, callId)` pairs.
|
|
197
|
-
*/
|
|
198
|
-
export const agentTeamContextProjectionDefinition = (sessionId) => ({
|
|
199
|
-
key: 'agentTeamContext',
|
|
200
|
-
// v4: first-arrival boundary labels — notice-class boundaries label the
|
|
201
|
-
// Threads they first introduce (`First arrival: …`) instead of the
|
|
202
|
-
// notice's own summary, and the claim boundary label names its Task
|
|
203
|
-
// surface (`Team task claim change`). Labels are fold-time synthesized,
|
|
204
|
-
// not stored facts, so a persisted v3 row must not survive next to the
|
|
205
|
-
// new vocabulary: the ver mismatch refolds it from the full log.
|
|
206
|
-
stateVersion: 4,
|
|
207
|
-
stateSchema,
|
|
208
|
-
init: (_header, _inheritedEventCount) => emptyState(),
|
|
209
|
-
apply: (state, event) => applyContextEvent(state, event, sessionId),
|
|
210
|
-
});
|
|
211
|
-
/**
|
|
212
|
-
* Pure transition: previous state + one committed event → next state. Returns
|
|
213
|
-
* the same reference when the event is not this unit's.
|
|
214
|
-
*/
|
|
215
|
-
function applyContextEvent(state, event, sessionId) {
|
|
216
|
-
if (event.type === 'tool/call') {
|
|
217
|
-
if (!isRolloverToolName(event.data.name) && event.data.name !== CONTEXT_CHECKPOINT_TOOL_NAME && !isTeamEffectToolName(event.data.name))
|
|
218
|
-
return state;
|
|
219
|
-
// Only Team-effect calls that can produce a boundary are tracked; `list`
|
|
220
|
-
// and other non-mutations are not semantic timeline candidates.
|
|
221
|
-
if (event.data.name === TEAM_CLAIM_TOOL_NAME && !argumentsAreClaimMutation(event.data.arguments))
|
|
222
|
-
return state;
|
|
223
|
-
if (event.data.name === TEAM_THREAD_TOOL_NAME && !argumentsAreAttentionMutation(event.data.arguments))
|
|
224
|
-
return state;
|
|
225
|
-
if (event.data.name === TEAM_MESSAGE_TOOL_NAME && !argumentsAreMessageCommit(event.data.arguments))
|
|
226
|
-
return state;
|
|
227
|
-
return { ...state, openCalls: [...state.openCalls, { callId: event.data.callId, name: event.data.name, arguments: event.data.arguments }] };
|
|
228
|
-
}
|
|
229
|
-
if (event.type === 'tool/result') {
|
|
230
|
-
return applyToolResult(state, event.seq, event.data.turn, event.data.message, event.data.error !== undefined, sessionId, event.data.meta);
|
|
231
|
-
}
|
|
232
|
-
if (event.type === 'turn/end') {
|
|
233
|
-
return applyTurnEnd(state, event.seq, event.data.turn);
|
|
234
|
-
}
|
|
235
|
-
if (event.type === 'user/message') {
|
|
236
|
-
return applyUserMessage(state, event.seq, event.data, sessionId);
|
|
237
|
-
}
|
|
238
|
-
if (event.type === 'agent/inbox/spliced') {
|
|
239
|
-
return applyInboxSpliced(state, event.data);
|
|
240
|
-
}
|
|
241
|
-
if (event.type === 'assistant/message') {
|
|
242
|
-
return applyAssistantMessage(state, event);
|
|
243
|
-
}
|
|
244
|
-
if (event.type === 'turn/start') {
|
|
245
|
-
return event.data.turn === state.lastTurn ? state : { ...state, lastTurn: event.data.turn };
|
|
246
|
-
}
|
|
247
|
-
return state;
|
|
114
|
+
/** Whether one tool name can produce a Team-effect boundary from a successful call. */
|
|
115
|
+
function isTeamEffectToolName(name) {
|
|
116
|
+
return name === TEAM_CLAIM_TOOL_NAME || name === TEAM_MESSAGE_TOOL_NAME || name === TEAM_THREAD_TOOL_NAME;
|
|
248
117
|
}
|
|
249
|
-
/**
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
* "work just arrived" anchor; every later re-delivery of the same Thread is
|
|
255
|
-
* noise. Reminder notices (progress nudges, recovery instructions) never
|
|
256
|
-
* anchor. All anchor to the containing completed turn. Plain Human/agent
|
|
257
|
-
* prose and quiet checkpoint continuations are not boundaries.
|
|
258
|
-
*/
|
|
259
|
-
function boundaryFromUserMessage(sessionId, seq, message, seenThreads) {
|
|
260
|
-
const source = message.source;
|
|
261
|
-
if (handoffOf(message) !== undefined) {
|
|
262
|
-
return { key: `handoff:${seq}`, source: 'handoff', label: 'context handoff', seq, turn: -1, turnEndSeq: -1 };
|
|
118
|
+
/** One parsed JSON argument object, or undefined when the raw string carries none. */
|
|
119
|
+
function parseArguments(raw) {
|
|
120
|
+
let parsed;
|
|
121
|
+
try {
|
|
122
|
+
parsed = JSON.parse(raw);
|
|
263
123
|
}
|
|
264
|
-
|
|
265
|
-
return undefined;
|
|
266
|
-
if (source.form === 'relay')
|
|
124
|
+
catch {
|
|
267
125
|
return undefined;
|
|
268
|
-
if (source.form === 'notice' && source.summary === PRE_COMPACTION_NOTICE_SUMMARY) {
|
|
269
|
-
return { key: `compaction:${seq}`, source: 'compaction', label: 'compaction notice', seq, turn: -1, turnEndSeq: -1 };
|
|
270
|
-
}
|
|
271
|
-
if (source.form === 'notice' && source.summary !== undefined && REMINDER_NOTICE_SUMMARIES.has(source.summary)) {
|
|
272
|
-
return { reminder: true };
|
|
273
126
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
127
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : undefined;
|
|
128
|
+
}
|
|
129
|
+
/** The `action` one raw tool-call argument string declares, when it declares one. */
|
|
130
|
+
function actionOf(raw) {
|
|
131
|
+
return parseArguments(raw)?.action;
|
|
132
|
+
}
|
|
133
|
+
/** Whether one raw Team-claim arguments string is a mutation (not `list`). */
|
|
134
|
+
function argumentsAreClaimMutation(raw) {
|
|
135
|
+
const action = actionOf(raw);
|
|
136
|
+
return action === 'claim' || action === 'done' || action === 'release';
|
|
137
|
+
}
|
|
138
|
+
/** Whether one raw team_thread arguments string is an attention mutation (follow/unfollow). */
|
|
139
|
+
function argumentsAreAttentionMutation(raw) {
|
|
140
|
+
const action = actionOf(raw);
|
|
141
|
+
return action === 'follow' || action === 'unfollow';
|
|
142
|
+
}
|
|
143
|
+
/** Whether one raw team_message arguments string is a Thread-effect attempt (start/reply — dm is not a Thread fact). */
|
|
144
|
+
function argumentsAreMessageCommit(raw) {
|
|
145
|
+
const action = actionOf(raw);
|
|
146
|
+
return action === 'start' || action === 'reply';
|
|
147
|
+
}
|
|
148
|
+
/** The Thread ref one raw argument string names, when it names one. */
|
|
149
|
+
function threadRefIn(raw) {
|
|
150
|
+
const threadRef = parseArguments(raw)?.threadRef;
|
|
151
|
+
return typeof threadRef === 'string' && threadRef !== '' ? threadRef : undefined;
|
|
286
152
|
}
|
|
287
153
|
/** Thread refs structurally quoted in one delivered message body (`Thread: <ref>` lines). */
|
|
288
154
|
function threadsQuotedInMessage(message) {
|
|
@@ -295,294 +161,205 @@ function threadsQuotedInMessage(message) {
|
|
|
295
161
|
return refs;
|
|
296
162
|
}
|
|
297
163
|
/**
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
* the message vanish silently — only surfacing it onto the model-visible
|
|
302
|
-
* `user/message` surface consumes it.
|
|
164
|
+
* Team's domain half of the context-continuity projection. Every method is a
|
|
165
|
+
* pure judgement over the event the engine hands it plus Team's own durable
|
|
166
|
+
* vocabulary; the engine keeps the state machine.
|
|
303
167
|
*/
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
// Effect boundaries — the attribution matrix:
|
|
360
|
-
// claim mutation → the Task ref from its call arguments (Task → Thread
|
|
361
|
-
// resolution stays a query-time ledger concern);
|
|
362
|
-
// team_message committed → reply/taskRef from args, start from the
|
|
363
|
-
// structured presentation meta (`kind === 'committed'` guards every
|
|
364
|
-
// typed rejection; no meta means no boundary — old logs refold without
|
|
365
|
-
// start anchors by design, never by parsing render text);
|
|
366
|
-
// team_thread follow/unfollow → the threadRef from its call arguments.
|
|
367
|
-
// A boundary without a resolvable ref is not produced.
|
|
368
|
-
const boundary = effectBoundaryFor(recorded, sessionId, seq, turn, meta);
|
|
369
|
-
if (boundary === undefined)
|
|
370
|
-
return { ...state, openCalls };
|
|
371
|
-
return { ...state, openCalls, boundaries: [...state.boundaries, boundary] };
|
|
372
|
-
}
|
|
373
|
-
/** The effect boundary one successful Team-effect call produces, if its attribution resolves. */
|
|
374
|
-
function effectBoundaryFor(recorded, sessionId, seq, turn, meta) {
|
|
375
|
-
if (recorded.name === TEAM_CLAIM_TOOL_NAME) {
|
|
376
|
-
return { key: boundaryRefFor(sessionId, seq), source: 'team-boundary', label: TEAM_CLAIM_BOUNDARY_LABEL, seq, turn, turnEndSeq: -1 };
|
|
377
|
-
}
|
|
378
|
-
if (recorded.name === TEAM_THREAD_TOOL_NAME) {
|
|
379
|
-
return { key: boundaryRefFor(sessionId, seq), source: 'team-boundary', label: TEAM_ATTENTION_BOUNDARY_LABEL, seq, turn, turnEndSeq: -1 };
|
|
380
|
-
}
|
|
381
|
-
if (recorded.name === TEAM_MESSAGE_TOOL_NAME) {
|
|
382
|
-
// The structured meta projection is the single attribution source for a
|
|
383
|
-
// start (the Thread is born in the result); replies carry their ref in
|
|
384
|
-
// the call arguments already, but the committed guard is meta-side for
|
|
385
|
-
// every action — typed rejections are successful calls, not errors.
|
|
386
|
-
if (meta === undefined || typeof meta !== 'object' || meta === null)
|
|
168
|
+
export class TeamContextProjectionHost {
|
|
169
|
+
options;
|
|
170
|
+
constructor(options = {}) {
|
|
171
|
+
this.options = options;
|
|
172
|
+
}
|
|
173
|
+
/** Durable checkpoint ref: Team's existing log vocabulary, unchanged. */
|
|
174
|
+
checkpointRefFor(sessionId, toolCallId) {
|
|
175
|
+
return checkpointRefFor(sessionId, toolCallId);
|
|
176
|
+
}
|
|
177
|
+
/** Durable boundary ref: the same session-scoped hash shape. */
|
|
178
|
+
boundaryRefFor(sessionId, seq) {
|
|
179
|
+
return boundaryRefFor(sessionId, seq);
|
|
180
|
+
}
|
|
181
|
+
/** A Team-owned notice the successor generation rederives from ledger facts. */
|
|
182
|
+
isEphemeralNotice(message) {
|
|
183
|
+
return isTeamNotice(message);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Only a Team-effect call that can produce a boundary is tracked at all:
|
|
187
|
+
* `list` on a claim, `read` on a Thread, and `dm` are not semantic timeline
|
|
188
|
+
* candidates, so their results must never open an anchor.
|
|
189
|
+
*/
|
|
190
|
+
tracksCall(name, raw) {
|
|
191
|
+
if (!isTeamEffectToolName(name))
|
|
192
|
+
return false;
|
|
193
|
+
if (name === TEAM_CLAIM_TOOL_NAME)
|
|
194
|
+
return argumentsAreClaimMutation(raw);
|
|
195
|
+
if (name === TEAM_THREAD_TOOL_NAME)
|
|
196
|
+
return argumentsAreAttentionMutation(raw);
|
|
197
|
+
return argumentsAreMessageCommit(raw);
|
|
198
|
+
}
|
|
199
|
+
/** Team's boundary for one event, or undefined when the event anchors nothing. */
|
|
200
|
+
domainBoundaryOf(input) {
|
|
201
|
+
return input.source === 'user-message'
|
|
202
|
+
? this.boundaryFromUserMessage(input)
|
|
203
|
+
: this.boundaryFromToolResult(input);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Structural boundary from one delivered user message: a rollover handoff
|
|
207
|
+
* starts a generation; a compaction notice rewrites the visible surface. A
|
|
208
|
+
* structured Team notification is a boundary ONLY on the first arrival of
|
|
209
|
+
* each Thread's facts into this Session — the preserved "work just arrived"
|
|
210
|
+
* anchor; every later re-delivery of the same Thread is noise. Reminder
|
|
211
|
+
* notices (progress nudges, recovery instructions) never anchor. Plain
|
|
212
|
+
* Human/agent prose and quiet checkpoint continuations are not boundaries.
|
|
213
|
+
*/
|
|
214
|
+
boundaryFromUserMessage(input) {
|
|
215
|
+
const { message, seenTopics } = input;
|
|
216
|
+
if (handoffOf(message) !== undefined) {
|
|
217
|
+
return { kind: 'handoff', label: HANDOFF_BOUNDARY_LABEL, topics: [] };
|
|
218
|
+
}
|
|
219
|
+
const source = message.source;
|
|
220
|
+
if (source.kind !== 'plugin' || source.plugin !== AGENT_TEAM_PLUGIN_ID)
|
|
387
221
|
return undefined;
|
|
388
|
-
|
|
389
|
-
if (kind !== 'committed' || typeof threadRef !== 'string' || threadRef === '')
|
|
222
|
+
if (source.form === 'relay')
|
|
390
223
|
return undefined;
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return undefined;
|
|
394
|
-
}
|
|
395
|
-
function applyTurnEnd(state, seq, _turn) {
|
|
396
|
-
let changed = false;
|
|
397
|
-
const checkpoints = state.checkpoints.map(entry => {
|
|
398
|
-
if (entry.turnEndSeq !== -1)
|
|
399
|
-
return entry;
|
|
400
|
-
changed = true;
|
|
401
|
-
return { ...entry, turnEndSeq: seq };
|
|
402
|
-
});
|
|
403
|
-
let pending = state.pending;
|
|
404
|
-
if (pending !== null && pending.turnEndSeq === -1) {
|
|
405
|
-
pending = { ...pending, turnEndSeq: seq };
|
|
406
|
-
changed = true;
|
|
407
|
-
}
|
|
408
|
-
const boundaries = state.boundaries.map(entry => {
|
|
409
|
-
if (entry.turnEndSeq !== -1)
|
|
410
|
-
return entry;
|
|
411
|
-
changed = true;
|
|
412
|
-
return { ...entry, turnEndSeq: seq, turn: entry.turn === -1 ? _turn : entry.turn };
|
|
413
|
-
});
|
|
414
|
-
if (state.lastTurnEndSeq !== seq)
|
|
415
|
-
changed = true;
|
|
416
|
-
return changed ? { ...state, checkpoints, pending, boundaries, lastTurnEndSeq: seq } : state;
|
|
417
|
-
}
|
|
418
|
-
function applyUserMessage(state, seq, message, sessionId) {
|
|
419
|
-
let next = state;
|
|
420
|
-
// A surfaced candidate is NOT consumed yet: the loop appends user/message
|
|
421
|
-
// before the step runs, so cancellation can still land between them. Only
|
|
422
|
-
// a completed, uninterrupted assistant answer for the same turn proves the
|
|
423
|
-
// old generation handled it; anything else stays carried, so an aborted
|
|
424
|
-
// partial request re-delivers the input instead of silently dropping it.
|
|
425
|
-
const surfaced = next.carriedCandidates.some(candidate => !candidate.consumed && candidate.message.id === message.id);
|
|
426
|
-
if (surfaced) {
|
|
427
|
-
// The user/message event carries no turn field, but the projection folds
|
|
428
|
-
// in log order: the containing step/start preceded this message, and
|
|
429
|
-
// the fold tracks the current turn from turn/start. Candidates surface
|
|
430
|
-
// inside the turn the inbox claimed them for.
|
|
431
|
-
next = { ...next, carriedCandidates: next.carriedCandidates.map(candidate => candidate.message.id === message.id && !candidate.consumed ? { ...candidate, surfacedTurn: next.lastTurn } : candidate) };
|
|
432
|
-
}
|
|
433
|
-
// A structural boundary delivery (handoff start, first-arrival Team notice,
|
|
434
|
-
// compaction notice) becomes a timeline candidate anchored to the containing
|
|
435
|
-
// turn; it resolves when that turn ends. Threads the delivery first
|
|
436
|
-
// introduces join seenThreads so later re-deliveries produce nothing.
|
|
437
|
-
const boundary = boundaryFromUserMessage(sessionId, seq, message, next.seenThreads);
|
|
438
|
-
if (boundary !== undefined && !('reminder' in boundary)) {
|
|
439
|
-
if ('firstArrival' in boundary) {
|
|
440
|
-
const { firstArrival, ...timelineBoundary } = boundary;
|
|
441
|
-
next = {
|
|
442
|
-
...next,
|
|
443
|
-
boundaries: [...next.boundaries, timelineBoundary],
|
|
444
|
-
seenThreads: [...next.seenThreads, ...firstArrival],
|
|
445
|
-
};
|
|
224
|
+
if (source.form === 'notice' && source.summary === PRE_COMPACTION_NOTICE_SUMMARY) {
|
|
225
|
+
return { kind: 'compaction', label: COMPACTION_BOUNDARY_LABEL, topics: [] };
|
|
446
226
|
}
|
|
447
|
-
|
|
448
|
-
|
|
227
|
+
if (source.form === 'notice' && source.summary !== undefined && isReminderNoticeSummary(source.summary))
|
|
228
|
+
return undefined;
|
|
229
|
+
// The Threads this delivery first introduces anchor the boundary; a
|
|
230
|
+
// delivery that introduces none (pure re-delivery) is noise.
|
|
231
|
+
const firstArrivals = threadsQuotedInMessage(message).filter(ref => !seenTopics.includes(ref));
|
|
232
|
+
if (firstArrivals.length === 0)
|
|
233
|
+
return undefined;
|
|
234
|
+
// The label states WHAT first arrived — the Thread refs this delivery
|
|
235
|
+
// introduced into the context — never the notice's own generic account
|
|
236
|
+
// (e.g. "unread work"): the timeline's decision surface needs the
|
|
237
|
+
// attribution before a checkpointRef pick, and it is only knowable here,
|
|
238
|
+
// at the fold, where first arrival is computed.
|
|
239
|
+
return { kind: 'team-boundary', label: `First arrival: ${firstArrivals.join(', ')}`, topics: firstArrivals };
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Effect boundary from one successful Team-effect call — the attribution
|
|
243
|
+
* matrix: claim mutation → the Thread its Task was created in (resolved
|
|
244
|
+
* through the ledger, whose binding is immutable); team_message committed →
|
|
245
|
+
* the Thread from the call arguments or, for a start, from the structured
|
|
246
|
+
* presentation meta (`kind === 'committed'` guards every typed rejection; no
|
|
247
|
+
* meta means no boundary — old logs refold without start anchors by design,
|
|
248
|
+
* never by parsing render text); team_thread follow/unfollow → the threadRef
|
|
249
|
+
* from its call arguments. An unresolvable attribution leaves the boundary
|
|
250
|
+
* anchored but not single-Thread selectable.
|
|
251
|
+
*/
|
|
252
|
+
boundaryFromToolResult(input) {
|
|
253
|
+
const { name, arguments: raw, meta } = input;
|
|
254
|
+
if (name === TEAM_CLAIM_TOOL_NAME) {
|
|
255
|
+
const taskRef = parseArguments(raw)?.taskRef;
|
|
256
|
+
const threadRef = typeof taskRef === 'string' && taskRef !== ''
|
|
257
|
+
? this.options.threadForTask?.(taskRef)
|
|
258
|
+
: undefined;
|
|
259
|
+
return { kind: 'team-boundary', label: TEAM_CLAIM_BOUNDARY_LABEL, topics: threadRef === undefined ? [] : [threadRef] };
|
|
449
260
|
}
|
|
261
|
+
if (name === TEAM_THREAD_TOOL_NAME) {
|
|
262
|
+
const threadRef = threadRefIn(raw);
|
|
263
|
+
return { kind: 'team-boundary', label: TEAM_ATTENTION_BOUNDARY_LABEL, topics: threadRef === undefined ? [] : [threadRef] };
|
|
264
|
+
}
|
|
265
|
+
if (name === TEAM_MESSAGE_TOOL_NAME) {
|
|
266
|
+
// The structured meta projection is the single attribution source for a
|
|
267
|
+
// start (the Thread is born in the result); replies carry their ref in
|
|
268
|
+
// the call arguments already, but the committed guard is meta-side for
|
|
269
|
+
// every action — typed rejections are successful calls, not errors.
|
|
270
|
+
if (meta === undefined || typeof meta !== 'object' || meta === null)
|
|
271
|
+
return undefined;
|
|
272
|
+
const { kind, threadRef } = meta;
|
|
273
|
+
if (kind !== 'committed' || typeof threadRef !== 'string' || threadRef === '')
|
|
274
|
+
return undefined;
|
|
275
|
+
return { kind: 'team-boundary', label: TEAM_MESSAGE_BOUNDARY_LABEL, topics: [threadRef] };
|
|
276
|
+
}
|
|
277
|
+
return undefined;
|
|
450
278
|
}
|
|
451
|
-
// The quiet continuation notice delivered for one checkpoint completes its
|
|
452
|
-
// delivery state; replay repair reads this to avoid re-scheduling it.
|
|
453
|
-
const continuation = continuationCheckpointRefOf(message);
|
|
454
|
-
if (continuation === undefined)
|
|
455
|
-
return next;
|
|
456
|
-
const checkpointRef = continuation;
|
|
457
|
-
const existing = next.continuations.find(entry => entry.checkpointRef === checkpointRef);
|
|
458
|
-
if (existing !== undefined) {
|
|
459
|
-
if (existing.deliveredSeq !== -1)
|
|
460
|
-
return next;
|
|
461
|
-
return { ...next, continuations: next.continuations.map(entry => entry === existing ? { ...entry, deliveredSeq: seq } : entry) };
|
|
462
|
-
}
|
|
463
|
-
return { ...next, continuations: [...next.continuations, { checkpointRef, deliveredSeq: seq }] };
|
|
464
|
-
}
|
|
465
|
-
/** A completed, uninterrupted assistant turn answers every candidate surfaced into it. */
|
|
466
|
-
function applyAssistantMessage(state, event) {
|
|
467
|
-
if (state.carriedCandidates.length === 0 || event.data.interrupted === true)
|
|
468
|
-
return state;
|
|
469
|
-
let changed = false;
|
|
470
|
-
const carriedCandidates = state.carriedCandidates.map(candidate => {
|
|
471
|
-
if (candidate.consumed || candidate.surfacedTurn !== event.data.turn)
|
|
472
|
-
return candidate;
|
|
473
|
-
changed = true;
|
|
474
|
-
return { ...candidate, consumed: true };
|
|
475
|
-
});
|
|
476
|
-
return changed ? { ...state, carriedCandidates } : state;
|
|
477
|
-
}
|
|
478
|
-
/** The carried input one transition must deliver: unconsumed post-intent candidates, in queue order. */
|
|
479
|
-
export function carriedInputOf(state) {
|
|
480
|
-
return state.carriedCandidates.filter(candidate => !candidate.consumed).map(candidate => candidate.message);
|
|
481
279
|
}
|
|
482
280
|
/**
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
281
|
+
* The engine fold configured for Team: the codec Team's durable envelopes are
|
|
282
|
+
* written with, Team's projection host, and every recorded rollover name
|
|
283
|
+
* (current and legacy). One config drives both the registered unit and every
|
|
284
|
+
* cold fold, so a replayed log and the live unit converge on one state.
|
|
487
285
|
*/
|
|
488
|
-
function
|
|
489
|
-
return isAgentTeamSource(message.source) && !isAgentTeamContextSource(message);
|
|
490
|
-
}
|
|
491
|
-
/** Whether a quiet continuation for one checkpoint was already delivered in this Session. */
|
|
492
|
-
export function continuationDelivered(state, checkpointRef) {
|
|
493
|
-
return state.continuations.some(entry => entry.checkpointRef === checkpointRef && entry.deliveredSeq !== -1);
|
|
494
|
-
}
|
|
495
|
-
/** Record that a continuation for one checkpoint is scheduled (delivery not yet seen). */
|
|
496
|
-
export function withScheduledContinuation(state, checkpointRef) {
|
|
497
|
-
if (state.continuations.some(entry => entry.checkpointRef === checkpointRef))
|
|
498
|
-
return state;
|
|
499
|
-
return { ...state, continuations: [...state.continuations, { checkpointRef, deliveredSeq: -1 }] };
|
|
500
|
-
}
|
|
501
|
-
/** Whether one raw Team-claim arguments string is a mutation (not `list`). */
|
|
502
|
-
function argumentsAreClaimMutation(raw) {
|
|
503
|
-
let parsed;
|
|
504
|
-
try {
|
|
505
|
-
parsed = JSON.parse(raw);
|
|
506
|
-
}
|
|
507
|
-
catch {
|
|
508
|
-
return false;
|
|
509
|
-
}
|
|
510
|
-
if (typeof parsed !== 'object' || parsed === null)
|
|
511
|
-
return false;
|
|
512
|
-
const { action } = parsed;
|
|
513
|
-
return action === 'claim' || action === 'done' || action === 'release';
|
|
514
|
-
}
|
|
515
|
-
function parseRolloverArguments(raw) {
|
|
516
|
-
let parsed;
|
|
517
|
-
try {
|
|
518
|
-
parsed = JSON.parse(raw);
|
|
519
|
-
}
|
|
520
|
-
catch {
|
|
521
|
-
return undefined;
|
|
522
|
-
}
|
|
523
|
-
if (typeof parsed !== 'object' || parsed === null)
|
|
524
|
-
return undefined;
|
|
525
|
-
const { handoff, checkpointRef, relatedFiles } = parsed;
|
|
526
|
-
if (typeof handoff !== 'string' || handoff.trim() === '')
|
|
527
|
-
return undefined;
|
|
528
|
-
if (checkpointRef !== undefined && typeof checkpointRef !== 'string')
|
|
529
|
-
return undefined;
|
|
530
|
-
if (relatedFiles !== undefined && !isRelatedFiles(relatedFiles))
|
|
531
|
-
return undefined;
|
|
286
|
+
export function createTeamContextProjectionConfig(host) {
|
|
532
287
|
return {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
288
|
+
codec: TEAM_CONTEXT_CODEC,
|
|
289
|
+
host,
|
|
290
|
+
rolloverToolNames: TEAM_ROLLOVER_TOOL_NAMES,
|
|
291
|
+
checkpointToolName: CONTEXT_CHECKPOINT_TOOL_NAME,
|
|
536
292
|
};
|
|
537
293
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
return undefined;
|
|
545
|
-
}
|
|
546
|
-
if (typeof parsed !== 'object' || parsed === null)
|
|
547
|
-
return undefined;
|
|
548
|
-
const { name } = parsed;
|
|
549
|
-
if (typeof name !== 'string' || name.trim() === '')
|
|
550
|
-
return undefined;
|
|
551
|
-
return { name };
|
|
294
|
+
/**
|
|
295
|
+
* Cold-fold one immutable Team Session log through the engine's fold with
|
|
296
|
+
* Team's config. The registered unit uses the same config, so the two agree.
|
|
297
|
+
*/
|
|
298
|
+
export function foldTeamContextProjection(events, target, host = new TeamContextProjectionHost()) {
|
|
299
|
+
return foldEngineContextProjection(events, createTeamContextProjectionConfig(host), target);
|
|
552
300
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
301
|
+
/**
|
|
302
|
+
* The host-only projection unit for Team Members. Register it **once per
|
|
303
|
+
* Host**: the framework keeps one unit per projection key and drives it for
|
|
304
|
+
* every Session, so the definition closes over no Session identity.
|
|
305
|
+
*/
|
|
306
|
+
export function createTeamContextProjectionDefinition(host) {
|
|
307
|
+
return createContextProjectionDefinition(createTeamContextProjectionConfig(host));
|
|
556
308
|
}
|
|
557
|
-
// Re-exported for callers that only want the predicate view of continuation
|
|
558
|
-
// delivery without importing the source module's message constructors.
|
|
559
|
-
export { isCheckpointContinuationMessage };
|
|
560
309
|
/**
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
310
|
+
* The Threads Team's own fold attributed to boundaries resolved by one
|
|
311
|
+
* completed turn, order-stable and deduplicated: a delivered notice's first
|
|
312
|
+
* arrival, a claim mutation's Task→Thread binding, a committed Thread effect.
|
|
313
|
+
*
|
|
314
|
+
* This is the accumulated attribution of the RETAINED PREFIX through that
|
|
315
|
+
* turn, and it is one set with two readers — the timeline publishes it as the
|
|
316
|
+
* item's affected Threads, and the rollover guard refuses a boundary whose
|
|
317
|
+
* prefix spans more than one Thread, so a ref the timeline offers is a ref
|
|
318
|
+
* `context_rollover` accepts.
|
|
565
319
|
*/
|
|
566
|
-
export function
|
|
567
|
-
const
|
|
568
|
-
for (const checkpoint of state.checkpoints) {
|
|
569
|
-
if (checkpoint.turnEndSeq === -1)
|
|
570
|
-
continue;
|
|
571
|
-
candidates.push({ ref: checkpoint.checkpointRef, label: checkpoint.name, source: 'agent', seq: checkpoint.resultSeq, turnEndSeq: checkpoint.turnEndSeq });
|
|
572
|
-
}
|
|
320
|
+
export function retainedTopicsThrough(state, turnEndSeq) {
|
|
321
|
+
const topics = [];
|
|
573
322
|
for (const boundary of state.boundaries) {
|
|
574
|
-
if (boundary.turnEndSeq === -1)
|
|
323
|
+
if (boundary.turnEndSeq === -1 || boundary.turnEndSeq > turnEndSeq)
|
|
575
324
|
continue;
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
325
|
+
for (const topic of boundary.attributions) {
|
|
326
|
+
if (!topics.includes(topic))
|
|
327
|
+
topics.push(topic);
|
|
328
|
+
}
|
|
580
329
|
}
|
|
581
|
-
return
|
|
582
|
-
.sort((a, b) => b.turnEndSeq - a.turnEndSeq || b.seq - a.seq)
|
|
583
|
-
.slice(0, Math.max(1, Math.trunc(limit)));
|
|
330
|
+
return topics;
|
|
584
331
|
}
|
|
585
332
|
/** Find one resolved checkpoint entry by its stable ref, if it exists. */
|
|
586
333
|
export function checkpointByRef(state, checkpointRef) {
|
|
587
334
|
return state.checkpoints.find(entry => entry.checkpointRef === checkpointRef && entry.turnEndSeq !== -1);
|
|
588
335
|
}
|
|
336
|
+
/** Whether one engine-held boundary is the recorded anchor of `ref` in this state's Session. */
|
|
337
|
+
export function boundaryByRef(state, ref) {
|
|
338
|
+
return state.boundaries.find(boundary => boundaryRefFor(state.sessionId, boundary.resultSeq) === ref);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* The carried input one transition must deliver: unconsumed post-intent
|
|
342
|
+
* candidates in queue order, resolved to their durable message bodies. The
|
|
343
|
+
* engine keys carry candidates by message id (that is what the transition
|
|
344
|
+
* dedupes on); the `agent/inbox/spliced` events that recorded them are where
|
|
345
|
+
* the message itself lives, so the log stays the only authority.
|
|
346
|
+
*/
|
|
347
|
+
export function carriedInputOf(state, events) {
|
|
348
|
+
const outstanding = state.carriedCandidates.filter(candidate => !candidate.consumed);
|
|
349
|
+
if (outstanding.length === 0)
|
|
350
|
+
return [];
|
|
351
|
+
const byId = new Map();
|
|
352
|
+
for (const event of events) {
|
|
353
|
+
if (event.type !== 'agent/inbox/spliced')
|
|
354
|
+
continue;
|
|
355
|
+
for (const message of event.data.inserted)
|
|
356
|
+
byId.set(message.id, message);
|
|
357
|
+
}
|
|
358
|
+
const carried = [];
|
|
359
|
+
for (const candidate of outstanding) {
|
|
360
|
+
const message = byId.get(candidate.messageId);
|
|
361
|
+
if (message !== undefined)
|
|
362
|
+
carried.push(message);
|
|
363
|
+
}
|
|
364
|
+
return carried;
|
|
365
|
+
}
|