@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.
Files changed (119) hide show
  1. package/README.md +17 -11
  2. package/README.zh.md +17 -11
  3. package/package.json +61 -47
  4. package/packages/agent-team/README.md +6 -4
  5. package/packages/agent-team/README.zh.md +6 -3
  6. package/packages/agent-team/lib/context-management.js +48 -55
  7. package/packages/agent-team/lib/context-projection.js +22 -8
  8. package/packages/agent-team/lib/context-source.js +163 -23
  9. package/packages/agent-team/lib/index.js +349 -176
  10. package/packages/agent-team/lib/invariant.js +38 -4
  11. package/packages/agent-team/lib/ledger.js +658 -294
  12. package/packages/agent-team/lib/member-context.js +6 -2
  13. package/packages/agent-team/lib/member-time-context.js +141 -0
  14. package/packages/agent-team/lib/pressure-policy.js +34 -11
  15. package/packages/agent-team/lib/progress-nudge.js +12 -4
  16. package/packages/agent-team/lib/session-event-cursor.js +116 -0
  17. package/packages/agent-team/lib/session-remediation.js +493 -0
  18. package/packages/agent-team/lib/spec.js +49 -36
  19. package/packages/agent-team/lib/stored-session-reader.js +138 -0
  20. package/packages/agent-team/lib/time-format.js +56 -0
  21. package/packages/agent-team/lib/typert.host.js +163 -63
  22. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  23. package/packages/agent-team/lib/typert.remote-client.js +105 -32
  24. package/packages/agent-team/lib/types/context-management.d.ts +13 -1
  25. package/packages/agent-team/lib/types/context-management.d.ts.map +1 -1
  26. package/packages/agent-team/lib/types/context-projection.d.ts +8 -0
  27. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
  28. package/packages/agent-team/lib/types/context-source.d.ts +70 -36
  29. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
  30. package/packages/agent-team/lib/types/index.d.ts +110 -9
  31. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  32. package/packages/agent-team/lib/types/invariant.d.ts.map +1 -1
  33. package/packages/agent-team/lib/types/ledger.d.ts +205 -7
  34. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  35. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  36. package/packages/agent-team/lib/types/member-time-context.d.ts +83 -0
  37. package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -0
  38. package/packages/agent-team/lib/types/pressure-policy.d.ts +18 -0
  39. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
  40. package/packages/agent-team/lib/types/progress-nudge.d.ts.map +1 -1
  41. package/packages/agent-team/lib/types/session-event-cursor.d.ts +119 -0
  42. package/packages/agent-team/lib/types/session-event-cursor.d.ts.map +1 -0
  43. package/packages/agent-team/lib/types/session-remediation.d.ts +172 -0
  44. package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
  45. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  46. package/packages/agent-team/lib/types/stored-session-reader.d.ts +92 -0
  47. package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -0
  48. package/packages/agent-team/lib/types/time-format.d.ts +28 -0
  49. package/packages/agent-team/lib/types/time-format.d.ts.map +1 -0
  50. package/packages/agent-team/lib/types/types/entities.d.ts +36 -2
  51. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  52. package/packages/agent-team/lib/types/types/operations.d.ts +45 -19
  53. package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
  54. package/packages/agent-team/lib/types/types/requests-results.d.ts +50 -3
  55. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  56. package/packages/agent-team/preset/team-member/agent.cordis.yml +21 -4
  57. package/packages/client-agent-team/README.md +1 -1
  58. package/packages/client-agent-team/README.zh.md +1 -1
  59. package/packages/client-agent-team/lib/client.js +1121 -402
  60. package/packages/client-agent-team/lib/client.js.map +1 -1
  61. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  62. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +24 -4
  63. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  64. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +42 -22
  65. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +13 -24
  67. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +11 -5
  68. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  69. package/packages/client-agent-team/lib/types/client/TeamComposer.js +15 -9
  70. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  71. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/TeamConversation.js +8 -1
  73. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +21 -0
  74. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -0
  75. package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +71 -0
  76. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
  77. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +8 -24
  78. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +9 -2
  79. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  80. package/packages/client-agent-team/lib/types/client/TeamMessage.js +13 -6
  81. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +8 -0
  82. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
  83. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +21 -1
  84. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  85. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +106 -32
  86. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  87. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  88. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +50 -7
  89. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  90. package/packages/client-agent-team/lib/types/client/index.js +25 -9
  91. package/packages/client-agent-team/lib/types/client/locales.d.ts +18 -0
  92. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  93. package/packages/client-agent-team/lib/types/client/locales.js +18 -0
  94. package/packages/client-agent-team/lib/types/client/navigation.d.ts +10 -0
  95. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  96. package/packages/client-agent-team/lib/types/client/navigation.js +18 -7
  97. package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts +41 -0
  98. package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts.map +1 -0
  99. package/packages/client-agent-team/lib/types/client/scope-coverage.js +64 -0
  100. package/packages/client-agent-team/lib/types/client/slots.d.ts +8 -2
  101. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  102. package/packages/client-agent-team/lib/types/client/team-changes.d.ts +26 -1
  103. package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -1
  104. package/packages/client-agent-team/lib/types/client/team-changes.js +66 -15
  105. package/packages/client-agent-team/lib/types/client/team-dialog-save.d.ts +20 -0
  106. package/packages/client-agent-team/lib/types/client/team-dialog-save.d.ts.map +1 -0
  107. package/packages/client-agent-team/lib/types/client/team-dialog-save.js +36 -0
  108. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +9 -0
  109. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  110. package/packages/client-agent-team/lib/types/client/team-formatters.js +34 -6
  111. package/packages/tool-agent-team/README.md +8 -6
  112. package/packages/tool-agent-team/README.zh.md +8 -6
  113. package/packages/tool-agent-team/lib/context-tools.js +18 -14
  114. package/packages/tool-agent-team/lib/host-access.js +20 -0
  115. package/packages/tool-agent-team/lib/index.js +314 -123
  116. package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
  117. package/packages/tool-agent-team/lib/types/host-access.d.ts +14 -0
  118. package/packages/tool-agent-team/lib/types/host-access.d.ts.map +1 -0
  119. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
