@nanmicoder/dsh-agent-teams 0.1.6 → 0.1.7
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 +2 -2
- package/README_ZH.md +2 -2
- package/lib/index.js +1 -1
- package/lib/members.js +28 -8
- package/lib/state.js +104 -5
- package/lib/tools.js +16 -2
- package/lib/types/members.d.ts +8 -4
- package/lib/types/state.d.ts +29 -0
- package/lib/types/types.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ Then ask for a team directly:
|
|
|
78
78
|
|
|
79
79
|
Team state is stored under `<workspace>/.agent-teams/`; the Web panel reads that disk truth and combines it with live sub-agent activity.
|
|
80
80
|
|
|
81
|
-
Member creation is zero-interaction by default:
|
|
81
|
+
Member creation is zero-interaction by default: a member on the captain's current LLM route snapshots that provider, model, and reasoning effort, while a member on a requested alternative route snapshots the target model's default effort; later continuations restore the resolved snapshot. Only an explicit heterogeneous-team request (for example, “backend on provider A/model X, frontend on provider B/model Y”) supplies a member-specific `provider` + `model`; there is no per-member model or reasoning prompt.
|
|
82
82
|
|
|
83
83
|
## Configuration
|
|
84
84
|
|
|
@@ -94,7 +94,7 @@ Defaults work without extra setup. A trusted profile can override member behavio
|
|
|
94
94
|
maxMembers: 8
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
`memberProvider` is the sub-agent runtime backend (`spawn` / `fork`), not an LLM provider. Cross-LLM-provider routing uses the optional `provider` + `model` fields of `agent_teams_add_member`; `memberModel` is only a model default for all members.
|
|
97
|
+
`memberProvider` is the sub-agent runtime backend (`spawn` / `fork`), not an LLM provider. Cross-LLM-provider routing uses the optional `provider` + `model` fields of `agent_teams_add_member`; `memberModel` is only a model default for all members. A member on the captain's current provider/model inherits the captain's reasoning effort, while a changed provider or model automatically uses the target model's default. To request a particular effort, pass the optional `reasoning_effort` field — one of the target model's supported effort ids, or `"default"` to force the model's own default.
|
|
98
98
|
|
|
99
99
|
## Boundaries
|
|
100
100
|
|
package/README_ZH.md
CHANGED
|
@@ -78,7 +78,7 @@ dsh web
|
|
|
78
78
|
|
|
79
79
|
团队状态保存在 `<workspace>/.agent-teams/`;Web 面板读取这份磁盘真相,并与实时子 Agent 活动合并展示。
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
成员创建默认零交互:成员沿用队长当前 LLM 路由时会快照该 provider、model 与思考强度;用户要求改用其他路由时,则快照目标模型的默认强度,成员后续续跑仍使用最终解析出的快照。只有当用户明确提出异构分工(例如“后端用 provider A/model X,前端用 provider B/model Y”)时,队长才会把对应的 `provider` + `model` 传给该成员;不会逐个弹出模型或思考强度选择。
|
|
82
82
|
|
|
83
83
|
## 配置
|
|
84
84
|
|
|
@@ -94,7 +94,7 @@ dsh web
|
|
|
94
94
|
maxMembers: 8
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
这里的 `memberProvider` 指子 Agent 的运行后端(`spawn` / `fork`),不是 LLM provider。跨 LLM provider 由 `agent_teams_add_member` 的可选 `provider` + `model` 参数表达;`memberModel`
|
|
97
|
+
这里的 `memberProvider` 指子 Agent 的运行后端(`spawn` / `fork`),不是 LLM provider。跨 LLM provider 由 `agent_teams_add_member` 的可选 `provider` + `model` 参数表达;`memberModel` 只是所有成员的模型默认覆盖。成员沿用队长当前 provider/model 时会继承队长的思考强度;provider 或 model 任一改变时会自动使用目标模型的默认档。需要指定特定强度时,可传入可选的 `reasoning_effort` 参数(目标模型支持的档位 id,或 `"default"` 表示强制使用模型自身默认档)。
|
|
98
98
|
|
|
99
99
|
## 使用边界
|
|
100
100
|
|
package/lib/index.js
CHANGED
|
@@ -40,7 +40,7 @@ export const Config = z.object({
|
|
|
40
40
|
function usageSectionText(toolNames) {
|
|
41
41
|
return `When the user asks to run something with AgentTeams (e.g. "use AgentTeams to do X"), you are the captain of a multi-agent team. Follow this protocol:
|
|
42
42
|
1. Call agent_teams_create with a team name and the goal as description. You become the captain and may lead one team at a time.
|
|
43
|
-
2. Call agent_teams_add_member once per role the goal needs (researcher, engineer, reviewer, ...). Members are durable subagents: they wait for your messages, then work a full turn. By default
|
|
43
|
+
2. Call agent_teams_add_member once per role the goal needs (researcher, engineer, reviewer, ...). Members are durable subagents: they wait for your messages, then work a full turn. By default a member on your current provider/model snapshots your current reasoning effort; a member routed to a different provider or model automatically uses that target model's default effort. Never ask the user to choose these per member; only pass provider/model when the user explicitly requests a different route for that role, and reasoning_effort only when the user explicitly requests a particular effort ("default" explicitly selects the target model's default).
|
|
44
44
|
3. Break the goal into tasks with agent_teams_create_task and wire dependencies. Assign role-specific work when useful; unassigned ready work belongs to the shared pool. The scheduler automatically claims one ready task for each truly idle member and wakes it, including across later rounds.
|
|
45
45
|
4. Lead by delegation: monitor with agent_teams_status, send guidance with agent_teams_send_message, and let idle teammates execute ready work. Do not duplicate a teammate's work merely because its turn is slow.
|
|
46
46
|
5. If work is blocked, stale, or needs takeover, always call agent_teams_reassign_task first. Reassign to another idle member, or use assignee=captain before doing it yourself. Reassignment revokes the old attempt and waits for that member to quiesce, preventing late results from overwriting the new attempt.
|
package/lib/members.js
CHANGED
|
@@ -64,15 +64,18 @@ function modelSelection(selection) {
|
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
66
|
* Resolve one member's complete model selection. Ordinary members snapshot the
|
|
67
|
-
* captain's current request route and reasoning effort.
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
67
|
+
* captain's current request route and reasoning effort. When provider or model
|
|
68
|
+
* changes, effort is intentionally omitted so the target model materializes
|
|
69
|
+
* its own default instead of receiving an adapter-owned id from another route.
|
|
70
|
+
* An explicit effort overrides either policy; the sentinel "default" also
|
|
71
|
+
* selects the target model's default. The final effort is validated against
|
|
72
|
+
* the target model before a child is created.
|
|
71
73
|
*/
|
|
72
74
|
export async function resolveMemberLlmSelection(ctx, captain, request, signal) {
|
|
73
75
|
const explicitProvider = request.provider?.trim();
|
|
74
76
|
const explicitModel = request.model?.trim();
|
|
75
77
|
const defaultModel = request.defaultModel?.trim();
|
|
78
|
+
const explicitEffort = request.reasoningEffort?.trim();
|
|
76
79
|
if (request.provider !== undefined && explicitProvider === '') {
|
|
77
80
|
throw new Error('member LLM provider must not be empty');
|
|
78
81
|
}
|
|
@@ -82,21 +85,38 @@ export async function resolveMemberLlmSelection(ctx, captain, request, signal) {
|
|
|
82
85
|
if (request.defaultModel !== undefined && defaultModel === '') {
|
|
83
86
|
throw new Error('configured memberModel must not be empty');
|
|
84
87
|
}
|
|
88
|
+
if (request.reasoningEffort !== undefined && explicitEffort === '') {
|
|
89
|
+
throw new Error('member reasoning effort must not be empty');
|
|
90
|
+
}
|
|
85
91
|
if (explicitProvider !== undefined && explicitModel === undefined) {
|
|
86
92
|
throw new Error('an explicit member LLM provider requires an explicit member model');
|
|
87
93
|
}
|
|
88
94
|
const current = captain.session.requestHeader()?.config;
|
|
89
|
-
const
|
|
90
|
-
const
|
|
95
|
+
const currentProvider = current?.provider ?? captain.options.provider;
|
|
96
|
+
const currentModel = current?.model ?? captain.options.model;
|
|
97
|
+
const provider = explicitProvider ?? currentProvider;
|
|
98
|
+
const model = explicitModel ?? defaultModel ?? currentModel;
|
|
91
99
|
if (provider === undefined || model === undefined) {
|
|
92
100
|
throw new Error('cannot resolve the member LLM route from the current captain session');
|
|
93
101
|
}
|
|
102
|
+
// Effort ids belong to one exact provider/model capability. Preserve the
|
|
103
|
+
// captain's effort only on the same route; a changed route must resolve its
|
|
104
|
+
// own default. Explicit effort still wins, while "default" forces that
|
|
105
|
+
// target-default behavior even when the route did not change.
|
|
106
|
+
const sameRoute = provider === currentProvider && model === currentModel;
|
|
107
|
+
const reasoningEffort = explicitEffort === undefined
|
|
108
|
+
? sameRoute
|
|
109
|
+
? current?.reasoningEffort
|
|
110
|
+
: undefined
|
|
111
|
+
: explicitEffort === 'default'
|
|
112
|
+
? undefined
|
|
113
|
+
: ReasoningEffortId(explicitEffort);
|
|
94
114
|
const resolved = await ctx.llm.resolveCallConfig({
|
|
95
115
|
provider,
|
|
96
116
|
model,
|
|
97
|
-
...
|
|
117
|
+
...reasoningEffort === undefined
|
|
98
118
|
? {}
|
|
99
|
-
: { reasoningEffort
|
|
119
|
+
: { reasoningEffort },
|
|
100
120
|
}, signal);
|
|
101
121
|
return {
|
|
102
122
|
provider: resolved.provider,
|
package/lib/state.js
CHANGED
|
@@ -444,17 +444,87 @@ export async function acknowledgeMailbox(stateRoot, teamId, agentKey, messageIds
|
|
|
444
444
|
function stripLeadingBom(value) {
|
|
445
445
|
return value.charCodeAt(0) === 0xFEFF ? value.slice(1) : value;
|
|
446
446
|
}
|
|
447
|
-
/**
|
|
447
|
+
/** Rename attempts before falling back to a direct overwrite. */
|
|
448
|
+
const ATOMIC_RENAME_RETRIES = 3;
|
|
449
|
+
/** Pause between rename attempts, giving a briefly-locking owner time to finish. */
|
|
450
|
+
const ATOMIC_RENAME_RETRY_DELAY_MS = 50;
|
|
451
|
+
/**
|
|
452
|
+
* Rename error codes worth retrying before the direct-write fallback. On
|
|
453
|
+
* Windows, replacing an existing file whose target is momentarily held open
|
|
454
|
+
* without FILE_SHARE_DELETE surfaces as EPERM (or EACCES/EBUSY variants);
|
|
455
|
+
* EEXIST/ENOTEMPTY cover other "target busy" edge shapes.
|
|
456
|
+
*/
|
|
457
|
+
const RETRYABLE_RENAME_CODES = new Set(['EPERM', 'EACCES', 'EBUSY', 'EEXIST', 'ENOTEMPTY']);
|
|
458
|
+
function isRetryableRenameError(error) {
|
|
459
|
+
return error instanceof Error
|
|
460
|
+
&& 'code' in error
|
|
461
|
+
&& RETRYABLE_RENAME_CODES.has(error.code ?? '');
|
|
462
|
+
}
|
|
463
|
+
function sleep(ms) {
|
|
464
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Replace `file` with `content`, preferring an atomic same-directory rename of
|
|
468
|
+
* an already-written temp file.
|
|
469
|
+
*
|
|
470
|
+
* On Windows, `rename(tmp, file)` over an existing target throws EPERM while
|
|
471
|
+
* any other process keeps the target open without FILE_SHARE_DELETE (editors,
|
|
472
|
+
* indexers, antivirus scans, preview panes). By that point the payload has
|
|
473
|
+
* already been fully written to the temp file, so a direct overwrite of the
|
|
474
|
+
* target is a content-equivalent degraded path: retry the rename a few times
|
|
475
|
+
* (transient locks clear quickly), then write the target in place. Every path
|
|
476
|
+
* removes the temp file; when both the atomic rename and the direct write
|
|
477
|
+
* fail, the combined error surfaces as an {@link AggregateError}.
|
|
478
|
+
*
|
|
479
|
+
* @returns nothing once the file has been replaced by one of the two paths.
|
|
480
|
+
*/
|
|
481
|
+
export async function replaceFileAtomicOrDirect(temporary, file, content, primitives, options = {}) {
|
|
482
|
+
const retries = options.retries ?? ATOMIC_RENAME_RETRIES;
|
|
483
|
+
const retryDelayMs = options.retryDelayMs ?? ATOMIC_RENAME_RETRY_DELAY_MS;
|
|
484
|
+
for (let attempt = 0;; attempt += 1) {
|
|
485
|
+
try {
|
|
486
|
+
await primitives.rename(temporary, file);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
if (isRetryableRenameError(error) && attempt < retries) {
|
|
491
|
+
await sleep(retryDelayMs);
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
let fallbackError;
|
|
495
|
+
try {
|
|
496
|
+
await primitives.writeFile(file, content);
|
|
497
|
+
}
|
|
498
|
+
catch (writeError) {
|
|
499
|
+
fallbackError = writeError;
|
|
500
|
+
}
|
|
501
|
+
await primitives.remove(temporary).catch(() => undefined);
|
|
502
|
+
if (fallbackError !== undefined) {
|
|
503
|
+
throw new AggregateError([error, fallbackError], `failed to replace "${file}" atomically (${String(error)}) or by direct write (${String(fallbackError)})`);
|
|
504
|
+
}
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Atomically replace one UTF-8 state file from a same-directory temp file,
|
|
511
|
+
* degrading to a direct overwrite when the atomic rename cannot proceed
|
|
512
|
+
* (see {@link replaceFileAtomicOrDirect} for the Windows EPERM rationale).
|
|
513
|
+
*/
|
|
448
514
|
async function atomicWriteText(file, content) {
|
|
449
515
|
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
450
516
|
try {
|
|
451
517
|
await writeFile(temporary, content, { encoding: 'utf8', flag: 'wx' });
|
|
452
|
-
await rename(temporary, file);
|
|
453
518
|
}
|
|
454
519
|
catch (error) {
|
|
455
520
|
await rm(temporary, { force: true }).catch(() => undefined);
|
|
456
521
|
throw error;
|
|
457
522
|
}
|
|
523
|
+
await replaceFileAtomicOrDirect(temporary, file, content, {
|
|
524
|
+
rename: (from, to) => rename(from, to),
|
|
525
|
+
writeFile: (target, payload) => writeFile(target, payload, 'utf8'),
|
|
526
|
+
remove: (path) => rm(path, { force: true }),
|
|
527
|
+
});
|
|
458
528
|
}
|
|
459
529
|
/** Whether a parsed JSON value is a plain record. */
|
|
460
530
|
function isRecord(value) {
|
|
@@ -566,6 +636,30 @@ function isTeamMessage(value) {
|
|
|
566
636
|
export async function removeTeamDir(stateRoot, teamId) {
|
|
567
637
|
await rm(join(stateRoot, teamId), { recursive: true, force: true });
|
|
568
638
|
}
|
|
639
|
+
/**
|
|
640
|
+
* `rename` with the same transient retry policy as the state-file atomic
|
|
641
|
+
* write, for paths (like archiving a whole team directory) where there is no
|
|
642
|
+
* content-equivalent direct-write degradation on Windows. A short-lived
|
|
643
|
+
* delete-sharing lock on any file below the renamed path is retried a few
|
|
644
|
+
* times before the error propagates.
|
|
645
|
+
* @param from - source path.
|
|
646
|
+
* @param to - destination path.
|
|
647
|
+
*/
|
|
648
|
+
async function renameWithRetry(from, to) {
|
|
649
|
+
for (let attempt = 0;; attempt += 1) {
|
|
650
|
+
try {
|
|
651
|
+
await rename(from, to);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
catch (error) {
|
|
655
|
+
if (isRetryableRenameError(error) && attempt < ATOMIC_RENAME_RETRIES) {
|
|
656
|
+
await sleep(ATOMIC_RENAME_RETRY_DELAY_MS);
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
throw error;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
569
663
|
/**
|
|
570
664
|
* Archive a team instead of deleting it: the whole directory (team.json with
|
|
571
665
|
* tasks and dependency graph, plus the mailboxes) moves under
|
|
@@ -583,21 +677,26 @@ export async function archiveTeamDir(stateRoot, teamId) {
|
|
|
583
677
|
const previous = join(archiveRoot, `.${teamId}.previous-${randomUUID()}`);
|
|
584
678
|
let displaced = false;
|
|
585
679
|
try {
|
|
586
|
-
|
|
680
|
+
// The same Windows EPERM-on-rename applies at the directory boundary: a
|
|
681
|
+
// delete-sharing violation on any file below `target` blocks the move, so
|
|
682
|
+
// retry the transient-lock case before giving up.
|
|
683
|
+
await renameWithRetry(target, previous);
|
|
587
684
|
displaced = true;
|
|
588
685
|
}
|
|
589
686
|
catch (error) {
|
|
687
|
+
// Only ENOENT means there was nothing to displace; any other failure
|
|
688
|
+
// (including a persistent EPERM lock) surfaces to the caller.
|
|
590
689
|
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
|
|
591
690
|
throw error;
|
|
592
691
|
}
|
|
593
692
|
}
|
|
594
693
|
try {
|
|
595
|
-
await
|
|
694
|
+
await renameWithRetry(source, target);
|
|
596
695
|
}
|
|
597
696
|
catch (error) {
|
|
598
697
|
if (displaced) {
|
|
599
698
|
try {
|
|
600
|
-
await
|
|
699
|
+
await renameWithRetry(previous, target);
|
|
601
700
|
}
|
|
602
701
|
catch (restoreError) {
|
|
603
702
|
throw new AggregateError([error, restoreError], `failed to archive team "${teamId}" and restore its previous archive`);
|
package/lib/tools.js
CHANGED
|
@@ -221,12 +221,13 @@ export function registerAgentTeamsTools(ctx, config) {
|
|
|
221
221
|
}));
|
|
222
222
|
ctx.tools.register(defineTool({
|
|
223
223
|
name: 'agent_teams_add_member',
|
|
224
|
-
description: 'Add a durable continuable member. By default it snapshots the captain\'s current LLM
|
|
224
|
+
description: 'Add a durable continuable member. By default it snapshots the captain\'s current LLM route and effort. Supply provider/model only for an explicitly requested role-specific route; a changed provider or model automatically uses the target model\'s default effort. Set reasoning_effort only to request one of the target model\'s supported ids explicitly (or "default" to force its default). The member waits for messages, works on assigned tasks, and can message the team.',
|
|
225
225
|
parameters: {
|
|
226
226
|
name: { type: 'string', required: true, description: 'Unique member name inside the team.' },
|
|
227
227
|
role: { type: 'string', description: 'Role of the member (e.g. researcher, engineer, reviewer).' },
|
|
228
228
|
provider: { type: 'string', description: 'Optional LLM provider route. Use only when the user explicitly requests a different provider; requires model.' },
|
|
229
229
|
model: { type: 'string', description: 'Optional model override. Omit for the captain\'s current model (or the configured memberModel default).' },
|
|
230
|
+
reasoning_effort: { type: 'string', description: 'Optional reasoning effort override: one of the target model\'s supported effort ids, or "default" to force its default. When omitted, the captain\'s effort is inherited only for the same provider/model; a changed route uses the target default.' },
|
|
230
231
|
},
|
|
231
232
|
output: {
|
|
232
233
|
schema: {
|
|
@@ -270,6 +271,7 @@ export function registerAgentTeamsTools(ctx, config) {
|
|
|
270
271
|
provider: args.provider,
|
|
271
272
|
model: args.model,
|
|
272
273
|
defaultModel: config.memberModel,
|
|
274
|
+
reasoningEffort: args.reasoning_effort,
|
|
273
275
|
}, exec.signal);
|
|
274
276
|
const member = {
|
|
275
277
|
id: '',
|
|
@@ -283,7 +285,19 @@ export function registerAgentTeamsTools(ctx, config) {
|
|
|
283
285
|
};
|
|
284
286
|
await spawnMember(ctx, memberRuntime(config), memberSelections, selection, captain, fresh, member, config.stateDir, exec.signal);
|
|
285
287
|
fresh.members.push(member);
|
|
286
|
-
|
|
288
|
+
try {
|
|
289
|
+
await writeTeam(stateRoot, fresh);
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
// The continuable child is already live, but the durable team record
|
|
293
|
+
// never saw it. Retire the orphan so it disappears from subagent
|
|
294
|
+
// listings and cannot be resumed, then surface the write failure.
|
|
295
|
+
if (member.id !== '') {
|
|
296
|
+
await recordRetiredMemberIds(stateRoot, [member.id]).catch(() => undefined);
|
|
297
|
+
interruptMember(ctx, captain, member.id);
|
|
298
|
+
}
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
287
301
|
appendTeamEvent(ctx, captainSessionOf(ctx, fresh.captainSessionId, captain.session), 'agent-teams/member-added', {
|
|
288
302
|
teamId: fresh.id,
|
|
289
303
|
memberId: member.id,
|
package/lib/types/members.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface MemberLlmSelectionRequest {
|
|
|
38
38
|
model?: string;
|
|
39
39
|
/** Plugin-level member model default. */
|
|
40
40
|
defaultModel?: string;
|
|
41
|
+
/** Explicit reasoning effort; "default" selects the target model's default effort. */
|
|
42
|
+
reasoningEffort?: string;
|
|
41
43
|
}
|
|
42
44
|
/** Process-local bridge between spawn admission and synchronous child setup. */
|
|
43
45
|
export interface MemberSelectionRuntime {
|
|
@@ -46,10 +48,12 @@ export interface MemberSelectionRuntime {
|
|
|
46
48
|
}
|
|
47
49
|
/**
|
|
48
50
|
* Resolve one member's complete model selection. Ordinary members snapshot the
|
|
49
|
-
* captain's current request route and reasoning effort.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* captain's current request route and reasoning effort. When provider or model
|
|
52
|
+
* changes, effort is intentionally omitted so the target model materializes
|
|
53
|
+
* its own default instead of receiving an adapter-owned id from another route.
|
|
54
|
+
* An explicit effort overrides either policy; the sentinel "default" also
|
|
55
|
+
* selects the target model's default. The final effort is validated against
|
|
56
|
+
* the target model before a child is created.
|
|
53
57
|
*/
|
|
54
58
|
export declare function resolveMemberLlmSelection(ctx: Context, captain: Agent, request: MemberLlmSelectionRequest, signal?: AbortSignal): Promise<MemberLlmSelection>;
|
|
55
59
|
/**
|
package/lib/types/state.d.ts
CHANGED
|
@@ -149,6 +149,35 @@ export declare function releaseMailboxDelivery(stateRoot: string, teamId: string
|
|
|
149
149
|
* malformed lines for diagnostics. Callers serialize this with the team lock.
|
|
150
150
|
*/
|
|
151
151
|
export declare function acknowledgeMailbox(stateRoot: string, teamId: string, agentKey: string, messageIds: readonly string[]): Promise<void>;
|
|
152
|
+
/** Filesystem primitives used by {@link replaceFileAtomicOrDirect}; injectable for tests. */
|
|
153
|
+
export interface AtomicReplacePrimitives {
|
|
154
|
+
rename: (from: string, to: string) => Promise<void>;
|
|
155
|
+
writeFile: (file: string, content: string) => Promise<void>;
|
|
156
|
+
remove: (file: string) => Promise<void>;
|
|
157
|
+
}
|
|
158
|
+
/** Tuning knobs for {@link replaceFileAtomicOrDirect} (defaults match production). */
|
|
159
|
+
export interface AtomicReplaceOptions {
|
|
160
|
+
/** Rename attempts before the direct-write fallback (default 3). */
|
|
161
|
+
retries?: number;
|
|
162
|
+
/** Delay between rename attempts in ms (default 50). */
|
|
163
|
+
retryDelayMs?: number;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Replace `file` with `content`, preferring an atomic same-directory rename of
|
|
167
|
+
* an already-written temp file.
|
|
168
|
+
*
|
|
169
|
+
* On Windows, `rename(tmp, file)` over an existing target throws EPERM while
|
|
170
|
+
* any other process keeps the target open without FILE_SHARE_DELETE (editors,
|
|
171
|
+
* indexers, antivirus scans, preview panes). By that point the payload has
|
|
172
|
+
* already been fully written to the temp file, so a direct overwrite of the
|
|
173
|
+
* target is a content-equivalent degraded path: retry the rename a few times
|
|
174
|
+
* (transient locks clear quickly), then write the target in place. Every path
|
|
175
|
+
* removes the temp file; when both the atomic rename and the direct write
|
|
176
|
+
* fail, the combined error surfaces as an {@link AggregateError}.
|
|
177
|
+
*
|
|
178
|
+
* @returns nothing once the file has been replaced by one of the two paths.
|
|
179
|
+
*/
|
|
180
|
+
export declare function replaceFileAtomicOrDirect(temporary: string, file: string, content: string, primitives: AtomicReplacePrimitives, options?: AtomicReplaceOptions): Promise<void>;
|
|
152
181
|
/**
|
|
153
182
|
* Remove a team's whole directory (members should be interrupted first).
|
|
154
183
|
* @param stateRoot - resolved absolute state root directory.
|
package/lib/types/types.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface TeamMember {
|
|
|
51
51
|
provider?: string;
|
|
52
52
|
/** Resolved model captured when this member was created. */
|
|
53
53
|
model?: string;
|
|
54
|
-
/** Resolved reasoning effort captured from the captain
|
|
54
|
+
/** Resolved reasoning effort captured from the captain or target model default. */
|
|
55
55
|
reasoningEffort?: string;
|
|
56
56
|
joinedAt: number;
|
|
57
57
|
status: MemberStatus;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanmicoder/dsh-agent-teams",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "AgentTeams for DeepSeek Harness: multi-agent team collaboration (captain, members, tasks with dependencies, messaging) driven by natural language, with a tree monitor in the web GUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|