@wowyuarm/dsh-agent-team 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/README.zh.md +17 -4
- package/package.json +53 -47
- package/packages/agent-team/README.md +6 -4
- package/packages/agent-team/README.zh.md +6 -4
- package/packages/agent-team/lib/context-management.js +40 -50
- package/packages/agent-team/lib/context-projection.js +15 -1
- package/packages/agent-team/lib/index.js +244 -154
- package/packages/agent-team/lib/invariant.js +38 -4
- package/packages/agent-team/lib/ledger.js +775 -383
- package/packages/agent-team/lib/member-context.js +6 -2
- package/packages/agent-team/lib/member-runtime.js +47 -9
- package/packages/agent-team/lib/member-time-context.js +20 -5
- package/packages/agent-team/lib/mentions.js +56 -0
- package/packages/agent-team/lib/pressure-policy.js +33 -9
- package/packages/agent-team/lib/session-event-cursor.js +116 -0
- package/packages/agent-team/lib/session-remediation.js +38 -26
- package/packages/agent-team/lib/spec.js +47 -34
- package/packages/agent-team/lib/stored-session-reader.js +138 -0
- package/packages/agent-team/lib/typert.host.js +157 -41
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +129 -32
- package/packages/agent-team/lib/types/context-management.d.ts +7 -0
- package/packages/agent-team/lib/types/context-management.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-projection.d.ts +8 -0
- package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +77 -7
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/invariant.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +251 -20
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-runtime.d.ts +2 -1
- package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/mentions.d.ts +34 -0
- package/packages/agent-team/lib/types/mentions.d.ts.map +1 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts +18 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
- package/packages/agent-team/lib/types/session-event-cursor.d.ts +119 -0
- package/packages/agent-team/lib/types/session-event-cursor.d.ts.map +1 -0
- package/packages/agent-team/lib/types/session-remediation.d.ts +23 -0
- package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -1
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/stored-session-reader.d.ts +92 -0
- package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -0
- package/packages/agent-team/lib/types/types/entities.d.ts +28 -20
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/operations.d.ts +45 -19
- package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +87 -4
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +49 -7
- 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 +2357 -1207
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +27 -7
- 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 +167 -35
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +23 -27
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +11 -5
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +26 -26
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +9 -2
- 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 +25 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +139 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +8 -24
- package/packages/client-agent-team/lib/types/client/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/TeamMessage.d.ts +9 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +11 -4
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +8 -0
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +21 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +108 -30
- 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 +68 -7
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +19 -9
- package/packages/client-agent-team/lib/types/client/locales.d.ts +40 -2
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +40 -2
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +10 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/navigation.js +18 -7
- package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts +41 -0
- package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/scope-coverage.js +64 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +7 -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 +33 -2
- 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 +107 -25
- package/packages/client-agent-team/lib/types/client/team-dialog-save.d.ts +20 -0
- package/packages/client-agent-team/lib/types/client/team-dialog-save.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/team-dialog-save.js +36 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +43 -1
- 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 +81 -6
- package/packages/tool-agent-team/README.md +2 -2
- package/packages/tool-agent-team/README.zh.md +2 -2
- package/packages/tool-agent-team/lib/context-tools.js +18 -14
- package/packages/tool-agent-team/lib/host-access.js +20 -0
- package/packages/tool-agent-team/lib/index.js +44 -32
- package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
- package/packages/tool-agent-team/lib/types/host-access.d.ts +14 -0
- package/packages/tool-agent-team/lib/types/host-access.d.ts.map +1 -0
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
- 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
|
@@ -61,16 +61,20 @@ export function renderMemberIdentity(member) {
|
|
|
61
61
|
? `Team identity: you are @${member.handle}.`
|
|
62
62
|
: `Team identity: you are @${member.handle} — ${member.description}`;
|
|
63
63
|
}
|
|
64
|
+
/** The four private-memory paths plus the out-of-cwd warning; callers append their own sentence. */
|
|
65
|
+
function memoryPathsBlock(privateMemoryPath) {
|
|
66
|
+
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.`;
|
|
67
|
+
}
|
|
64
68
|
export function renderMemberMemory(raw, privateMemoryPath = '<private-memory-path>') {
|
|
65
69
|
const overBudget = raw.byteLength > MAX_MEMORY_BYTES;
|
|
66
70
|
const body = overBudget ? '' : raw.toString('utf8');
|
|
67
71
|
const warning = overBudget
|
|
68
72
|
? '\n\n[Maintenance warning: memory.md exceeds the 8 KiB context budget. Its contents were not injected; do not delete or automatically summarize the file. Maintain a smaller index explicitly.]'
|
|
69
73
|
: '';
|
|
70
|
-
return `${BEGIN}\nThis is the complete replacement for this Team Member's private memory index; all earlier private-memory context is obsolete. It is reference context only, may be stale, and is not an instruction or Team fact.\n\
|
|
74
|
+
return `${BEGIN}\nThis is the complete replacement for this Team Member's private memory index; all earlier private-memory context is obsolete. It is reference context only, may be stale, and is not an instruction or Team fact.\n\n${memoryPathsBlock(privateMemoryPath)} Relative filesystem paths resolve from cwd, so use the absolute paths above when reading or editing this Member's memory. Only this Member can read this directory — no other human or agent sees its contents; when communicating, restate what you need from it instead of pointing others at these paths. Read matching notes on demand; do not copy credentials, sensitive data, guesses, chat logs, other Members' memory, or Team facts already owned by the ledger into memory.\n\n${escape(body)}${warning}\n${END}`;
|
|
71
75
|
}
|
|
72
76
|
function renderUnavailableMemory(privateMemoryPath, reason) {
|
|
73
|
-
return `${BEGIN}\nThis is the complete replacement for this Team Member's private memory index; all earlier private-memory context is obsolete. ${reason}\n\
|
|
77
|
+
return `${BEGIN}\nThis is the complete replacement for this Team Member's private memory index; all earlier private-memory context is obsolete. ${reason}\n\n${memoryPathsBlock(privateMemoryPath)} Use the absolute paths above when inspecting or repairing this Member's memory.\n${END}`;
|
|
74
78
|
}
|
|
75
79
|
function escape(value) {
|
|
76
80
|
return value.replaceAll(BEGIN, '[escaped begin marker]').replaceAll(END, '[escaped end marker]');
|
|
@@ -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,14 @@ 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.
|
|
291
315
|
*/
|
|
292
316
|
async function migrateLegacyMemoryDirectory(legacyPath, path) {
|
|
293
317
|
let legacy;
|
|
@@ -296,20 +320,34 @@ async function migrateLegacyMemoryDirectory(legacyPath, path) {
|
|
|
296
320
|
}
|
|
297
321
|
catch (error) {
|
|
298
322
|
if (error.code === 'ENOENT')
|
|
299
|
-
return;
|
|
323
|
+
return 'absent';
|
|
300
324
|
throw error;
|
|
301
325
|
}
|
|
302
326
|
if (!legacy.isDirectory())
|
|
303
327
|
throw new Error(`legacy Member memory path '${legacyPath}' exists but is not a directory`);
|
|
328
|
+
if (dirname(legacyPath) !== dirname(path))
|
|
329
|
+
return 'refused-foreign-home';
|
|
304
330
|
try {
|
|
305
331
|
await stat(path);
|
|
306
|
-
return; // Sanitized directory already present: migration already done or a new install.
|
|
332
|
+
return 'target-present'; // Sanitized directory already present: migration already done or a new install.
|
|
307
333
|
}
|
|
308
334
|
catch (error) {
|
|
309
335
|
if (error.code !== 'ENOENT')
|
|
310
336
|
throw error;
|
|
311
337
|
}
|
|
312
338
|
await rename(legacyPath, path);
|
|
339
|
+
return 'migrated';
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Whether one absolute path names a Member directory inside THIS process's
|
|
343
|
+
* members root. Only such a path is this process's to delete: a recorded path
|
|
344
|
+
* may name the same Member in another DSH home, where the removal happening
|
|
345
|
+
* here has no authority.
|
|
346
|
+
*/
|
|
347
|
+
function isInsideMembersRoot(candidate) {
|
|
348
|
+
const fromRoot = relative(dshHomePath('agent-team', 'members'), resolve(candidate));
|
|
349
|
+
// '' is the members root itself and '..' escapes it: only a real descendant is authorized.
|
|
350
|
+
return fromRoot.length > 0 && !fromRoot.startsWith('..') && !isAbsolute(fromRoot);
|
|
313
351
|
}
|
|
314
352
|
/** The colon-form twin path of one sanitized Member memory directory, when the segment form admits one. */
|
|
315
353
|
function twinMemoryDirectoryPath(sanitizedPath) {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
30
30
|
import { formatTeamDuration, formatTeamTimestamp } from "./time-format.js";
|
|
31
|
+
import { advanceOwnedSessionEventCursor } from "./session-event-cursor.js";
|
|
31
32
|
export const name = 'wowyuarm-agent-team-member-time-context';
|
|
32
33
|
/** Default minimum spacing between two snapshots within one turn, in ms. */
|
|
33
34
|
export const CLOCK_REFRESH_INTERVAL_MS = 1_800_000;
|
|
@@ -90,8 +91,22 @@ export function renderClockSnapshot(input) {
|
|
|
90
91
|
+ `Elapsed since the preceding ${baseline}: ${elapsed}.\n`
|
|
91
92
|
+ 'Team collaboration timestamps use UTC+8. Sequence and revision, not wall-clock time, determine ordering and concurrency.';
|
|
92
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* The clock baseline folded incrementally over one Session's own events. A
|
|
96
|
+
* rollover starts a fresh Session log, and the cache below is keyed by Session
|
|
97
|
+
* id, so the fold never guesses elapsed across generations: a missing prior
|
|
98
|
+
* event renders `unavailable`, not a fabricated baseline.
|
|
99
|
+
*/
|
|
100
|
+
const CLOCK_FOLD = {
|
|
101
|
+
start: emptyBaseline(),
|
|
102
|
+
step: (state, event) => applyClockEvent(state, event),
|
|
103
|
+
};
|
|
93
104
|
export function apply(ctx, config = {}) {
|
|
94
105
|
const refreshIntervalMs = config.refreshIntervalMs ?? CLOCK_REFRESH_INTERVAL_MS;
|
|
106
|
+
// Per-step fold state, reused for this plugin lifecycle: one entry per
|
|
107
|
+
// Member, replaced when that Member's Session changes, so a rollover neither
|
|
108
|
+
// resumes across generations nor retains every generation it leaves behind.
|
|
109
|
+
const cursors = new Map();
|
|
95
110
|
ctx.on('agent/pre-step', async ({ agent, turn, step, signal }, next) => {
|
|
96
111
|
const decision = await next();
|
|
97
112
|
if (decision.kind === 'reject' || signal.aborted)
|
|
@@ -102,13 +117,13 @@ export function apply(ctx, config = {}) {
|
|
|
102
117
|
const host = ctx.get('agentTeam');
|
|
103
118
|
if (host === undefined)
|
|
104
119
|
return decision;
|
|
105
|
-
|
|
120
|
+
const member = host.memberForAgent(agent);
|
|
121
|
+
if (member === undefined)
|
|
106
122
|
return decision;
|
|
107
123
|
const now = Date.now();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const baseline = foldClockBaseline(agent.session.ownEvents());
|
|
124
|
+
const owned = advanceOwnedSessionEventCursor(cursors.get(member.memberId), agent.session.id, CLOCK_FOLD, agent.session.ownEvents(), agent.session.inheritedEventCount);
|
|
125
|
+
cursors.set(member.memberId, owned);
|
|
126
|
+
const baseline = owned.cursor.value;
|
|
112
127
|
// Turn-first-step always samples; later steps sample only at the refresh
|
|
113
128
|
// interval, so a quick tool-dense turn stays at one line.
|
|
114
129
|
if (!shouldSampleClock(step, now, baseline, refreshIntervalMs))
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
/**
|
|
21
|
+
* Resolve the `@Handle` mentions authored in one Message body. Matching is
|
|
22
|
+
* deliberately the same shape the Client renders with — case-insensitive, on
|
|
23
|
+
* Unicode word boundaries, longest handle first — so a name that renders as a
|
|
24
|
+
* chip is the same name that delivers a notification.
|
|
25
|
+
*
|
|
26
|
+
* Callers pass the candidates reachable in the Message's Channel, so a name
|
|
27
|
+
* that resolves here is already an addressable target; handles the Channel
|
|
28
|
+
* cannot reach simply stay prose. Writing `@` is required: bare handles are
|
|
29
|
+
* ordinary words, and treating them as mentions would notify on every
|
|
30
|
+
* incidental name-drop.
|
|
31
|
+
*
|
|
32
|
+
* `sender` is excluded because a Message never mentions its own author, which
|
|
33
|
+
* also keeps the result usable as a recipient set without further filtering.
|
|
34
|
+
*/
|
|
35
|
+
export function resolveBodyMentions(body, candidates, sender) {
|
|
36
|
+
const usable = candidates.filter(candidate => candidate.memberId !== sender && candidate.handle.trim() !== '');
|
|
37
|
+
const excluded = codeRanges(body);
|
|
38
|
+
const inCode = (index) => excluded.some(([start, end]) => index >= start && index < end);
|
|
39
|
+
const allMarker = ALL_MARKER.exec(body);
|
|
40
|
+
const all = allMarker !== null && !inCode(allMarker.index);
|
|
41
|
+
if (usable.length === 0)
|
|
42
|
+
return { memberIds: Object.freeze([]), all };
|
|
43
|
+
// Longest first so `@Reeves` never resolves as `@Reeve` plus a stray letter.
|
|
44
|
+
const ordered = [...usable].sort((left, right) => right.handle.length - left.handle.length);
|
|
45
|
+
const pattern = new RegExp(`(?<![\\p{L}\\p{N}_@])@(?:${ordered.map(candidate => escapeRegExp(candidate.handle)).join('|')})(?=$|[^\\p{L}\\p{N}_])`, 'giu');
|
|
46
|
+
const matched = new Set();
|
|
47
|
+
for (const match of body.matchAll(pattern)) {
|
|
48
|
+
if (inCode(match.index ?? 0))
|
|
49
|
+
continue;
|
|
50
|
+
const written = match[0].slice(1).toLowerCase();
|
|
51
|
+
const hit = ordered.find(candidate => candidate.handle.toLowerCase() === written);
|
|
52
|
+
if (hit !== undefined)
|
|
53
|
+
matched.add(hit.memberId);
|
|
54
|
+
}
|
|
55
|
+
return { memberIds: Object.freeze(usable.filter(candidate => matched.has(candidate.memberId)).map(candidate => candidate.memberId)), all };
|
|
56
|
+
}
|
|
@@ -18,6 +18,26 @@ import { CONTEXT_WINDOW_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm';
|
|
|
18
18
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
19
19
|
import { CONTEXT_PRESSURE_NOTICE_SUMMARY } from "./context-management.js";
|
|
20
20
|
import { AGENT_TEAM_PLUGIN_ID } from "./context-source.js";
|
|
21
|
+
import { advanceOwnedSessionEventCursor } from "./session-event-cursor.js";
|
|
22
|
+
/**
|
|
23
|
+
* Whether this Session's own span already carries the pressure notice: either
|
|
24
|
+
* surfaced as a `user/message` or still queued in a durable
|
|
25
|
+
* `agent/inbox/spliced` insert. Expressed as a fold rather than a scan because
|
|
26
|
+
* the answer is monotone — once delivered it stays delivered for the
|
|
27
|
+
* generation — so the cursor only ever has to read events it has not seen.
|
|
28
|
+
*/
|
|
29
|
+
export const PRESSURE_NOTICE_FOLD = {
|
|
30
|
+
start: false,
|
|
31
|
+
step: (delivered, event) => {
|
|
32
|
+
if (delivered)
|
|
33
|
+
return true;
|
|
34
|
+
if (event.type === 'user/message' && isPressureNotice(event.data))
|
|
35
|
+
return true;
|
|
36
|
+
if (event.type === 'agent/inbox/spliced' && event.data.inserted.some(isPressureNotice))
|
|
37
|
+
return true;
|
|
38
|
+
return false;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
21
41
|
/** One pressure-notice text; concise, structured, and inside the reserve. */
|
|
22
42
|
export function contextPressureNoticeText(input) {
|
|
23
43
|
const claims = input.activeClaims.length === 0 ? 'none' : input.activeClaims.join(', ');
|
|
@@ -41,6 +61,14 @@ export class PressurePolicyCoordinator {
|
|
|
41
61
|
* Process-only by design: a restart re-earns one sequence per chain.
|
|
42
62
|
*/
|
|
43
63
|
overflowRetries = new Map();
|
|
64
|
+
/**
|
|
65
|
+
* Whether the one-shot notice was already delivered, folded incrementally per
|
|
66
|
+
* Member. The scan below is a monotone "has this ever happened" fold over the
|
|
67
|
+
* Session's own events, so a cursor can replace re-scanning the whole log on
|
|
68
|
+
* every step; identity guarding falls back to a cold fold when the Member's
|
|
69
|
+
* Session changed under the entry.
|
|
70
|
+
*/
|
|
71
|
+
noticeSeen = new Map();
|
|
44
72
|
disposed = false;
|
|
45
73
|
constructor(options) {
|
|
46
74
|
this.options = options;
|
|
@@ -55,14 +83,10 @@ export class PressurePolicyCoordinator {
|
|
|
55
83
|
* rollover starts a fresh Session whose own event span has no notice yet,
|
|
56
84
|
* which is exactly the documented re-arm.
|
|
57
85
|
*/
|
|
58
|
-
noticeDelivered(agent) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (event.type === 'agent/inbox/spliced' && event.data.inserted.some(isPressureNotice))
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
return false;
|
|
86
|
+
noticeDelivered(agent, memberId) {
|
|
87
|
+
const owned = advanceOwnedSessionEventCursor(this.noticeSeen.get(memberId), agent.session.id, PRESSURE_NOTICE_FOLD, agent.session.ownEvents(), agent.session.inheritedEventCount);
|
|
88
|
+
this.noticeSeen.set(memberId, owned);
|
|
89
|
+
return owned.cursor.value;
|
|
66
90
|
}
|
|
67
91
|
dispose() {
|
|
68
92
|
this.disposed = true;
|
|
@@ -98,7 +122,7 @@ export class PressurePolicyCoordinator {
|
|
|
98
122
|
const outcome = await this.enforceHardLimit(agent, member.memberId, member.sessionId, signal);
|
|
99
123
|
return outcome ? { kind: 'continue' } : { kind: 'reject' };
|
|
100
124
|
}
|
|
101
|
-
if (usageTokens >= handoffAt && !this.noticeDelivered(agent)) {
|
|
125
|
+
if (usageTokens >= handoffAt && !this.noticeDelivered(agent, member.memberId)) {
|
|
102
126
|
const notice = createUserMessage({
|
|
103
127
|
content: [{ type: 'text', text: contextPressureNoticeText({
|
|
104
128
|
usageTokens, handoffAt, hardLimit,
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incremental Session-log fold with an identity guard.
|
|
3
|
+
*
|
|
4
|
+
* Several Host projections are pure left folds over one Member Session's own
|
|
5
|
+
* events: context intent, the clock baseline, and the one-shot pressure
|
|
6
|
+
* notice. Folding the whole log on every event makes each of them cost
|
|
7
|
+
* O(session length) per step, so a long Session gets steadily slower. This
|
|
8
|
+
* module keeps the fold value together with how far it has consumed the log,
|
|
9
|
+
* so a step folds only the tail that arrived since — the same idiom the
|
|
10
|
+
* Harness Session itself uses for its own derived folds.
|
|
11
|
+
*
|
|
12
|
+
* The module is deliberately Session-agnostic: it takes a plain event array
|
|
13
|
+
* plus the log offset that array starts at, so a caller (including a
|
|
14
|
+
* measurement harness) can drive it without constructing a Session.
|
|
15
|
+
*
|
|
16
|
+
* Correctness rests on the log being append-only, which it is: Session logs
|
|
17
|
+
* have no truncation API, `seq` is the log index, and `seq === log.length` is
|
|
18
|
+
* a documented contiguity contract. A Session adopts a different log only by
|
|
19
|
+
* being constructed from another seed, which replaces the whole slice rather
|
|
20
|
+
* than editing one position.
|
|
21
|
+
*
|
|
22
|
+
* The guard covers the observable ways that contract can break: a changed
|
|
23
|
+
* `logFrom`, a shorter log, a different event type at the anchor. Each
|
|
24
|
+
* re-folds from `logFrom`, so a fork, resume, or rollover degrades to the cost
|
|
25
|
+
* of a cold fold and never to a wrong picture.
|
|
26
|
+
*
|
|
27
|
+
* Named residual: the guard anchors on `(seq, type)`, so an event replaced in
|
|
28
|
+
* place by another of the SAME type at the SAME seq is not detected, and the
|
|
29
|
+
* cursor would keep its old value and fold only the tail. Reaching that state
|
|
30
|
+
* requires a caller to violate the append-only contract above — reading one
|
|
31
|
+
* Session's cursor against a log someone rewrote in place. A content-sensitive
|
|
32
|
+
* anchor would not close it either: only the anchor position is re-read, so a
|
|
33
|
+
* swap at any earlier folded position passes a boundary digest just as
|
|
34
|
+
* silently, while costing a payload digest on every advance. Closing it
|
|
35
|
+
* properly means fingerprinting every folded event, which is the full refold
|
|
36
|
+
* this module exists to avoid; if the contract ever changes, that is the fix.
|
|
37
|
+
* @module @wowyuarm/dsh-agent-team/session-event-cursor
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Open a cursor for a log slice that starts at `logFrom` (a Session's
|
|
41
|
+
* `inheritedEventCount` for its own events, or 0 for a full log). Nothing is
|
|
42
|
+
* consumed yet, so the first {@link advanceSessionEventCursor} folds the whole
|
|
43
|
+
* slice.
|
|
44
|
+
*/
|
|
45
|
+
export function initSessionEventCursor(fold, logFrom = 0) {
|
|
46
|
+
return { value: fold.start, foldedThrough: logFrom, logFrom, anchor: null };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fold the log tail that arrived since the cursor stopped and return the
|
|
50
|
+
* advanced cursor; the same reference comes back when there is nothing new.
|
|
51
|
+
*
|
|
52
|
+
* `events` is the slice starting at `logFrom`, and `logEnd` is the EXCLUSIVE
|
|
53
|
+
* end offset of that slice in the owning log — the same coordinate space as
|
|
54
|
+
* `logFrom`, not `events.length`. An inherited Session's own events start at
|
|
55
|
+
* its `inheritedEventCount`, so for a 14-event log with 10 inherited events
|
|
56
|
+
* the slice is `events[0..4]` spanning `logFrom = 10` to `logEnd = 14`. This
|
|
57
|
+
* mirrors the Session's own `snapshotEvents(fromSeq, toSeqExclusive)`.
|
|
58
|
+
*
|
|
59
|
+
* Both offsets are passed explicitly rather than read from the cursor so the
|
|
60
|
+
* caller states the log it is folding, which is also what makes the fallback
|
|
61
|
+
* below decidable.
|
|
62
|
+
*/
|
|
63
|
+
export function advanceSessionEventCursor(cursor, events, logFrom, logEnd, fold) {
|
|
64
|
+
if (logEnd !== logFrom + events.length) {
|
|
65
|
+
// The slice and the log it claims to describe disagree, so the tail it is
|
|
66
|
+
// missing is indistinguishable from events that were never there. Refuse
|
|
67
|
+
// rather than fold a shape the caller did not state.
|
|
68
|
+
throw new RangeError(`session event cursor: ${events.length} events cannot span [${logFrom}, ${logEnd})`);
|
|
69
|
+
}
|
|
70
|
+
// Resume only from a position the log can still justify. Every other case
|
|
71
|
+
// re-folds from `logFrom`: one path handles truncation, a changed fork
|
|
72
|
+
// prefix, and a rebuilt log alike, so no caller has to classify them.
|
|
73
|
+
const resumable = cursor.logFrom === logFrom
|
|
74
|
+
&& cursor.foldedThrough >= logFrom
|
|
75
|
+
&& cursor.foldedThrough <= logEnd
|
|
76
|
+
&& (cursor.foldedThrough === logFrom || anchorMatches(cursor, events));
|
|
77
|
+
const from = resumable ? cursor.foldedThrough : logFrom;
|
|
78
|
+
if (from === logEnd)
|
|
79
|
+
return cursor;
|
|
80
|
+
let value = resumable ? cursor.value : fold.start;
|
|
81
|
+
for (let index = from; index < logEnd; index += 1)
|
|
82
|
+
value = fold.step(value, events[index - logFrom]);
|
|
83
|
+
return { value, foldedThrough: logEnd, logFrom, anchor: anchorOf(events[logEnd - 1 - logFrom]) };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Whether the event the cursor stopped on still occupies that position with
|
|
87
|
+
* the type it had. The position is checked as well as the type because `seq`
|
|
88
|
+
* is a log index: an event that moved is a different event.
|
|
89
|
+
*/
|
|
90
|
+
function anchorMatches(cursor, events) {
|
|
91
|
+
const anchor = cursor.anchor;
|
|
92
|
+
if (anchor === null)
|
|
93
|
+
return false;
|
|
94
|
+
const seen = events[anchor.seq - cursor.logFrom];
|
|
95
|
+
return seen !== undefined && seen.seq === anchor.seq && seen.type === anchor.type;
|
|
96
|
+
}
|
|
97
|
+
/** The identity a cursor anchors on when it stops at the last consumed event. */
|
|
98
|
+
function anchorOf(event) {
|
|
99
|
+
if (event === undefined)
|
|
100
|
+
return null;
|
|
101
|
+
return { seq: event.seq, type: event.type };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Advance one owner's fold and answer with the entry to store back, so every
|
|
105
|
+
* caller keeps the same one-entry-per-owner invariant.
|
|
106
|
+
*
|
|
107
|
+
* `events` is that Session's whole own-events slice starting at `logFrom` (its
|
|
108
|
+
* `inheritedEventCount`), so the slice's end is the log's end. The cursor is
|
|
109
|
+
* resumed only while `owned` still belongs to `sessionId`; every other case
|
|
110
|
+
* starts cold at `logFrom`, which is the same degradation the cursor's own
|
|
111
|
+
* guard performs for a rewritten log.
|
|
112
|
+
*/
|
|
113
|
+
export function advanceOwnedSessionEventCursor(owned, sessionId, fold, events, logFrom) {
|
|
114
|
+
const cursor = owned !== undefined && owned.sessionId === sessionId ? owned.cursor : initSessionEventCursor(fold, logFrom);
|
|
115
|
+
return { sessionId, cursor: advanceSessionEventCursor(cursor, events, logFrom, logFrom + events.length, fold) };
|
|
116
|
+
}
|
|
@@ -266,11 +266,6 @@ function physicalParentSession(rows) {
|
|
|
266
266
|
const parent = rows[0]?.parentSession;
|
|
267
267
|
return typeof parent === 'string' ? parent : undefined;
|
|
268
268
|
}
|
|
269
|
-
/**
|
|
270
|
-
* The startup remediation pass. Never throws to its caller: a failed or
|
|
271
|
-
* partial walk logs and leaves the completion cache unrecorded, so the next
|
|
272
|
-
* start retries exactly the Members that need it.
|
|
273
|
-
*/
|
|
274
269
|
export class SessionRemediation {
|
|
275
270
|
ctx;
|
|
276
271
|
persistence;
|
|
@@ -303,29 +298,43 @@ export class SessionRemediation {
|
|
|
303
298
|
for (const member of members) {
|
|
304
299
|
if (member.state !== 'enabled')
|
|
305
300
|
continue;
|
|
306
|
-
|
|
301
|
+
const outcome = await this.remediateMember(member);
|
|
302
|
+
if (outcome.cacheHit)
|
|
307
303
|
continue;
|
|
308
304
|
walked += 1;
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
try {
|
|
312
|
-
const summary = await this.remediateLineage(member);
|
|
313
|
-
repairedCount = summary.repaired;
|
|
314
|
-
untouchedCount = summary.untouched;
|
|
315
|
-
}
|
|
316
|
-
catch (error) {
|
|
317
|
-
// Unexpected per-Member failure: log and leave the cache unrecorded.
|
|
318
|
-
this.ctx.logger.warn(`agent-team: legacy Session remediation for member '${member.handle}' did not complete: ${error instanceof Error ? error.message : String(error)}`);
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
repaired += repairedCount;
|
|
322
|
-
untouched += untouchedCount;
|
|
323
|
-
await this.recordCompletion(member);
|
|
305
|
+
repaired += outcome.repaired;
|
|
306
|
+
untouched += outcome.untouched;
|
|
324
307
|
}
|
|
325
308
|
if (walked > 0) {
|
|
326
309
|
this.ctx.logger.info(`agent-team: legacy Session remediation walked ${walked} member lineage(s): ${repaired} artifact(s) repaired, ${untouched} left untouched`);
|
|
327
310
|
}
|
|
328
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Remediate one Member's lineage; the bounded in-place heal a restart
|
|
314
|
+
* performs after an activation refused on a session. `completed` with zero
|
|
315
|
+
* repairs is the deterministic nothing-to-do answer (a finished walk found
|
|
316
|
+
* nothing provably this plugin's, or the cache already covered the Member);
|
|
317
|
+
* `completed: false` means the walk itself failed and a later attempt
|
|
318
|
+
* should retry. Never throws.
|
|
319
|
+
*/
|
|
320
|
+
async remediateMember(member) {
|
|
321
|
+
if (member.state !== 'enabled' || this.cacheStillValid(member))
|
|
322
|
+
return { repaired: 0, untouched: 0, completed: true, cacheHit: true };
|
|
323
|
+
try {
|
|
324
|
+
const summary = await this.remediateLineage(member);
|
|
325
|
+
// A walk that failed a repair is not evidence of a clean lineage: it is
|
|
326
|
+
// left uncached so the next start retries exactly this Member, and it
|
|
327
|
+
// reports `completed: false` so no caller reads it as the deterministic
|
|
328
|
+
// nothing-to-do answer.
|
|
329
|
+
if (summary.failed === 0)
|
|
330
|
+
await this.recordCompletion(member);
|
|
331
|
+
return { repaired: summary.repaired, untouched: summary.untouched, completed: summary.failed === 0, cacheHit: false };
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
this.ctx.logger.warn(`agent-team: legacy Session remediation for member '${member.handle}' did not complete: ${error instanceof Error ? error.message : String(error)}`);
|
|
335
|
+
return { repaired: 0, untouched: 0, completed: false, cacheHit: false };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
329
338
|
/** Whether the cached walk still covers this Member under the current format. */
|
|
330
339
|
cacheStillValid(member) {
|
|
331
340
|
const record = this.table?.get(member.memberId);
|
|
@@ -360,6 +369,7 @@ export class SessionRemediation {
|
|
|
360
369
|
async remediateLineage(member) {
|
|
361
370
|
let repaired = 0;
|
|
362
371
|
let untouched = 0;
|
|
372
|
+
let failed = 0;
|
|
363
373
|
const seen = new Set();
|
|
364
374
|
let sessionId = member.sessionId;
|
|
365
375
|
for (let depth = 0; sessionId !== undefined && !seen.has(sessionId) && depth < MAX_LINEAGE_DEPTH; depth += 1) {
|
|
@@ -369,9 +379,11 @@ export class SessionRemediation {
|
|
|
369
379
|
repaired += 1;
|
|
370
380
|
else if (outcome.status === 'left-untouched')
|
|
371
381
|
untouched += 1;
|
|
382
|
+
else if (outcome.status === 'repair-failed')
|
|
383
|
+
failed += 1;
|
|
372
384
|
sessionId = outcome.parentSession === undefined ? undefined : outcome.parentSession;
|
|
373
385
|
}
|
|
374
|
-
return { repaired, untouched };
|
|
386
|
+
return { repaired, untouched, failed };
|
|
375
387
|
}
|
|
376
388
|
/** Remediate one lineage Session: verify readability, and repair when the refusal is this plugin's legacy source shape. */
|
|
377
389
|
async remediateOne(sessionId, member) {
|
|
@@ -409,7 +421,7 @@ export class SessionRemediation {
|
|
|
409
421
|
}
|
|
410
422
|
catch (readError) {
|
|
411
423
|
this.ctx.logger.warn(`agent-team: could not decode the stored artifact of Session '${sessionId}' (${basename(path)}): ${readError instanceof Error ? readError.message : String(readError)}`);
|
|
412
|
-
return { status: '
|
|
424
|
+
return { status: 'repair-failed', parentSession: undefined, reason: 'artifact undecodable' };
|
|
413
425
|
}
|
|
414
426
|
const parentSession = physicalParentSession(rows);
|
|
415
427
|
if (!rows.some(row => containsLegacySource(row))) {
|
|
@@ -423,7 +435,7 @@ export class SessionRemediation {
|
|
|
423
435
|
}
|
|
424
436
|
catch (proofError) {
|
|
425
437
|
this.ctx.logger.warn(`agent-team: Session '${sessionId}' of member '${member.handle}' still refuses after the legacy source rewrite (${proofError instanceof Error ? proofError.message : String(proofError)}); leaving it untouched`);
|
|
426
|
-
return { status: '
|
|
438
|
+
return { status: 'repair-failed', parentSession, reason: 'residual refusal after rewrite' };
|
|
427
439
|
}
|
|
428
440
|
try {
|
|
429
441
|
const published = await this.publishSibling(path, encodeCurrentArtifact(artifact));
|
|
@@ -434,7 +446,7 @@ export class SessionRemediation {
|
|
|
434
446
|
}
|
|
435
447
|
catch (publishError) {
|
|
436
448
|
this.ctx.logger.warn(`agent-team: could not publish the remediated sibling for Session '${sessionId}' (${basename(path)}): ${publishError instanceof Error ? publishError.message : String(publishError)}`);
|
|
437
|
-
return { status: '
|
|
449
|
+
return { status: 'repair-failed', parentSession, reason: 'publish failed' };
|
|
438
450
|
}
|
|
439
451
|
// Success is proven by the storage layer itself, not by our own writer.
|
|
440
452
|
try {
|
|
@@ -448,7 +460,7 @@ export class SessionRemediation {
|
|
|
448
460
|
}
|
|
449
461
|
catch (verifyError) {
|
|
450
462
|
this.ctx.logger.warn(`agent-team: the remediated sibling for Session '${sessionId}' was published but still refuses: ${verifyError instanceof Error ? verifyError.message : String(verifyError)}`);
|
|
451
|
-
return { status: '
|
|
463
|
+
return { status: 'repair-failed', parentSession, reason: 'sibling refused after publish' };
|
|
452
464
|
}
|
|
453
465
|
this.ctx.logger.info(`agent-team: repaired legacy source kinds in Session '${sessionId}' of member '${member.handle}' by publishing a current-format sibling; the original artifact is untouched`);
|
|
454
466
|
return { status: 'repaired', parentSession };
|