@@ -1,16 +1,29 @@
1
1
  function scopeKey(scope) {
2
- return scope.kind === 'workspace' ? `workspace:${scope.workspaceId}`
3
- : scope.kind === 'channel' ? `channel:${scope.channelRef}`
4
- : `thread:${scope.threadRef}`;
2
+ return scope === undefined ? 'all'
3
+ : scope.kind === 'workspace' ? `workspace:${scope.workspaceId}`
4
+ : scope.kind === 'channel' ? `channel:${scope.channelRef}`
5
+ : scope.kind === 'presence' ? `presence:${scope.workspaceId}`
6
+ : `thread:${scope.threadRef}`;
5
7
  }
6
8
  /**
7
9
  * One long-poll per change scope, shared by every listening surface: panels
8
10
  * and pages never open parallel `changes` requests for the same scope, and
9
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.
10
17
  */
11
18
  export class TeamChangeStream {
12
19
  changes;
13
20
  polls = new Map();
21
+ /**
22
+ * The last version each scope key answered with, kept after that scope's poll
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();
14
27
  constructor(changes) {
15
28
  this.changes = changes;
16
29
  }
@@ -18,7 +31,7 @@ export class TeamChangeStream {
18
31
  const key = scopeKey(scope);
19
32
  let poll = this.polls.get(key);
20
33
  if (poll === undefined) {
21
- poll = { controller: new AbortController(), listeners: new Set() };
34
+ poll = { controller: new AbortController(), listeners: new Set(), resumeFrom: this.observed.get(key) };
22
35
  this.polls.set(key, poll);
23
36
  void this.run(key, scope, poll);
24
37
  }
@@ -35,25 +48,43 @@ export class TeamChangeStream {
35
48
  }
36
49
  async run(key, scope, poll) {
37
50
  const { signal } = poll.controller;
38
- // Sample the current version silently first: subscribers just fetched
39
- // their initial projection, and an immediate wake would double-fetch.
40
- const probe = await this.changes({ afterVersion: 0, scope }, signal);
41
- if (signal.aborted)
42
- return;
43
- if (!probe.ok) {
44
- this.fail(key, poll, probe.error.message);
45
- return;
51
+ const request = (afterVersion) => ({ afterVersion, ...(scope === undefined ? {} : { scope }) });
52
+ let version = poll.resumeFrom;
53
+ if (version === undefined) {
54
+ // Nothing has been observed for this scope yet, so there is no cursor to
55
+ // resume from: sample the current version silently, because the
56
+ // subscribers just fetched their initial projection and an immediate wake
57
+ // would double-fetch. This leaves one named gap — a commit landing between
58
+ // that initial fetch being processed and this sample being answered is
59
+ // missed until the next commit in the scope — which resuming from a known
60
+ // version closes for every later subscription.
61
+ const probe = await this.changes(request(0), signal);
62
+ if (signal.aborted)
63
+ return;
64
+ if (!probe.ok) {
65
+ this.fail(key, poll, probe.error.message);
66
+ return;
67
+ }
68
+ version = probe.value.version;
69
+ this.observed.set(key, version);
46
70
  }
47
- let version = probe.value.version;
48
71
  while (!signal.aborted) {
49
- const result = await this.changes({ afterVersion: version, scope }, signal);
72
+ const result = await this.changes(request(version), signal);
50
73
  if (signal.aborted)
51
74
  return;
52
75
  if (!result.ok) {
53
76
  this.fail(key, poll, result.error.message);
54
77
  return;
55
78
  }
56
- if (result.value.version > version) {
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) {
57
88
  version = result.value.version;
58
89
  for (const listener of poll.listeners)
59
90
  listener({ type: 'changed', version });
@@ -68,3 +99,23 @@ export class TeamChangeStream {
68
99
  listener({ type: 'failed', message });
69
100
  }
70
101
  }
102
+ /**
103
+ * The Host's `changes` stream never wakes on a Thread read — a read advances
104
+ * only the reader's private watermark, so no shared projection changes. A
105
+ * durable read does consume the reader's own mention markers, so the Human's
106
+ * badge and Inbox page refresh from the completed read itself instead of
107
+ * waiting for the next unrelated commit.
108
+ */
109
+ export class TeamReadStream {
110
+ version = 0;
111
+ listeners = new Set();
112
+ bump() {
113
+ this.version += 1;
114
+ for (const listener of this.listeners)
115
+ listener();
116
+ }
117
+ subscribe(listener) {
118
+ this.listeners.add(listener);
119
+ return () => { this.listeners.delete(listener); };
120
+ }
121
+ }
@@ -0,0 +1,20 @@
1
+ import type { MutableRefObject } from 'react';
2
+ import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
3
+ /**
4
+ * Shared edit-dialog save lifecycle: one in-flight durable update, the dialog
5
+ * held disabled while it runs, a refusal reported in place, and a close only
6
+ * after the parent has re-read the committed state. Callers keep their own
7
+ * payload and request reuse, and hold `pendingRequest` so that editing a field
8
+ * drops a request the Host may already have committed.
9
+ */
10
+ export declare function useEditDialogSave<Request>({ save, onCommitted, onClose }: {
11
+ readonly save: (request: Request) => Promise<RemoteResult<unknown>>;
12
+ readonly onCommitted: () => Promise<void> | void;
13
+ readonly onClose: () => void;
14
+ }): {
15
+ readonly saving: boolean;
16
+ readonly error: string | undefined;
17
+ readonly pendingRequest: MutableRefObject<Request | undefined>;
18
+ readonly save: (request: Request) => Promise<void>;
19
+ };
20
+ //# sourceMappingURL=team-dialog-save.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team-dialog-save.d.ts","sourceRoot":"","sources":["../../../src/client/team-dialog-save.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEpE;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE;IACzE,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAA;IACnE,QAAQ,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAC7B,GAAG;IACF,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,cAAc,EAAE,gBAAgB,CAAC,OAAO,GAAG,SAAS,CAAC,CAAA;IAC9D,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACnD,CAwBA"}
@@ -0,0 +1,36 @@
1
+ import { useRef, useState } from 'react';
2
+ /**
3
+ * Shared edit-dialog save lifecycle: one in-flight durable update, the dialog
4
+ * held disabled while it runs, a refusal reported in place, and a close only
5
+ * after the parent has re-read the committed state. Callers keep their own
6
+ * payload and request reuse, and hold `pendingRequest` so that editing a field
7
+ * drops a request the Host may already have committed.
8
+ */
9
+ export function useEditDialogSave({ save, onCommitted, onClose }) {
10
+ const [saving, setSaving] = useState(false);
11
+ const [error, setError] = useState();
12
+ const pendingRequest = useRef();
13
+ const submit = async (request) => {
14
+ pendingRequest.current = request;
15
+ setSaving(true);
16
+ setError(undefined);
17
+ try {
18
+ const result = await save(request);
19
+ if (result.ok) {
20
+ pendingRequest.current = undefined;
21
+ await onCommitted();
22
+ onClose();
23
+ }
24
+ else {
25
+ setError(result.error.message);
26
+ }
27
+ }
28
+ catch (cause) {
29
+ setError(cause instanceof Error ? cause.message : String(cause));
30
+ }
31
+ finally {
32
+ setSaving(false);
33
+ }
34
+ };
35
+ return { saving, error, pendingRequest, save: submit };
36
+ }
@@ -62,11 +62,20 @@ export declare function mentionNamesOf(mentions: readonly AgentTeamMemberId[], h
62
62
  * renders block-level documents that cannot interleave inline chips.
63
63
  */
64
64
  export declare function isPlainTextBody(text: string): boolean;
65
+ /** Absolute local `YYYY-MM-DD HH:mm` label: the precise instant behind every shorter form. */
66
+ export declare function formatAbsoluteTime(occurredAt: string): string;
65
67
  /**
66
68
  * Wall-clock label for one Message instant: time within the current day,
67
69
  * month-day time within the year, full date otherwise.
68
70
  */
69
71
  export declare function formatMessageTime(occurredAt: string, now?: Date): string;
72
+ /**
73
+ * Queue label for one Inbox row's newest mention. The mention queue scans by
74
+ * recency, so the two days a reader reasons about by name are named ("今天" /
75
+ * "昨天") while everything older keeps the Message date form, so the row and
76
+ * the Thread it opens agree about the same instant.
77
+ */
78
+ export declare function formatInboxTime(occurredAt: string, t: TeamConversationProps['t'], now?: Date): string;
70
79
  export declare function formatActivity(activity: AgentTeamActivity, options: {
71
80
  readonly t: TeamConversationProps['t'];
72
81
  readonly actorName: (memberId: AgentTeamMemberId) => string;
@@ -1 +1 @@
1
- {"version":3,"file":"team-formatters.d.ts","sourceRoot":"","sources":["../../../src/client/team-formatters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"team-formatters.d.ts","sourceRoot":"","sources":["../../../src/client/team-formatters.ts"],"names":[],"mappings":"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"}
@@ -138,6 +138,13 @@ export function isPlainTextBody(text) {
138
138
  return !(MARKDOWN_BLOCK_CONSTRUCT.test(text) || MARKDOWN_INLINE_CONSTRUCT.test(text));
139
139
  }
140
140
  const pad = (value) => String(value).padStart(2, '0');
141
+ /** Absolute local `YYYY-MM-DD HH:mm` label: the precise instant behind every shorter form. */
142
+ export function formatAbsoluteTime(occurredAt) {
143
+ const at = new Date(occurredAt);
144
+ if (Number.isNaN(at.getTime()))
145
+ return '';
146
+ return `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())} ${pad(at.getHours())}:${pad(at.getMinutes())}`;
147
+ }
141
148
  /**
142
149
  * Wall-clock label for one Message instant: time within the current day,
143
150
  * month-day time within the year, full date otherwise.
@@ -146,14 +153,35 @@ export function formatMessageTime(occurredAt, now = new Date()) {
146
153
  const at = new Date(occurredAt);
147
154
  if (Number.isNaN(at.getTime()))
148
155
  return '';
149
- const time = `${pad(at.getHours())}:${pad(at.getMinutes())}`;
150
156
  const sameDay = at.getFullYear() === now.getFullYear() && at.getMonth() === now.getMonth() && at.getDate() === now.getDate();
151
157
  if (sameDay)
152
- return time;
153
- const date = `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())}`;
154
- if (at.getFullYear() === now.getFullYear())
155
- return `${date.slice(5)} ${time}`;
156
- return `${date} ${time}`;
158
+ return `${pad(at.getHours())}:${pad(at.getMinutes())}`;
159
+ const absolute = formatAbsoluteTime(occurredAt);
160
+ return at.getFullYear() === now.getFullYear() ? absolute.slice(5) : absolute;
161
+ }
162
+ /**
163
+ * Calendar days between two instants in the viewer's own zone, so "yesterday"
164
+ * means yesterday locally rather than 24 hours earlier.
165
+ */
166
+ function calendarDayDelta(at, now) {
167
+ const midnight = (date) => new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
168
+ return Math.round((midnight(now) - midnight(at)) / 86_400_000);
169
+ }
170
+ /**
171
+ * Queue label for one Inbox row's newest mention. The mention queue scans by
172
+ * recency, so the two days a reader reasons about by name are named ("今天" /
173
+ * "昨天") while everything older keeps the Message date form, so the row and
174
+ * the Thread it opens agree about the same instant.
175
+ */
176
+ export function formatInboxTime(occurredAt, t, now = new Date()) {
177
+ const at = new Date(occurredAt);
178
+ if (Number.isNaN(at.getTime()))
179
+ return '';
180
+ const days = calendarDayDelta(at, now);
181
+ if (days === 0 || days === 1) {
182
+ return `${t(days === 0 ? 'inboxTimeToday' : 'inboxTimeYesterday')} ${pad(at.getHours())}:${pad(at.getMinutes())}`;
183
+ }
184
+ return formatMessageTime(occurredAt, now);
157
185
  }
158
186
  export function formatActivity(activity, options) {
159
187
  const actor = options.actorName(activity.actor);
@@ -6,18 +6,20 @@ Model-facing tools for Agent Team Members. The package registers tools in the ca
6
6
 
7
7
  ## Tools
8
8
 
9
- - `team_inbox` lists bounded unread Thread summaries for the calling Member, ordered by direct work and then recency; Task status/number appear only where the Thread has a real Task overlay. The render states total unread/direct counts (including Threads beyond the bounded list) and each item's exact unread/direct counts, Channel ref, and Task status.
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. `read` atomically returns the Thread anchor, optional current Task and Claim snapshot, bounded orientation facts, and one contiguous unread batch while advancing the durable watermark; `history` never changes read state. Activity facts render structured actor, Task ref, and the Claim refs each activity claimed, completed, accepted, or released and the header states the Task's status/resolution on taskful Threads. The Claims snapshot renders one line per Claim, the anchor renders whenever it is not already among the facts, message facts carry unread/direct markers, a read footer states read-through sequence and remaining unread count, and a history footer states the cursor and hasMore for paging. 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
- - `team_message` starts a top-level Thread or replies to an existing Thread. It defaults to a taskless Thread; pass `asTask: true` for atomic Task creation. Replies require the exact `baseRevision` and reject unread work before checking revision freshness. A committed start renders the new Thread ref (and Task ref when taskful); typed rejections render the refs and counts needed to reread and retry.
12
- - `team_claim` lists or mutates the calling Member's Direction Claims through `list`, `claim`, `done`, and `release`; it applies only to real Tasks. Direction exclusion uses Unicode NFKC normalization, trim, whitespace compression, and deterministic case folding.
13
- - `team_view` discovers bounded, membership-authorized Channel, top-level Thread, Task, and Member summaries. Thread entries carry refs, revision, message count, and Task status where applicable; it does not return Thread messages or activities. The render ends with the shared cursor and a hasMore verdict; Task rows carry their Thread and Channel refs with the current revision.
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.
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
+ - `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
+ - `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.
14
14
  - `context_rollover` schedules one rollover of the calling Member into a fresh context. The tool validates the bounded private `handoff` (plus optional `relatedFiles`, or a `checkpointRef` to return to a recorded checkpoint instead) against the Host, concludes the turn, and returns `status: 'scheduled'`; the Host performs the actual swap only after the successful tool result is durably appended. The `checkpointRef` copy is hardened on both the tool and parameter descriptions: omit it for ordinary generation changes and pressure-driven handoffs, and supply it only when citing the exact ref a `context_timeline` result listed as restorable — never a synthesized or guessed one. A `checkpointRef` that cannot succeed now — fabricated, unresolved, unattributable, nonshrinking, unmeasurable, over-budget, or blocked by multiple active Claims — rejects as a model-visible error result instead of a fake `scheduled`; the mutable guards (jobs, route limits) are revalidated at the swap seam, and a seam failure leaves the Member recoverable through a later-turn fresh rollover. `context_rollover` and `context_checkpoint` both conclude the Agent turn.
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
18
  An Agent cannot silently enroll an unfollowed Agent through a mention; the Host returns `member_not_following`. 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
- Canonical results expose stable refs, optional Task status, Thread revision, Claim history, Attention, and unread facts. Typed `unread_required` and `stale_revision` results include the fields needed to reread and retry deliberately. Tool execution resolves the exact live `exec.agent`; arguments cannot select or impersonate the actor or Workspace. 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.
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
+
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 or Workspace. 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.
21
23
 
22
24
  The complete implemented protocol is documented in [`../../docs/team-collaboration.md`](../../docs/team-collaboration.md).
23
25
 
@@ -6,18 +6,20 @@
6
6
 
7
7
  ## 工具
8
8
 
9
- - `team_inbox` 列出调用方 Member 的有界未读 Thread 摘要,先按 direct work、再按最近活动排序;只有 Thread 存在真实 Task overlay 时才出现 Task status/number。渲染给出 unread/direct 总数(含有界列表之外的 Threads),每个条目显示精确的 unread/direct 计数、Channel ref 与 Task status。
10
- - `team_thread` 读取 Thread、分页历史、follow 或 unfollow。`threadRef` 是主身份;`taskRef` 只对 released task-only Client 是 Host alias。`read` 原子返回 Thread anchor、可选的当前 Task Claim 快照、有界背景和一批连续未读事实,并推进持久 watermark;`history` 不改变 read 状态。Activity facts 结构化渲染——actorTask ref,以及每条 activity claim、完成、接受或 release Claim refs——taskful Thread header 呈现 Task status/resolution。Claims 快照逐条渲染,anchor 未出现在 facts 中时也会渲染,message facts 携带 unread/direct 标记,read 页脚给出 read-through sequence 与剩余 unread 计数,history 页脚给出 cursor hasMore 供翻页。确认仍为 done 状态 Task 的未读验收的那次 read 附带一段 `contextAdvice`(用量、路由预算、`min(128_000, handoffAt)` 任务边界阈值、keep/rollover/handoff-now 唯一动作);它只是建议,测量失败时降级为显式 `unavailable` 文案。
11
- - `team_message` 创建顶层 Thread,或回复已有 Thread。它默认创建 taskless Thread;传入 `asTask: true` 才原子创建 Task。Reply 必须携带准确的 `baseRevision`,并在检查 revision 前先处理未读门禁。commit start 渲染新 Thread ref(taskful 时含 Task ref);类型化拒绝渲染重读与重试所需的 refs 与计数。
12
- - `team_claim` 通过 `list`、`claim`、`done`、`release` 读取或修改调用方 Member 的 Direction Claim;它只适用于真实 TaskDirection 互斥键执行 Unicode NFKC normalization、trim、空白压缩和确定性大小写折叠。
13
- - `team_view` 发现有界、按 membership 授权的 Channel、顶层 Thread、Task Member 摘要。Thread 条目携带 refsrevisionmessage 数及适用的 Task status;不返回 Thread Message Activity。渲染以共享 cursor hasMore 结论收尾;Task 行携带其 Thread、Channel ref 与当前 revision
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,anchor 已在 facts 中时绝不重复)、只含 active Claims、带行内 unread/direct 标记的按时间 facts、read-through/剩余未读页脚;`history` 渲染历史页(首页 full anchorcontinuation 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
+ - `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
+ - `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
+ - `team_view` address book:有界、按 membership 授权的 Channel、顶层 Thread Member 摘要——是当前的地址簿,不是工作队列。Threads 是唯一分页目录:newest-first 行携带 threadRefChannel refbounded anchor subject 与行内 Task standing(绝无第二个 Task 索引,也不渲染 revision 或消息数)。cursor 只延续 Thread 行;continuation 页只渲染 Threads,翻页可达全部获授权的顶层 Threads,包括 taskless 与不在首页的 taskful
14
14
  - `context_rollover` 为调用方 Member 安排一次进入全新上下文的 rollover。工具向 Host 校验有界的私有 `handoff`(及可选 `relatedFiles`;传入 `checkpointRef` 则改为回返到已记录的 checkpoint),结束当前 turn 并返回 `status: 'scheduled'`;Host 只在成功的 tool result 持久落盘后才执行真正的换窗。`checkpointRef` 在工具与参数两级 description 上做了 copy hardening:普通续代与压力换窗必须省略,只有引用 `context_timeline` 结果中列为 restorable 的精确 ref 时才提供——绝不合成或猜测。当下不可能成功的 `checkpointRef`——伪造、未 resolve、无法归属、不缩减、不可测量、超预算或被多个 active Claim 阻塞——以 model-visible 的 error result 拒绝,而不是返回假 `scheduled`;可变 guards(jobs、route limits)在换窗 seam 复查,seam 失败后 Member 仍可通过后续 turn 的显式 fresh rollover 恢复。`context_rollover` 与 `context_checkpoint` 都会结束 Agent turn(换窗后不得再接旧代工作;checkpoint 在其所属 turn 结束时 resolve)。
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
  Agent 不能通过 mention 静默把另一个 unfollowed Agent 加入 Thread;Host 返回 `member_not_following`。Human confirmation 属于单独的 Host/Client 流程。Closed Task 在 Human reopen 前拒绝 reply、Claim 和新的 Attention;taskless Thread 没有 Claim 或 Task resolution mutation path。
19
19
 
20
- Canonical result 包含稳定 refs、可选的 Task status、Thread revision、Claim history、Attention 和未读 facts。类型化的 `unread_required` `stale_revision` 结果包含重新读取和审慎重试所需字段。工具执行通过准确的 live `exec.agent` 解析 actor;参数不能选择或冒充 actor Workspace。写操作的 request identity sessionId tool callId 派生。`context_rollover` `context_checkpoint` 结束 Agent turn;其余 Team tools 将结果返回模型循环,不主动结束 turn
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
+
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。写操作的 request identity 由 sessionId 与 tool callId 派生。`context_rollover` 与 `context_checkpoint` 结束 Agent turn;其余 Team tools 将结果返回模型循环,不主动结束 turn。
21
23
 
22
24
  完整的已实现协议见 [`../../docs/team-collaboration.md`](../../docs/team-collaboration.md)。
23
25
 
@@ -9,18 +9,7 @@
9
9
  * @module @wowyuarm/dsh-agent-team/context-tools
10
10
  */
11
11
  import { defineTool } from '@deepseek-ai/dsh-tools';
12
- function service(agent) {
13
- const host = agent.ctx.get('agentTeam');
14
- if (host === undefined)
15
- throw new Error('Agent Team Host is unavailable');
16
- return host;
17
- }
18
- function member(agent) {
19
- const current = service(agent).memberForAgent(agent);
20
- if (current === undefined)
21
- throw new Error('team tool requires an active Team Member');
22
- return current;
23
- }
12
+ import { member, service } from "./host-access.js";
24
13
  const MAX_HANDOFF_CHARS = 32 * 1024;
25
14
  const MAX_RELATED_FILES = 32;
26
15
  const contextRollover = defineTool({
@@ -139,13 +128,19 @@ const contextTimeline = defineTool({
139
128
  reason: { type: 'string' },
140
129
  sourceSessionId: { type: 'string' },
141
130
  } } },
131
+ incompleteFrom: { type: 'object', additionalProperties: false, properties: {
132
+ sessionId: { type: 'string', required: true },
133
+ reason: { type: 'string', required: true },
134
+ } },
142
135
  } },
143
136
  // The item list is the whole decision surface: without each anchor's
144
137
  // ref, label, source, size estimates, affected Threads, and
145
138
  // restorable/reason verdict, the model cannot pick a `checkpointRef` for
146
139
  // `context_rollover` — the summary line alone left the tool unusable for
147
140
  // seeded returns. The Host bounds items (default 12, at most 24), so this
148
- // list cannot grow unbounded.
141
+ // list cannot grow unbounded. `incompleteFrom` states where and why the
142
+ // lineage walk stopped early, so history read up to that ancestor is
143
+ // known to be a truncation, not everything that exists.
149
144
  render: (_args, value) => {
150
145
  const lines = [`Context timeline: ${value.usageTokens} tokens used (handoff at ${value.handoffAt}, hard limit ${value.hardLimit}). ${value.items.length} item(s):`];
151
146
  for (const item of value.items) {
@@ -156,6 +151,9 @@ const contextTimeline = defineTool({
156
151
  : `not restorable — ${item.reason ?? 'no reason given'}`;
157
152
  lines.push(`- ${item.name} [source: ${item.source}] (${size}; ${threads}) — ${restorable}`);
158
153
  }
154
+ if (value.incompleteFrom !== undefined) {
155
+ lines.push(`History incomplete: the lineage walk stopped at Session ${value.incompleteFrom.sessionId} (${value.incompleteFrom.reason}); ancestors before it could not be read and are not reflected above.`);
156
+ }
159
157
  return [{ type: 'text', text: lines.join('\n') }];
160
158
  },
161
159
  },
@@ -169,7 +167,13 @@ const contextTimeline = defineTool({
169
167
  const result = await host.contextTimelineForAgent(agent, { memberId: current.memberId, ...(limit === undefined ? {} : { limit }) });
170
168
  // The Host result is deeply immutable; the tool output contract carries
171
169
  // plain arrays, so re-shape without any semantic change.
172
- return { usageTokens: result.usageTokens, hardLimit: result.hardLimit, handoffAt: result.handoffAt, items: result.items.map(item => ({ ...item, affectedThreads: [...item.affectedThreads] })) };
170
+ return {
171
+ usageTokens: result.usageTokens,
172
+ hardLimit: result.hardLimit,
173
+ handoffAt: result.handoffAt,
174
+ items: result.items.map(item => ({ ...item, affectedThreads: [...item.affectedThreads] })),
175
+ ...(result.incompleteFrom === undefined ? {} : { incompleteFrom: result.incompleteFrom }),
176
+ };
173
177
  },
174
178
  });
175
179
  export function registerContextTools(ctx) {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Shared Host accessors for every Team tool module. `agent.ctx.get` is the
3
+ * one resolution path a tool row has — the preset mounts these tools beside
4
+ * the Host, so a missing service or an inactive Member is a model-visible
5
+ * rejection rather than a silent no-op. Both messages are user-facing text.
6
+ */
7
+ /** Resolve the Team Host service, or reject the tool call. */
8
+ export function service(agent) {
9
+ const host = agent.ctx.get('agentTeam');
10
+ if (host === undefined)
11
+ throw new Error('Agent Team Host is unavailable');
12
+ return host;
13
+ }
14
+ /** Resolve the calling Team Member, or reject the tool call. */
15
+ export function member(agent) {
16
+ const current = service(agent).memberForAgent(agent);
17
+ if (current === undefined)
18
+ throw new Error('team tool requires an active Team Member');
19
+ return current;
20
+ }