@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
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* Team notices, and how a rollover's durable identity is named).
|
|
7
7
|
*
|
|
8
8
|
* The engine owns every mechanic this module does not state: the idle-boundary
|
|
9
|
-
* swap, carried input, checkpoint continuations, crash repair
|
|
10
|
-
* Member vocabulary and the
|
|
11
|
-
*
|
|
9
|
+
* swap, carried input, checkpoint continuations, crash repair, and the
|
|
10
|
+
* context-pressure policy itself. Team owns the Member vocabulary and the
|
|
11
|
+
* ledger-backed lifecycle behind
|
|
12
|
+
* {@link TeamContextContinuityOptions.executeTransition}, and the per-Member
|
|
13
|
+
* readings the pressure policy asks for.
|
|
12
14
|
*
|
|
13
15
|
* The projection state this host hands the coordinator is the engine's own
|
|
14
16
|
* (`ContextProjectionState`), read from the unit `context-projection.ts`
|
|
@@ -16,8 +18,9 @@
|
|
|
16
18
|
* @module @wowyuarm/dsh-agent-team/context-continuity-host
|
|
17
19
|
*/
|
|
18
20
|
import { createHash } from 'node:crypto';
|
|
19
|
-
import { ContextContinuityCoordinator, ContextMessageCodec, } from '@wowyuarm/dsh-context-continuity';
|
|
21
|
+
import { ContextContinuityCoordinator, ContextMessageCodec, ContextPressurePolicy, } from '@wowyuarm/dsh-context-continuity';
|
|
20
22
|
import { SessionId as SessionIdBrand } from '@deepseek-ai/dsh-session';
|
|
23
|
+
import z from '@deepseek-ai/schemastery';
|
|
21
24
|
import { AGENT_TEAM_PLUGIN_ID, isAgentTeamSource } from "./context-source.js";
|
|
22
25
|
/**
|
|
23
26
|
* The one writer of context-continuity messages for Team. Section names are
|
|
@@ -88,3 +91,194 @@ export class TeamContextContinuityHost {
|
|
|
88
91
|
export function createTeamContextManagement(options) {
|
|
89
92
|
return new ContextContinuityCoordinator(new TeamContextContinuityHost(options), TEAM_CONTEXT_CODEC);
|
|
90
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Team's wording for the engine's pressure notice: the two labels name work in
|
|
96
|
+
* Team's own vocabulary, while the numbers, the ordering and the default action
|
|
97
|
+
* stay the engine's. A label is presentation; the notice's substance is not a
|
|
98
|
+
* knob.
|
|
99
|
+
*/
|
|
100
|
+
const TEAM_PRESSURE_TEXT = {
|
|
101
|
+
inHandLabel: 'Active Claims',
|
|
102
|
+
jobsLabel: 'Owner jobs',
|
|
103
|
+
};
|
|
104
|
+
/** How many of the generation's earlier user inputs one judgement is given. */
|
|
105
|
+
const RECENT_INPUT_LIMIT = 8;
|
|
106
|
+
/**
|
|
107
|
+
* The long-gap gate as row configuration: every field is optional, so a
|
|
108
|
+
* deployment states only the thresholds it disagrees with and the engine's own
|
|
109
|
+
* default answers the rest. The three answer different questions — how large a
|
|
110
|
+
* context the gate applies to, what counts as a long gap, and how long one
|
|
111
|
+
* judgement may take before the step proceeds without one — and the last is the
|
|
112
|
+
* one that decides whether a configured judge is ever heard, because a judge
|
|
113
|
+
* whose own budget is longer than this deadline never answers in time.
|
|
114
|
+
*
|
|
115
|
+
* All three are volatile, and the engine is what makes that safe: it reads
|
|
116
|
+
* every field at the step it applies to, never at construction, so a Host that
|
|
117
|
+
* keeps one gate object and refreshes its fields in place changes the policy a
|
|
118
|
+
* running generation is judged by. `judgeTimeoutMs` is the field that needs it
|
|
119
|
+
* most — the deadline a judge can answer within is a property of the endpoint,
|
|
120
|
+
* not of the bundle, and a deployment that discovers its endpoint is slow must
|
|
121
|
+
* be able to raise it without editing a patch layer and restarting.
|
|
122
|
+
*/
|
|
123
|
+
export const TEAM_PRESSURE_GATE_SCHEMA = z.object({
|
|
124
|
+
tokens: z.natural().volatile().description('Context size at or above which the long-gap gate may hold a step. Default: 128000.'),
|
|
125
|
+
idleMs: z.natural().volatile().description('How long a generation must have been idle for a step to count as a long gap. Default: 1800000 (30 minutes).'),
|
|
126
|
+
judgeTimeoutMs: z.natural().volatile().description("How long one relatedness judgement may take before the step proceeds without one. Default: 10000, not the engine's own 5000."),
|
|
127
|
+
});
|
|
128
|
+
/**
|
|
129
|
+
* Team's `PressurePolicyHost`. Every member is resolved per Member at call
|
|
130
|
+
* time, because one policy serves every Member this Host runs. The readings
|
|
131
|
+
* that need the step's own argument are fed for exactly one policy call —
|
|
132
|
+
* {@link beginStep} to {@link endStep} — so a judgement is never made from a
|
|
133
|
+
* neighbour Member's messages.
|
|
134
|
+
*/
|
|
135
|
+
export class TeamPressurePolicyHost {
|
|
136
|
+
options;
|
|
137
|
+
pluginId = AGENT_TEAM_PLUGIN_ID;
|
|
138
|
+
admitting = new Map();
|
|
139
|
+
constructor(options) {
|
|
140
|
+
this.options = options;
|
|
141
|
+
}
|
|
142
|
+
/** Record the input one step is admitting: the host's own pre-step argument. */
|
|
143
|
+
beginStep(memberId, messages) {
|
|
144
|
+
this.admitting.set(memberId, messages);
|
|
145
|
+
}
|
|
146
|
+
/** Forget that input again: the view lives only while its policy call runs. */
|
|
147
|
+
endStep(memberId) {
|
|
148
|
+
this.admitting.delete(memberId);
|
|
149
|
+
}
|
|
150
|
+
limitsFor(memberId) {
|
|
151
|
+
const agent = this.options.agentForMember(memberId);
|
|
152
|
+
return agent === undefined ? undefined : this.options.limitsForAgent(agent);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The Member's durable surface, for proving a reduction. Without a live Agent
|
|
156
|
+
* there is nothing to measure; the policy reaches this only for a Member whose
|
|
157
|
+
* limits resolved, which already requires one.
|
|
158
|
+
*/
|
|
159
|
+
surfaceFor(memberId) {
|
|
160
|
+
const agent = this.options.agentForMember(memberId);
|
|
161
|
+
if (agent === undefined)
|
|
162
|
+
return { generation: 0 };
|
|
163
|
+
const tokens = agent.ctx.get('tokenMeter')?.measure(agent.session)?.totalTokens;
|
|
164
|
+
return { generation: agent.session.surface.replaceGeneration, ...(tokens === undefined ? {} : { tokens }) };
|
|
165
|
+
}
|
|
166
|
+
compactionFor(memberId) {
|
|
167
|
+
const agent = this.options.agentForMember(memberId);
|
|
168
|
+
return agent === undefined ? undefined : this.options.compactionForAgent(agent);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The Member's durable log, as the notice latch and the idle gap read it. The
|
|
172
|
+
* engine's contract asks for the span's own events and the inherited count
|
|
173
|
+
* beside them; the `ownEvents()` reader is what states that boundary today.
|
|
174
|
+
*/
|
|
175
|
+
logSpanFor(memberId) {
|
|
176
|
+
const agent = this.options.agentForMember(memberId);
|
|
177
|
+
if (agent === undefined)
|
|
178
|
+
return { sessionId: '', inheritedEventCount: 0, events: [] };
|
|
179
|
+
return { sessionId: agent.session.id, inheritedEventCount: agent.session.inheritedEventCount, events: agent.session.ownEvents() };
|
|
180
|
+
}
|
|
181
|
+
inHandFor(memberId) {
|
|
182
|
+
return {
|
|
183
|
+
inHand: this.options.activeClaimLabels(memberId),
|
|
184
|
+
jobs: this.options.runningJobLabels(memberId),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The input this step is admitting, and the generation's earlier user input
|
|
189
|
+
* beside it. Both sides exclude Team's own notices: what the gate judges is
|
|
190
|
+
* what a human sent, and a rederived Team notification is neither the request
|
|
191
|
+
* being held nor evidence of what the request continues.
|
|
192
|
+
*/
|
|
193
|
+
relatednessFor(memberId) {
|
|
194
|
+
const agent = this.options.agentForMember(memberId);
|
|
195
|
+
const admitted = this.admitting.get(memberId);
|
|
196
|
+
if (agent === undefined || admitted === undefined)
|
|
197
|
+
return undefined;
|
|
198
|
+
const input = externalInputText(admitted);
|
|
199
|
+
if (input.trim().length === 0)
|
|
200
|
+
return undefined;
|
|
201
|
+
return { input, recent: this.recentInput(agent, admitted) };
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The generation's earlier human input, oldest first, without the messages
|
|
205
|
+
* this step is admitting: the judge is asked what the arriving request
|
|
206
|
+
* continues, so the arriving request is not also part of its own evidence.
|
|
207
|
+
*/
|
|
208
|
+
recentInput(agent, admitted) {
|
|
209
|
+
const admittedIds = new Set(admitted.map(message => message.id));
|
|
210
|
+
return agent.session.deriveMessages()
|
|
211
|
+
.flatMap(message => message.role === 'user' && !admittedIds.has(message.id) && !isAgentTeamSource(message.source)
|
|
212
|
+
? [messageText(message)]
|
|
213
|
+
: [])
|
|
214
|
+
.filter(text => text.trim().length > 0)
|
|
215
|
+
.slice(-RECENT_INPUT_LIMIT);
|
|
216
|
+
}
|
|
217
|
+
judgeFor(memberId) {
|
|
218
|
+
const resolve = this.options.judgeForAgent;
|
|
219
|
+
const agent = this.options.agentForMember(memberId);
|
|
220
|
+
if (resolve === undefined || agent === undefined)
|
|
221
|
+
return undefined;
|
|
222
|
+
return resolve(agent);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Steer one notice into the Member's running turn. A Member with no live
|
|
226
|
+
* Agent throws rather than reporting success: the caller holds a step on the
|
|
227
|
+
* strength of this call, and a held step whose instruction never arrived
|
|
228
|
+
* would stall the Member behind a message that does not exist.
|
|
229
|
+
*/
|
|
230
|
+
steer(memberId, notice) {
|
|
231
|
+
const agent = this.options.agentForMember(memberId);
|
|
232
|
+
if (agent === undefined)
|
|
233
|
+
throw new Error('the Member has no live Agent to steer the notice into');
|
|
234
|
+
agent.steer(notice);
|
|
235
|
+
}
|
|
236
|
+
failedFor(memberId, diagnostic) {
|
|
237
|
+
this.options.failed(memberId, diagnostic);
|
|
238
|
+
}
|
|
239
|
+
log(message, memberId) {
|
|
240
|
+
this.options.log(message, memberId);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* The engine's context-pressure policy over Team's Member lifecycle: the one
|
|
245
|
+
* place the two sides meet. It owns no decision of its own — the ordering, the
|
|
246
|
+
* notice latch, the fail-closed reduction proof and the long-gap gate are all
|
|
247
|
+
* the engine's — and adds only the step-scoped input view that only the host
|
|
248
|
+
* can have.
|
|
249
|
+
*/
|
|
250
|
+
export class TeamPressurePolicy {
|
|
251
|
+
host;
|
|
252
|
+
policy;
|
|
253
|
+
constructor(options) {
|
|
254
|
+
this.host = new TeamPressurePolicyHost(options);
|
|
255
|
+
this.policy = new ContextPressurePolicy(this.host, TEAM_PRESSURE_TEXT, options.gate);
|
|
256
|
+
}
|
|
257
|
+
/** The pre-step decision for one Member, with the input that step claims. */
|
|
258
|
+
async onPreStep(memberId, messages, signal) {
|
|
259
|
+
this.host.beginStep(memberId, messages);
|
|
260
|
+
try {
|
|
261
|
+
return await this.policy.onPreStep(memberId, signal);
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
this.host.endStep(memberId);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
onRequestError(memberId, failure, signal) {
|
|
268
|
+
return this.policy.onRequestError(memberId, failure, signal);
|
|
269
|
+
}
|
|
270
|
+
onAssistantMessage(memberId) {
|
|
271
|
+
this.policy.onAssistantMessage(memberId);
|
|
272
|
+
}
|
|
273
|
+
dispose() {
|
|
274
|
+
this.policy.dispose();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/** The text a step is admitting that came from outside Team, in arrival order. */
|
|
278
|
+
function externalInputText(messages) {
|
|
279
|
+
return messages.flatMap(message => isAgentTeamSource(message.source) ? [] : [messageText(message)]).join('\n');
|
|
280
|
+
}
|
|
281
|
+
/** One message's visible text, blocks in order. */
|
|
282
|
+
function messageText(message) {
|
|
283
|
+
return message.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('\n');
|
|
284
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Team's relatedness judge: the long-gap gate's `jev` service, mounted by this
|
|
3
|
+
* bundle when a key resolves and left unmounted when none does.
|
|
4
|
+
*
|
|
5
|
+
* The key is the switch, so nothing here decides policy: a key in the Host
|
|
6
|
+
* row's `jev` config — or in the environment variable that config names —
|
|
7
|
+
* mounts the service, clearing it unmounts the service, and the gate asks
|
|
8
|
+
* whatever is mounted. A deployment that mounts its own `jev` row keeps
|
|
9
|
+
* working, because the judge is read as a service rather than as this mount.
|
|
10
|
+
*
|
|
11
|
+
* The mount also carries the one setting the two sides must agree on: jev's own
|
|
12
|
+
* call budget is derived from the gate's deadline, so a judge can never be
|
|
13
|
+
* configured to outlive the time the gate gives it — the mismatch that would
|
|
14
|
+
* otherwise leave every long-gap step ungated without a word.
|
|
15
|
+
* @module @wowyuarm/dsh-agent-team/context-judge
|
|
16
|
+
*/
|
|
17
|
+
import { DEFAULT_API_KEY_ENV, Jev } from '@wowyuarm/dsh-jev';
|
|
18
|
+
/**
|
|
19
|
+
* The judge's call budget keeps this much of the gate's deadline for the hop
|
|
20
|
+
* back: the gate abandons a judgement that outlives its own deadline, so a
|
|
21
|
+
* judge allowed to use the whole deadline would always be abandoned first.
|
|
22
|
+
*/
|
|
23
|
+
const JUDGE_DEADLINE_MARGIN_MS = 1_000;
|
|
24
|
+
/**
|
|
25
|
+
* Wait before the judge's second attempt. Pinned rather than inherited from the
|
|
26
|
+
* service's own default, because it is half of the same budget the deadline
|
|
27
|
+
* above sizes: a backoff longer than this would spend the gate's remaining time
|
|
28
|
+
* waiting instead of asking again.
|
|
29
|
+
*/
|
|
30
|
+
const JUDGE_RETRY_DELAY_MS = 500;
|
|
31
|
+
/** The key a row states, or the one the named environment variable holds. */
|
|
32
|
+
function resolvedKey(config, envName) {
|
|
33
|
+
const stated = config?.apiKey;
|
|
34
|
+
if (stated !== undefined && stated !== '')
|
|
35
|
+
return stated;
|
|
36
|
+
const fromEnv = process.env[envName];
|
|
37
|
+
return fromEnv === undefined || fromEnv === '' ? undefined : fromEnv;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The config one mount runs with. Exported because it is the whole contract
|
|
41
|
+
* between the gate's deadline and the judge's own budget, and a pure function
|
|
42
|
+
* is what a test can hold still.
|
|
43
|
+
*/
|
|
44
|
+
export function judgeMountConfig(config, key, envName, deadlineMs) {
|
|
45
|
+
return {
|
|
46
|
+
apiKey: key,
|
|
47
|
+
apiKeyEnv: envName,
|
|
48
|
+
...(config?.model === undefined ? {} : { model: config.model }),
|
|
49
|
+
...(config?.apiBase === undefined ? {} : { apiBase: config.apiBase }),
|
|
50
|
+
timeoutMs: Math.max(JUDGE_DEADLINE_MARGIN_MS, deadlineMs - JUDGE_DEADLINE_MARGIN_MS),
|
|
51
|
+
// Two attempts: this call sits on the path that starts a turn, so the
|
|
52
|
+
// budget stays small — but one transport blip should not cost the step its
|
|
53
|
+
// gate, which is what a single attempt did. The gate races the whole call
|
|
54
|
+
// against its deadline anyway, so a second attempt that cannot finish in
|
|
55
|
+
// time costs the turn nothing beyond what it already spent.
|
|
56
|
+
attempts: 2,
|
|
57
|
+
retryDelayMs: JUDGE_RETRY_DELAY_MS,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* One Host's judge: the service it mounted, the failure that kept one from
|
|
62
|
+
* mounting, and the reconciliation that keeps the two in step with the row.
|
|
63
|
+
*/
|
|
64
|
+
export class TeamContextJudge {
|
|
65
|
+
ctx;
|
|
66
|
+
options;
|
|
67
|
+
fiber;
|
|
68
|
+
failure;
|
|
69
|
+
/** The mount the live fiber runs with, so an unchanged row does not restart it. */
|
|
70
|
+
mounted;
|
|
71
|
+
constructor(ctx, options) {
|
|
72
|
+
this.ctx = ctx;
|
|
73
|
+
this.options = options;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The judge a step may ask, or undefined when the deployment has none: this
|
|
77
|
+
* bundle's own mount first, then a service another row mounted.
|
|
78
|
+
*/
|
|
79
|
+
judge() {
|
|
80
|
+
return this.fiber?.ctx.get('jev') ?? this.ctx.get('jev');
|
|
81
|
+
}
|
|
82
|
+
/** The environment variable this deployment reads the key from. */
|
|
83
|
+
keyEnv() {
|
|
84
|
+
const name = this.options.config()?.apiKeyEnv;
|
|
85
|
+
return name === undefined || name === '' ? DEFAULT_API_KEY_ENV : name;
|
|
86
|
+
}
|
|
87
|
+
/** Whether a key is resolvable right now, from the row or from its environment. */
|
|
88
|
+
key() {
|
|
89
|
+
return resolvedKey(this.options.config(), this.keyEnv());
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Make the mount match the row: mount when a key resolves and none is
|
|
93
|
+
* mounted, re-configure a live mount whose settings changed, unmount when the
|
|
94
|
+
* key is gone. Called at init and on every volatile update of the row, so a
|
|
95
|
+
* key written in the Team settings surface takes effect without a restart.
|
|
96
|
+
*/
|
|
97
|
+
async sync() {
|
|
98
|
+
const config = this.options.config();
|
|
99
|
+
const key = this.key();
|
|
100
|
+
if (key === undefined) {
|
|
101
|
+
this.failure = undefined;
|
|
102
|
+
await this.unmount();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const wanted = judgeMountConfig(config, key, this.keyEnv(), this.options.deadlineMs());
|
|
106
|
+
const signature = JSON.stringify(wanted);
|
|
107
|
+
if (this.fiber !== undefined && signature === this.mounted)
|
|
108
|
+
return;
|
|
109
|
+
if (this.fiber === undefined) {
|
|
110
|
+
this.fiber = this.ctx.plugin(Jev, wanted);
|
|
111
|
+
this.mounted = signature;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.mounted = signature;
|
|
115
|
+
// `update` re-activates the fiber with the new config, and clears a
|
|
116
|
+
// previous failure, so a corrected endpoint recovers without a restart.
|
|
117
|
+
this.fiber.update(wanted);
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
await this.fiber.await();
|
|
121
|
+
this.failure = undefined;
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
// A rejected key or endpoint is a configuration failure the settings
|
|
125
|
+
// surface reports: the row keeps what the Human wrote, and the status
|
|
126
|
+
// says why the judge is not answering.
|
|
127
|
+
this.failure = error instanceof Error ? error.message : String(error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Drop this bundle's mount, if it has one. */
|
|
131
|
+
async unmount() {
|
|
132
|
+
const fiber = this.fiber;
|
|
133
|
+
this.fiber = undefined;
|
|
134
|
+
this.mounted = undefined;
|
|
135
|
+
if (fiber !== undefined)
|
|
136
|
+
await fiber.dispose();
|
|
137
|
+
}
|
|
138
|
+
/** What the Team settings surface states about the judge. */
|
|
139
|
+
status() {
|
|
140
|
+
const keyEnv = this.keyEnv();
|
|
141
|
+
if (this.judge() !== undefined)
|
|
142
|
+
return { enabled: true, keyEnv };
|
|
143
|
+
return {
|
|
144
|
+
enabled: false,
|
|
145
|
+
reason: this.key() === undefined ? 'no-key' : 'unavailable',
|
|
146
|
+
...(this.failure === undefined ? {} : { detail: this.failure }),
|
|
147
|
+
keyEnv,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -34,19 +34,24 @@ export const AGENT_TEAM_V3_RENAMED_KIND = `plugin:${AGENT_TEAM_PLUGIN_ID}`;
|
|
|
34
34
|
/** The kind the read-time conversion renames one producer's released V3 sources into. */
|
|
35
35
|
export const v3RenamedSourceKind = (producer) => `plugin:${producer}`;
|
|
36
36
|
/**
|
|
37
|
-
* Whether one
|
|
38
|
-
*
|
|
39
|
-
* prefix test, which would claim
|
|
37
|
+
* Whether one source kind is a producer's own: the kind it writes now and the
|
|
38
|
+
* read-time conversion of its released V3 history. Both identities are matched
|
|
39
|
+
* by exact kind equality — never by a `plugin:` prefix test, which would claim
|
|
40
|
+
* third-party producers' rows as that producer's facts.
|
|
40
41
|
*/
|
|
42
|
+
export function matchesProducerKind(kind, producer) {
|
|
43
|
+
return kind === producer || kind === v3RenamedSourceKind(producer);
|
|
44
|
+
}
|
|
45
|
+
/** Whether one message source carries this plugin's own attribution. */
|
|
41
46
|
export function isAgentTeamSource(source) {
|
|
42
|
-
return source.kind
|
|
47
|
+
return matchesProducerKind(source.kind, AGENT_TEAM_PLUGIN_ID);
|
|
43
48
|
}
|
|
44
49
|
/**
|
|
45
50
|
* Whether one source kind carries this plugin's attribution, for call sites
|
|
46
|
-
* whose sources arrive untyped.
|
|
51
|
+
* whose sources arrive untyped.
|
|
47
52
|
*/
|
|
48
53
|
export function isAgentTeamSourceKind(kind) {
|
|
49
|
-
return kind
|
|
54
|
+
return matchesProducerKind(kind, AGENT_TEAM_PLUGIN_ID);
|
|
50
55
|
}
|
|
51
56
|
/** Handoff snapshot section name carrying the model-authored prose. */
|
|
52
57
|
export const HANDOFF_SECTION_NAME = 'HANDOFF';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** The one local environment check: how the running DSH compares to the line this bundle declares. */
|
|
2
|
+
import { evaluatePluginCompatibility, getDshRuntimeVersion } from '@deepseek-ai/dsh-app-boot';
|
|
3
|
+
import { bundleVersionOf, readInstalledManifest } from "./installed-manifest.js";
|
|
4
|
+
/** Prefix of every peer that sits on the DSH version line; the bare scope is not one. */
|
|
5
|
+
const DSH_PEER_PREFIX = '@deepseek-ai/dsh-';
|
|
6
|
+
/** The `>=lower <upper>` shape every DSH peer carries; a comparator series, not a single version. */
|
|
7
|
+
const SUPPORT_RANGE_SHAPE = /^>=(\S+)\s+<(\S+)$/u;
|
|
8
|
+
/**
|
|
9
|
+
* Read the support line from the installed manifest's DSH peers, or nothing.
|
|
10
|
+
*
|
|
11
|
+
* Every `@deepseek-ai/dsh-*` peer must carry exactly the same range, and it must
|
|
12
|
+
* be a `>=<lower> <upper>` series: the version line is the manifest's own claim
|
|
13
|
+
* and a partially drifted manifest cannot state one. `scripts/check-version-consistency.mjs`
|
|
14
|
+
* holds the same shape to the certified baseline, so a run of this bundle
|
|
15
|
+
* states the same line its README does. Returns `undefined` when the peers
|
|
16
|
+
* disagree or the shape is unreadable.
|
|
17
|
+
*/
|
|
18
|
+
export function supportRangeOf(manifest) {
|
|
19
|
+
const fields = manifest;
|
|
20
|
+
if (typeof fields.peerDependencies !== 'object' || fields.peerDependencies === null)
|
|
21
|
+
return undefined;
|
|
22
|
+
const ranges = new Set();
|
|
23
|
+
for (const [name, range] of Object.entries(fields.peerDependencies)) {
|
|
24
|
+
if (!name.startsWith(DSH_PEER_PREFIX))
|
|
25
|
+
continue;
|
|
26
|
+
if (typeof range !== 'string')
|
|
27
|
+
return undefined;
|
|
28
|
+
ranges.add(range);
|
|
29
|
+
}
|
|
30
|
+
const distinct = [...ranges];
|
|
31
|
+
if (distinct.length !== 1)
|
|
32
|
+
return undefined;
|
|
33
|
+
const shape = SUPPORT_RANGE_SHAPE.exec(distinct[0]);
|
|
34
|
+
if (shape === null)
|
|
35
|
+
return undefined;
|
|
36
|
+
return Object.freeze({ lower: shape[1], upper: shape[2] });
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Judge one already-resolved triple, so every branch is testable without an
|
|
40
|
+
* environment. The verdict comes from the Harness's own
|
|
41
|
+
* `evaluatePluginCompatibility` — the plugin manager decides installability with
|
|
42
|
+
* that same function, and prerelease ordering is exactly what a hand-written
|
|
43
|
+
* comparison gets wrong.
|
|
44
|
+
*
|
|
45
|
+
* @param manifest - the installed bundle's parsed manifest, the source of the declared support line.
|
|
46
|
+
* @param runtimeVersion - the running DSH version, or `'unknown'` when it could not be read.
|
|
47
|
+
* @param bundleVersion - the installed bundle version, or `'unknown'`.
|
|
48
|
+
* @param unavailable - why the running version is not known, recorded when it is `'unknown'`.
|
|
49
|
+
* @returns the report the settings page renders.
|
|
50
|
+
*/
|
|
51
|
+
export function evaluateEnvironment(manifest, runtimeVersion, bundleVersion, unavailable) {
|
|
52
|
+
const range = supportRangeOf(manifest);
|
|
53
|
+
const base = {
|
|
54
|
+
bundleVersion,
|
|
55
|
+
...(range === undefined ? {} : { certifiedDshVersion: range.lower, supportRange: range }),
|
|
56
|
+
};
|
|
57
|
+
if (runtimeVersion === 'unknown') {
|
|
58
|
+
return Object.freeze({ ...base, verdict: 'undetermined', reason: unavailable ?? 'the running dsh version is unavailable' });
|
|
59
|
+
}
|
|
60
|
+
const running = { ...base, dshVersion: runtimeVersion };
|
|
61
|
+
// A real violation is answered first, and it is never downgraded to
|
|
62
|
+
// `undetermined`: a peer that drifted to a range of its own is a genuine
|
|
63
|
+
// "not satisfied" even though no single range can then be stated for the
|
|
64
|
+
// page. Only when nothing is violated does the question become whether the
|
|
65
|
+
// declared line can be stated at all.
|
|
66
|
+
// One evaluation covers every dsh peer in the manifest, and `range` above is
|
|
67
|
+
// derived from exactly those peers: re-judging the range it prints could not
|
|
68
|
+
// change this answer.
|
|
69
|
+
const satisfied = evaluatePluginCompatibility(manifest, {}, runtimeVersion) === undefined;
|
|
70
|
+
if (!satisfied)
|
|
71
|
+
return Object.freeze({ ...running, verdict: 'out-of-range' });
|
|
72
|
+
// Nothing is violated but there is no line to state: the page withholds the
|
|
73
|
+
// range rather than inventing one.
|
|
74
|
+
if (range === undefined) {
|
|
75
|
+
return Object.freeze({ ...base, verdict: 'undetermined', reason: 'the declared dsh support range is unreadable or drifted' });
|
|
76
|
+
}
|
|
77
|
+
return Object.freeze({ ...running, verdict: 'ok' });
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Read this Host's own environment: the installed bundle version, the declared
|
|
81
|
+
* support line, and how the running DSH compares to it.
|
|
82
|
+
*
|
|
83
|
+
* The certified DSH version is the range's lower bound, never the newest cut
|
|
84
|
+
* tested against it: `docs/dsh-release-compatibility.md` §4 records a baseline
|
|
85
|
+
* inside the declared range without moving the peers, so every version spot
|
|
86
|
+
* keeps naming the bound. Reading it from anywhere else would reintroduce the
|
|
87
|
+
* hand-maintained constant this check exists to remove.
|
|
88
|
+
*
|
|
89
|
+
* @returns the report, with `verdict: 'undetermined'` for any fact not established.
|
|
90
|
+
*/
|
|
91
|
+
export function reportEnvironment() {
|
|
92
|
+
const manifest = readInstalledManifest() ?? {};
|
|
93
|
+
const bundleVersion = bundleVersionOf(manifest);
|
|
94
|
+
let runtime;
|
|
95
|
+
try {
|
|
96
|
+
runtime = getDshRuntimeVersion();
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
return evaluateEnvironment(manifest, 'unknown', bundleVersion, error instanceof Error ? error.message : String(error));
|
|
100
|
+
}
|
|
101
|
+
return evaluateEnvironment(manifest, runtime, bundleVersion);
|
|
102
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, resolve } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
1
|
import z from '@deepseek-ai/schemastery';
|
|
5
2
|
import { parse } from 'yaml';
|
|
3
|
+
import { bundleVersionOf, readInstalledManifest } from "./installed-manifest.js";
|
|
6
4
|
/**
|
|
7
5
|
* Human identity profile: the one configurable display name plus the avatar
|
|
8
6
|
* reference. `member:human` stays the durable identity everywhere; only the
|
|
@@ -17,6 +15,12 @@ import { parse } from 'yaml';
|
|
|
17
15
|
* - avatar bytes live under a persistent directory below; the profile holds
|
|
18
16
|
* only the reference, never a data URL. The composer attachment cache is
|
|
19
17
|
* TTL-bound and must not hold avatar bytes.
|
|
18
|
+
*
|
|
19
|
+
* The legacy-section adoption below is transitional: it exists for installs
|
|
20
|
+
* upgraded from a pre-rc.1 profile document, and retires — with
|
|
21
|
+
* `LEGACY_HUMAN_PROFILE_SECTION`, `parseLegacyHumanProfile`, and
|
|
22
|
+
* `planLegacyAdoption` — once such installs are no longer supported. Nothing
|
|
23
|
+
* else in the Host reads that document.
|
|
20
24
|
*/
|
|
21
25
|
/**
|
|
22
26
|
* Settings namespace of the Human profile: the Team Host row's id in
|
|
@@ -33,36 +37,18 @@ export const HUMAN_PROFILE_DEFAULT_NAME = 'human';
|
|
|
33
37
|
export const HUMAN_PROFILE_REPO_URL = 'https://github.com/wowyuarm/dsh-agent-team';
|
|
34
38
|
/**
|
|
35
39
|
* Bundle version shown in the settings footnote, and the current side of the
|
|
36
|
-
* update check: the version of the package THIS Host runs from —
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* hand-maintained string: the 0.1.14
|
|
40
|
-
* it, which made the footnote name the
|
|
41
|
-
* offer the release the user already had.
|
|
40
|
+
* update check: the version of the package THIS Host runs from — the installed
|
|
41
|
+
* manifest's, read once at load through `./installed-manifest.ts`, so a `link:`
|
|
42
|
+
* checkout under the development profile and a registry tarball under stable
|
|
43
|
+
* each state their own truth. It is not a hand-maintained string: the 0.1.14
|
|
44
|
+
* bundle shipped with `0.1.13` written in it, which made the footnote name the
|
|
45
|
+
* previous release and the update check offer the release the user already had.
|
|
42
46
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* manifest degrades to `'unknown'`: the Remote's `version: string` contract
|
|
47
|
-
* holds and the update comparison simply compares nothing. The footnote is
|
|
48
|
-
* informational only and never gates behavior.
|
|
47
|
+
* An unreadable or malformed manifest degrades to `'unknown'`: the Remote's
|
|
48
|
+
* `version: string` contract holds and the update comparison simply compares
|
|
49
|
+
* nothing. The footnote is informational only and never gates behavior.
|
|
49
50
|
*/
|
|
50
|
-
export const HUMAN_PROFILE_VERSION =
|
|
51
|
-
/** Version of the manifest this package installed from, or `'unknown'`. */
|
|
52
|
-
function readInstalledBundleVersion() {
|
|
53
|
-
try {
|
|
54
|
-
const manifestPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../../package.json');
|
|
55
|
-
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
56
|
-
if (typeof manifest.version === 'string' && manifest.version !== '')
|
|
57
|
-
return manifest.version;
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// Reading our own manifest must never fail the Host boot: a host that
|
|
61
|
-
// cannot find its own package.json is a broken install, and the footnote
|
|
62
|
-
// reporting 'unknown' says so more honestly than a stale number.
|
|
63
|
-
}
|
|
64
|
-
return 'unknown';
|
|
65
|
-
}
|
|
51
|
+
export const HUMAN_PROFILE_VERSION = bundleVersionOf(readInstalledManifest());
|
|
66
52
|
/**
|
|
67
53
|
* Schemastery schema of the Human profile: the Team Host row's Config. Both
|
|
68
54
|
* fields are volatile, which is what lets an edit reach the running Host
|
|
@@ -139,12 +125,12 @@ export function parseLegacyHumanProfile(yamlText) {
|
|
|
139
125
|
* Decide what adoption may write: nothing unless the stored profile is still
|
|
140
126
|
* the pristine default, so a value the Human re-entered after the upgrade
|
|
141
127
|
* always wins, and never the legacy default name itself. The current profile
|
|
142
|
-
* carries explicit `undefined` on `avatarRef` — the Host getter spreads
|
|
143
|
-
*
|
|
144
|
-
* exactOptionalPropertyTypes; taking
|
|
145
|
-
* cast-free. The returned fields are exactly the ops the profile page
|
|
146
|
-
* have written; byte existence for `avatarRef` is the caller's I/O and
|
|
147
|
-
* already hold.
|
|
128
|
+
* carries explicit `undefined` on `avatarRef` — the Host getter spreads the
|
|
129
|
+
* live Config read — and that shape does not satisfy the optional-property
|
|
130
|
+
* form under exactOptionalPropertyTypes; taking it directly keeps the Host
|
|
131
|
+
* call cast-free. The returned fields are exactly the ops the profile page
|
|
132
|
+
* would have written; byte existence for `avatarRef` is the caller's I/O and
|
|
133
|
+
* must already hold.
|
|
148
134
|
*/
|
|
149
135
|
export function planLegacyAdoption(current, legacy) {
|
|
150
136
|
if (current.name !== HUMAN_PROFILE_DEFAULT_NAME || current.avatarRef !== undefined)
|