@wowyuarm/dsh-agent-team 0.1.9 → 0.1.10
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 +4 -11
- package/README.zh.md +4 -11
- package/package.json +53 -44
- package/packages/agent-team/README.md +2 -2
- package/packages/agent-team/README.zh.md +2 -1
- package/packages/agent-team/lib/context-management.js +8 -5
- package/packages/agent-team/lib/context-projection.js +12 -8
- package/packages/agent-team/lib/context-source.js +163 -23
- package/packages/agent-team/lib/index.js +174 -124
- package/packages/agent-team/lib/ledger.js +111 -30
- package/packages/agent-team/lib/member-time-context.js +126 -0
- package/packages/agent-team/lib/pressure-policy.js +1 -2
- package/packages/agent-team/lib/progress-nudge.js +12 -4
- package/packages/agent-team/lib/session-remediation.js +481 -0
- package/packages/agent-team/lib/spec.js +3 -2
- package/packages/agent-team/lib/time-format.js +56 -0
- package/packages/agent-team/lib/typert.host.js +82 -49
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +46 -25
- package/packages/agent-team/lib/types/context-management.d.ts +6 -1
- package/packages/agent-team/lib/types/context-management.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-source.d.ts +70 -36
- package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +32 -7
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +25 -0
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-time-context.d.ts +83 -0
- package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
- package/packages/agent-team/lib/types/progress-nudge.d.ts.map +1 -1
- package/packages/agent-team/lib/types/session-remediation.d.ts +149 -0
- package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/time-format.d.ts +28 -0
- package/packages/agent-team/lib/types/time-format.d.ts.map +1 -0
- package/packages/agent-team/lib/types/types/entities.d.ts +8 -0
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +6 -0
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +19 -4
- package/packages/client-agent-team/lib/client.js +80 -54
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +4 -4
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +10 -5
- package/packages/client-agent-team/lib/types/client/slots.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/tool-agent-team/README.md +8 -6
- package/packages/tool-agent-team/README.zh.md +8 -6
- package/packages/tool-agent-team/lib/index.js +302 -110
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
|
@@ -39,58 +39,6 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
39
39
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
40
40
|
done = true;
|
|
41
41
|
};
|
|
42
|
-
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
43
|
-
if (value !== null && value !== void 0) {
|
|
44
|
-
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
45
|
-
var dispose, inner;
|
|
46
|
-
if (async) {
|
|
47
|
-
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
48
|
-
dispose = value[Symbol.asyncDispose];
|
|
49
|
-
}
|
|
50
|
-
if (dispose === void 0) {
|
|
51
|
-
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
52
|
-
dispose = value[Symbol.dispose];
|
|
53
|
-
if (async) inner = dispose;
|
|
54
|
-
}
|
|
55
|
-
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
56
|
-
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
57
|
-
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
58
|
-
}
|
|
59
|
-
else if (async) {
|
|
60
|
-
env.stack.push({ async: true });
|
|
61
|
-
}
|
|
62
|
-
return value;
|
|
63
|
-
};
|
|
64
|
-
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
65
|
-
return function (env) {
|
|
66
|
-
function fail(e) {
|
|
67
|
-
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
68
|
-
env.hasError = true;
|
|
69
|
-
}
|
|
70
|
-
var r, s = 0;
|
|
71
|
-
function next() {
|
|
72
|
-
while (r = env.stack.pop()) {
|
|
73
|
-
try {
|
|
74
|
-
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
75
|
-
if (r.dispose) {
|
|
76
|
-
var result = r.dispose.call(r.value);
|
|
77
|
-
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
78
|
-
}
|
|
79
|
-
else s |= 1;
|
|
80
|
-
}
|
|
81
|
-
catch (e) {
|
|
82
|
-
fail(e);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
86
|
-
if (env.hasError) throw env.error;
|
|
87
|
-
}
|
|
88
|
-
return next();
|
|
89
|
-
};
|
|
90
|
-
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
91
|
-
var e = new Error(message);
|
|
92
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
93
|
-
});
|
|
94
42
|
import { randomUUID } from 'node:crypto';
|
|
95
43
|
import { isDeepStrictEqual } from 'node:util';
|
|
96
44
|
import { Service } from '@deepseek-ai/cordis';
|
|
@@ -98,25 +46,27 @@ import { installModelSelection } from '@deepseek-ai/dsh-agent';
|
|
|
98
46
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
99
47
|
import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
|
|
100
48
|
import { scopeOf } from '@deepseek-ai/dsh-scope';
|
|
101
|
-
import { SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
49
|
+
import { Session, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
50
|
+
import { SessionFormatUnsupportedError } from '@deepseek-ai/dsh-session-persistence';
|
|
102
51
|
import { setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy';
|
|
103
52
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
104
53
|
import { ATTACHMENT_MAX_BYTES, attachmentPayloadPath, attachmentsRoot, copyPathAttachment, newAttachmentId, readAttachment, sanitizeMediaType, sweepAttachmentCache, validatePathAttachment, writeAttachment } from "./attachments.js";
|
|
105
54
|
import { PressurePolicyCoordinator } from "./pressure-policy.js";
|
|
106
55
|
import { ContextManagementCoordinator } from "./context-management.js";
|
|
107
|
-
import { createHandoffMessage } from "./context-source.js";
|
|
56
|
+
import { AGENT_TEAM_PLUGIN_ID, createHandoffMessage } from "./context-source.js";
|
|
108
57
|
import { carriedInputOf, checkpointByRef, checkpointRefFor, foldContextProjection, isReminderNoticeSummary, timelineCandidates } from "./context-projection.js";
|
|
109
58
|
import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
|
|
110
59
|
import { AGENT_TEAM_TOOL_NAMES, deepCopyCapabilities, memberMemoryDirectoryName, MemberRuntime } from "./member-runtime.js";
|
|
111
60
|
import { ProgressNudgeCoordinator } from "./progress-nudge.js";
|
|
112
61
|
import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
|
|
62
|
+
import { SessionRemediation, handoffAlreadyInLog } from "./session-remediation.js";
|
|
113
63
|
import { agentTeamDomainSpec } from "./spec.js";
|
|
64
|
+
import { formatTeamTimestamp } from "./time-format.js";
|
|
114
65
|
export { agentTeamDomainSpec, agentTeamOperationSchema } from "./spec.js";
|
|
115
66
|
export { AGENT_TEAM_HUMAN_MEMBER_ID, AGENT_TEAM_INITIALIZE_REQUEST_ID } from "./ledger.js";
|
|
116
67
|
export { AGENT_TEAM_TOOL_NAMES } from "./member-runtime.js";
|
|
117
68
|
/** Process-stable marker carried by the final Team message tool definition. */
|
|
118
69
|
export const AGENT_TEAM_PRESET_MARKER = Symbol.for('@wowyuarm/dsh-agent-team.preset');
|
|
119
|
-
const AGENT_TEAM_PLUGIN_ID = '@wowyuarm/dsh-agent-team';
|
|
120
70
|
const INBOX_NOTICE_SUMMARY = 'Team Inbox has unread work.';
|
|
121
71
|
const RECOVERY_NOTICE_SUMMARY = 'Recovery: continue your interrupted work.';
|
|
122
72
|
const ORPHANED_MEMBER_DIAGNOSTIC = 'Member preset composition was lost after a reload; its tools are unavailable. Resume rebuilds the member in place.';
|
|
@@ -152,6 +102,35 @@ export function markAgentTeamPreset(definition) {
|
|
|
152
102
|
Object.defineProperty(definition, AGENT_TEAM_PRESET_MARKER, { value: true });
|
|
153
103
|
return definition;
|
|
154
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Whether the running dsh loads workspace packages from source via tsx.
|
|
107
|
+
*
|
|
108
|
+
* The Harness `tsconfig.base.json` maps `@deepseek-ai/*` package names onto
|
|
109
|
+
* `src/` directories; tsx honors those paths, so a CLI launched with
|
|
110
|
+
* `node --import tsx/esm apps/cli/src/bin.ts` imports `@deepseek-ai/dsh-scope`
|
|
111
|
+
* from `src/index.ts` while a profile-installed bundle resolves the compiled
|
|
112
|
+
* `lib/index.js` — two module instances with independent scope keys.
|
|
113
|
+
*/
|
|
114
|
+
export function isTsxDevMode() {
|
|
115
|
+
const flags = [...process.execArgv];
|
|
116
|
+
const nodeOptions = process.env.NODE_OPTIONS;
|
|
117
|
+
if (nodeOptions !== undefined)
|
|
118
|
+
flags.push(...nodeOptions.split(/\s+/));
|
|
119
|
+
return flags.some(flag => flag.includes('tsx'));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The activation diagnostic for a dsh-scope module-instance mismatch.
|
|
123
|
+
*
|
|
124
|
+
* `agentPresets.mount` already rejected an unscoped context, so a scope key
|
|
125
|
+
* the harness sees but this bundle does not can only mean the two sides
|
|
126
|
+
* loaded different physical copies of `@deepseek-ai/dsh-scope`.
|
|
127
|
+
*/
|
|
128
|
+
export function teamPresetScopeMismatchMessage(tsxDevMode) {
|
|
129
|
+
if (tsxDevMode) {
|
|
130
|
+
return 'selected preset is not team-enabled: the dsh CLI is running from source via tsx (tsconfig paths resolve @deepseek-ai/dsh-scope to src/), so the harness and this bundle load different module instances; start dsh with the compiled CLI instead (pnpm dsh, node apps/cli/lib/bin.js, or npx @deepseek-ai/dsh)';
|
|
131
|
+
}
|
|
132
|
+
return 'selected preset is not team-enabled: this bundle and the harness resolved different physical copies of @deepseek-ai/dsh-scope; run pnpm install in the profile directory so node_modules matches the lockfile, then restart';
|
|
133
|
+
}
|
|
155
134
|
/**
|
|
156
135
|
* A DM was durably recorded but its session injection could not run (no live
|
|
157
136
|
* handle, or the wake itself failed). The recorded DM stays durable; the
|
|
@@ -454,9 +433,20 @@ let AgentTeam = (() => {
|
|
|
454
433
|
if (initialization.committed)
|
|
455
434
|
this.emitCommitted(initialization.value);
|
|
456
435
|
this.startAttachmentGc(ledger);
|
|
436
|
+
// Legacy-artifact remediation runs before any Member activation: no write
|
|
437
|
+
// lease exists yet, so publishing sibling generations for refused Session
|
|
438
|
+
// logs cannot race a live writer. Remediation failure never blocks
|
|
439
|
+
// startup — the next start retries exactly what the cache does not cover.
|
|
440
|
+
try {
|
|
441
|
+
const remediation = new SessionRemediation(this.ctx, this.ctx.sessionPersistence, await SessionRemediation.open(this.ctx));
|
|
442
|
+
await remediation.remediateEnabledMembers(ledger.listMembers());
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
this.ctx.logger.warn(`agent-team: legacy Session remediation did not run to completion (it will retry on the next start): ${error instanceof Error ? error.message : String(error)}`);
|
|
446
|
+
}
|
|
457
447
|
// One metadata listing serves every Member restore; per-member list calls
|
|
458
448
|
// would repeat the same I/O linearly during startup.
|
|
459
|
-
const persistedSessions = new Set((await this.persistedSessionHeaders()).map(
|
|
449
|
+
const persistedSessions = new Set((await this.persistedSessionHeaders()).map(snapshot => snapshot.header.id));
|
|
460
450
|
for (const member of ledger.listMembers()) {
|
|
461
451
|
if (member.state === 'enabled')
|
|
462
452
|
await this.activateMember(member, undefined, persistedSessions);
|
|
@@ -466,20 +456,14 @@ let AgentTeam = (() => {
|
|
|
466
456
|
}
|
|
467
457
|
/**
|
|
468
458
|
* Whether one Member Session has durable persisted content, decided through
|
|
469
|
-
* {@link
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
459
|
+
* {@link SessionPersistence.stat} rather than a bare metadata listing: the
|
|
460
|
+
* backend reports a still-draining session through its pending header, so a
|
|
461
|
+
* resume racing a suspend's fire-and-forget final flush cannot mistake a
|
|
462
|
+
* still-draining persisted Session for an unpersisted one and fork a fresh
|
|
463
|
+
* generation over it.
|
|
474
464
|
*/
|
|
475
465
|
async sessionPersisted(sessionId) {
|
|
476
|
-
|
|
477
|
-
await this.ctx.sessionPersistence.inspect(sessionId);
|
|
478
|
-
return true;
|
|
479
|
-
}
|
|
480
|
-
catch {
|
|
481
|
-
return false;
|
|
482
|
-
}
|
|
466
|
+
return (await this.ctx.sessionPersistence.stat(sessionId)) !== undefined;
|
|
483
467
|
}
|
|
484
468
|
/** Resolve one exact live Agent to its durable Team Member; forks do not inherit identity. */
|
|
485
469
|
memberForAgent(agent) {
|
|
@@ -1240,7 +1224,7 @@ let AgentTeam = (() => {
|
|
|
1240
1224
|
}
|
|
1241
1225
|
try {
|
|
1242
1226
|
const message = createUserMessage({
|
|
1243
|
-
content: [{ type: 'text', text: this.dmRelayText(agent, recipient, request.body.trim(), result.value.receipt.operationId) }],
|
|
1227
|
+
content: [{ type: 'text', text: this.dmRelayText(agent, recipient, request.body.trim(), result.value.receipt.occurredAt, result.value.receipt.operationId) }],
|
|
1244
1228
|
source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'relay' },
|
|
1245
1229
|
});
|
|
1246
1230
|
// An idle recipient gets one ordinary turn; a busy one is steered into
|
|
@@ -1256,10 +1240,10 @@ let AgentTeam = (() => {
|
|
|
1256
1240
|
return result.value;
|
|
1257
1241
|
}
|
|
1258
1242
|
/** Relay body: the DM itself plus one bounded line of adjacent context. */
|
|
1259
|
-
dmRelayText(senderAgent, recipient, body, excluding) {
|
|
1243
|
+
dmRelayText(senderAgent, recipient, body, occurredAt, excluding) {
|
|
1260
1244
|
const sender = this.memberForAgent(senderAgent);
|
|
1261
1245
|
const prior = this.requireLedger().dmHistoryBetween(senderAgent.id, recipient.memberId, excluding);
|
|
1262
|
-
const header = `Direct message from @${sender?.handle ?? 'a Team Member'}:`;
|
|
1246
|
+
const header = `Direct message from @${sender?.handle ?? 'a Team Member'} at ${formatTeamTimestamp(occurredAt)}:`;
|
|
1263
1247
|
const context = prior === undefined ? '' : `\n\n[most recent prior DM between you: ${prior}]`;
|
|
1264
1248
|
return `${header}\n\n${body}${context}`;
|
|
1265
1249
|
}
|
|
@@ -1379,10 +1363,15 @@ let AgentTeam = (() => {
|
|
|
1379
1363
|
}
|
|
1380
1364
|
else {
|
|
1381
1365
|
try {
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1366
|
+
const handle = await this.ctx.sessionPersistence.open(sessionId, 'read');
|
|
1367
|
+
try {
|
|
1368
|
+
events = (await handle.read()).events;
|
|
1369
|
+
inheritedEventCount = handle.inheritedEventCount;
|
|
1370
|
+
parentSession = handle.header.parentSession;
|
|
1371
|
+
}
|
|
1372
|
+
finally {
|
|
1373
|
+
await handle.close();
|
|
1374
|
+
}
|
|
1386
1375
|
}
|
|
1387
1376
|
catch (error) {
|
|
1388
1377
|
throw new Error(`checkpoint '${checkpointRef}' could not be resolved: its source Session is unreadable (${error instanceof Error ? error.message : String(error)})`);
|
|
@@ -1465,8 +1454,13 @@ let AgentTeam = (() => {
|
|
|
1465
1454
|
return;
|
|
1466
1455
|
let inspection;
|
|
1467
1456
|
try {
|
|
1468
|
-
const
|
|
1469
|
-
|
|
1457
|
+
const handle = await this.ctx.sessionPersistence.open(previousSessionId, 'read');
|
|
1458
|
+
try {
|
|
1459
|
+
inspection = { events: (await handle.read()).events, inheritedEventCount: handle.inheritedEventCount };
|
|
1460
|
+
}
|
|
1461
|
+
finally {
|
|
1462
|
+
await handle.close();
|
|
1463
|
+
}
|
|
1470
1464
|
}
|
|
1471
1465
|
catch (error) {
|
|
1472
1466
|
this.ctx.logger.warn(`agent-team: rollover handoff reconstruction could not read the previous Session '${previousSessionId}': ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1502,7 +1496,13 @@ let AgentTeam = (() => {
|
|
|
1502
1496
|
async recordedCheckpointPrefix(seed) {
|
|
1503
1497
|
let inspection;
|
|
1504
1498
|
try {
|
|
1505
|
-
|
|
1499
|
+
const handle = await this.ctx.sessionPersistence.open(seed.sourceSessionId, 'read');
|
|
1500
|
+
try {
|
|
1501
|
+
inspection = { events: (await handle.read()).events, inheritedEventCount: handle.inheritedEventCount };
|
|
1502
|
+
}
|
|
1503
|
+
finally {
|
|
1504
|
+
await handle.close();
|
|
1505
|
+
}
|
|
1506
1506
|
}
|
|
1507
1507
|
catch (error) {
|
|
1508
1508
|
throw new Error(`the recorded checkpoint-return seed source Session '${seed.sourceSessionId}' is unreadable: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1530,21 +1530,63 @@ let AgentTeam = (() => {
|
|
|
1530
1530
|
* CURRENTLY present: delivered `user/message` ids plus the live inbox's
|
|
1531
1531
|
* pending next-step/next-turn ids — a historical insert that was already
|
|
1532
1532
|
* claimed (and removed) but never surfaced is NOT known and must replay.
|
|
1533
|
-
* Fail-closed
|
|
1534
|
-
*
|
|
1533
|
+
* Fail-closed for genuinely unreadable previous Sessions (missing/IO) so
|
|
1534
|
+
* the Member's input is never dropped silently, with three bounded
|
|
1535
|
+
* exceptions: a generation that already started its own turns needs no
|
|
1536
|
+
* replay (its carried input was delivered or superseded while it ran), a
|
|
1537
|
+
* log-corruption class error skips with a warning (the Host repairs torn
|
|
1538
|
+
* tails; a retired generation's corrupt log must not permanently block the
|
|
1539
|
+
* Member's activation), and a deterministic released-format refusal skips
|
|
1540
|
+
* with a warning (the candidate's own migration audit refuses that
|
|
1541
|
+
* artifact, so no retry can ever read it).
|
|
1535
1542
|
*/
|
|
1536
|
-
async replayCarriedInput(member, agent) {
|
|
1543
|
+
async replayCarriedInput(member, agent, generationStarted) {
|
|
1537
1544
|
const transition = this.requireLedger().lastTransitionForMember(member.memberId);
|
|
1538
1545
|
if (transition === undefined || transition.targetSessionId !== agent.session.id)
|
|
1539
1546
|
return 0;
|
|
1540
|
-
|
|
1547
|
+
// A generation that already started its own turns needs no previous-Session
|
|
1548
|
+
// replay: its carried input was delivered with the handoff (or superseded)
|
|
1549
|
+
// before any own turn could run. Skipping the inspect also keeps a later
|
|
1550
|
+
// corruption or loss of the retired Session from re-blocking this Member
|
|
1551
|
+
// on every restart of the current generation.
|
|
1552
|
+
if (generationStarted) {
|
|
1553
|
+
this.ctx.logger.info(`agent-team: skipping carried-input replay for member '${this.memberLabel(member.memberId)}': the current Session '${agent.session.id}' already started a generation`);
|
|
1554
|
+
return 0;
|
|
1555
|
+
}
|
|
1556
|
+
let inspectionEvents;
|
|
1557
|
+
let inspectionInherited;
|
|
1541
1558
|
try {
|
|
1542
|
-
|
|
1559
|
+
const handle = await this.ctx.sessionPersistence.open(transition.previousSessionId, 'read');
|
|
1560
|
+
try {
|
|
1561
|
+
inspectionEvents = (await handle.read()).events;
|
|
1562
|
+
inspectionInherited = handle.inheritedEventCount;
|
|
1563
|
+
}
|
|
1564
|
+
finally {
|
|
1565
|
+
await handle.close();
|
|
1566
|
+
}
|
|
1543
1567
|
}
|
|
1544
1568
|
catch (error) {
|
|
1545
|
-
|
|
1569
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1570
|
+
// Log-corruption class: bounded fail-open. The current Session's own
|
|
1571
|
+
// fold is intact and the Host repairs the retired log's torn tail; a
|
|
1572
|
+
// corrupted retired generation must not permanently block activation.
|
|
1573
|
+
if (/corrupt session log/.test(detail)) {
|
|
1574
|
+
this.ctx.logger.warn(`agent-team: previous Session '${transition.previousSessionId}' holding carried input for member '${this.memberLabel(member.memberId)}' is corrupt: ${detail}; skipping the replay`);
|
|
1575
|
+
return 0;
|
|
1576
|
+
}
|
|
1577
|
+
// Deterministic released-format refusal: the retired artifact is refused
|
|
1578
|
+
// by the candidate's own migration audit, which no retry can change.
|
|
1579
|
+
// Failing activation here would turn a data problem in a Session the
|
|
1580
|
+
// Member no longer runs in into permanent unavailability — the exact
|
|
1581
|
+
// failure class this hardening removes. Genuine IO and unknown failures
|
|
1582
|
+
// stay fail-closed so carried input is never dropped silently.
|
|
1583
|
+
if (error instanceof SessionFormatUnsupportedError) {
|
|
1584
|
+
this.ctx.logger.warn(`agent-team: previous Session '${transition.previousSessionId}' holding carried input for member '${this.memberLabel(member.memberId)}' is refused by the session-format migration: ${detail}; skipping the replay`);
|
|
1585
|
+
return 0;
|
|
1586
|
+
}
|
|
1587
|
+
throw new Error(`the previous Session '${transition.previousSessionId}' holding the Member's carried input is unreadable: ${detail}`);
|
|
1546
1588
|
}
|
|
1547
|
-
const state = foldContextProjection(
|
|
1589
|
+
const state = foldContextProjection(inspectionEvents, inspectionInherited, transition.previousSessionId);
|
|
1548
1590
|
const carried = carriedInputOf(state);
|
|
1549
1591
|
if (carried.length === 0)
|
|
1550
1592
|
return 0;
|
|
@@ -1635,11 +1677,17 @@ let AgentTeam = (() => {
|
|
|
1635
1677
|
}
|
|
1636
1678
|
else {
|
|
1637
1679
|
try {
|
|
1638
|
-
const
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1680
|
+
const handle = await this.ctx.sessionPersistence.open(sessionId, 'read');
|
|
1681
|
+
try {
|
|
1682
|
+
const { events } = await handle.read();
|
|
1683
|
+
state = foldContextProjection(events, handle.inheritedEventCount, sessionId);
|
|
1684
|
+
sourceEvents = events;
|
|
1685
|
+
sourceSessionId = sessionId;
|
|
1686
|
+
sessionId = handle.header.parentSession;
|
|
1687
|
+
}
|
|
1688
|
+
finally {
|
|
1689
|
+
await handle.close();
|
|
1690
|
+
}
|
|
1643
1691
|
}
|
|
1644
1692
|
catch {
|
|
1645
1693
|
// An unreadable ancestor ends the lineage walk here.
|
|
@@ -1682,29 +1730,17 @@ let AgentTeam = (() => {
|
|
|
1682
1730
|
if (live)
|
|
1683
1731
|
return meter.measure(agent.session)?.totalTokens;
|
|
1684
1732
|
try {
|
|
1685
|
-
|
|
1733
|
+
// 0.1.5 removed borrowSession: rebuild a detached Session from the
|
|
1734
|
+
// stored log so the seed's retained cost is priced by the SOURCE's own
|
|
1735
|
+
// replay, never the current generation's.
|
|
1736
|
+
const handle = await this.ctx.sessionPersistence.open(sessionId, 'read');
|
|
1686
1737
|
try {
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
// archived ancestor. Measuring through the service context's store
|
|
1691
|
-
// would need an inject this service does not declare.
|
|
1692
|
-
, false);
|
|
1693
|
-
// The borrow resolves the exact Session object for both shapes: the
|
|
1694
|
-
// live instance for an attached Session, the prepared one for an
|
|
1695
|
-
// archived ancestor. Measuring through the service context's store
|
|
1696
|
-
// would need an inject this service does not declare.
|
|
1697
|
-
const session = borrowed.source === 'live'
|
|
1698
|
-
? agent.session.id === sessionId ? agent.session : undefined
|
|
1699
|
-
: borrowed.preparedSession;
|
|
1700
|
-
return session === undefined ? undefined : meter.measure(session)?.totalTokens;
|
|
1701
|
-
}
|
|
1702
|
-
catch (e_1) {
|
|
1703
|
-
env_1.error = e_1;
|
|
1704
|
-
env_1.hasError = true;
|
|
1738
|
+
const { events } = await handle.read();
|
|
1739
|
+
const session = Session.create(sessionId, events, handle.header, handle.inheritedEventCount);
|
|
1740
|
+
return meter.measure(session)?.totalTokens;
|
|
1705
1741
|
}
|
|
1706
1742
|
finally {
|
|
1707
|
-
|
|
1743
|
+
await handle.close();
|
|
1708
1744
|
}
|
|
1709
1745
|
}
|
|
1710
1746
|
catch {
|
|
@@ -2060,7 +2096,7 @@ let AgentTeam = (() => {
|
|
|
2060
2096
|
// allow-list filters the catalog by name through the live ref below.
|
|
2061
2097
|
// `swap` is bound by the provider at activation (no-op until then).
|
|
2062
2098
|
const skillSelection = { current: member.capabilities?.skills?.allow, swap: () => { } };
|
|
2063
|
-
const setup = async (agentCtx) => {
|
|
2099
|
+
const setup = async (agentCtx, agent) => {
|
|
2064
2100
|
await this.ctx.agentPresets.mount(agentCtx, member.presetId);
|
|
2065
2101
|
this.memberRuntime.applyMemberToolPolicy(agentCtx, member);
|
|
2066
2102
|
this.validateMemberPreset(agentCtx);
|
|
@@ -2107,9 +2143,6 @@ let AgentTeam = (() => {
|
|
|
2107
2143
|
});
|
|
2108
2144
|
return {
|
|
2109
2145
|
commit: () => {
|
|
2110
|
-
const agent = agentCtx.agent;
|
|
2111
|
-
if (agent === undefined)
|
|
2112
|
-
throw new Error('agent-team setup has no unpublished Agent');
|
|
2113
2146
|
// The member scope composes no sandbox-policy service (the preset
|
|
2114
2147
|
// owns no sandbox row), so read the last logged mode straight from
|
|
2115
2148
|
// the session log; `sandbox/mode` is log-only and never joins the
|
|
@@ -2179,6 +2212,11 @@ let AgentTeam = (() => {
|
|
|
2179
2212
|
// The one-shot pressure notice needs no explicit re-arm here: it latches
|
|
2180
2213
|
// on durable Session evidence, and a fresh generation's own event span
|
|
2181
2214
|
// starts empty — a new Session is itself the re-arm.
|
|
2215
|
+
// Snapshot whether this generation had already started its own turns
|
|
2216
|
+
// BEFORE any recovery delivery below: the handoff steer and the Inbox
|
|
2217
|
+
// wake legitimately append `turn/start` to this Session, and those must
|
|
2218
|
+
// never be mistaken for the generation having run on its own.
|
|
2219
|
+
const generationStarted = created.agent.session.ownEvents().some(event => event.type === 'turn/start');
|
|
2182
2220
|
// A restart between a durable rollover intent and its swap replays the
|
|
2183
2221
|
// old Session; the projection still carries the intent, so finish the
|
|
2184
2222
|
// transition (or keep waiting for the containing turn) from here.
|
|
@@ -2194,14 +2232,18 @@ let AgentTeam = (() => {
|
|
|
2194
2232
|
// delivered activates the new Session with no handoff in its own
|
|
2195
2233
|
// log — never treat that as an ordinary blank Member Session. The
|
|
2196
2234
|
// operation's recorded previous Session (the lineage parent) still
|
|
2197
|
-
// holds the intent; rebuild the handoff from it.
|
|
2198
|
-
|
|
2235
|
+
// holds the intent; rebuild the handoff from it. "No handoff in its
|
|
2236
|
+
// own log" is judged over both shapes: a generation rescued from the
|
|
2237
|
+
// retired custom kinds carries its handoff as a source the projection
|
|
2238
|
+
// does not classify, and rebuilding on top of it would inject the same
|
|
2239
|
+
// handoff twice.
|
|
2240
|
+
if (!handoffAlreadyInLog(state.boundaries, created.agent.session.ownEvents())) {
|
|
2199
2241
|
await this.reconstructMissingHandoff(member, created.agent);
|
|
2200
2242
|
}
|
|
2201
2243
|
// Carried input redelivery binds to the committed transition target —
|
|
2202
2244
|
// this Session — never to the handoff's presence: a crash after the
|
|
2203
2245
|
// handoff landed but before the carried enqueue still replays here.
|
|
2204
|
-
recoveryCarried = await this.replayCarriedInput(member, created.agent);
|
|
2246
|
+
recoveryCarried = await this.replayCarriedInput(member, created.agent, generationStarted);
|
|
2205
2247
|
}
|
|
2206
2248
|
else if (forkedFrom === undefined && lineageParent !== undefined) {
|
|
2207
2249
|
// A restart recreated a Session that never materialized before the
|
|
@@ -2215,7 +2257,7 @@ let AgentTeam = (() => {
|
|
|
2215
2257
|
// handoff right after — that delivery is the plan, never this
|
|
2216
2258
|
// recovery.
|
|
2217
2259
|
await this.reconstructMissingHandoff(member, created.agent);
|
|
2218
|
-
recoveryCarried = await this.replayCarriedInput(member, created.agent);
|
|
2260
|
+
recoveryCarried = await this.replayCarriedInput(member, created.agent, generationStarted);
|
|
2219
2261
|
}
|
|
2220
2262
|
// The ordinary Inbox wake runs LAST, after any recovery delivery above:
|
|
2221
2263
|
// the handoff must stay the first model-facing context of a recovered
|
|
@@ -2232,7 +2274,9 @@ let AgentTeam = (() => {
|
|
|
2232
2274
|
await created?.dispose();
|
|
2233
2275
|
this.modelSelections.delete(member.memberId);
|
|
2234
2276
|
this.memberRuntime.forgetMember(member.memberId);
|
|
2235
|
-
|
|
2277
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2278
|
+
this.ctx.logger.warn(`agent-team: activation failed for member '${this.memberLabel(member.memberId)}': ${message}`);
|
|
2279
|
+
this.setMemberFailure(member.memberId, 'activation', message);
|
|
2236
2280
|
}
|
|
2237
2281
|
finally {
|
|
2238
2282
|
// Activation only changes this Workspace's presence projection.
|
|
@@ -2292,6 +2336,12 @@ let AgentTeam = (() => {
|
|
|
2292
2336
|
const scope = scopeOf(agentCtx);
|
|
2293
2337
|
const teamMessage = this.ctx.tools.get('team_message', scope);
|
|
2294
2338
|
if (teamMessage?.[AGENT_TEAM_PRESET_MARKER] !== true) {
|
|
2339
|
+
// `agentPresets.mount` already rejected an unscoped context, so a scope
|
|
2340
|
+
// key the harness sees but this bundle does not means the two sides
|
|
2341
|
+
// loaded different module instances of @deepseek-ai/dsh-scope (the
|
|
2342
|
+
// common trigger is running the CLI from source via tsx).
|
|
2343
|
+
if (scope === undefined)
|
|
2344
|
+
throw new Error(teamPresetScopeMismatchMessage(isTsxDevMode()));
|
|
2295
2345
|
throw new Error('selected preset is not team-enabled');
|
|
2296
2346
|
}
|
|
2297
2347
|
const available = new Set(this.ctx.tools.schemas(scope).map(tool => tool.name));
|
|
@@ -2503,7 +2553,7 @@ let AgentTeam = (() => {
|
|
|
2503
2553
|
if (direct && fact.kind === 'message') {
|
|
2504
2554
|
const sender = fact.message.sender === AGENT_TEAM_HUMAN_MEMBER_ID
|
|
2505
2555
|
? 'human' : this.requireLedger().getMember(fact.message.sender)?.handle ?? fact.message.sender;
|
|
2506
|
-
const detail = ['Direct Team mention', `From: ${sender}`, `Channel: ${item.channelRef}`,
|
|
2556
|
+
const detail = ['Direct Team mention', `Occurred at: ${formatTeamTimestamp(fact.occurredAt)}`, `From: ${sender}`, `Channel: ${item.channelRef}`,
|
|
2507
2557
|
...(item.task === undefined ? [] : [`Task: ${item.task.taskRef}`]),
|
|
2508
2558
|
`Thread: ${item.thread.threadRef}`, `Message ref: ${fact.message.messageRef}`,
|
|
2509
2559
|
`Message: ${this.boundedNotificationBody(fact.message.body)}`].join('\n');
|
|
@@ -2511,15 +2561,15 @@ let AgentTeam = (() => {
|
|
|
2511
2561
|
detailedFactCount += 1;
|
|
2512
2562
|
}
|
|
2513
2563
|
else if (fact.kind === 'activity') {
|
|
2514
|
-
const detail = `${this.activityNotification(fact.activity, readerId)}\nThread: ${item.thread.threadRef}`;
|
|
2564
|
+
const detail = `${this.activityNotification(fact.activity, readerId)}\nOccurred at: ${formatTeamTimestamp(fact.occurredAt)}\nThread: ${item.thread.threadRef}`;
|
|
2515
2565
|
if (append(detail))
|
|
2516
2566
|
detailedFactCount += 1;
|
|
2517
2567
|
}
|
|
2518
2568
|
}
|
|
2519
|
-
const
|
|
2520
|
-
if (
|
|
2569
|
+
const ordinary = facts.filter(entry => entry.fact.kind === 'message' && !entry.direct);
|
|
2570
|
+
if (ordinary.length > 0) {
|
|
2521
2571
|
const route = item.task === undefined ? `Thread ${item.thread.threadRef}` : `Task ${item.task.taskRef}`;
|
|
2522
|
-
append(`${route}: ${
|
|
2572
|
+
append(`${route}: ${ordinary.length} unread update${ordinary.length === 1 ? '' : 's'} · newest at ${formatTeamTimestamp(ordinary.at(-1).fact.occurredAt)}.`);
|
|
2523
2573
|
}
|
|
2524
2574
|
}
|
|
2525
2575
|
if (omitted)
|