@wowyuarm/dsh-agent-team 0.1.9 → 0.1.11
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 +17 -11
- package/README.zh.md +17 -11
- package/package.json +61 -47
- package/packages/agent-team/README.md +6 -4
- package/packages/agent-team/README.zh.md +6 -3
- package/packages/agent-team/lib/context-management.js +48 -55
- package/packages/agent-team/lib/context-projection.js +22 -8
- package/packages/agent-team/lib/context-source.js +163 -23
- package/packages/agent-team/lib/index.js +349 -176
- package/packages/agent-team/lib/invariant.js +38 -4
- package/packages/agent-team/lib/ledger.js +658 -294
- package/packages/agent-team/lib/member-context.js +6 -2
- package/packages/agent-team/lib/member-time-context.js +141 -0
- package/packages/agent-team/lib/pressure-policy.js +34 -11
- package/packages/agent-team/lib/progress-nudge.js +12 -4
- package/packages/agent-team/lib/session-event-cursor.js +116 -0
- package/packages/agent-team/lib/session-remediation.js +493 -0
- package/packages/agent-team/lib/spec.js +49 -36
- package/packages/agent-team/lib/stored-session-reader.js +138 -0
- package/packages/agent-team/lib/time-format.js +56 -0
- package/packages/agent-team/lib/typert.host.js +163 -63
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +105 -32
- package/packages/agent-team/lib/types/context-management.d.ts +13 -1
- package/packages/agent-team/lib/types/context-management.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-projection.d.ts +8 -0
- package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-source.d.ts +70 -36
- package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +110 -9
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/invariant.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +205 -7
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-time-context.d.ts +83 -0
- package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts +18 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
- package/packages/agent-team/lib/types/progress-nudge.d.ts.map +1 -1
- package/packages/agent-team/lib/types/session-event-cursor.d.ts +119 -0
- package/packages/agent-team/lib/types/session-event-cursor.d.ts.map +1 -0
- 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/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/stored-session-reader.d.ts +92 -0
- package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -0
- package/packages/agent-team/lib/types/time-format.d.ts +28 -0
- package/packages/agent-team/lib/types/time-format.d.ts.map +1 -0
- package/packages/agent-team/lib/types/types/entities.d.ts +36 -2
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/operations.d.ts +45 -19
- package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +50 -3
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +21 -4
- package/packages/client-agent-team/README.md +1 -1
- package/packages/client-agent-team/README.zh.md +1 -1
- package/packages/client-agent-team/lib/client.js +1121 -402
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +24 -4
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +42 -22
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +13 -24
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +11 -5
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +15 -9
- 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 +8 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +21 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +71 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +8 -24
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +9 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +13 -6
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +8 -0
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +21 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +106 -32
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +50 -7
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +25 -9
- package/packages/client-agent-team/lib/types/client/locales.d.ts +18 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +18 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +10 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/navigation.js +18 -7
- package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts +41 -0
- package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/scope-coverage.js +64 -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-changes.d.ts +26 -1
- package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-changes.js +66 -15
- package/packages/client-agent-team/lib/types/client/team-dialog-save.d.ts +20 -0
- package/packages/client-agent-team/lib/types/client/team-dialog-save.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/team-dialog-save.js +36 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +9 -0
- 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 +34 -6
- package/packages/tool-agent-team/README.md +8 -6
- package/packages/tool-agent-team/README.zh.md +8 -6
- package/packages/tool-agent-team/lib/context-tools.js +18 -14
- package/packages/tool-agent-team/lib/host-access.js +20 -0
- package/packages/tool-agent-team/lib/index.js +314 -123
- package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
- package/packages/tool-agent-team/lib/types/host-access.d.ts +14 -0
- package/packages/tool-agent-team/lib/types/host-access.d.ts.map +1 -0
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { AgentTeamDmDeliveryError, markAgentTeamPreset } from '@wowyuarm/dsh-agent-team/host';
|
|
2
|
+
import { formatTeamTimestamp } from '@wowyuarm/dsh-agent-team/time-format';
|
|
2
3
|
import { registerContextTools } from "./context-tools.js";
|
|
4
|
+
import { member, service } from "./host-access.js";
|
|
3
5
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
4
6
|
export const name = 'wowyuarm-agent-team-tools';
|
|
5
7
|
export const inject = ['tools'];
|
|
6
|
-
|
|
8
|
+
/** One shared bound for every bounded anchor subject a render shows. */
|
|
9
|
+
const SUBJECT_BOUND = 80;
|
|
10
|
+
function claimView(claim) {
|
|
11
|
+
return { claimRef: claim.claimRef, direction: claim.direction, state: claim.state, owner: claim.owner };
|
|
12
|
+
}
|
|
13
|
+
function activityFactView(sequence, activity, markers, occurredAt) {
|
|
7
14
|
return {
|
|
8
15
|
sequence, kind: 'activity', activity: activity.kind, actor: activity.actor, taskRef: activity.taskRef,
|
|
16
|
+
...(occurredAt === undefined ? {} : { occurredAt }),
|
|
9
17
|
...(activity.claimRef === undefined ? {} : { claimRef: activity.claimRef }),
|
|
10
18
|
...(activity.claimRefs === undefined || activity.claimRefs.length === 0 ? {} : { claimRefs: [...activity.claimRefs] }),
|
|
11
19
|
...(activity.completedClaimRefs === undefined || activity.completedClaimRefs.length === 0 ? {} : { completedClaimRefs: [...activity.completedClaimRefs] }),
|
|
@@ -23,6 +31,26 @@ function adviceView(advice) {
|
|
|
23
31
|
action: advice.action, guidance: advice.guidance,
|
|
24
32
|
};
|
|
25
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The one deterministic bounded subject, shared by directory rows and Thread
|
|
36
|
+
* orientation: whitespace collapses to one line, then one bounded cut with an
|
|
37
|
+
* explicit truncation mark. Directory, read, and history orientation must
|
|
38
|
+
* never disagree about what a Thread is about.
|
|
39
|
+
*/
|
|
40
|
+
function boundedSubject(body) {
|
|
41
|
+
const collapsed = body.replaceAll(/\s+/gu, ' ').trim();
|
|
42
|
+
return collapsed.length <= SUBJECT_BOUND ? collapsed : `${collapsed.slice(0, SUBJECT_BOUND - 1)}…`;
|
|
43
|
+
}
|
|
44
|
+
/** Unread/direct markers sit on the fact line itself; a fact carrying neither renders neither. */
|
|
45
|
+
function factMarkers(fact) {
|
|
46
|
+
if (fact.unread === undefined)
|
|
47
|
+
return '';
|
|
48
|
+
if (fact.direct === true)
|
|
49
|
+
return ' [direct]';
|
|
50
|
+
if (fact.unread === true)
|
|
51
|
+
return ' [unread]';
|
|
52
|
+
return '';
|
|
53
|
+
}
|
|
26
54
|
/** Render one structured activity fact as a self-describing decision-surface line. */
|
|
27
55
|
function activityLine(fact) {
|
|
28
56
|
const segments = [`${fact.sequence}`, fact.actor, fact.activity];
|
|
@@ -39,24 +67,54 @@ function activityLine(fact) {
|
|
|
39
67
|
segments.push(`released claims ${fact.releasedClaimRefs.join(', ')}`);
|
|
40
68
|
return segments.join(' ');
|
|
41
69
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
throw new Error('Agent Team Host is unavailable');
|
|
46
|
-
return host;
|
|
70
|
+
/** The one hand-off line a fully drained read or a committed public mutation may render. */
|
|
71
|
+
function nextWriteLine(revision) {
|
|
72
|
+
return `Next write — baseRevision: ${revision} (copy exactly; never derive or cite).`;
|
|
47
73
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
return
|
|
74
|
+
/** Task standing inline on a Thread identity line; absent on taskless Threads. */
|
|
75
|
+
function taskStanding(value) {
|
|
76
|
+
if (value.taskRef === undefined)
|
|
77
|
+
return '';
|
|
78
|
+
return `${value.taskRef}${value.taskNumber === undefined ? '' : ` (#${value.taskNumber})`}${value.status === undefined ? '' : `, ${value.status}${value.resolution === undefined ? '' : `/${value.resolution}`}`}`;
|
|
53
79
|
}
|
|
54
80
|
function requestId(agentId, callId) {
|
|
55
81
|
return `agent-team:tool:${agentId}:${callId}`;
|
|
56
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* One typed-rejection renderer parameterized by the mutation noun, shared by
|
|
85
|
+
* team_message and team_claim. Outcome first, action-local recovery facts,
|
|
86
|
+
* then the single recovery route: read the named Thread and reconsider — a
|
|
87
|
+
* rejection carries no changed facts, so it never renders a numeric revision
|
|
88
|
+
* or a next-write token.
|
|
89
|
+
*/
|
|
90
|
+
function rejectionLines(noun, value) {
|
|
91
|
+
const identity = [
|
|
92
|
+
value.threadRef === undefined ? '' : value.threadRef,
|
|
93
|
+
value.taskRef === undefined ? '' : value.taskRef,
|
|
94
|
+
].filter(part => part !== '').join(' · ');
|
|
95
|
+
if (value.kind === 'unread_required') {
|
|
96
|
+
return [
|
|
97
|
+
'Not committed — unread_required.',
|
|
98
|
+
`${identity}${identity === '' ? '' : ' · '}${value.unreadCount ?? 0} unread, ${value.directCount ?? 0} direct`,
|
|
99
|
+
`Read the pending updates with team_thread read before reconsidering this ${noun}.`,
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
if (value.kind === 'stale_revision') {
|
|
103
|
+
return [
|
|
104
|
+
'Not committed — the Thread changed after your last read (stale_revision).',
|
|
105
|
+
identity,
|
|
106
|
+
`Read the Thread, reconsider the new facts, then retry this ${noun} with the token that read returns.`,
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
return [
|
|
110
|
+
'Not committed — member_not_following.',
|
|
111
|
+
`${(value.memberIds ?? []).join(', ')} not following${value.threadRef === undefined ? '' : ` ${value.threadRef}`}${value.taskRef === undefined ? '' : ` (${value.taskRef})`}; no message was added.`,
|
|
112
|
+
'Only a Human can invite an unfollowed Agent — retry without mentioning them, or ask the Human.',
|
|
113
|
+
];
|
|
114
|
+
}
|
|
57
115
|
const teamInbox = defineTool({
|
|
58
116
|
name: 'team_inbox',
|
|
59
|
-
description: 'List your bounded Team Inbox
|
|
117
|
+
description: 'List your bounded Team Inbox for triage: unread Thread summaries with counts, without message bodies and without marking anything read. Read a selected Thread with team_thread read; the inbox itself authorizes no mutation.',
|
|
60
118
|
parameters: { limit: { type: 'number' } },
|
|
61
119
|
output: {
|
|
62
120
|
schema: { type: 'object', additionalProperties: false, properties: {
|
|
@@ -64,12 +122,27 @@ const teamInbox = defineTool({
|
|
|
64
122
|
items: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: {
|
|
65
123
|
threadRef: { type: 'string', required: true }, channelRef: { type: 'string', required: true },
|
|
66
124
|
taskRef: { type: 'string' }, status: { type: 'string' }, revision: { type: 'number', required: true }, unreadCount: { type: 'number', required: true }, directCount: { type: 'number', required: true },
|
|
67
|
-
taskNumber: { type: 'number' },
|
|
125
|
+
taskNumber: { type: 'number' }, newestOccurredAt: { type: 'string' },
|
|
68
126
|
} } },
|
|
69
127
|
} },
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
128
|
+
// Triage surface only: totals plus both counters per row (direct renders
|
|
129
|
+
// even when zero), a bounded-list conclusion so truncation can never read
|
|
130
|
+
// as drained, and one route to the tool that can actually acknowledge the
|
|
131
|
+
// work. No subject, no body, no revision label, no write token: the inbox
|
|
132
|
+
// routes to a required current read, which supplies the write basis.
|
|
133
|
+
render: (_args, value) => {
|
|
134
|
+
if (value.items.length === 0) {
|
|
135
|
+
return [{ type: 'text', text: value.totalUnreadCount > 0
|
|
136
|
+
? `Inbox — ${value.totalUnreadCount} unread update(s) on Threads beyond this bounded list — call again with a larger limit.`
|
|
137
|
+
: 'Inbox empty — no unread Team work.' }];
|
|
138
|
+
}
|
|
139
|
+
const shown = value.items.reduce((sum, item) => sum + item.unreadCount, 0);
|
|
140
|
+
return [{ type: 'text', text: [
|
|
141
|
+
`Inbox — ${value.totalUnreadCount} unread update(s) total, ${value.totalDirectCount} direct, across ${value.items.length} Thread(s) shown${value.totalUnreadCount > shown ? `; ${value.totalUnreadCount - shown} more on Threads beyond this bounded list — call again with a larger limit.` : '.'}`,
|
|
142
|
+
...value.items.map(item => `${item.threadRef}${item.channelRef === undefined ? '' : ` · ${item.channelRef}`}${item.taskRef === undefined ? '' : ` · ${taskStanding(item)}`} · ${item.unreadCount} unread, ${item.directCount} direct${item.newestOccurredAt === undefined ? '' : ` · newest ${formatTeamTimestamp(item.newestOccurredAt)}`}`),
|
|
143
|
+
'Read a selected Thread with team_thread read. Listing changes no read state and supplies no write token.',
|
|
144
|
+
].join('\n') }];
|
|
145
|
+
},
|
|
73
146
|
},
|
|
74
147
|
async execute(args, exec) {
|
|
75
148
|
const agent = exec.agent;
|
|
@@ -78,7 +151,7 @@ const teamInbox = defineTool({
|
|
|
78
151
|
const current = member(agent);
|
|
79
152
|
const host = service(agent);
|
|
80
153
|
const inbox = host.inboxForAgent(agent, { workspaceId: current.workspaceId, ...(args.limit === undefined ? {} : { limit: args.limit }) });
|
|
81
|
-
const taskNumbers = new Map(host.viewForAgent(agent, { workspaceId: current.workspaceId, topLevelOnly: true, includeActivities: false })
|
|
154
|
+
const taskNumbers = new Map(host.viewForAgent(agent, { workspaceId: current.workspaceId, topLevelOnly: true, includeActivities: false, direction: 'before' })
|
|
82
155
|
.taskNumbers.map(entry => [entry.taskRef, entry.taskNumber]));
|
|
83
156
|
return {
|
|
84
157
|
totalUnreadCount: inbox.totalUnreadCount, totalDirectCount: inbox.totalDirectCount,
|
|
@@ -86,7 +159,7 @@ const teamInbox = defineTool({
|
|
|
86
159
|
const taskNumber = item.task === undefined ? undefined : taskNumbers.get(item.task.taskRef);
|
|
87
160
|
return { threadRef: item.thread.threadRef, channelRef: item.channelRef,
|
|
88
161
|
...(item.task === undefined ? {} : { taskRef: item.task.taskRef, status: item.task.status }),
|
|
89
|
-
revision: item.thread.revision, unreadCount: item.unreadCount, directCount: item.directCount,
|
|
162
|
+
revision: item.thread.revision, unreadCount: item.unreadCount, directCount: item.directCount, newestOccurredAt: item.newestOccurredAt,
|
|
90
163
|
...(taskNumber === undefined ? {} : { taskNumber }) };
|
|
91
164
|
}),
|
|
92
165
|
};
|
|
@@ -94,7 +167,7 @@ const teamInbox = defineTool({
|
|
|
94
167
|
});
|
|
95
168
|
const teamThread = defineTool({
|
|
96
169
|
name: 'team_thread',
|
|
97
|
-
description: 'Read or manage your Attention on one Thread. read acknowledges one chronological batch; history
|
|
170
|
+
description: 'Read or manage your Attention on one Thread. read acknowledges one chronological batch of unread facts and is the only read-side source of a next-write token — and only once no unread remains; your own committed public mutations hand off the token as well. history pages older facts without changing read state; status, follow, and unfollow change or report Attention only and render no Thread timeline. Prefer threadRef; taskRef is a compatibility alias when the Thread has a Task.',
|
|
98
171
|
parameters: {
|
|
99
172
|
action: { type: 'string', required: true, enum: ['status', 'follow', 'unfollow', 'read', 'history'] },
|
|
100
173
|
threadRef: { type: 'string', description: "Full branded Thread ref exactly as returned by Team tools, including the 'thread:' prefix. An unambiguous abbreviation of the first 6+ UUID hex characters also resolves." },
|
|
@@ -104,63 +177,96 @@ const teamThread = defineTool({
|
|
|
104
177
|
output: {
|
|
105
178
|
schema: { type: 'object', additionalProperties: false, properties: {
|
|
106
179
|
kind: { type: 'string', required: true }, threadRef: { type: 'string', required: true }, taskRef: { type: 'string' },
|
|
107
|
-
revision: { type: 'number', required: true }, status: { type: 'string' }, resolution: { type: 'string' },
|
|
180
|
+
revision: { type: 'number', required: true }, status: { type: 'string' }, resolution: { type: 'string' }, taskNumber: { type: 'number' },
|
|
108
181
|
following: { type: 'boolean', required: true }, readThroughSequence: { type: 'number' }, remainingUnreadCount: { type: 'number' }, cursor: { type: 'number' }, hasMore: { type: 'boolean' },
|
|
109
182
|
anchor: { type: 'object', required: true, additionalProperties: false, properties: {
|
|
110
|
-
messageRef: { type: 'string', required: true }, sender: { type: 'string', required: true }, body: { type: 'string', required: true }, sequence: { type: 'number', required: true },
|
|
183
|
+
messageRef: { type: 'string', required: true }, sender: { type: 'string', required: true }, body: { type: 'string', required: true }, sequence: { type: 'number', required: true }, occurredAt: { type: 'string' },
|
|
111
184
|
} },
|
|
112
185
|
claims: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: {
|
|
113
186
|
claimRef: { type: 'string', required: true }, direction: { type: 'string', required: true }, state: { type: 'string', required: true }, owner: { type: 'string', required: true },
|
|
114
187
|
} } },
|
|
115
188
|
facts: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: {
|
|
116
|
-
sequence: { type: 'number', required: true }, kind: { type: 'string', required: true }, body: { type: 'string' }, sender: { type: 'string' }, mentions: { type: 'array', items: { type: 'string' } }, activity: { type: 'string' }, actor: { type: 'string' }, taskRef: { type: 'string' }, claimRef: { type: 'string' }, claimRefs: { type: 'array', items: { type: 'string' } }, completedClaimRefs: { type: 'array', items: { type: 'string' } }, acceptedClaimRefs: { type: 'array', items: { type: 'string' } }, releasedClaimRefs: { type: 'array', items: { type: 'string' } }, unread: { type: 'boolean' }, direct: { type: 'boolean' },
|
|
189
|
+
sequence: { type: 'number', required: true }, kind: { type: 'string', required: true }, body: { type: 'string' }, sender: { type: 'string' }, occurredAt: { type: 'string' }, mentions: { type: 'array', items: { type: 'string' } }, activity: { type: 'string' }, actor: { type: 'string' }, taskRef: { type: 'string' }, claimRef: { type: 'string' }, claimRefs: { type: 'array', items: { type: 'string' } }, completedClaimRefs: { type: 'array', items: { type: 'string' } }, acceptedClaimRefs: { type: 'array', items: { type: 'string' } }, releasedClaimRefs: { type: 'array', items: { type: 'string' } }, unread: { type: 'boolean' }, direct: { type: 'boolean' },
|
|
117
190
|
} } },
|
|
118
191
|
contextAdvice: { type: 'object', additionalProperties: false, properties: {
|
|
119
192
|
usageTokens: { type: 'number' }, taskBoundaryThreshold: { type: 'number' }, handoffAt: { type: 'number' }, hardLimit: { type: 'number' },
|
|
120
193
|
action: { type: 'string', required: true }, guidance: { type: 'string', required: true },
|
|
121
194
|
} },
|
|
122
195
|
} },
|
|
123
|
-
// Renders are the only channel a tool result reaches the model through
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// when the Thread is taskful. An empty-facts status/follow result
|
|
134
|
-
// still carries the same identifying surface.
|
|
135
|
-
const header = [
|
|
136
|
-
value.threadRef,
|
|
137
|
-
value.taskRef === undefined ? '' : `${value.taskRef} · ${value.status}${value.resolution === undefined ? '' : `/${value.resolution}`}`,
|
|
138
|
-
`revision ${value.revision}, following=${value.following}`,
|
|
139
|
-
].filter(part => part !== '').join(' · ');
|
|
140
|
-
const lines = [header];
|
|
141
|
-
// The Claims snapshot is the collision surface: another Member's
|
|
142
|
-
// active Claim on this Task is invisible while facts alone render,
|
|
143
|
-
// yet exactly what the model must see before claiming its own angle.
|
|
144
|
-
for (const claim of value.claims)
|
|
145
|
-
lines.push(`Claim ${claim.claimRef} · ${claim.state} — ${claim.owner}: ${claim.direction}`);
|
|
146
|
-
// The anchor is the Thread's root task statement. Render it whenever
|
|
147
|
-
// it is not already among the facts, so a model reading a Thread for
|
|
148
|
-
// the first time never loses the original ask.
|
|
149
|
-
if (!value.facts.some(fact => fact.sequence === value.anchor.sequence))
|
|
150
|
-
lines.push(`Anchor ${value.anchor.sequence} [${value.anchor.sender}] ${value.anchor.body}`);
|
|
151
|
-
for (const fact of value.facts) {
|
|
152
|
-
lines.push(fact.kind === 'message'
|
|
153
|
-
? `${fact.sequence} [${fact.sender ?? 'unknown sender'}]${fact.unread === undefined ? '' : fact.direct === true ? ' [direct]' : fact.unread === true ? ' [unread]' : ''} ${fact.body}`
|
|
154
|
-
: activityLine(fact));
|
|
155
|
-
if (fact.kind !== 'message' && fact.unread === true)
|
|
156
|
-
lines.push(`${fact.sequence} … (unread activity)`);
|
|
196
|
+
// Renders are the only channel a tool result reaches the model through.
|
|
197
|
+
// status/follow/unfollow answer only the Attention question. read renders
|
|
198
|
+
// outcome → context → orientation → active collision surface → facts →
|
|
199
|
+
// watermark/advice, and hands off the opaque next-write token only when
|
|
200
|
+
// no unread remains. history renders deep orientation and never the
|
|
201
|
+
// current collision surface or any write token.
|
|
202
|
+
render: (args, value) => {
|
|
203
|
+
const standing = taskStanding(value);
|
|
204
|
+
if (value.kind === 'status') {
|
|
205
|
+
return [{ type: 'text', text: `Attention status — ${value.following ? 'following' : 'not following'} ${value.threadRef}${standing === '' ? '' : ` · ${standing}`}.` }];
|
|
157
206
|
}
|
|
158
|
-
if (value.kind === '
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
lines
|
|
207
|
+
if (value.kind === 'follow' || value.kind === 'unfollow') {
|
|
208
|
+
return [{ type: 'text', text: `Attention changed — ${value.following ? 'now following' : 'no longer following'} ${value.threadRef}${standing === '' ? '' : ` · ${standing}`}.` }];
|
|
209
|
+
}
|
|
210
|
+
if (value.kind === 'history') {
|
|
211
|
+
const facts = value.facts;
|
|
212
|
+
const lines = [`History for ${value.threadRef}${standing === '' ? '' : ` · ${standing}`}`];
|
|
213
|
+
// First page (no beforeSequence supplied) orients on the full
|
|
214
|
+
// anchor; a continuation orients on the shared bounded subject. An
|
|
215
|
+
// anchor already selected as a fact never repeats.
|
|
216
|
+
if (!facts.some(fact => fact.sequence === value.anchor.sequence)) {
|
|
217
|
+
if (args.beforeSequence === undefined)
|
|
218
|
+
lines.push('', `Anchor ${value.anchor.sequence}${value.anchor.occurredAt === undefined ? '' : ` ${formatTeamTimestamp(value.anchor.occurredAt)}`} [${value.anchor.sender}]`, value.anchor.body);
|
|
219
|
+
else
|
|
220
|
+
lines.push(` — ${boundedSubject(value.anchor.body)}`);
|
|
221
|
+
}
|
|
222
|
+
lines.push('', 'Facts');
|
|
223
|
+
if (facts.length === 0)
|
|
224
|
+
lines.push('No facts before this cursor.');
|
|
225
|
+
else
|
|
226
|
+
lines.push(...facts.map(fact => factLine(fact)));
|
|
227
|
+
lines.push('', `History cursor ${value.cursor}; hasMore=${value.hasMore ? 'true' : 'false'}${value.hasMore ? ' — older facts exist; page again with beforeSequence set to the cursor.' : ' — no older facts remain.'}`);
|
|
228
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
229
|
+
}
|
|
230
|
+
// kind === 'read': outcome → context → orientation → collision
|
|
231
|
+
// surface → facts → watermark → token (iff nothing unread remains).
|
|
232
|
+
const facts = value.facts;
|
|
233
|
+
const acknowledged = facts.filter(fact => fact.unread === true).length;
|
|
234
|
+
const remaining = value.remainingUnreadCount ?? 0;
|
|
235
|
+
// A read with nothing unread writes no durable operation, so the opening
|
|
236
|
+
// line claims a commit only when unread facts were actually acknowledged.
|
|
237
|
+
const lines = [
|
|
238
|
+
acknowledged === 0
|
|
239
|
+
? `Read — no unread updates on ${value.threadRef}; nothing remains.`
|
|
240
|
+
: `Read committed — acknowledged ${acknowledged} unread update(s) on ${value.threadRef}; ${remaining} remain.`,
|
|
241
|
+
`${value.threadRef}${standing === '' ? '' : ` · ${standing}`} · ${value.following ? 'following' : 'not following'}`,
|
|
242
|
+
];
|
|
243
|
+
// Orientation: A the anchor is one of the returned facts (renders as
|
|
244
|
+
// that fact once, no separate anchor); B any Host-supplied background
|
|
245
|
+
// fact carries positive `unread === false`, so the full anchor renders
|
|
246
|
+
// before the facts; C otherwise (continuation or unfollowed ad-hoc
|
|
247
|
+
// read) the shared bounded subject — never the full anchor.
|
|
248
|
+
const anchorInFacts = facts.some(fact => fact.sequence === value.anchor.sequence);
|
|
249
|
+
const hasBackground = facts.some(fact => fact.unread === false);
|
|
250
|
+
if (!anchorInFacts && hasBackground)
|
|
251
|
+
lines.push('', `Anchor ${value.anchor.sequence}${value.anchor.occurredAt === undefined ? '' : ` ${formatTeamTimestamp(value.anchor.occurredAt)}`} [${value.anchor.sender}]`, value.anchor.body);
|
|
252
|
+
if (!anchorInFacts && !hasBackground)
|
|
253
|
+
lines.push(` — ${boundedSubject(value.anchor.body)}`);
|
|
254
|
+
const activeClaims = value.claims.filter(claim => claim.state === 'active');
|
|
255
|
+
if (activeClaims.length > 0) {
|
|
256
|
+
lines.push('', 'Active Claims');
|
|
257
|
+
for (const claim of activeClaims)
|
|
258
|
+
lines.push(claimLine(claim));
|
|
259
|
+
}
|
|
260
|
+
lines.push('', 'Facts');
|
|
261
|
+
if (facts.length === 0)
|
|
262
|
+
lines.push('No new facts to acknowledge.');
|
|
263
|
+
else
|
|
264
|
+
lines.push(...facts.map(fact => factLine(fact)));
|
|
265
|
+
lines.push('', `Read through sequence ${value.readThroughSequence}; ${remaining} unread update(s) remaining${remaining > 0 ? ' — call team_thread read again.' : '.'}`);
|
|
266
|
+
if (value.contextAdvice !== undefined)
|
|
267
|
+
lines.push('', ...adviceLines(value.contextAdvice));
|
|
268
|
+
if (remaining === 0)
|
|
269
|
+
lines.push('', nextWriteLine(value.revision));
|
|
164
270
|
return [{ type: 'text', text: lines.join('\n') }];
|
|
165
271
|
},
|
|
166
272
|
},
|
|
@@ -173,44 +279,62 @@ const teamThread = defineTool({
|
|
|
173
279
|
if (args.threadRef === undefined && args.taskRef === undefined)
|
|
174
280
|
throw new Error('team_thread requires threadRef');
|
|
175
281
|
const base = { workspaceId: current.workspaceId, ...(args.threadRef === undefined ? {} : { threadRef: args.threadRef }), ...(args.taskRef === undefined ? {} : { taskRef: args.taskRef }) };
|
|
282
|
+
const taskNumberOf = (task) => {
|
|
283
|
+
if (task === undefined)
|
|
284
|
+
return {};
|
|
285
|
+
const resolved = host.resolveTaskRefs({ workspaceId: current.workspaceId, taskRefs: [task.taskRef] }).resolved[0];
|
|
286
|
+
return resolved === undefined ? {} : { taskNumber: resolved.taskNumber };
|
|
287
|
+
};
|
|
176
288
|
if (args.action === 'status') {
|
|
177
289
|
if (args.beforeSequence !== undefined || args.limit !== undefined)
|
|
178
290
|
throw new Error('status does not accept history arguments');
|
|
179
291
|
const status = host.attentionStatusForAgent(agent, base);
|
|
180
292
|
const snapshot = host.threadHistoryForAgent(agent, { ...base, beforeSequence: 1, limit: 1 });
|
|
181
|
-
return threadResult('status', snapshot, status.attention, []);
|
|
293
|
+
return threadResult('status', snapshot, status.attention, [], taskNumberOf(snapshot.task));
|
|
182
294
|
}
|
|
183
295
|
if (args.action === 'follow' || args.action === 'unfollow') {
|
|
184
296
|
if (args.beforeSequence !== undefined || args.limit !== undefined)
|
|
185
297
|
throw new Error(`${args.action} does not accept history arguments`);
|
|
186
298
|
const result = await host.changeAttentionForAgent(agent, { requestId: requestId(agent.id, exec.callId), ...base, action: args.action });
|
|
187
299
|
const snapshot = host.threadHistoryForAgent(agent, { ...base, beforeSequence: 1, limit: 1 });
|
|
188
|
-
return threadResult(args.action, snapshot, result.attention, []);
|
|
300
|
+
return threadResult(args.action, snapshot, result.attention, [], taskNumberOf(snapshot.task));
|
|
189
301
|
}
|
|
190
302
|
if (args.action === 'history') {
|
|
191
303
|
const history = host.threadHistoryForAgent(agent, { ...base, ...(args.beforeSequence === undefined ? {} : { beforeSequence: args.beforeSequence }), ...(args.limit === undefined ? {} : { limit: args.limit }) });
|
|
192
304
|
const status = host.attentionStatusForAgent(agent, base);
|
|
193
305
|
return threadResult('history', history, status.attention, history.facts.map(fact => fact.kind === 'message'
|
|
194
|
-
? { sequence: fact.sequence, kind: 'message', body: fact.message.body, sender: fact.message.sender, mentions: [...fact.mentions] }
|
|
195
|
-
: activityFactView(fact.sequence, fact.activity)), { cursor: history.cursor, hasMore: history.hasMore });
|
|
306
|
+
? { sequence: fact.sequence, kind: 'message', body: fact.message.body, sender: fact.message.sender, mentions: [...fact.mentions], occurredAt: fact.occurredAt }
|
|
307
|
+
: activityFactView(fact.sequence, fact.activity, undefined, fact.occurredAt)), { cursor: history.cursor, hasMore: history.hasMore, ...taskNumberOf(history.task) });
|
|
196
308
|
}
|
|
197
309
|
if (args.beforeSequence !== undefined || args.limit !== undefined)
|
|
198
310
|
throw new Error('read does not accept history arguments');
|
|
199
311
|
const read = await host.readThreadForAgent(agent, { requestId: requestId(agent.id, exec.callId), ...base });
|
|
200
312
|
return threadResult('read', read, read.attention, read.facts.map(entry => entry.fact.kind === 'message'
|
|
201
|
-
? { sequence: entry.fact.sequence, kind: 'message', body: entry.fact.message.body, sender: entry.fact.message.sender, mentions: [...entry.fact.mentions], unread: entry.unread, direct: entry.direct }
|
|
202
|
-
: activityFactView(entry.fact.sequence, entry.fact.activity, { unread: entry.unread, direct: entry.direct })), { readThroughSequence: read.readThroughSequence, remainingUnreadCount: read.remainingUnreadCount, ...(read.contextAdvice === undefined ? {} : { contextAdvice: adviceView(read.contextAdvice) }) });
|
|
313
|
+
? { sequence: entry.fact.sequence, kind: 'message', body: entry.fact.message.body, sender: entry.fact.message.sender, mentions: [...entry.fact.mentions], unread: entry.unread, direct: entry.direct, occurredAt: entry.fact.occurredAt }
|
|
314
|
+
: activityFactView(entry.fact.sequence, entry.fact.activity, { unread: entry.unread, direct: entry.direct }, entry.fact.occurredAt)), { readThroughSequence: read.readThroughSequence, remainingUnreadCount: read.remainingUnreadCount, ...(read.contextAdvice === undefined ? {} : { contextAdvice: adviceView(read.contextAdvice) }), ...taskNumberOf(read.task) });
|
|
203
315
|
},
|
|
204
316
|
});
|
|
317
|
+
/** One rendered fact line; markers are inline, never a separate ellipsis line. */
|
|
318
|
+
function factLine(fact) {
|
|
319
|
+
const at = fact.occurredAt === undefined ? '' : ` ${formatTeamTimestamp(fact.occurredAt)}`;
|
|
320
|
+
return fact.kind === 'message'
|
|
321
|
+
? `${fact.sequence}${at} [${fact.sender ?? 'unknown sender'}]${factMarkers(fact)} ${fact.body}`
|
|
322
|
+
: `${activityLine(fact)}${at}${factMarkers(fact)}`;
|
|
323
|
+
}
|
|
324
|
+
/** One collision-surface Claim line, shared by read and history-free listing. */
|
|
325
|
+
function claimLine(claim) {
|
|
326
|
+
return `${claim.claimRef} — ${claim.owner}: ${claim.direction}`;
|
|
327
|
+
}
|
|
205
328
|
function threadResult(kind, snapshot, attention, facts, extra = {}) {
|
|
206
329
|
return {
|
|
207
330
|
kind, threadRef: snapshot.thread.threadRef, revision: snapshot.thread.revision,
|
|
208
331
|
...(snapshot.task === undefined ? {} : { taskRef: snapshot.task.taskRef, status: snapshot.task.status, resolution: snapshot.task.resolution }),
|
|
332
|
+
...(extra.taskNumber === undefined ? {} : { taskNumber: extra.taskNumber }),
|
|
209
333
|
following: attention !== undefined,
|
|
210
334
|
...extra,
|
|
211
335
|
...(attention === undefined || extra.readThroughSequence !== undefined ? {} : { readThroughSequence: attention.readThroughSequence }),
|
|
212
|
-
anchor: { messageRef: snapshot.anchor.messageRef, sender: snapshot.anchor.sender, body: snapshot.anchor.body, sequence: snapshot.anchor.sequence },
|
|
213
|
-
claims: snapshot.claims.map(
|
|
336
|
+
anchor: { messageRef: snapshot.anchor.messageRef, sender: snapshot.anchor.sender, body: snapshot.anchor.body, sequence: snapshot.anchor.sequence, occurredAt: snapshot.anchor.occurredAt },
|
|
337
|
+
claims: snapshot.claims.map(claimView),
|
|
214
338
|
facts,
|
|
215
339
|
};
|
|
216
340
|
}
|
|
@@ -224,7 +348,7 @@ function adviceLines(advice) {
|
|
|
224
348
|
}
|
|
225
349
|
const teamMessage = markAgentTeamPreset(defineTool({
|
|
226
350
|
name: 'team_message',
|
|
227
|
-
description: 'Start a top-level Thread, reply to an existing Thread, or send a direct message (DM).
|
|
351
|
+
description: 'Start a top-level Thread, reply to an existing Thread, or send a direct message (DM). Read the Thread first: a reply needs the current next-write token from a fully drained team_thread read (or your own last committed mutation), and unread work rejects before staleness is even checked. start defaults to a taskless Thread; pass asTask true to create a Task in the same send. A top-level start may mention related Agents directly; in replies, only a Human can invite an unfollowed Agent. Pass Member refs in mentions and spell their handles inside the body; only mentioned Members render as mention chips. dm sends a private direct message to one enabled Agent Member in your Workspace: use it for quick clarifications and status syncs — never for task work, decisions, or anything that needs team visibility or traceability (use a Thread); if a DM exchange with the same Member exceeds about 3 exchanges, move it to a Thread, because every DM costs the recipient a full agent turn.',
|
|
228
352
|
parameters: {
|
|
229
353
|
action: { type: 'string', required: true, enum: ['start', 'reply', 'dm'] },
|
|
230
354
|
channelRef: { type: 'string', description: "Full branded Channel ref exactly as returned by Team tools, including the 'channel:' prefix. An unambiguous abbreviation of the first 6+ UUID hex characters also resolves." },
|
|
@@ -232,22 +356,36 @@ const teamMessage = markAgentTeamPreset(defineTool({
|
|
|
232
356
|
taskRef: { type: 'string', description: "Optional Task ref alias for reply on a Taskful Thread. Prefer threadRef; an unambiguous abbreviation of the first 6+ UUID hex characters also resolves." },
|
|
233
357
|
memberRef: { type: 'string', description: "Full branded Member ref exactly as returned by Team tools, including the 'member:' prefix. An unambiguous abbreviation of the first 6+ UUID hex characters also resolves. Required for dm; the Member must be an enabled Agent in your Workspace (the Human cannot be DMed)." },
|
|
234
358
|
asTask: { type: 'boolean', description: 'When true, start creates a Task with the Thread. Default false creates a taskless Thread.' },
|
|
235
|
-
body: { type: 'string', required: true, description: "Markdown body. Cite Team refs exactly as returned, as bare text with one colon (e.g. task:0f0a…) — never a double colon, never inside backticks or quotes. Unambiguous UUID abbreviations (first 6+ hex chars) also resolve. Spell each mentioned Member's handle in the prose so the mention renders inline." }, baseRevision: { type: 'number', description: "
|
|
359
|
+
body: { type: 'string', required: true, description: "Markdown body. Lead with the conclusion or state. Mention the Human only when they must know or decide, with a one-to-three-sentence opening and a `Decision needed: X (default: Y)` line when a decision is owed; mechanical detail follows below. Cite Team refs exactly as returned, as bare text with one colon (e.g. task:0f0a…) — never a double colon, never inside backticks or quotes. Unambiguous UUID abbreviations (first 6+ hex chars) also resolve. Spell each mentioned Member's handle in the prose so the mention renders inline." }, baseRevision: { type: 'number', description: "The next-write token from your latest fully drained team_thread read (or your own last committed mutation) on this Thread. Copy the explicitly rendered value verbatim; never increment, derive, compare, or cite it — it is an opaque concurrency token, not a fact about the Thread." },
|
|
236
360
|
mentions: { type: 'array', items: { type: 'string' }, description: 'Member refs to mention. Mentioned Agents receive the Message directly; write their handles in the body (any casing, optional @) so the mention renders inline.' },
|
|
237
361
|
attachments: { type: 'array', items: { type: 'string' }, description: 'Absolute file paths to share, e.g. screenshots or generated artifacts; images render as thumbnails for recipients. The Host validates each path and copies the file into the attachment cache, and members also receive one cached path per attachment; if any path fails validation the whole send is rejected.' },
|
|
238
362
|
},
|
|
239
363
|
output: {
|
|
240
364
|
schema: { type: 'object', additionalProperties: false, properties: {
|
|
241
|
-
kind: { type: 'string', required: true }, taskRef: { type: 'string' }, threadRef: { type: 'string' }, revision: { type: 'number' },
|
|
365
|
+
kind: { type: 'string', required: true }, action: { type: 'string' }, taskRef: { type: 'string' }, threadRef: { type: 'string' }, revision: { type: 'number' },
|
|
242
366
|
expectedRevision: { type: 'number' }, messageRef: { type: 'string' }, memberIds: { type: 'array', items: { type: 'string' } }, unreadCount: { type: 'number' }, directCount: { type: 'number' },
|
|
243
|
-
recipientMemberId: { type: 'string' }, recipientHandle: { type: 'string' }, delivered: { type: 'boolean' }, deliveryNote: { type: 'string' },
|
|
367
|
+
recipientMemberId: { type: 'string' }, recipientHandle: { type: 'string' }, delivered: { type: 'boolean' }, deliveryNote: { type: 'string' }, occurredAt: { type: 'string' },
|
|
244
368
|
} },
|
|
245
|
-
render: (_args, value) =>
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
369
|
+
render: (_args, value) => {
|
|
370
|
+
if (value.kind === 'dm-sent') {
|
|
371
|
+
if (value.delivered === false)
|
|
372
|
+
return [{ type: 'text', text: [
|
|
373
|
+
`Recorded, not delivered — DM to @${value.recipientHandle} (${value.recipientMemberId}).`,
|
|
374
|
+
'No automatic redelivery will occur; do not blindly send a duplicate.',
|
|
375
|
+
`Reason: ${value.deliveryNote ?? 'the recipient session could not be woken'}`,
|
|
376
|
+
].join('\n') }];
|
|
377
|
+
return [{ type: 'text', text: `Delivered — DM to @${value.recipientHandle} (${value.recipientMemberId})${value.occurredAt === undefined ? '' : ` at ${formatTeamTimestamp(value.occurredAt)}`}.` }];
|
|
378
|
+
}
|
|
379
|
+
if (value.kind === 'committed' && value.messageRef !== undefined && value.threadRef !== undefined && value.revision !== undefined) {
|
|
380
|
+
return [{ type: 'text', text: [
|
|
381
|
+
value.action === 'start' ? 'Committed — Thread created.' : 'Committed — reply added.',
|
|
382
|
+
[value.messageRef, value.threadRef, ...(value.taskRef === undefined ? [] : [value.taskRef])].join(' · '),
|
|
383
|
+
...(value.occurredAt === undefined ? [] : [`Committed at ${formatTeamTimestamp(value.occurredAt)}`]),
|
|
384
|
+
nextWriteLine(value.revision),
|
|
385
|
+
].join('\n') }];
|
|
386
|
+
}
|
|
387
|
+
return [{ type: 'text', text: rejectionLines('message', value).join('\n') }];
|
|
388
|
+
},
|
|
251
389
|
// Minimal durable projection for the Host's context timeline: the
|
|
252
390
|
// structured outcome identity (never the render text). The effect-anchor
|
|
253
391
|
// fold reads `kind === 'committed'` + threadRef from the persisted
|
|
@@ -271,7 +409,7 @@ const teamMessage = markAgentTeamPreset(defineTool({
|
|
|
271
409
|
throw new Error('start requires channelRef and does not accept threadRef, taskRef, or baseRevision');
|
|
272
410
|
const result = await host.sendMessageForAgent(agent, { requestId: requestId(agent.id, exec.callId), workspaceId: current.workspaceId,
|
|
273
411
|
channelRef: args.channelRef, body: args.body, asTask: args.asTask === true, ...(mentions === undefined ? {} : { recipients: mentions }), ...paths });
|
|
274
|
-
return messageOutcome(result);
|
|
412
|
+
return messageOutcome(result, 'start');
|
|
275
413
|
}
|
|
276
414
|
if (args.action === 'dm') {
|
|
277
415
|
if (args.memberRef === undefined || args.channelRef !== undefined || args.threadRef !== undefined || args.taskRef !== undefined
|
|
@@ -281,7 +419,7 @@ const teamMessage = markAgentTeamPreset(defineTool({
|
|
|
281
419
|
try {
|
|
282
420
|
const result = await host.dmForAgent(agent, { requestId: requestId(agent.id, exec.callId), workspaceId: current.workspaceId,
|
|
283
421
|
recipientMemberId: args.memberRef, body: args.body });
|
|
284
|
-
return { kind: 'dm-sent', recipientMemberId: result.recipient.memberId, recipientHandle: result.recipient.handle, delivered: true };
|
|
422
|
+
return { kind: 'dm-sent', recipientMemberId: result.recipient.memberId, recipientHandle: result.recipient.handle, delivered: true, occurredAt: result.receipt.occurredAt };
|
|
285
423
|
}
|
|
286
424
|
catch (error) {
|
|
287
425
|
if (error instanceof AgentTeamDmDeliveryError) {
|
|
@@ -292,21 +430,21 @@ const teamMessage = markAgentTeamPreset(defineTool({
|
|
|
292
430
|
}
|
|
293
431
|
const baseRevision = args.baseRevision;
|
|
294
432
|
if ((args.threadRef === undefined && args.taskRef === undefined) || args.channelRef !== undefined || args.asTask !== undefined || typeof baseRevision !== 'number' || !Number.isSafeInteger(baseRevision) || baseRevision < 1) {
|
|
295
|
-
throw new Error(
|
|
433
|
+
throw new Error('reply requires threadRef and a positive baseRevision; drain the Thread with team_thread read and copy the token it renders, or reuse the one your own last committed mutation rendered');
|
|
296
434
|
}
|
|
297
435
|
const result = await host.replyForAgent(agent, { requestId: requestId(agent.id, exec.callId), workspaceId: current.workspaceId,
|
|
298
436
|
...(args.threadRef === undefined ? {} : { threadRef: args.threadRef }),
|
|
299
437
|
...(args.taskRef === undefined ? {} : { taskRef: args.taskRef }),
|
|
300
438
|
body: args.body, baseRevision,
|
|
301
439
|
...(mentions === undefined ? {} : { recipients: mentions }), ...paths });
|
|
302
|
-
return messageOutcome(result);
|
|
440
|
+
return messageOutcome(result, 'reply');
|
|
303
441
|
},
|
|
304
442
|
}));
|
|
305
|
-
function messageOutcome(result) {
|
|
443
|
+
function messageOutcome(result, action) {
|
|
306
444
|
if (result.kind === 'committed')
|
|
307
|
-
return { kind: result.kind, threadRef: result.thread.threadRef,
|
|
445
|
+
return { kind: result.kind, action, threadRef: result.thread.threadRef,
|
|
308
446
|
...(result.task === undefined ? {} : { taskRef: result.task.taskRef }),
|
|
309
|
-
revision: result.thread.revision, messageRef: result.message.messageRef };
|
|
447
|
+
revision: result.thread.revision, messageRef: result.message.messageRef, occurredAt: result.receipt.occurredAt };
|
|
310
448
|
if (result.kind === 'member_not_following')
|
|
311
449
|
return { kind: result.kind, memberIds: [...result.memberIds],
|
|
312
450
|
...(result.taskRef === undefined ? {} : { taskRef: result.taskRef }), ...(result.threadRef === undefined ? {} : { threadRef: result.threadRef, revision: result.revision }) };
|
|
@@ -320,28 +458,48 @@ function messageOutcome(result) {
|
|
|
320
458
|
}
|
|
321
459
|
const teamClaim = defineTool({
|
|
322
460
|
name: 'team_claim',
|
|
323
|
-
description: 'List or mutate your Direction Claims.
|
|
461
|
+
description: 'List or mutate your Direction Claims. A Claim is your one-sentence direction statement on a Task — "the angle I am taking" — so others can spot collisions and track progress: Tasks define scope (owned by Humans), Claims declare the angle (owned by you). Good direction: "Unify the four form dialogs on shared field components before wiring submits." Bad direction: a multi-paragraph plan with step order, file lists, or acceptance criteria — those belong in Thread messages, not the Claim. Mutations require the current next-write token from a fully drained team_thread read (or your own last committed mutation); list refreshes the collision surface only and authorizes no mutation.',
|
|
324
462
|
parameters: {
|
|
325
463
|
action: { type: 'string', required: true, enum: ['list', 'claim', 'done', 'release'] },
|
|
326
464
|
taskRef: { type: 'string', required: true, description: "Full branded Task ref exactly as returned by Team tools, including the 'task:' prefix. An unambiguous abbreviation of the first 6+ UUID hex characters also resolves." },
|
|
327
|
-
baseRevision: { type: 'number', description: "
|
|
465
|
+
baseRevision: { type: 'number', description: "The next-write token from your latest fully drained team_thread read (or your own last committed mutation) on this Task's Thread. Copy the explicitly rendered value verbatim; never increment, derive, compare, or cite it — it is an opaque concurrency token, not a fact about the Task." }, direction: { type: 'string' },
|
|
328
466
|
claimRef: { type: 'string', description: "Full branded Claim ref exactly as returned by team_claim, including the 'claim:' prefix. An unambiguous abbreviation of the first 6+ UUID hex characters also resolves." },
|
|
329
467
|
},
|
|
330
468
|
output: {
|
|
331
469
|
schema: { type: 'object', additionalProperties: false, properties: {
|
|
332
|
-
kind: { type: 'string', required: true }, taskRef: { type: 'string', required: true }, threadRef: { type: 'string', required: true },
|
|
470
|
+
kind: { type: 'string', required: true }, action: { type: 'string' }, taskRef: { type: 'string', required: true }, threadRef: { type: 'string', required: true },
|
|
333
471
|
revision: { type: 'number', required: true }, expectedRevision: { type: 'number' }, status: { type: 'string', required: true },
|
|
334
|
-
unreadCount: { type: 'number' }, directCount: { type: 'number' },
|
|
472
|
+
unreadCount: { type: 'number' }, directCount: { type: 'number' }, occurredAt: { type: 'string' },
|
|
473
|
+
claim: { type: 'object', additionalProperties: false, properties: {
|
|
474
|
+
claimRef: { type: 'string', required: true }, direction: { type: 'string', required: true }, state: { type: 'string', required: true }, owner: { type: 'string', required: true },
|
|
475
|
+
} },
|
|
335
476
|
claims: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: { claimRef: { type: 'string', required: true }, direction: { type: 'string', required: true }, state: { type: 'string', required: true }, owner: { type: 'string', required: true } } } },
|
|
336
477
|
} },
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
478
|
+
// A committed mutation renders the authoritative affected Claim first —
|
|
479
|
+
// the Host result carries it; the full archive is never appended. list
|
|
480
|
+
// renders the active collision surface and no write token: a current
|
|
481
|
+
// Thread read remains the required mutation basis. Rejections share the
|
|
482
|
+
// outcome-first recovery form and never a numeric revision.
|
|
483
|
+
render: (_args, value) => {
|
|
484
|
+
if (value.kind === 'listed') {
|
|
485
|
+
const active = value.claims.filter(claim => claim.state === 'active');
|
|
486
|
+
return [{ type: 'text', text: [
|
|
487
|
+
`Claims for ${value.taskRef} · ${value.threadRef} · ${value.status}`,
|
|
488
|
+
'Active Claims',
|
|
489
|
+
...(active.length === 0 ? ['No active Claims.'] : active.map(claim => claimLine(claim))),
|
|
490
|
+
].join('\n') }];
|
|
491
|
+
}
|
|
492
|
+
if (value.kind === 'committed' && value.claim !== undefined) {
|
|
493
|
+
return [{ type: 'text', text: [
|
|
494
|
+
`Committed — Claim ${value.action === 'claim' ? 'created' : value.action === 'done' ? 'completed' : 'released'}.`,
|
|
495
|
+
`${value.claim.claimRef} · ${value.claim.state} — ${value.claim.owner}: ${value.claim.direction}`,
|
|
496
|
+
`${value.threadRef} · ${value.taskRef} · ${value.status}`,
|
|
497
|
+
...(value.occurredAt === undefined ? [] : [`Committed at ${formatTeamTimestamp(value.occurredAt)}`]),
|
|
498
|
+
nextWriteLine(value.revision),
|
|
499
|
+
].join('\n') }];
|
|
500
|
+
}
|
|
501
|
+
return [{ type: 'text', text: rejectionLines('Claim mutation', value).join('\n') }];
|
|
502
|
+
},
|
|
345
503
|
},
|
|
346
504
|
async execute(args, exec) {
|
|
347
505
|
const agent = exec.agent;
|
|
@@ -355,22 +513,25 @@ const teamClaim = defineTool({
|
|
|
355
513
|
throw new Error('list accepts only taskRef');
|
|
356
514
|
const listed = host.listClaimsForAgent(agent, base);
|
|
357
515
|
return { kind: 'listed', taskRef: listed.task.taskRef, threadRef: listed.thread.threadRef, revision: listed.thread.revision, status: listed.task.status,
|
|
358
|
-
claims: listed.claims.map(
|
|
516
|
+
claims: listed.claims.map(claimView) };
|
|
359
517
|
}
|
|
360
518
|
const baseRevision = args.baseRevision;
|
|
361
519
|
if (typeof baseRevision !== 'number' || !Number.isSafeInteger(baseRevision) || baseRevision < 1)
|
|
362
|
-
throw new Error(
|
|
520
|
+
throw new Error('claim mutation requires a positive baseRevision; drain the Thread with team_thread read and copy the token it renders, or reuse the one your own last committed mutation rendered');
|
|
363
521
|
if (args.action === 'claim' && (args.direction === undefined || args.claimRef !== undefined))
|
|
364
522
|
throw new Error('claim requires direction and does not accept claimRef');
|
|
365
523
|
if ((args.action === 'done' || args.action === 'release') && (args.claimRef === undefined || args.direction !== undefined))
|
|
366
524
|
throw new Error(`${args.action} requires claimRef and does not accept direction`);
|
|
367
525
|
const result = await host.changeClaimForAgent(agent, { requestId: requestId(agent.id, exec.callId), ...base, action: args.action,
|
|
368
526
|
baseRevision, ...(args.direction === undefined ? {} : { direction: args.direction }), ...(args.claimRef === undefined ? {} : { claimRef: args.claimRef }) });
|
|
527
|
+
// Structured compatibility: every mutation outcome re-reads the real
|
|
528
|
+
// Claim archive and Task status, exactly as the parent version did —
|
|
529
|
+
// the render layer is what omits the archive, never the structured value.
|
|
369
530
|
const listed = host.listClaimsForAgent(agent, base);
|
|
370
|
-
const claims = listed.claims.map(
|
|
531
|
+
const claims = listed.claims.map(claimView);
|
|
371
532
|
if (result.kind === 'committed')
|
|
372
|
-
return { kind: result.kind, taskRef: result.task.taskRef, threadRef: result.thread.threadRef,
|
|
373
|
-
revision: result.thread.revision, status: result.task.status, claims };
|
|
533
|
+
return { kind: result.kind, action: args.action, taskRef: result.task.taskRef, threadRef: result.thread.threadRef,
|
|
534
|
+
revision: result.thread.revision, status: result.task.status, claim: claimView(result.claim), claims, occurredAt: result.receipt.occurredAt };
|
|
374
535
|
if (result.kind === 'unread_required')
|
|
375
536
|
return { kind: result.kind, taskRef: listed.task.taskRef, threadRef: result.threadRef,
|
|
376
537
|
revision: result.revision, status: listed.task.status, unreadCount: result.unreadCount, directCount: result.directCount, claims };
|
|
@@ -380,7 +541,7 @@ const teamClaim = defineTool({
|
|
|
380
541
|
});
|
|
381
542
|
const teamView = defineTool({
|
|
382
543
|
name: 'team_view',
|
|
383
|
-
description: 'Discover authorized Team Channels, top-level Threads
|
|
544
|
+
description: 'Discover your authorized Team addresses: current Channels, a newest-first page of top-level Threads (each with its bounded anchor subject; Task standing inline on taskful rows), and current Members. This is an address book, not a work queue — unread work lives in team_inbox, and a Thread is read with team_thread read. The cursor pages Thread rows only.',
|
|
384
545
|
parameters: {
|
|
385
546
|
channelRef: { type: 'string', description: "Full branded Channel ref exactly as returned by Team tools, including the 'channel:' prefix. An unambiguous abbreviation of the first 6+ UUID hex characters also resolves." },
|
|
386
547
|
limit: { type: 'number' }, cursor: { type: 'number' },
|
|
@@ -392,23 +553,45 @@ const teamView = defineTool({
|
|
|
392
553
|
memberId: { type: 'string', required: true }, kind: { type: 'string', required: true }, handle: { type: 'string', required: true }, description: { type: 'string', required: true }, presence: { type: 'string', required: true },
|
|
393
554
|
} } },
|
|
394
555
|
threads: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: {
|
|
395
|
-
threadRef: { type: 'string', required: true }, channelRef: { type: 'string', required: true }, revision: { type: 'number', required: true }, messageCount: { type: 'number', required: true },
|
|
396
|
-
taskRef: { type: 'string' }, status: { type: 'string' }, taskNumber: { type: 'number' },
|
|
556
|
+
threadRef: { type: 'string', required: true }, channelRef: { type: 'string', required: true }, revision: { type: 'number', required: true }, messageCount: { type: 'number', required: true }, subject: { type: 'string', required: true },
|
|
557
|
+
taskRef: { type: 'string' }, status: { type: 'string' }, taskNumber: { type: 'number' }, lastActivityAt: { type: 'string' },
|
|
397
558
|
} } },
|
|
398
|
-
tasks: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: { taskRef: { type: 'string', required: true }, threadRef: { type: 'string', required: true }, channelRef: { type: 'string', required: true }, status: { type: 'string', required: true }, revision: { type: 'number'
|
|
399
|
-
cursor: { type: 'number', required: true }, hasMore: { type: 'boolean', required: true },
|
|
559
|
+
tasks: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: { taskRef: { type: 'string', required: true }, threadRef: { type: 'string', required: true }, channelRef: { type: 'string', required: true }, status: { type: 'string', required: true }, revision: { type: 'number' } } } },
|
|
560
|
+
cursor: { type: 'number', required: true }, hasMore: { type: 'boolean', required: true }, page: { type: 'string' },
|
|
400
561
|
} },
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
562
|
+
// Address book, newest Thread first: labelled sections, one bounded
|
|
563
|
+
// anchor subject per Thread row, Task standing inline on its Thread (no
|
|
564
|
+
// second Task index), and a footer that calls the value a Thread cursor.
|
|
565
|
+
// Continuation pages repeat only Threads — Channels and Members are
|
|
566
|
+
// current address context, not members of the Thread page. No message
|
|
567
|
+
// count and no revision label: neither changes the next legal action,
|
|
568
|
+
// which is reading, never writing from a directory snapshot.
|
|
569
|
+
render: (_args, value) => {
|
|
570
|
+
const continuation = value.page === 'threads';
|
|
571
|
+
const lines = [];
|
|
572
|
+
if (!continuation) {
|
|
573
|
+
lines.push('Team directory', '', 'Channels — current');
|
|
574
|
+
if (value.channels.length === 0)
|
|
575
|
+
lines.push('No authorized Channels.');
|
|
576
|
+
else
|
|
577
|
+
lines.push(...value.channels.map(channel => `${channel.channelRef} · ${channel.name}`));
|
|
578
|
+
lines.push('');
|
|
579
|
+
}
|
|
580
|
+
lines.push('Threads');
|
|
581
|
+
if (value.threads.length === 0)
|
|
582
|
+
lines.push(`No top-level Threads${!continuation && value.channels.length === 1 ? ` in ${value.channels[0].channelRef}` : ''} at this cursor.`);
|
|
583
|
+
else
|
|
584
|
+
lines.push(...value.threads.map(thread => `${thread.threadRef} · ${thread.channelRef}${thread.taskRef === undefined ? ' · taskless' : ` · ${taskStanding(thread)}`} — ${thread.subject}${thread.lastActivityAt === undefined ? '' : ` · last activity ${formatTeamTimestamp(thread.lastActivityAt)}`}`));
|
|
585
|
+
lines.push(`Thread cursor ${value.cursor}; hasMore=${value.hasMore ? 'true' : 'false'}${value.hasMore ? ' — older Thread anchors exist; page again with this cursor.' : ' — no older Threads remain.'}`);
|
|
586
|
+
if (!continuation) {
|
|
587
|
+
lines.push('', 'Members — current');
|
|
588
|
+
if (value.members.length === 0)
|
|
589
|
+
lines.push('No visible Members.');
|
|
590
|
+
else
|
|
591
|
+
lines.push(...value.members.map(m => `${m.memberId} · @${m.handle} (${m.kind}, ${m.presence})${m.description === '' ? '' : ` — ${m.description}`}`));
|
|
592
|
+
}
|
|
593
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
594
|
+
},
|
|
412
595
|
},
|
|
413
596
|
async execute(args, exec) {
|
|
414
597
|
const agent = exec.agent;
|
|
@@ -416,7 +599,7 @@ const teamView = defineTool({
|
|
|
416
599
|
throw new Error('team_view requires an Agent session');
|
|
417
600
|
const current = member(agent);
|
|
418
601
|
const host = service(agent);
|
|
419
|
-
const view = host.viewForAgent(agent, { workspaceId: current.workspaceId, ...(args.channelRef === undefined ? {} : { channelRef: args.channelRef }), ...(args.limit === undefined ? {} : { limit: args.limit }), ...(args.cursor === undefined ? {} : { cursor: args.cursor }), topLevelOnly: true, includeActivities: false });
|
|
602
|
+
const view = host.viewForAgent(agent, { workspaceId: current.workspaceId, ...(args.channelRef === undefined ? {} : { channelRef: args.channelRef }), ...(args.limit === undefined ? {} : { limit: args.limit }), ...(args.cursor === undefined ? {} : { cursor: args.cursor }), topLevelOnly: true, includeActivities: false, direction: 'before' });
|
|
420
603
|
const visibleMemberIds = new Set(view.members.map(membership => membership.memberId));
|
|
421
604
|
return {
|
|
422
605
|
channels: view.channels.map(channel => ({ channelRef: channel.channelRef, name: channel.name })),
|
|
@@ -429,11 +612,19 @@ const teamView = defineTool({
|
|
|
429
612
|
const thread = item.thread;
|
|
430
613
|
const task = item.task;
|
|
431
614
|
return { threadRef: thread.threadRef, channelRef: item.message.channelRef, revision: thread.revision, messageCount: item.messageCount,
|
|
615
|
+
subject: boundedSubject(item.message.body), lastActivityAt: item.lastActivityAt,
|
|
432
616
|
...(task === undefined ? {} : { taskRef: task.taskRef, status: task.status, ...(item.taskNumber === undefined ? {} : { taskNumber: item.taskNumber }) }) };
|
|
433
617
|
}),
|
|
434
|
-
|
|
435
|
-
|
|
618
|
+
// A Task's revision is its Thread's; it is omitted rather than reported as
|
|
619
|
+
// `0` when the Task's Thread is outside this directory page's scope. A
|
|
620
|
+
// fabricated revision would read as "this Thread has never been written".
|
|
621
|
+
tasks: view.tasks.map(task => {
|
|
622
|
+
const revision = view.threads.find(thread => thread.threadRef === task.threadRef)?.revision;
|
|
623
|
+
return { taskRef: task.taskRef, threadRef: task.threadRef, channelRef: task.channelRef, status: task.status,
|
|
624
|
+
...(revision === undefined ? {} : { revision }) };
|
|
625
|
+
}),
|
|
436
626
|
cursor: view.cursor, hasMore: view.hasMore,
|
|
627
|
+
...(args.cursor === undefined ? {} : { page: 'threads' }),
|
|
437
628
|
};
|
|
438
629
|
},
|
|
439
630
|
});
|