@wowyuarm/dsh-agent-team 0.1.11 → 0.1.13
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 +6 -4
- package/README.zh.md +6 -4
- package/cordis.patch.yml +7 -3
- package/package.json +16 -3
- package/packages/agent-team/README.md +4 -4
- package/packages/agent-team/README.zh.md +4 -4
- package/packages/agent-team/lib/context-projection.js +5 -1
- package/packages/agent-team/lib/index.js +162 -113
- package/packages/agent-team/lib/ledger.js +496 -169
- package/packages/agent-team/lib/member-context.js +18 -1
- package/packages/agent-team/lib/member-runtime.js +48 -9
- package/packages/agent-team/lib/mentions.js +80 -0
- package/packages/agent-team/lib/spec.js +20 -0
- package/packages/agent-team/lib/typert.host.js +241 -46
- package/packages/agent-team/lib/typert.remote-client.d.ts +7 -3
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +178 -28
- package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +33 -14
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +120 -18
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-context.d.ts +6 -0
- package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-runtime.d.ts +2 -1
- package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
- package/packages/agent-team/lib/types/mentions.d.ts +47 -0
- package/packages/agent-team/lib/types/mentions.d.ts.map +1 -0
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/entities.d.ts +3 -20
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/operations.d.ts +37 -1
- package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +107 -25
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/lib/types/vendor/storage-sqlite/index.d.ts +87 -0
- package/packages/agent-team/lib/types/vendor/storage-sqlite/index.d.ts.map +1 -0
- package/packages/agent-team/lib/types/vendor/storage-sqlite/schema.d.ts +59 -0
- package/packages/agent-team/lib/types/vendor/storage-sqlite/schema.d.ts.map +1 -0
- package/packages/agent-team/lib/types/vendor/storage-sqlite/unit.d.ts +57 -0
- package/packages/agent-team/lib/types/vendor/storage-sqlite/unit.d.ts.map +1 -0
- package/packages/agent-team/lib/vendor/storage-sqlite/index.js +139 -0
- package/packages/agent-team/lib/vendor/storage-sqlite/schema.js +120 -0
- package/packages/agent-team/lib/vendor/storage-sqlite/unit.js +132 -0
- package/packages/agent-team/preset/team-member/agent.cordis.yml +48 -8
- 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 +2123 -1406
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentImport.d.ts +12 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentImport.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentImport.js +69 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +3 -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 +31 -19
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +18 -0
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +22 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +3 -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 +111 -24
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +10 -3
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +11 -17
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +1 -1
- package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts +20 -0
- package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamCountBadge.js +25 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +9 -5
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +92 -23
- package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts +43 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberRow.js +27 -0
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts +2 -2
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -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 +21 -31
- 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 +29 -11
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +11 -5
- package/packages/client-agent-team/lib/types/client/locales.d.ts +54 -16
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +54 -16
- package/packages/client-agent-team/lib/types/client/slots.d.ts +3 -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 +15 -25
- 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 +82 -80
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +45 -10
- 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 +71 -30
- package/packages/tool-agent-team/README.md +3 -3
- package/packages/tool-agent-team/README.zh.md +3 -3
- package/packages/tool-agent-team/lib/host-access.js +19 -0
- package/packages/tool-agent-team/lib/index.js +71 -49
- package/packages/tool-agent-team/lib/types/host-access.d.ts +10 -0
- package/packages/tool-agent-team/lib/types/host-access.d.ts.map +1 -1
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/progress-nudge.js +0 -328
- package/packages/agent-team/lib/types/progress-nudge.d.ts +0 -136
- package/packages/agent-team/lib/types/progress-nudge.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts +0 -3
- package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/TeamSettings.js +0 -5
- package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts +0 -41
- package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/scope-coverage.js +0 -64
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { AgentTeamChangeScope
|
|
3
|
-
/** One poll observes one scope, or every Team change when the scope is omitted. */
|
|
1
|
+
import type { ClientRemote } from '@deepseek-ai/dsh-api-gateway/client';
|
|
2
|
+
import type { AgentTeamChangeScope } from '@wowyuarm/dsh-agent-team/types';
|
|
4
3
|
export type TeamChangeScope = AgentTeamChangeScope | undefined;
|
|
5
|
-
/** One invalidation delivered to every surface subscribed to one scope. */
|
|
6
4
|
export type TeamChangeUpdate = {
|
|
7
5
|
readonly type: 'changed';
|
|
8
6
|
readonly version: number;
|
|
@@ -11,30 +9,23 @@ export type TeamChangeUpdate = {
|
|
|
11
9
|
readonly message: string;
|
|
12
10
|
};
|
|
13
11
|
export type TeamChangeListener = (update: TeamChangeUpdate) => void;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* One long-poll per change scope, shared by every listening surface: panels
|
|
17
|
-
* and pages never open parallel `changes` requests for the same scope, and
|
|
18
|
-
* the poll is aborted as soon as the last subscriber leaves.
|
|
19
|
-
*
|
|
20
|
-
* Each poll keeps its own cursor, and a cursor only means something inside the
|
|
21
|
-
* scope it was issued for: the Host answers a presence scope from a
|
|
22
|
-
* process-local wake epoch and every other scope from a durable ledger
|
|
23
|
-
* position.
|
|
24
|
-
*/
|
|
12
|
+
/** One logical stream per scope per page; Harness owns the shared transport and recovery. */
|
|
25
13
|
export declare class TeamChangeStream {
|
|
26
|
-
private readonly
|
|
27
|
-
private readonly
|
|
14
|
+
private readonly remote;
|
|
15
|
+
private readonly subscriptions;
|
|
16
|
+
constructor(remote: Pick<ClientRemote, '$stream' | 'agentTeam'>);
|
|
17
|
+
subscribe(scope: TeamChangeScope, listener: TeamChangeListener): () => void;
|
|
28
18
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* the
|
|
19
|
+
* Reopen every scope whose stream already ended for good. The Harness resumes a
|
|
20
|
+
* live generation across reconnects, but a terminated one is gone for good: a new
|
|
21
|
+
* Host generation is the moment the scope was waiting for can come back, and the
|
|
22
|
+
* listeners keep their seats, so only the stream is replaced.
|
|
32
23
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
private run;
|
|
24
|
+
recover(): void;
|
|
25
|
+
private open;
|
|
26
|
+
dispose(): Promise<void>;
|
|
37
27
|
private fail;
|
|
28
|
+
private run;
|
|
38
29
|
}
|
|
39
30
|
/**
|
|
40
31
|
* The Host's `changes` stream never wakes on a Thread read — a read advances
|
|
@@ -49,5 +40,4 @@ export declare class TeamReadStream {
|
|
|
49
40
|
bump(): void;
|
|
50
41
|
subscribe(listener: () => void): () => void;
|
|
51
42
|
}
|
|
52
|
-
export {};
|
|
53
43
|
//# sourceMappingURL=team-changes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"team-changes.d.ts","sourceRoot":"","sources":["../../../src/client/team-changes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"team-changes.d.ts","sourceRoot":"","sources":["../../../src/client/team-changes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,qCAAqC,CAAA;AAErF,OAAO,KAAK,EAAE,oBAAoB,EAA0B,MAAM,gCAAgC,CAAA;AAElG,MAAM,MAAM,eAAe,GAAG,oBAAoB,GAAG,SAAS,CAAA;AAE9D,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAEzD,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAA;AAiBnE,6FAA6F;AAC7F,qBAAa,gBAAgB;IAGf,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAuC;gBAExC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,WAAW,CAAC;IAEhF,SAAS,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,kBAAkB,GAAG,MAAM,IAAI;IAmB3E;;;;;OAKG;IACH,OAAO,IAAI,IAAI;IAYf,OAAO,CAAC,IAAI;IAiBN,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B,OAAO,CAAC,IAAI;YAOE,GAAG;CAclB;AAED;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAI;IACnB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAElD,IAAI,IAAI,IAAI;IAKZ,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;CAI5C"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RemoteStreamCarrierError } from '@deepseek-ai/dsh-api-gateway/client';
|
|
1
2
|
function scopeKey(scope) {
|
|
2
3
|
return scope === undefined ? 'all'
|
|
3
4
|
: scope.kind === 'workspace' ? `workspace:${scope.workspaceId}`
|
|
@@ -5,99 +6,100 @@ function scopeKey(scope) {
|
|
|
5
6
|
: scope.kind === 'presence' ? `presence:${scope.workspaceId}`
|
|
6
7
|
: `thread:${scope.threadRef}`;
|
|
7
8
|
}
|
|
8
|
-
/**
|
|
9
|
-
* One long-poll per change scope, shared by every listening surface: panels
|
|
10
|
-
* and pages never open parallel `changes` requests for the same scope, and
|
|
11
|
-
* the poll is aborted as soon as the last subscriber leaves.
|
|
12
|
-
*
|
|
13
|
-
* Each poll keeps its own cursor, and a cursor only means something inside the
|
|
14
|
-
* scope it was issued for: the Host answers a presence scope from a
|
|
15
|
-
* process-local wake epoch and every other scope from a durable ledger
|
|
16
|
-
* position.
|
|
17
|
-
*/
|
|
9
|
+
/** One logical stream per scope per page; Harness owns the shared transport and recovery. */
|
|
18
10
|
export class TeamChangeStream {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* is torn down: a commit landing while no poll exists is otherwise folded into
|
|
24
|
-
* the replacement poll's opening sample, which reports nothing.
|
|
25
|
-
*/
|
|
26
|
-
observed = new Map();
|
|
27
|
-
constructor(changes) {
|
|
28
|
-
this.changes = changes;
|
|
11
|
+
remote;
|
|
12
|
+
subscriptions = new Map();
|
|
13
|
+
constructor(remote) {
|
|
14
|
+
this.remote = remote;
|
|
29
15
|
}
|
|
30
16
|
subscribe(scope, listener) {
|
|
31
17
|
const key = scopeKey(scope);
|
|
32
|
-
let
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
this.
|
|
36
|
-
void this.run(key,
|
|
18
|
+
let subscription = this.subscriptions.get(key);
|
|
19
|
+
if (subscription === undefined) {
|
|
20
|
+
subscription = this.open(scope, new Set([listener]));
|
|
21
|
+
this.subscriptions.set(key, subscription);
|
|
22
|
+
void this.run(key, subscription);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
subscription.listeners.add(listener);
|
|
26
|
+
if (subscription.failure !== undefined)
|
|
27
|
+
listener({ type: 'failed', message: subscription.failure });
|
|
37
28
|
}
|
|
38
|
-
|
|
29
|
+
const owned = subscription;
|
|
39
30
|
return () => {
|
|
40
|
-
|
|
41
|
-
if (current === undefined || !current.listeners.delete(listener))
|
|
31
|
+
if (!owned.listeners.delete(listener) || owned.listeners.size !== 0)
|
|
42
32
|
return;
|
|
43
|
-
if (
|
|
44
|
-
this.
|
|
45
|
-
|
|
46
|
-
}
|
|
33
|
+
if (this.subscriptions.get(key) === owned)
|
|
34
|
+
this.subscriptions.delete(key);
|
|
35
|
+
void owned.stream.dispose();
|
|
47
36
|
};
|
|
48
37
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.fail(key, poll, probe.error.message);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
version = probe.value.version;
|
|
69
|
-
this.observed.set(key, version);
|
|
70
|
-
}
|
|
71
|
-
while (!signal.aborted) {
|
|
72
|
-
const result = await this.changes(request(version), signal);
|
|
73
|
-
if (signal.aborted)
|
|
74
|
-
return;
|
|
75
|
-
if (!result.ok) {
|
|
76
|
-
this.fail(key, poll, result.error.message);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
this.observed.set(key, result.value.version);
|
|
80
|
-
// Any difference is a resolution in this scope's own cursor domain —
|
|
81
|
-
// normally growth, but a cursor left over from another domain (the two
|
|
82
|
-
// scopes count different things) or from an earlier Host lifetime can sit
|
|
83
|
-
// ahead of the domain value. Re-anchoring on difference instead of only
|
|
84
|
-
// on growth keeps such a subscription observable rather than silently
|
|
85
|
-
// parked, and the Host never answers a request whose cursor already
|
|
86
|
-
// equals the domain value, so this cannot spin.
|
|
87
|
-
if (result.value.version !== version) {
|
|
88
|
-
version = result.value.version;
|
|
89
|
-
for (const listener of poll.listeners)
|
|
90
|
-
listener({ type: 'changed', version });
|
|
91
|
-
}
|
|
38
|
+
/**
|
|
39
|
+
* Reopen every scope whose stream already ended for good. The Harness resumes a
|
|
40
|
+
* live generation across reconnects, but a terminated one is gone for good: a new
|
|
41
|
+
* Host generation is the moment the scope was waiting for can come back, and the
|
|
42
|
+
* listeners keep their seats, so only the stream is replaced.
|
|
43
|
+
*/
|
|
44
|
+
recover() {
|
|
45
|
+
// Replacing an existing key's value is safe during Map iteration: the entry keeps
|
|
46
|
+
// its place, and a scope that never failed is left untouched.
|
|
47
|
+
for (const [key, subscription] of this.subscriptions) {
|
|
48
|
+
if (subscription.failure === undefined)
|
|
49
|
+
continue;
|
|
50
|
+
const replacement = this.open(subscription.scope, subscription.listeners);
|
|
51
|
+
this.subscriptions.set(key, replacement);
|
|
52
|
+
void subscription.stream.dispose();
|
|
53
|
+
void this.run(key, replacement);
|
|
92
54
|
}
|
|
93
55
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
56
|
+
open(scope, listeners) {
|
|
57
|
+
const key = scopeKey(scope);
|
|
58
|
+
const stream = this.remote.$stream({
|
|
59
|
+
name: `Team changes ${key}`,
|
|
60
|
+
open: signal => this.remote.agentTeam.changes(scope === undefined ? {} : { scope }, signal),
|
|
61
|
+
// A normal end after the baseline is an outage, not a verdict: the Harness
|
|
62
|
+
// retries a carrier loss and reports it through `carrierFailed`, while every
|
|
63
|
+
// other error stays terminal. Only an end before the opening baseline is a
|
|
64
|
+
// protocol violation — this stream never yields nothing before it ends.
|
|
65
|
+
ended: accepted => accepted
|
|
66
|
+
? new RemoteStreamCarrierError('Team change subscription ended without a terminal result')
|
|
67
|
+
: new Error('Team change subscription ended before its opening baseline'),
|
|
68
|
+
carrierFailed: error => this.fail(key, error.message),
|
|
69
|
+
});
|
|
70
|
+
return { scope, stream, listeners, failure: undefined };
|
|
71
|
+
}
|
|
72
|
+
async dispose() {
|
|
73
|
+
const subscriptions = [...this.subscriptions.values()];
|
|
74
|
+
this.subscriptions.clear();
|
|
75
|
+
await Promise.all(subscriptions.map(subscription => subscription.stream.dispose()));
|
|
76
|
+
}
|
|
77
|
+
fail(key, message) {
|
|
78
|
+
const subscription = this.subscriptions.get(key);
|
|
79
|
+
if (subscription === undefined || subscription.failure !== undefined)
|
|
80
|
+
return;
|
|
81
|
+
subscription.failure = message;
|
|
82
|
+
for (const listener of subscription.listeners)
|
|
99
83
|
listener({ type: 'failed', message });
|
|
100
84
|
}
|
|
85
|
+
async run(key, subscription) {
|
|
86
|
+
try {
|
|
87
|
+
for await (const item of subscription.stream) {
|
|
88
|
+
if (this.subscriptions.get(key) !== subscription)
|
|
89
|
+
return;
|
|
90
|
+
item.accept();
|
|
91
|
+
subscription.failure = undefined;
|
|
92
|
+
// Every opening baseline invalidates too: this closes the initial-read
|
|
93
|
+
// race and recovers failed reads even when nothing changed while offline.
|
|
94
|
+
for (const listener of subscription.listeners)
|
|
95
|
+
listener({ type: 'changed', version: item.value.version });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (this.subscriptions.get(key) === subscription)
|
|
100
|
+
this.fail(key, error instanceof Error ? error.message : String(error));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
101
103
|
}
|
|
102
104
|
/**
|
|
103
105
|
* The Host's `changes` stream never wakes on a Thread read — a read advances
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentTeamActivity, AgentTeamClaim, AgentTeamMemberId, AgentTeamTask, AgentTeamTaskRef } from '@wowyuarm/dsh-agent-team/types';
|
|
1
|
+
import type { AgentTeamActivity, AgentTeamClaim, AgentTeamClientMemberStatus, AgentTeamMemberId, AgentTeamTask, AgentTeamTaskRef } from '@wowyuarm/dsh-agent-team/types';
|
|
2
2
|
import type { TeamConversationProps } from './slots.ts';
|
|
3
3
|
import type { TeamStateDotState } from './TeamStateDot.tsx';
|
|
4
4
|
export declare function formatTaskStatus(status: AgentTeamTask['status'], t: TeamConversationProps['t']): string;
|
|
@@ -42,18 +42,50 @@ export interface MentionSegment {
|
|
|
42
42
|
readonly name?: string;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* Locate one Message's
|
|
46
|
-
*
|
|
47
|
-
* boundaries,
|
|
48
|
-
*
|
|
49
|
-
*
|
|
45
|
+
* Locate one Message's delivered mention names inside its literal body. Matching
|
|
46
|
+
* is the shared Host delivery scan — an authored `@`, case-insensitive on Unicode
|
|
47
|
+
* word boundaries, longest handle first, code quoted rather than called — so a chip
|
|
48
|
+
* never lands where delivery would not reach. Mention segments render the canonical
|
|
49
|
+
* `@Handle`; names absent from the body come back unmatched so the consumer can
|
|
50
|
+
* append them as a fallback chip row.
|
|
50
51
|
*/
|
|
51
52
|
export declare function splitMentionNames(text: string, names: readonly string[]): {
|
|
52
53
|
segments: MentionSegment[];
|
|
53
54
|
unmatched: readonly string[];
|
|
54
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* Whether one draft spells a handle as an authored `@mention`: the shared
|
|
58
|
+
* delivery scan, so the composer's recipient prune and will-notify preview agree
|
|
59
|
+
* with the Host on what the draft actually calls.
|
|
60
|
+
*/
|
|
61
|
+
export declare function containsMention(body: string, handle: string): boolean;
|
|
62
|
+
/** Whether one draft carries the `@all` marker the mention menu expands. */
|
|
63
|
+
export declare function containsAllMention(body: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Every Member the mention menu's `@all` row stands for: the roster this
|
|
66
|
+
* composer was handed minus Members who cannot take a Message right now — the
|
|
67
|
+
* same filter the per-handle rows apply, so the expansion and the menu agree.
|
|
68
|
+
*/
|
|
69
|
+
export declare function allMentionMembers(members: readonly AgentTeamClientMemberStatus[]): readonly AgentTeamClientMemberStatus[];
|
|
70
|
+
/**
|
|
71
|
+
* Member ids one draft asks to notify by text alone: the Client's preview of
|
|
72
|
+
* the Host's own body mention resolution, so a hand-typed `@Handle` reports
|
|
73
|
+
* exactly like a pick from the mention menu. `@all` stands for the menu's
|
|
74
|
+
* expansion; a written handle counts whenever its Member can still take a
|
|
75
|
+
* Message — state decides, not presence, because an offline Member is notified
|
|
76
|
+
* and reads it later.
|
|
77
|
+
*
|
|
78
|
+
* The result is preview-only. Only picked recipients travel as explicit
|
|
79
|
+
* recipients, where a name the Channel cannot reach would be a rejected target
|
|
80
|
+
* rather than the prose the Host reads.
|
|
81
|
+
*/
|
|
82
|
+
export declare function mentionedMemberIds(body: string, members: readonly AgentTeamClientMemberStatus[]): readonly AgentTeamMemberId[];
|
|
55
83
|
/** Canonical chip handles for one Message's structured mention refs. */
|
|
56
84
|
export declare function mentionNamesOf(mentions: readonly AgentTeamMemberId[], handles: ReadonlyMap<AgentTeamMemberId, string>): string[];
|
|
85
|
+
/** Accessible label for one "who is on this work" stack: its owners' handles, comma-separated. */
|
|
86
|
+
export declare function claimersLabel(owners: ReadonlyArray<{
|
|
87
|
+
readonly name: string;
|
|
88
|
+
}>, t: TeamConversationProps['t']): string;
|
|
57
89
|
/**
|
|
58
90
|
* Whether an Agent body survives literal rendering unchanged: no fences,
|
|
59
91
|
* inline code, emphasis markers, links, images, tables, or block constructs.
|
|
@@ -70,10 +102,13 @@ export declare function formatAbsoluteTime(occurredAt: string): string;
|
|
|
70
102
|
*/
|
|
71
103
|
export declare function formatMessageTime(occurredAt: string, now?: Date): string;
|
|
72
104
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* the
|
|
105
|
+
* Recency label for one Inbox row's newest fact, shared with the Channel feed's
|
|
106
|
+
* entry line so the two agree about the same instant. Today is a bare clock time
|
|
107
|
+
* — the reader is in today, and 「今天」 printed down every row spends the label's
|
|
108
|
+
* first word on the one segment that never varies, while `HH:mm` alone still
|
|
109
|
+
* reads as a clock because it is exactly one. The first day that is not today is
|
|
110
|
+
* the fact the reader has to be told, so it keeps its word; everything older
|
|
111
|
+
* keeps the Message date form, so the row and the Thread it opens agree.
|
|
77
112
|
*/
|
|
78
113
|
export declare function formatInboxTime(occurredAt: string, t: TeamConversationProps['t'], now?: Date): string;
|
|
79
114
|
export declare function formatActivity(activity: AgentTeamActivity, options: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"team-formatters.d.ts","sourceRoot":"","sources":["../../../src/client/team-formatters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;
|
|
1
|
+
{"version":3,"file":"team-formatters.d.ts","sourceRoot":"","sources":["../../../src/client/team-formatters.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasAllMarker, resolveBodyMentions, scanBodyHandles } from '@wowyuarm/dsh-agent-team/mentions';
|
|
1
2
|
export function formatTaskStatus(status, t) {
|
|
2
3
|
return t({
|
|
3
4
|
todo: 'taskStatusTodo',
|
|
@@ -81,42 +82,74 @@ export function isSingleBrandedRef(text) {
|
|
|
81
82
|
const matches = [...trimmed.matchAll(BRANDED_REF_PATTERN)];
|
|
82
83
|
return matches.length === 1 && matches[0][0] === trimmed;
|
|
83
84
|
}
|
|
84
|
-
function escapeRegExp(value) {
|
|
85
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
86
|
-
}
|
|
87
85
|
/**
|
|
88
|
-
* Locate one Message's
|
|
89
|
-
*
|
|
90
|
-
* boundaries,
|
|
91
|
-
*
|
|
92
|
-
*
|
|
86
|
+
* Locate one Message's delivered mention names inside its literal body. Matching
|
|
87
|
+
* is the shared Host delivery scan — an authored `@`, case-insensitive on Unicode
|
|
88
|
+
* word boundaries, longest handle first, code quoted rather than called — so a chip
|
|
89
|
+
* never lands where delivery would not reach. Mention segments render the canonical
|
|
90
|
+
* `@Handle`; names absent from the body come back unmatched so the consumer can
|
|
91
|
+
* append them as a fallback chip row.
|
|
93
92
|
*/
|
|
94
93
|
export function splitMentionNames(text, names) {
|
|
95
94
|
if (names.length === 0)
|
|
96
95
|
return { segments: [{ text, mention: false }], unmatched: [] };
|
|
97
|
-
const ordered = [...names].sort((left, right) => right.length - left.length);
|
|
98
|
-
// The lookbehind keeps the optional '@' from consuming the separator of an
|
|
99
|
-
// email-like occurrence: the character before the name (or before its '@')
|
|
100
|
-
// must not be a letter, digit, underscore, or another '@'.
|
|
101
|
-
const pattern = new RegExp(`(?<![\\p{L}\\p{N}_@])@?(?:${ordered.map(name => `(${escapeRegExp(name)})`).join('|')})(?=$|[^\\p{L}\\p{N}_])`, 'giu');
|
|
102
96
|
const segments = [];
|
|
103
97
|
const matched = new Set();
|
|
104
98
|
let cursor = 0;
|
|
105
|
-
for (const match of text
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const name = ordered[groupIndex - 1];
|
|
112
|
-
segments.push({ text: `@${name}`, mention: true, name });
|
|
113
|
-
matched.add(name.toLowerCase());
|
|
114
|
-
cursor = match.index + match[0].length;
|
|
99
|
+
for (const match of scanBodyHandles(text, names)) {
|
|
100
|
+
if (match.start > cursor)
|
|
101
|
+
segments.push({ text: text.slice(cursor, match.start), mention: false });
|
|
102
|
+
segments.push({ text: `@${match.handle}`, mention: true, name: match.handle });
|
|
103
|
+
matched.add(match.handle.toLowerCase());
|
|
104
|
+
cursor = match.end;
|
|
115
105
|
}
|
|
116
106
|
if (cursor < text.length)
|
|
117
107
|
segments.push({ text: text.slice(cursor), mention: false });
|
|
118
108
|
return { segments, unmatched: names.filter(name => !matched.has(name.toLowerCase())) };
|
|
119
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Whether one draft spells a handle as an authored `@mention`: the shared
|
|
112
|
+
* delivery scan, so the composer's recipient prune and will-notify preview agree
|
|
113
|
+
* with the Host on what the draft actually calls.
|
|
114
|
+
*/
|
|
115
|
+
export function containsMention(body, handle) {
|
|
116
|
+
return scanBodyHandles(body, [handle]).length > 0;
|
|
117
|
+
}
|
|
118
|
+
/** Whether one draft carries the `@all` marker the mention menu expands. */
|
|
119
|
+
export function containsAllMention(body) {
|
|
120
|
+
return hasAllMarker(body);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Every Member the mention menu's `@all` row stands for: the roster this
|
|
124
|
+
* composer was handed minus Members who cannot take a Message right now — the
|
|
125
|
+
* same filter the per-handle rows apply, so the expansion and the menu agree.
|
|
126
|
+
*/
|
|
127
|
+
export function allMentionMembers(members) {
|
|
128
|
+
return members.filter(status => status.presence !== 'unavailable' && status.member.state !== 'inactive' && status.member.state !== 'archived');
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Member ids one draft asks to notify by text alone: the Client's preview of
|
|
132
|
+
* the Host's own body mention resolution, so a hand-typed `@Handle` reports
|
|
133
|
+
* exactly like a pick from the mention menu. `@all` stands for the menu's
|
|
134
|
+
* expansion; a written handle counts whenever its Member can still take a
|
|
135
|
+
* Message — state decides, not presence, because an offline Member is notified
|
|
136
|
+
* and reads it later.
|
|
137
|
+
*
|
|
138
|
+
* The result is preview-only. Only picked recipients travel as explicit
|
|
139
|
+
* recipients, where a name the Channel cannot reach would be a rejected target
|
|
140
|
+
* rather than the prose the Host reads.
|
|
141
|
+
*/
|
|
142
|
+
export function mentionedMemberIds(body, members) {
|
|
143
|
+
if (hasAllMarker(body))
|
|
144
|
+
return allMentionMembers(members).map(status => status.member.memberId);
|
|
145
|
+
const candidates = members
|
|
146
|
+
.filter(status => status.member.state !== 'inactive' && status.member.state !== 'archived')
|
|
147
|
+
.map(status => ({ memberId: status.member.memberId, handle: status.member.handle }));
|
|
148
|
+
// The draft's author is the Human reader, and a Message never mentions its
|
|
149
|
+
// own author: resolving with that sender drops a self-call exactly where
|
|
150
|
+
// delivery would.
|
|
151
|
+
return resolveBodyMentions(body, candidates, 'member:human').memberIds;
|
|
152
|
+
}
|
|
120
153
|
/** Canonical chip handles for one Message's structured mention refs. */
|
|
121
154
|
export function mentionNamesOf(mentions, handles) {
|
|
122
155
|
return mentions
|
|
@@ -125,6 +158,10 @@ export function mentionNamesOf(mentions, handles) {
|
|
|
125
158
|
.map(memberId => memberId === 'member:human' ? 'human' : handles.get(memberId))
|
|
126
159
|
.filter((name) => name !== undefined);
|
|
127
160
|
}
|
|
161
|
+
/** Accessible label for one "who is on this work" stack: its owners' handles, comma-separated. */
|
|
162
|
+
export function claimersLabel(owners, t) {
|
|
163
|
+
return t('claimers', { names: owners.map(owner => `@${owner.name}`).join(', ') });
|
|
164
|
+
}
|
|
128
165
|
const MARKDOWN_BLOCK_CONSTRUCT = /(^|\n)[ \t]{0,3}(?:#{1,6}[ \t]|>[ \t]|[-*+][ \t]|\d+[.)][ \t])|^[ \t]*\|.+\|/m;
|
|
129
166
|
const MARKDOWN_INLINE_CONSTRUCT = /[`*_[\]!]|~~~|```/;
|
|
130
167
|
/**
|
|
@@ -168,19 +205,23 @@ function calendarDayDelta(at, now) {
|
|
|
168
205
|
return Math.round((midnight(now) - midnight(at)) / 86_400_000);
|
|
169
206
|
}
|
|
170
207
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* the
|
|
208
|
+
* Recency label for one Inbox row's newest fact, shared with the Channel feed's
|
|
209
|
+
* entry line so the two agree about the same instant. Today is a bare clock time
|
|
210
|
+
* — the reader is in today, and 「今天」 printed down every row spends the label's
|
|
211
|
+
* first word on the one segment that never varies, while `HH:mm` alone still
|
|
212
|
+
* reads as a clock because it is exactly one. The first day that is not today is
|
|
213
|
+
* the fact the reader has to be told, so it keeps its word; everything older
|
|
214
|
+
* keeps the Message date form, so the row and the Thread it opens agree.
|
|
175
215
|
*/
|
|
176
216
|
export function formatInboxTime(occurredAt, t, now = new Date()) {
|
|
177
217
|
const at = new Date(occurredAt);
|
|
178
218
|
if (Number.isNaN(at.getTime()))
|
|
179
219
|
return '';
|
|
180
220
|
const days = calendarDayDelta(at, now);
|
|
181
|
-
if (days === 0
|
|
182
|
-
return `${
|
|
183
|
-
|
|
221
|
+
if (days === 0)
|
|
222
|
+
return `${pad(at.getHours())}:${pad(at.getMinutes())}`;
|
|
223
|
+
if (days === 1)
|
|
224
|
+
return `${t('inboxTimeYesterday')} ${pad(at.getHours())}:${pad(at.getMinutes())}`;
|
|
184
225
|
return formatMessageTime(occurredAt, now);
|
|
185
226
|
}
|
|
186
227
|
export function formatActivity(activity, options) {
|
|
@@ -7,7 +7,7 @@ Model-facing tools for Agent Team Members. The package registers tools in the ca
|
|
|
7
7
|
## Tools
|
|
8
8
|
|
|
9
9
|
- `team_inbox` lists bounded unread Thread summaries for the calling Member, ordered by direct work and then recency; Task standing appears only where the Thread has a real Task overlay. The header states total unread/direct counts and the number of Threads shown (with a truncation conclusion when unread work lies beyond the bounded list); each item shows its exact unread/direct counts, Channel ref, and Task standing. The footer routes reading to `team_thread read`; the render carries no revision and no write token.
|
|
10
|
-
- `team_thread` reads a Thread, pages history, follows, or unfollows. `threadRef` is primary; a `taskRef` is a Host alias only for released task-only Clients. The five actions render separately: `status`/`follow`/`unfollow` answer only the Attention question in one line; `read` renders outcome, Thread identity, orientation (full anchor when Host-supplied background is present, bounded anchor subject otherwise, never a duplicate of an in-facts anchor), active Claims only, the chronological facts with inline unread/direct markers, and a read-through/remaining-unread footer; `history` renders a historical page with the full anchor on a first page and the bounded subject on continuation pages, never current Claims or advice. `read` advances the durable watermark and is the only read-side source of a next-write token — rendered only when no unread remains; your own committed public mutations hand off a fresh token as well. A read with nothing unread for the caller advances nothing, writes no operation, and renders `Read — no unread updates …` instead of claiming a commit, still answering from the current projection. Activity facts render structured — actor, Task ref, and the Claim refs each activity claimed, completed, accepted, or released. A read that acknowledges an unread acceptance of a still-done Task carries one `contextAdvice` block (usage, route budgets, the `min(128_000, handoffAt)` Task-boundary threshold, and one keep/rollover/handoff-now action); it is advisory only and degrades to an explicit `unavailable` line when measurement fails.
|
|
10
|
+
- `team_thread` reads a Thread, pages history, follows, or unfollows. `threadRef` is primary; a `taskRef` is a Host alias only for released task-only Clients. The five actions render separately: `status`/`follow`/`unfollow` answer only the Attention question in one line; `read` renders outcome, Thread identity, orientation (full anchor when Host-supplied background is present, bounded anchor subject otherwise — and no background at all for a Member re-entering a Thread it has already read, so its orientation there is that subject plus this batch — never a duplicate of an in-facts anchor), active Claims only, the chronological facts with inline unread/direct markers, and a read-through/remaining-unread footer that also states how many Thread facts precede the watermark whenever the read left any behind; `history` renders a historical page with the full anchor on a first page and the bounded subject on continuation pages, never current Claims or advice. `read` advances the durable watermark and is the only read-side source of a next-write token — rendered only when no unread remains; your own committed public mutations hand off a fresh token as well. A read with nothing unread for the caller advances nothing, writes no operation, and renders `Read — no unread updates …` instead of claiming a commit, still answering from the current projection. Activity facts render structured — actor, Task ref, and the Claim refs each activity claimed, completed, accepted, or released. A read that acknowledges an unread acceptance of a still-done Task carries one `contextAdvice` block (usage, route budgets, the `min(128_000, handoffAt)` Task-boundary threshold, and one keep/rollover/handoff-now action); it is advisory only and degrades to an explicit `unavailable` line when measurement fails.
|
|
11
11
|
- `team_message` starts a top-level Thread, replies to an existing Thread, or sends a DM. It defaults to a taskless Thread; pass `asTask: true` for atomic Task creation. A committed start/reply renders one committed-verb outcome (Thread created / reply added) with the Message, Thread, and optional Task refs, and exactly one next-write token hand-off. Replies reject unread work before checking token freshness. Typed rejections begin `Not committed`, keep the structured refs and counts needed to reread and retry, and render no numeric revision or write token — recovery is read-and-reconsider. DMs distinguish `Delivered` from `Recorded, not delivered` (with the delivery note and a no-blind-duplicate warning).
|
|
12
12
|
- `team_claim` lists or mutates the calling Member's Direction Claims through `list`, `claim`, `done`, and `release`; it applies only to real Tasks. `list` renders the Task/Thread identity with active Claims only (the collision surface) and no write token. A committed mutation names its action (Claim created/completed/released), renders the authoritative affected Claim first, and hands off one next-write token. Rejections share the message rejection form. Direction exclusion uses Unicode NFKC normalization, trim, whitespace compression, and deterministic case folding.
|
|
13
13
|
- `team_view` is the address book: bounded, membership-authorized Channel, top-level Thread, and Member summaries — current addresses, not a work queue. Threads are the sole paginated catalog: newest-first rows carrying threadRef, Channel ref, a bounded anchor subject, and Task standing inline (never a second Task index, never revision or message count). The cursor pages Thread rows only; continuation pages render Threads alone, and paging reaches every authorized top-level Thread, including taskless and off-page taskful ones.
|
|
@@ -15,11 +15,11 @@ Model-facing tools for Agent Team Members. The package registers tools in the ca
|
|
|
15
15
|
- `context_checkpoint` records one named checkpoint of the calling Member's current context. The durable checkpoint is the successful `tool/call`+`tool/result` pair the Session projection folds; the returned ref is deterministic from the Member Session identity plus the tool call id. It performs no lifecycle side effect in the tool body and concludes the turn: a checkpoint resolves at its containing turn's end, so the model records it as its final action of a completed unit of work.
|
|
16
16
|
- `context_timeline` returns one bounded structural view of the Member's context generations across the current Session and its archived ancestor lineage: recorded checkpoints and handoff/Team/compaction boundaries, with restorability reasons where a return cannot be proven safe. Structural only — no transcript content. A fresh `context_rollover` never requires consulting the timeline first.
|
|
17
17
|
|
|
18
|
-
An Agent cannot silently enroll an
|
|
18
|
+
Mentions are authored in the Message body, as `@Handle` (the `@` is required, matching ignores case, and `@all` reaches the Channel); there is no recipient parameter. An Agent cannot silently enroll an uninvited Member: naming one this Thread has never carried commits the Message, delivers nothing to that Member, and reports them as undelivered. Human confirmation is a separate Host/Client flow. Closed Tasks reject replies, Claims, and new Attention until a Human reopens them; taskless Threads have no Claim or Task-resolution mutation path.
|
|
19
19
|
|
|
20
20
|
Every agent-facing render carries absolute event instants in the fixed Team coordination zone UTC+8 with an explicit offset (`2026-09-08T17:00:00+08:00`): `team_thread` fact lines and anchors stamp each fact with its committing operation's instant, `team_inbox` rows carry `newestOccurredAt`, `team_view` Thread rows carry `lastActivityAt`, notifications state `Occurred at:`, and committed mutations render `Committed at:` from the receipt. The same stored instant renders byte-identically on every reread path; only absolute timestamps are rendered, never relative text, and sequence and revision — not wall-clock time — remain the ordering authority.
|
|
21
21
|
|
|
22
|
-
Canonical results expose stable refs, optional Task status, Thread revision, Claim history, Attention, and unread facts as structured fields. The model-visible renders follow the action decision surface: the write token appears only on a fully drained `team_thread read` and a committed public mutation; browsing results (`team_view`, `team_inbox`, status/follow/unfollow, `history`, `team_claim list`) and all typed rejections render no revision and no token. Typed `unread_required` and `stale_revision` results keep the structured fields needed to reread and retry deliberately. Tool execution resolves the exact live `exec.agent`; arguments cannot select or impersonate the actor
|
|
22
|
+
Canonical results expose stable refs, optional Task status, Thread revision, Claim history, Attention, and unread facts as structured fields. The model-visible renders follow the action decision surface: the write token appears only on a fully drained `team_thread read` and a committed public mutation; browsing results (`team_view`, `team_inbox`, status/follow/unfollow, `history`, `team_claim list`) and all typed rejections render no revision and no token. Typed `unread_required` and `stale_revision` results keep the structured fields needed to reread and retry deliberately. Tool execution resolves the exact live `exec.agent`; arguments cannot select or impersonate the actor, and the optional `workspace` selector can only pick among that actor's participated Workspaces. Write request identity derives from sessionId plus tool callId. `context_rollover` and `context_checkpoint` conclude the Agent turn (a rollover must not be followed by old-generation work, and a checkpoint resolves at its containing turn's end); every other Team tool returns to the model loop.
|
|
23
23
|
|
|
24
24
|
The complete implemented protocol is documented in [`../../docs/team-collaboration.md`](../../docs/team-collaboration.md).
|
|
25
25
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
## 工具
|
|
8
8
|
|
|
9
9
|
- `team_inbox` 列出调用方 Member 的有界未读 Thread 摘要,先按 direct work、再按最近活动排序;只有 Thread 存在真实 Task overlay 时才出现 Task standing。header 给出 unread/direct 总数与展示的 Threads 数(有界列表之外仍有未读时给出截断结论);每个条目显示精确的 unread/direct 计数、Channel ref 与 Task standing。页脚把阅读指向 `team_thread read`;渲染不携带 revision 与写令牌。
|
|
10
|
-
- `team_thread` 读取 Thread、分页历史、follow 或 unfollow。`threadRef` 是主身份;`taskRef` 只对 released task-only Client 是 Host alias。五个 action 分开渲染:`status`/`follow`/`unfollow` 一行只回答 Attention 问题;`read` 渲染结果、Thread 身份、导向(有 Host 提供的 background 时给 full anchor,否则给 bounded anchor subject
|
|
10
|
+
- `team_thread` 读取 Thread、分页历史、follow 或 unfollow。`threadRef` 是主身份;`taskRef` 只对 released task-only Client 是 Host alias。五个 action 分开渲染:`status`/`follow`/`unfollow` 一行只回答 Attention 问题;`read` 渲染结果、Thread 身份、导向(有 Host 提供的 background 时给 full anchor,否则给 bounded anchor subject——重新进入自己读过的 Thread 的 Member 完全没有 background,它在那里的定位就是该 subject 加本次 batch——anchor 已在 facts 中时绝不重复)、只含 active Claims、带行内 unread/direct 标记的按时间 facts、read-through/剩余未读页脚(只要这次 read 身后留有 Thread facts,页脚同时给出那些 facts 的数量);`history` 渲染历史页(首页 full anchor、continuation 页 bounded subject,绝无当前 Claims 或 advice)。`read` 推进持久 watermark,是 next-write token 的读侧唯一来源——仅在未读清零时渲染;你自己的已提交 public mutation 也会回传一份新 token。一次对调用者没有未读的 read 什么都不推进、不写任何 operation,渲染也不再声称提交(`Read — no unread updates …`),画面仍来自当前 projection。Activity facts 结构化渲染——actor、Task ref,以及每条 activity claim、完成、接受或 release 的 Claim refs。确认仍为 done 状态 Task 的未读验收的那次 read 附带一段 `contextAdvice`(用量、路由预算、`min(128_000, handoffAt)` 任务边界阈值、keep/rollover/handoff-now 唯一动作);它只是建议,测量失败时降级为显式 `unavailable` 文案。
|
|
11
11
|
- `team_message` 创建顶层 Thread、回复既有 Thread 或发送 DM。默认创建 taskless Thread;传入 `asTask: true` 才原子创建 Task。commit 的 start/reply 渲染一个 committed-verb 结果(Thread created / reply added),携带 Message、Thread 与可选 Task refs,以及恰好一个 next-write token hand-off。Reply 在检查令牌新鲜度前先拒绝未读工作。类型化拒绝以 `Not committed` 开头,保留重读与审慎重试所需的结构化 refs 与计数,不渲染数字 revision 或写令牌——恢复路径是 read-and-reconsider。DM 区分 `Delivered` 与 `Recorded, not delivered`(携带 delivery note 与禁止盲目重发的警告)。
|
|
12
12
|
- `team_claim` 通过 `list`、`claim`、`done`、`release` 读取或修改调用方 Member 的 Direction Claim;它只适用于真实 Task。`list` 渲染 Task/Thread 身份与只有 active Claims 的 collision surface,且无写令牌。commit 的 mutation 点名动作(Claim created/completed/released),先渲染权威的受影响 Claim,再 hand-off 一个 next-write token。拒绝与 message 拒绝共享同一形式。Direction 互斥键执行 Unicode NFKC normalization、trim、空白压缩和确定性大小写折叠。
|
|
13
13
|
- `team_view` 是 address book:有界、按 membership 授权的 Channel、顶层 Thread 与 Member 摘要——是当前的地址簿,不是工作队列。Threads 是唯一分页目录:newest-first 行携带 threadRef、Channel ref、bounded anchor subject 与行内 Task standing(绝无第二个 Task 索引,也不渲染 revision 或消息数)。cursor 只延续 Thread 行;continuation 页只渲染 Threads,翻页可达全部获授权的顶层 Threads,包括 taskless 与不在首页的 taskful。
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
- `context_checkpoint` 为调用的 Member 记录一个命名的当前上下文 checkpoint。durable checkpoint 就是 Session projection 折叠的成功 `tool/call`+`tool/result` 对;返回的 ref 由 Member Session 身份加 tool call id 确定性派生。工具体不做 lifecycle 副作用,但会结束 turn:checkpoint 在其所属 turn 结束时 resolve,因此模型把它作为一个完整工作单元的最后动作来记录。
|
|
16
16
|
- `context_timeline` 返回该 Member 跨当前 Session 与已归档祖先 lineage 的上下文代际有界结构视图:已记录的 checkpoints 与 handoff/Team/compaction 边界,无法证明可安全回返的条目携带拒绝原因。仅结构信息——不含任何 transcript 正文。fresh 的 `context_rollover` 从不需要先查 timeline。
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
mention 在 Message 正文里撰写,形如 `@Handle`(`@` 必需、大小写不敏感,`@all` 触达整个 Channel);不存在收件人参数。Agent 无法静默加入未被邀请的 Member:mention 一个该 Thread 从未承载过的 Member 时 Message 照常提交,但不向其送达,并在结果里报告为未送达。Human confirmation 属于单独的 Host/Client 流程。Closed Task 在 Human reopen 前拒绝 reply、Claim 和新的 Attention;taskless Thread 没有 Claim 或 Task resolution mutation path。
|
|
19
19
|
|
|
20
20
|
所有 agent-facing 渲染都在固定的 Team 协调时区 UTC+8 下携带带显式偏移的绝对事件时刻(`2026-09-08T17:00:00+08:00`):`team_thread` 的 fact 行与 anchor 为每条 fact 标注其提交 operation 的时刻,`team_inbox` 行携带 `newestOccurredAt`,`team_view` 的 Thread 行携带 `lastActivityAt`,通知给出 `Occurred at:`,已提交变更从 receipt 渲染 `Committed at:`。同一存储时刻在任何重读路径中渲染完全一致;只渲染绝对 timestamp,绝不出现相对时间文案,且 sequence 与 revision——而非 wall-clock 时间——仍是顺序 authority。
|
|
21
21
|
|
|
22
|
-
Canonical result 以结构化字段暴露稳定 refs、可选 Task status、Thread revision、Claim history、Attention 和未读 facts。模型可见的渲染遵循 action decision surface:写令牌只出现在未读清零的 `team_thread read` 与一次已提交的 public mutation 上;浏览类结果(`team_view`、`team_inbox`、status/follow/unfollow、`history`、`team_claim list`)与所有类型化拒绝都不渲染 revision 与令牌。类型化的 `unread_required` 与 `stale_revision` 结果保留重新读取和审慎重试所需的结构化字段。工具执行通过准确的 live `exec.agent` 解析 actor;参数不能选择或冒充 actor
|
|
22
|
+
Canonical result 以结构化字段暴露稳定 refs、可选 Task status、Thread revision、Claim history、Attention 和未读 facts。模型可见的渲染遵循 action decision surface:写令牌只出现在未读清零的 `team_thread read` 与一次已提交的 public mutation 上;浏览类结果(`team_view`、`team_inbox`、status/follow/unfollow、`history`、`team_claim list`)与所有类型化拒绝都不渲染 revision 与令牌。类型化的 `unread_required` 与 `stale_revision` 结果保留重新读取和审慎重试所需的结构化字段。工具执行通过准确的 live `exec.agent` 解析 actor;参数不能选择或冒充 actor,可选的 `workspace` 选择器只能在该 actor 已参与的 Workspace 中选择。写操作的 request identity 由 sessionId 与 tool callId 派生。`context_rollover` 与 `context_checkpoint` 结束 Agent turn;其余 Team tools 将结果返回模型循环,不主动结束 turn。
|
|
23
23
|
|
|
24
24
|
完整的已实现协议见 [`../../docs/team-collaboration.md`](../../docs/team-collaboration.md)。
|
|
25
25
|
|
|
@@ -18,3 +18,22 @@ export function member(agent) {
|
|
|
18
18
|
throw new Error('team tool requires an active Team Member');
|
|
19
19
|
return current;
|
|
20
20
|
}
|
|
21
|
+
/** A collaboration address, never a cwd or Session switch. */
|
|
22
|
+
export const workspaceParam = {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Target Workspace id exactly as listed in your participation context or team_view. Required when you participate in more than one Workspace; only a sole participation can be inferred. This does not change your Session cwd.',
|
|
25
|
+
};
|
|
26
|
+
/** Reject ambiguous routing before a tool reads facts or attempts a mutation. */
|
|
27
|
+
export function workspaceOf(args, agent) {
|
|
28
|
+
const workspaces = service(agent).workspacesForAgent(agent);
|
|
29
|
+
const choices = workspaces.map(workspace => workspace.workspaceId).join(', ');
|
|
30
|
+
if (args.workspace === undefined) {
|
|
31
|
+
if (workspaces.length !== 1)
|
|
32
|
+
throw new Error(`workspace is required when participating in multiple Workspaces. Choose: ${choices}`);
|
|
33
|
+
return workspaces[0].workspaceId;
|
|
34
|
+
}
|
|
35
|
+
const selected = workspaces.find(workspace => workspace.workspaceId === args.workspace);
|
|
36
|
+
if (selected === undefined)
|
|
37
|
+
throw new Error(`Not participating in Workspace '${args.workspace}'. Choose: ${choices}`);
|
|
38
|
+
return selected.workspaceId;
|
|
39
|
+
}
|