@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,4 +1,5 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
2
3
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
3
4
|
import { SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
4
5
|
import { memberMemoryDirectoryPath } from "./member-runtime.js";
|
|
@@ -33,7 +34,8 @@ export function apply(ctx) {
|
|
|
33
34
|
? 'memory.md is absent; the private memory index is empty.'
|
|
34
35
|
: 'memory.md is currently unreadable; do not use any earlier private memory context.');
|
|
35
36
|
}
|
|
36
|
-
const
|
|
37
|
+
const workspaces = renderMemberWorkspaces(host.workspacesForAgent(agent));
|
|
38
|
+
const text = `${renderMemberIdentity(member)}\n\n${memory}${workspaces === '' ? '' : `\n\n${workspaces}`}`;
|
|
37
39
|
const latestText = agent.session.surface.nodes.toReversed().flatMap(sequence => {
|
|
38
40
|
// `nodes` are event identities (SessionSeq); snapshotEvents takes log
|
|
39
41
|
// offsets. Re-entering through the validating constructor keeps the two
|
|
@@ -61,6 +63,21 @@ export function renderMemberIdentity(member) {
|
|
|
61
63
|
? `Team identity: you are @${member.handle}.`
|
|
62
64
|
: `Team identity: you are @${member.handle} — ${member.description}`;
|
|
63
65
|
}
|
|
66
|
+
/** A current address list, not a copy of instructions from another checkout. */
|
|
67
|
+
export function renderMemberWorkspaces(workspaces) {
|
|
68
|
+
if (workspaces.length <= 1)
|
|
69
|
+
return '';
|
|
70
|
+
return [
|
|
71
|
+
'Team Workspace participation — this list replaces all earlier participation context.',
|
|
72
|
+
...workspaces.map(workspace => `${workspace.workspaceId}${workspace.default ? ' (default; Session cwd)' : ''}: ${workspace.path === undefined
|
|
73
|
+
? 'Workspace path unavailable; ask @human before filesystem work.'
|
|
74
|
+
: `${JSON.stringify(workspace.path)}; instructions: ${JSON.stringify(join(workspace.path, 'AGENTS.md'))}`}`),
|
|
75
|
+
'Your single Session and cwd stay in the default Workspace. Joining another Workspace does not move them.',
|
|
76
|
+
'For filesystem work in another Workspace, use absolute paths (or an explicit command cwd). Before working there, read its AGENTS.md and applicable directory instructions yourself; their contents are not injected here.',
|
|
77
|
+
'Pass workspace explicitly to team_view, team_thread, team_message and team_claim. team_inbox is the cross-Workspace triage entry point.',
|
|
78
|
+
'If an instruction is ambiguous about which Workspace it concerns, ask @human before acting. Participation does not itself join Channels.',
|
|
79
|
+
].join('\n');
|
|
80
|
+
}
|
|
64
81
|
/** The four private-memory paths plus the out-of-cwd warning; callers append their own sentence. */
|
|
65
82
|
function memoryPathsBlock(privateMemoryPath) {
|
|
66
83
|
return `Private memory directory: ${privateMemoryPath}\nMemory index: ${privateMemoryPath}/memory.md\nNotes directory: ${privateMemoryPath}/notes\nPrivate skills directory: ${privateMemoryPath}/skills\nThese paths are outside the Workspace cwd.`;
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
* @module @wowyuarm/dsh-agent-team/member-runtime
|
|
15
15
|
*/
|
|
16
16
|
import { mkdir, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
17
|
-
import { dirname, join, resolve } from 'node:path';
|
|
17
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { scopeOf } from '@deepseek-ai/dsh-scope';
|
|
20
|
+
import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
|
|
20
21
|
import * as memberSkills from "./member-skills.js";
|
|
21
22
|
/**
|
|
22
23
|
* Read-only core skills shipped beside the preset (the meta skill first:
|
|
@@ -218,7 +219,8 @@ export class MemberRuntime {
|
|
|
218
219
|
* `privateMemoryPath` is a durable Member fact the renewal path cannot
|
|
219
220
|
* rewrite: when the legacy directory exists it is renamed onto the sanitized
|
|
220
221
|
* path once (same-parent rename, atomic), so existing private memory
|
|
221
|
-
* survives instead of being silently orphaned.
|
|
222
|
+
* survives instead of being silently orphaned. A recorded path that names a
|
|
223
|
+
* different DSH home is never acted on — see `migrateLegacyMemoryDirectory`.
|
|
222
224
|
*
|
|
223
225
|
* A colon-form twin directory is also merged when it exists: a Member may
|
|
224
226
|
* have written files under the ledger identity spelling (the branded
|
|
@@ -231,8 +233,14 @@ export class MemberRuntime {
|
|
|
231
233
|
* and twin-only notes/skills are moved in without overwriting.
|
|
232
234
|
*/
|
|
233
235
|
async initializePrivateMemory(path, legacyPath) {
|
|
234
|
-
if (legacyPath !== undefined && legacyPath !== path)
|
|
235
|
-
await migrateLegacyMemoryDirectory(legacyPath, path);
|
|
236
|
+
if (legacyPath !== undefined && legacyPath !== path) {
|
|
237
|
+
const migration = await migrateLegacyMemoryDirectory(legacyPath, path);
|
|
238
|
+
// A refusal means the record names a directory in ANOTHER DSH home: this
|
|
239
|
+
// process provisions the Member fresh here instead of taking that
|
|
240
|
+
// directory out of its own home.
|
|
241
|
+
if (migration === 'refused-foreign-home')
|
|
242
|
+
this.deps.ctx.logger.warn(`agent-team: refused to migrate the recorded Member private memory '${legacyPath}' into '${path}': they are not in the same directory, so the recorded directory names a different DSH home and is left untouched`);
|
|
243
|
+
}
|
|
236
244
|
const twinPath = twinMemoryDirectoryPath(path);
|
|
237
245
|
if (twinPath !== undefined)
|
|
238
246
|
await mergeTwinMemoryDirectory(twinPath, path);
|
|
@@ -250,12 +258,20 @@ export class MemberRuntime {
|
|
|
250
258
|
async cleanupRemovedMember(member) {
|
|
251
259
|
// The ledger path may still name the legacy colon directory (never
|
|
252
260
|
// activated after the fix): remove both spellings; rm is force-tolerant
|
|
253
|
-
// of the one that does not exist.
|
|
261
|
+
// of the one that does not exist. Every target must sit inside THIS
|
|
262
|
+
// process's members root: the recorded path is a durable absolute fact,
|
|
263
|
+
// and a process resolving another DSH home (an isolated test home, a
|
|
264
|
+
// second instance) would otherwise delete the directory that fact names —
|
|
265
|
+
// the real Member's private memory.
|
|
254
266
|
const sanitized = memberMemoryDirectoryPath(member);
|
|
267
|
+
const targets = sanitized === member.privateMemoryPath ? [sanitized] : [sanitized, member.privateMemoryPath];
|
|
268
|
+
const owned = targets.filter(target => isInsideMembersRoot(target));
|
|
269
|
+
const refused = targets.filter(target => !isInsideMembersRoot(target));
|
|
270
|
+
if (refused.length > 0)
|
|
271
|
+
this.deps.ctx.logger.warn(`agent-team: refused to delete recorded Member private memory outside this DSH home: ${refused.map(target => `'${target}'`).join(', ')}`);
|
|
255
272
|
const results = await Promise.allSettled([
|
|
256
273
|
this.deps.ctx.workspaceRegistry.archiveSession(member.sessionId),
|
|
257
|
-
rm(
|
|
258
|
-
...(sanitized === member.privateMemoryPath ? [] : [rm(member.privateMemoryPath, { recursive: true, force: true })]),
|
|
274
|
+
...owned.map(target => rm(target, { recursive: true, force: true })),
|
|
259
275
|
]);
|
|
260
276
|
const failures = results.flatMap(result => result.status === 'rejected' ? [result.reason] : []);
|
|
261
277
|
if (failures.length > 0)
|
|
@@ -288,6 +304,15 @@ export class MemberRuntime {
|
|
|
288
304
|
* directory onto its sanitized path. A sanitized target that already exists
|
|
289
305
|
* wins (idempotent across restarts and partially migrated installs); a legacy
|
|
290
306
|
* source that never existed is simply the fresh-install case.
|
|
307
|
+
*
|
|
308
|
+
* The rename is confined to one parent directory, which is what the migration
|
|
309
|
+
* has always meant: the recorded path is a durable absolute fact, so a process
|
|
310
|
+
* resolving a different DSH home (an isolated test home, a second instance, a
|
|
311
|
+
* moved home) holds a sanitized target in ITS tree while the record still names
|
|
312
|
+
* the real one. Renaming across trees would take a Member's whole private
|
|
313
|
+
* memory out of its home — silently, since only the next session notices an
|
|
314
|
+
* empty index. Such a source is left exactly where it is, and only that
|
|
315
|
+
* refusal is reported back: every other outcome needs nothing from the caller.
|
|
291
316
|
*/
|
|
292
317
|
async function migrateLegacyMemoryDirectory(legacyPath, path) {
|
|
293
318
|
let legacy;
|
|
@@ -296,20 +321,34 @@ async function migrateLegacyMemoryDirectory(legacyPath, path) {
|
|
|
296
321
|
}
|
|
297
322
|
catch (error) {
|
|
298
323
|
if (error.code === 'ENOENT')
|
|
299
|
-
return;
|
|
324
|
+
return undefined;
|
|
300
325
|
throw error;
|
|
301
326
|
}
|
|
302
327
|
if (!legacy.isDirectory())
|
|
303
328
|
throw new Error(`legacy Member memory path '${legacyPath}' exists but is not a directory`);
|
|
329
|
+
if (dirname(legacyPath) !== dirname(path))
|
|
330
|
+
return 'refused-foreign-home';
|
|
304
331
|
try {
|
|
305
332
|
await stat(path);
|
|
306
|
-
return; // Sanitized directory already present: migration already done or a new install.
|
|
333
|
+
return undefined; // Sanitized directory already present: migration already done or a new install.
|
|
307
334
|
}
|
|
308
335
|
catch (error) {
|
|
309
336
|
if (error.code !== 'ENOENT')
|
|
310
337
|
throw error;
|
|
311
338
|
}
|
|
312
339
|
await rename(legacyPath, path);
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Whether one absolute path names a Member directory inside THIS process's
|
|
344
|
+
* members root. Only such a path is this process's to delete: a recorded path
|
|
345
|
+
* may name the same Member in another DSH home, where the removal happening
|
|
346
|
+
* here has no authority.
|
|
347
|
+
*/
|
|
348
|
+
function isInsideMembersRoot(candidate) {
|
|
349
|
+
const fromRoot = relative(dshHomePath('agent-team', 'members'), resolve(candidate));
|
|
350
|
+
// '' is the members root itself and '..' escapes it: only a real descendant is authorized.
|
|
351
|
+
return fromRoot.length > 0 && !fromRoot.startsWith('..') && !isAbsolute(fromRoot);
|
|
313
352
|
}
|
|
314
353
|
/** The colon-form twin path of one sanitized Member memory directory, when the segment form admits one. */
|
|
315
354
|
function twinMemoryDirectoryPath(sanitizedPath) {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
function escapeRegExp(value) {
|
|
2
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Character ranges the scanner must not read as mentions: fenced code blocks
|
|
6
|
+
* and inline code spans. Authors quote handles there to talk *about* a name
|
|
7
|
+
* rather than call it, and resolving those would notify the wrong Member.
|
|
8
|
+
* The alternation tries the fenced form first so a block is never mistaken for
|
|
9
|
+
* a run of inline spans.
|
|
10
|
+
*/
|
|
11
|
+
function codeRanges(body) {
|
|
12
|
+
const ranges = [];
|
|
13
|
+
for (const match of body.matchAll(/```[\s\S]*?```|`[^`\n]*`/g)) {
|
|
14
|
+
const start = match.index ?? 0;
|
|
15
|
+
ranges.push([start, start + match[0].length]);
|
|
16
|
+
}
|
|
17
|
+
return ranges;
|
|
18
|
+
}
|
|
19
|
+
const ALL_MARKER = /(?<![\p{L}\p{N}_@])@all(?=$|[^\p{L}\p{N}_])/iu;
|
|
20
|
+
/** Whether one body carries the `@all` marker outside code. */
|
|
21
|
+
export function hasAllMarker(body) {
|
|
22
|
+
const excluded = codeRanges(body);
|
|
23
|
+
const marker = ALL_MARKER.exec(body);
|
|
24
|
+
return marker !== null && !excluded.some(([start, end]) => marker.index >= start && marker.index < end);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Every `@Handle` occurrence in one body that names one of `handles`:
|
|
28
|
+
* case-insensitive, on Unicode word boundaries, longest handle first, outside
|
|
29
|
+
* code. Writing `@` is required: bare handles are ordinary words. This is the
|
|
30
|
+
* single definition of an authored mention — the Host delivery resolution and
|
|
31
|
+
* the Client's chip rendering and draft preview all read through it, so a name
|
|
32
|
+
* that renders as a chip is the same name that delivers a notification.
|
|
33
|
+
*/
|
|
34
|
+
export function scanBodyHandles(body, handles) {
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
const usable = [];
|
|
37
|
+
for (const handle of handles) {
|
|
38
|
+
const key = handle.toLowerCase();
|
|
39
|
+
if (handle.trim() === '' || seen.has(key))
|
|
40
|
+
continue;
|
|
41
|
+
seen.add(key);
|
|
42
|
+
usable.push(handle);
|
|
43
|
+
}
|
|
44
|
+
if (usable.length === 0)
|
|
45
|
+
return Object.freeze([]);
|
|
46
|
+
const excluded = codeRanges(body);
|
|
47
|
+
// Longest first so `@Reeves` never resolves as `@Reeve` plus a stray letter.
|
|
48
|
+
const ordered = [...usable].sort((left, right) => right.length - left.length);
|
|
49
|
+
const pattern = new RegExp(`(?<![\\p{L}\\p{N}_@])@(?:${ordered.map(handle => escapeRegExp(handle)).join('|')})(?=$|[^\\p{L}\\p{N}_])`, 'giu');
|
|
50
|
+
const matches = [];
|
|
51
|
+
for (const match of body.matchAll(pattern)) {
|
|
52
|
+
const start = match.index ?? 0;
|
|
53
|
+
if (excluded.some(([rangeStart, rangeEnd]) => start >= rangeStart && start < rangeEnd))
|
|
54
|
+
continue;
|
|
55
|
+
const written = match[0].slice(1).toLowerCase();
|
|
56
|
+
const hit = ordered.find(handle => handle.toLowerCase() === written);
|
|
57
|
+
if (hit === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
matches.push(Object.freeze({ handle: hit, start, end: start + match[0].length }));
|
|
60
|
+
}
|
|
61
|
+
return Object.freeze(matches);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the `@Handle` mentions authored in one Message body.
|
|
65
|
+
*
|
|
66
|
+
* Callers pass the candidates reachable in the Message's Channel, so a name
|
|
67
|
+
* that resolves here is already an addressable target; handles the Channel
|
|
68
|
+
* cannot reach simply stay prose.
|
|
69
|
+
*
|
|
70
|
+
* `sender` is excluded because a Message never mentions its own author, which
|
|
71
|
+
* also keeps the result usable as a recipient set without further filtering.
|
|
72
|
+
*/
|
|
73
|
+
export function resolveBodyMentions(body, candidates, sender) {
|
|
74
|
+
const usable = candidates.filter(candidate => candidate.memberId !== sender && candidate.handle.trim() !== '');
|
|
75
|
+
const all = hasAllMarker(body);
|
|
76
|
+
if (usable.length === 0)
|
|
77
|
+
return { memberIds: Object.freeze([]), all };
|
|
78
|
+
const matched = new Set(scanBodyHandles(body, usable.map(candidate => candidate.handle)).map(match => match.handle.toLowerCase()));
|
|
79
|
+
return { memberIds: Object.freeze(usable.filter(candidate => matched.has(candidate.handle.toLowerCase())).map(candidate => candidate.memberId)), all };
|
|
80
|
+
}
|
|
@@ -254,6 +254,7 @@ const threadReadSnapshotDataSchema = z.object({
|
|
|
254
254
|
facts: z.array(readFactSchema),
|
|
255
255
|
readThroughSequence: z.number().int().nonnegative(),
|
|
256
256
|
remainingUnreadCount: z.number().int().nonnegative(),
|
|
257
|
+
earlierFactCount: z.number().int().nonnegative().optional(),
|
|
257
258
|
attention: attentionSchema.optional(),
|
|
258
259
|
inbox: inboxDeltaSchema,
|
|
259
260
|
}).strict().transform(omitUndefined);
|
|
@@ -364,6 +365,25 @@ const storedAgentTeamOperationSchema = z.discriminatedUnion('kind', [
|
|
|
364
365
|
...releaseSnapshotFields,
|
|
365
366
|
}).strict(),
|
|
366
367
|
}).strict(),
|
|
368
|
+
z.object({
|
|
369
|
+
...operationBase,
|
|
370
|
+
previousOperationId: operationIdSchema.nullable(),
|
|
371
|
+
kind: z.literal('team/member-workspace-joined'),
|
|
372
|
+
data: z.object({
|
|
373
|
+
workspaceId: workspaceIdSchema,
|
|
374
|
+
memberId: memberIdSchema,
|
|
375
|
+
}).strict(),
|
|
376
|
+
}).strict(),
|
|
377
|
+
z.object({
|
|
378
|
+
...operationBase,
|
|
379
|
+
previousOperationId: operationIdSchema.nullable(),
|
|
380
|
+
kind: z.literal('team/member-workspace-left'),
|
|
381
|
+
data: z.object({
|
|
382
|
+
workspaceId: workspaceIdSchema,
|
|
383
|
+
memberId: memberIdSchema,
|
|
384
|
+
...releaseSnapshotFields,
|
|
385
|
+
}).strict(),
|
|
386
|
+
}).strict(),
|
|
367
387
|
z.object({
|
|
368
388
|
...operationBase,
|
|
369
389
|
previousOperationId: operationIdSchema.nullable(),
|