@wowyuarm/dsh-agent-team 0.1.15 → 0.2.1
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 +8 -4
- package/README.zh.md +8 -4
- package/cordis.patch.yml +26 -12
- package/package.json +52 -47
- package/packages/agent-team/lib/attachments.js +63 -4
- package/packages/agent-team/lib/compaction-engine.js +18 -0
- package/packages/agent-team/lib/context-continuity-host.js +198 -4
- package/packages/agent-team/lib/context-judge.js +150 -0
- package/packages/agent-team/lib/context-source.js +11 -6
- package/packages/agent-team/lib/environment-check.js +102 -0
- package/packages/agent-team/lib/human-profile.js +23 -37
- package/packages/agent-team/lib/index.js +510 -99
- package/packages/agent-team/lib/installed-manifest.js +32 -0
- package/packages/agent-team/lib/invariant.js +36 -12
- package/packages/agent-team/lib/ledger.js +37 -1
- package/packages/agent-team/lib/member-context.js +2 -4
- package/packages/agent-team/lib/member-runtime.js +2 -1
- package/packages/agent-team/lib/member-time-context.js +2 -4
- package/packages/agent-team/lib/recovery.js +53 -4
- package/packages/agent-team/lib/typert.host.js +225 -54
- package/packages/agent-team/lib/typert.remote-client.d.ts +5 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +127 -39
- package/packages/agent-team/lib/types/attachments.d.ts +32 -2
- package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
- package/packages/agent-team/lib/types/compaction-engine.d.ts +19 -0
- package/packages/agent-team/lib/types/compaction-engine.d.ts.map +1 -0
- package/packages/agent-team/lib/types/context-continuity-host.d.ts +131 -4
- package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-judge.d.ts +82 -0
- package/packages/agent-team/lib/types/context-judge.d.ts.map +1 -0
- package/packages/agent-team/lib/types/context-source.d.ts +7 -4
- package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
- package/packages/agent-team/lib/types/environment-check.d.ts +66 -0
- package/packages/agent-team/lib/types/environment-check.d.ts.map +1 -0
- package/packages/agent-team/lib/types/human-profile.d.ts +21 -27
- package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +281 -18
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/installed-manifest.d.ts +16 -0
- package/packages/agent-team/lib/types/installed-manifest.d.ts.map +1 -0
- package/packages/agent-team/lib/types/invariant.d.ts +27 -5
- package/packages/agent-team/lib/types/invariant.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +12 -0
- 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 +1 -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/recovery.d.ts +19 -6
- package/packages/agent-team/lib/types/recovery.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/entities.d.ts +1 -8
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +94 -2
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +9 -0
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/client-agent-team/lib/client.js +1264 -383
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/EnvironmentCheck.d.ts +18 -0
- package/packages/client-agent-team/lib/types/client/EnvironmentCheck.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/EnvironmentCheck.js +58 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +0 -5
- 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/TeamPresenceDot.js +2 -7
- package/packages/client-agent-team/lib/types/client/TeamSettingsSection.d.ts +26 -0
- package/packages/client-agent-team/lib/types/client/TeamSettingsSection.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamSettingsSection.js +195 -0
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +7 -11
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/attachment-preview.js +13 -3
- package/packages/client-agent-team/lib/types/client/context-judge.d.ts +64 -0
- package/packages/client-agent-team/lib/types/client/context-judge.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/context-judge.js +78 -0
- package/packages/client-agent-team/lib/types/client/environment-check.d.ts +65 -0
- package/packages/client-agent-team/lib/types/client/environment-check.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/environment-check.js +78 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +84 -11
- package/packages/client-agent-team/lib/types/client/judge-form.d.ts +99 -0
- package/packages/client-agent-team/lib/types/client/judge-form.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/judge-form.js +208 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts +70 -14
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +76 -14
- package/packages/tool-agent-team/README.md +6 -3
- package/packages/tool-agent-team/README.zh.md +6 -3
- package/packages/tool-agent-team/lib/context-tools.js +148 -164
- package/packages/tool-agent-team/lib/index.js +17 -2
- package/packages/tool-agent-team/lib/types/context-tools.d.ts +32 -7
- package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/pressure-policy.js +0 -230
- package/packages/agent-team/lib/types/pressure-policy.d.ts +0 -130
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts +0 -15
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.js +0 -115
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** The installed bundle's own manifest, read once for both facts taken from it. */
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
/**
|
|
6
|
+
* Parsed manifest of the installed bundle, or `undefined` when it cannot be
|
|
7
|
+
* read.
|
|
8
|
+
*
|
|
9
|
+
* Three levels up: `packages/agent-team/{src,lib}` sits that deep in both a
|
|
10
|
+
* `link:` checkout and a registry tarball, so a development install and a
|
|
11
|
+
* published one each state their own truth. An unreadable or malformed manifest
|
|
12
|
+
* is a broken install, not a failure of whatever asked: every caller degrades to
|
|
13
|
+
* `'unknown'` or withholds the line it would have stated, so reading this can
|
|
14
|
+
* never fail the Host boot.
|
|
15
|
+
*/
|
|
16
|
+
export function readInstalledManifest() {
|
|
17
|
+
try {
|
|
18
|
+
const manifestPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../../package.json');
|
|
19
|
+
const parsed = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
20
|
+
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
|
21
|
+
? parsed
|
|
22
|
+
: undefined;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Version of the manifest this package installed from, or `'unknown'`. */
|
|
29
|
+
export function bundleVersionOf(manifest) {
|
|
30
|
+
const version = manifest?.version;
|
|
31
|
+
return typeof version === 'string' && version !== '' ? version : 'unknown';
|
|
32
|
+
}
|
|
@@ -2,9 +2,39 @@
|
|
|
2
2
|
const PACKAGE_NAME = '@wowyuarm/dsh-agent-team';
|
|
3
3
|
/** Cordis companion plugin name. */
|
|
4
4
|
export const name = 'agent-team-invariant';
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Services required before the companion can validate Team state. The check
|
|
7
|
+
* is self-hosted in this plugin's own fiber — companions used to register
|
|
8
|
+
* into `@deepseek-ai/dsh-invariants`, which the Harness line after `rc.2`
|
|
9
|
+
* removes wholesale.
|
|
10
|
+
*/
|
|
11
|
+
export const inject = ['agentTeam'];
|
|
12
|
+
/**
|
|
13
|
+
* A violated Team ledger invariant, raised on the frame that triggered the
|
|
14
|
+
* check: boot when the mount replay fails, the committing call while a
|
|
15
|
+
* divergence is latched. The Host's post-commit boundary recognizes this class
|
|
16
|
+
* and re-raises it instead of degrading it to a log line.
|
|
17
|
+
*/
|
|
18
|
+
export class AgentTeamInvariantError extends Error {
|
|
19
|
+
/** Stable failure code, carried over from the removed upstream error class. */
|
|
20
|
+
code = 'INVARIANT';
|
|
21
|
+
/** Full npm package name that owns the violated invariant. */
|
|
22
|
+
packageName = PACKAGE_NAME;
|
|
23
|
+
/**
|
|
24
|
+
* Construct a Team invariant failure.
|
|
25
|
+
* @param message - violated contract, without the standard error prefix.
|
|
26
|
+
*/
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super(`invariant violated by "${PACKAGE_NAME}": ${message}`);
|
|
29
|
+
this.name = 'AgentTeamInvariantError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Validate Team state at mount and keep validating it after every commit.
|
|
34
|
+
* @param ctx - Cordis context carrying the Agent Team service.
|
|
35
|
+
* @returns nothing; listeners and the pending check ride the plugin fiber.
|
|
36
|
+
*/
|
|
37
|
+
export const apply = (ctx) => {
|
|
8
38
|
const divergence = (error) => `durable ledger and Team projection diverged: ${String(error)}`;
|
|
9
39
|
// Mounting stays synchronous: a durable ledger that cannot be re-derived
|
|
10
40
|
// must fail startup instead of racing the first commit. The mount adopts
|
|
@@ -15,7 +45,7 @@ const install = Object.assign(async (ctx, fail) => {
|
|
|
15
45
|
ctx.agentTeam.validateLedgerAtMount();
|
|
16
46
|
}
|
|
17
47
|
catch (error) {
|
|
18
|
-
|
|
48
|
+
throw new AgentTeamInvariantError(divergence(error));
|
|
19
49
|
}
|
|
20
50
|
// The commit path cannot pay a full replay before its Remote response
|
|
21
51
|
// returns, and opening a Thread commits. The replay stays the same full one
|
|
@@ -47,12 +77,6 @@ const install = Object.assign(async (ctx, fail) => {
|
|
|
47
77
|
if (pending === undefined)
|
|
48
78
|
pending = setImmediate(check);
|
|
49
79
|
if (latched !== undefined)
|
|
50
|
-
|
|
80
|
+
throw new AgentTeamInvariantError(latched);
|
|
51
81
|
});
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Register the Agent Team ledger invariant.
|
|
55
|
-
* @param ctx - Cordis context carrying the invariant registry.
|
|
56
|
-
* @returns the installed registration disposer after setup succeeds.
|
|
57
|
-
*/
|
|
58
|
-
export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
82
|
+
};
|
|
@@ -1586,6 +1586,7 @@ export class AgentTeamLedger {
|
|
|
1586
1586
|
})),
|
|
1587
1587
|
taskNumbers: Object.freeze(visibleTasks.map(task => Object.freeze({ taskRef: task.taskRef, taskNumber: taskNumbers.get(task.taskRef) ?? 0 }))),
|
|
1588
1588
|
items: Object.freeze(items),
|
|
1589
|
+
activeTaskThreads: this.activeTaskThreads(visibleTasks, taskNumbers),
|
|
1589
1590
|
claims: this.claimsForVisibleTasks(visibleTasks),
|
|
1590
1591
|
activities: Object.freeze(selected.filter((fact) => fact.kind === 'activity').map(fact => fact.activity)),
|
|
1591
1592
|
cursor: nextCursor,
|
|
@@ -3172,6 +3173,40 @@ export class AgentTeamLedger {
|
|
|
3172
3173
|
const refs = new Set(tasks.map(task => task.taskRef));
|
|
3173
3174
|
return Object.freeze([...this.state.claims.values()].filter(claim => refs.has(claim.taskRef)));
|
|
3174
3175
|
}
|
|
3176
|
+
/**
|
|
3177
|
+
* The team_view「活跃 task thread」radar for a Workspace: every visible Task
|
|
3178
|
+
* still in flight — in_progress or in_review, the two states a live Claim
|
|
3179
|
+
* keeps a Task in — newest activity first. Unlike `recentInboxItems`, this is
|
|
3180
|
+
* not a participation slice: admission is the Task being active in a Channel
|
|
3181
|
+
* the reader can see, whether or not the reader wrote in it, and a Thread
|
|
3182
|
+
* still holding the reader's unread is not excluded. Rows carry the same
|
|
3183
|
+
* `liveClaimOwners`口径 the Inbox and Channel feed already use, so 「谁在这个
|
|
3184
|
+
* Task 上」 never gains a second definition. Archived Channels are already
|
|
3185
|
+
* absent from `visibleTasks`, so their Tasks never reach here.
|
|
3186
|
+
*/
|
|
3187
|
+
activeTaskThreads(visibleTasks, taskNumbers) {
|
|
3188
|
+
const rows = [];
|
|
3189
|
+
for (const task of visibleTasks) {
|
|
3190
|
+
if (task.status !== 'in_progress' && task.status !== 'in_review')
|
|
3191
|
+
continue;
|
|
3192
|
+
const thread = this.state.threads.get(task.threadRef);
|
|
3193
|
+
if (thread === undefined)
|
|
3194
|
+
continue;
|
|
3195
|
+
const newest = (this.state.factsByThread.get(thread.threadRef) ?? []).at(-1);
|
|
3196
|
+
if (newest === undefined)
|
|
3197
|
+
continue;
|
|
3198
|
+
const taskNumber = taskNumbers.get(task.taskRef);
|
|
3199
|
+
rows.push({
|
|
3200
|
+
row: Object.freeze({ taskRef: task.taskRef, threadRef: thread.threadRef, channelRef: task.channelRef,
|
|
3201
|
+
...(taskNumber === undefined ? {} : { taskNumber }), status: task.status,
|
|
3202
|
+
subject: boundedInboxPreview(this.threadAnchor(thread.threadRef).body),
|
|
3203
|
+
members: this.liveClaimOwners(task), lastActivityAt: newest.occurredAt }),
|
|
3204
|
+
sequence: newest.sequence,
|
|
3205
|
+
});
|
|
3206
|
+
}
|
|
3207
|
+
rows.sort((left, right) => right.sequence - left.sequence || left.row.threadRef.localeCompare(right.row.threadRef));
|
|
3208
|
+
return Object.freeze(rows.map(entry => entry.row));
|
|
3209
|
+
}
|
|
3175
3210
|
tasksForClaims(claims, projected) {
|
|
3176
3211
|
return Object.freeze([...new Set(claims.map(claim => claim.taskRef))].map(taskRef => {
|
|
3177
3212
|
const task = this.state.tasks.get(taskRef);
|
|
@@ -3745,7 +3780,7 @@ export class AgentTeamLedger {
|
|
|
3745
3780
|
if (operation.kind !== 'team/message-sent' || !this.sameActor(operation.actor, request.actor)
|
|
3746
3781
|
|| operation.data.workspaceId !== request.workspaceId || operation.data.message.channelRef !== request.channelRef
|
|
3747
3782
|
|| operation.data.message.body !== request.body.trim() || !this.sameList(operation.data.mentions, recipients)
|
|
3748
|
-
|| !this.sameList(operation.data.message.attachments?.map(attachment => attachment.attachmentId) ?? [], request.
|
|
3783
|
+
|| !this.sameList(operation.data.message.attachments?.map(attachment => attachment.attachmentId) ?? [], request.resolvedAttachments?.map(attachment => attachment.attachmentId) ?? [])
|
|
3749
3784
|
|| (request.asTask !== false) !== (operation.data.task !== undefined))
|
|
3750
3785
|
this.throwRequestCollision(request.requestId);
|
|
3751
3786
|
}
|
|
@@ -3756,6 +3791,7 @@ export class AgentTeamLedger {
|
|
|
3756
3791
|
|| (request.taskRef !== undefined && operation.data.task?.taskRef !== request.taskRef)
|
|
3757
3792
|
|| (request.threadRef === undefined && request.taskRef === undefined)
|
|
3758
3793
|
|| operation.data.message.body !== request.body.trim() || operation.data.baseRevision !== request.baseRevision
|
|
3794
|
+
|| !this.sameList(operation.data.message.attachments?.map(attachment => attachment.attachmentId) ?? [], request.resolvedAttachments?.map(attachment => attachment.attachmentId) ?? [])
|
|
3759
3795
|
|| !this.sameList(operation.data.mentions, recipients))
|
|
3760
3796
|
this.throwRequestCollision(request.requestId);
|
|
3761
3797
|
}
|
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
4
4
|
import { SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
5
|
-
import {
|
|
5
|
+
import { matchesProducerKind } from "./context-source.js";
|
|
6
6
|
import { memberMemoryDirectoryPath } from "./member-runtime.js";
|
|
7
7
|
export const name = 'wowyuarm-agent-team-member-context';
|
|
8
8
|
const MAX_MEMORY_BYTES = 16 * 1024;
|
|
@@ -43,9 +43,7 @@ export function apply(ctx) {
|
|
|
43
43
|
// number domains explicit (the seq = log.length contiguity contract).
|
|
44
44
|
const event = agent.session.snapshotEvents(SessionLogOffset(sequence), SessionLogOffset(sequence + 1))[0];
|
|
45
45
|
return event?.type === 'user/message'
|
|
46
|
-
|
|
47
|
-
// the read-time conversion's rename of this producer's V3 history.
|
|
48
|
-
&& (event.data.source.kind === name || event.data.source.kind === v3RenamedSourceKind(name))
|
|
46
|
+
&& matchesProducerKind(event.data.source.kind, name)
|
|
49
47
|
&& event.data.content[0]?.type === 'text'
|
|
50
48
|
? [event.data.content[0].text]
|
|
51
49
|
: [];
|
|
@@ -35,7 +35,8 @@ export const AGENT_TEAM_TOOL_NAMES = Object.freeze([
|
|
|
35
35
|
'team_view',
|
|
36
36
|
'context_rollover',
|
|
37
37
|
'context_checkpoint',
|
|
38
|
-
'
|
|
38
|
+
'context_status',
|
|
39
|
+
'context_compact',
|
|
39
40
|
]);
|
|
40
41
|
/** Copy a Remote-supplied capability overlay into owned frozen storage. */
|
|
41
42
|
export function deepCopyCapabilities(capabilities) {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* @module @wowyuarm/dsh-agent-team/member-time-context
|
|
28
28
|
*/
|
|
29
29
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
30
|
-
import {
|
|
30
|
+
import { matchesProducerKind } from "./context-source.js";
|
|
31
31
|
import { formatTeamDuration, formatTeamTimestamp } from "./time-format.js";
|
|
32
32
|
import { advanceOwnedSessionEventCursor } from "./session-event-cursor.js";
|
|
33
33
|
export const name = 'wowyuarm-agent-team-member-time-context';
|
|
@@ -50,9 +50,7 @@ export function applyClockEvent(state, event) {
|
|
|
50
50
|
return state.openTurn === -1 ? state : { ...state, lastTurnInjectionTime: null, openTurn: -1 };
|
|
51
51
|
case 'user/message': {
|
|
52
52
|
const source = event.data.source;
|
|
53
|
-
|
|
54
|
-
// read-time conversion's rename of this producer's V3 history.
|
|
55
|
-
const injected = source.kind === name || source.kind === v3RenamedSourceKind(name);
|
|
53
|
+
const injected = matchesProducerKind(source.kind, name);
|
|
56
54
|
const withMessage = state.lastMessageTime === event.time ? state : { ...state, lastMessageTime: event.time };
|
|
57
55
|
if (!injected)
|
|
58
56
|
return withMessage;
|
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
const TRANSIENT_NETWORK_PATTERNS = [/fetch failed/i, /econnreset/i, /etimedout/i, /socket hang up/i];
|
|
2
2
|
const RATE_LIMIT_PATTERNS = [/\b429\b/, /rate limit/i, /\b503\b/, /overloaded/i];
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Read the stable code/status off any Harness-shaped failure without trusting
|
|
5
|
+
* class identity: a cross-package copy of `dsh-llm` preserves own data but
|
|
6
|
+
* not `instanceof`, so the own fields are the contract.
|
|
7
|
+
*/
|
|
8
|
+
function structuredFailure(error) {
|
|
9
|
+
if (typeof error !== 'object' || error === null)
|
|
10
|
+
return {};
|
|
11
|
+
const root = error;
|
|
12
|
+
const failure = typeof root.failure === 'object' && root.failure !== null
|
|
13
|
+
? root.failure
|
|
14
|
+
: undefined;
|
|
15
|
+
const code = typeof failure?.code === 'string' && failure.code.length > 0 ? failure.code
|
|
16
|
+
: typeof root.code === 'string' && root.code.length > 0 ? root.code : undefined;
|
|
17
|
+
if (code === undefined)
|
|
18
|
+
return {};
|
|
19
|
+
const status = typeof failure?.status === 'number' && Number.isInteger(failure.status) ? failure.status
|
|
20
|
+
: typeof root.status === 'number' && Number.isInteger(root.status) ? root.status : undefined;
|
|
21
|
+
return { code, ...status === undefined ? {} : { status } };
|
|
22
|
+
}
|
|
23
|
+
/** The readable message of an arbitrary thrown value. */
|
|
24
|
+
function failureMessage(error) {
|
|
25
|
+
if (error instanceof Error)
|
|
26
|
+
return error.message;
|
|
27
|
+
if (typeof error === 'object' && error !== null) {
|
|
28
|
+
const message = error.message;
|
|
29
|
+
if (typeof message === 'string' && message.length > 0)
|
|
30
|
+
return message;
|
|
31
|
+
}
|
|
32
|
+
return String(error);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Classify one `agent/error` occurrence. Structured data outranks wording: a
|
|
36
|
+
* Harness code decides alone (a message can never upgrade a terminal failure
|
|
37
|
+
* back to recoverable), and the narrow message signatures run only when no
|
|
38
|
+
* usable code exists at all — `UNKNOWN` means the Harness folded a foreign
|
|
39
|
+
* error whose own taxonomy Team must not trust.
|
|
40
|
+
*/
|
|
41
|
+
export function classifyRecoverableError(error) {
|
|
42
|
+
const { code, status } = structuredFailure(error);
|
|
43
|
+
if (code !== undefined && code !== 'UNKNOWN') {
|
|
44
|
+
if (code === 'RATE_LIMIT')
|
|
45
|
+
return 'rate limiting';
|
|
46
|
+
if (code === 'TRANSPORT' || code === 'TIMEOUT')
|
|
47
|
+
return 'transient network';
|
|
48
|
+
if (status === 429 || status === 503)
|
|
49
|
+
return 'rate limiting';
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
const message = failureMessage(error);
|
|
4
53
|
if (RATE_LIMIT_PATTERNS.some(pattern => pattern.test(message)))
|
|
5
54
|
return 'rate limiting';
|
|
6
55
|
if (TRANSIENT_NETWORK_PATTERNS.some(pattern => pattern.test(message)))
|
|
@@ -26,9 +75,9 @@ export class RecoveryCoordinator {
|
|
|
26
75
|
this.delayMs = options.delayMs ?? RECOVERY_DELAY_MS;
|
|
27
76
|
this.maxConsecutiveErrors = options.maxConsecutiveErrors ?? RECOVERY_MAX_CONSECUTIVE_ERRORS;
|
|
28
77
|
}
|
|
29
|
-
/** Observe one `agent/error` occurrence for a Member. */
|
|
30
|
-
onError(memberId,
|
|
31
|
-
if (classifyRecoverableError(
|
|
78
|
+
/** Observe one `agent/error` occurrence for a Member; `error` is the raw event payload. */
|
|
79
|
+
onError(memberId, error) {
|
|
80
|
+
if (classifyRecoverableError(error) === undefined) {
|
|
32
81
|
// A non-recoverable failure cancels anything pending: retrying cannot help.
|
|
33
82
|
this.stopTracking(memberId);
|
|
34
83
|
return;
|