@gestaltrun/dsh-workspace-bundle 0.1.0 → 0.1.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.i18n.yaml +6 -6
- package/README.md +28 -8
- package/README.zh.md +28 -8
- package/lib/index.js +1737 -237
- package/lib/types/agent-config-contract/agent-configs.d.ts +621 -0
- package/lib/types/agent-config-contract/agent-configs.js +68 -0
- package/lib/types/agent-config-contract/cs-hosting.d.ts +1333 -0
- package/lib/types/agent-config-contract/cs-hosting.js +163 -0
- package/lib/types/agent-config-contract/errors.d.ts +224 -0
- package/lib/types/agent-config-contract/errors.js +202 -0
- package/lib/types/agent-config-contract/ids.d.ts +65 -0
- package/lib/types/agent-config-contract/ids.js +35 -0
- package/lib/types/agent-config-contract/route.d.ts +56 -0
- package/lib/types/agent-config-contract/route.js +1 -0
- package/lib/types/agent-config-contract/test-account-claim.d.ts +24 -0
- package/lib/types/agent-config-contract/test-account-claim.js +5 -0
- package/lib/types/agent-config-format.d.ts +5 -4
- package/lib/types/agent-config-format.js +7 -4
- package/lib/types/agent-config-observation.d.ts +64 -0
- package/lib/types/agent-config-observation.js +448 -0
- package/lib/types/agent-config-source.d.ts +31 -5
- package/lib/types/agent-config-source.js +175 -32
- package/lib/types/agent-config-types.d.ts +33 -0
- package/lib/types/bundled-agent-config.d.ts +29 -0
- package/lib/types/bundled-agent-config.js +47 -0
- package/lib/types/definition.d.ts +18 -4
- package/lib/types/definition.js +10 -2
- package/lib/types/index.d.ts +9 -1
- package/lib/types/index.js +5 -1
- package/lib/types/local-host-identity.d.ts +65 -0
- package/lib/types/local-host-identity.js +87 -0
- package/lib/types/local-host-replacement.d.ts +49 -0
- package/lib/types/local-host-replacement.js +112 -0
- package/lib/types/provider.d.ts +7 -1
- package/lib/types/provider.js +117 -19
- package/lib/types/types.d.ts +4 -1
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +17 -13
|
@@ -3,10 +3,13 @@ import { chmod, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from
|
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { brandString } from '@deepseek-ai/dsh-brand';
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
+
import semver from 'semver';
|
|
7
|
+
import { WorkspaceBundleError } from "./errors.js";
|
|
6
8
|
import { AgentConfigDescriptorSchema, AgentConfigError, HOST_AGENT_CONFIG_API_VERSION, decodeAgentConfig } from "./agent-config-format.js";
|
|
7
9
|
import { loadVerifiedManifest, overallDigest } from "./digest.js";
|
|
8
10
|
import { createNodeFs } from "./filesystem.js";
|
|
9
11
|
import { bundledDirectory } from "./paths.js";
|
|
12
|
+
import { journalAgentConfigSha256 } from "./bundled-agent-config.js";
|
|
10
13
|
const AssignmentSchema = z.discriminatedUnion('source', [
|
|
11
14
|
z.object({ source: z.literal('builtin'), workspaceKind: z.string() }),
|
|
12
15
|
z.object({ source: z.literal('registry'), workspaceKind: z.string(), agentConfig: AgentConfigDescriptorSchema }),
|
|
@@ -32,6 +35,7 @@ export class WorkspaceAgentConfigSourceOwner {
|
|
|
32
35
|
spec;
|
|
33
36
|
report;
|
|
34
37
|
download;
|
|
38
|
+
recoverIdle;
|
|
35
39
|
lifetime = new AbortController();
|
|
36
40
|
inFlight;
|
|
37
41
|
requestAbort;
|
|
@@ -41,22 +45,32 @@ export class WorkspaceAgentConfigSourceOwner {
|
|
|
41
45
|
selected;
|
|
42
46
|
candidate;
|
|
43
47
|
failure = null;
|
|
48
|
+
rejection;
|
|
49
|
+
admissionFailure;
|
|
44
50
|
root;
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
observation;
|
|
52
|
+
startup = Promise.resolve();
|
|
53
|
+
blockedVersion;
|
|
54
|
+
/** @param registration - 内置模板及鉴权查询回调。 @param spec - 部署预算。 @param report - 固定失败诊断的报告者。 @param download - 文件 HTTP 传输。 @param recoverIdle - Provider 串行检查活动租约后恢复已阻断的配置;省略时保留候选等待获取工作区。 */
|
|
55
|
+
constructor(registration, spec, report, download = fetch, recoverIdle = async () => { }) {
|
|
47
56
|
this.registration = registration;
|
|
48
57
|
this.spec = spec;
|
|
49
58
|
this.report = report;
|
|
50
59
|
this.download = download;
|
|
51
|
-
this.
|
|
60
|
+
this.recoverIdle = recoverIdle;
|
|
61
|
+
this.selected = { template: registration.template, sha256: registration.template.agentConfigSha256 ?? null };
|
|
52
62
|
this.root = bundledDirectory(join(spec.templateCacheRoot, 'agent-config-versions'), registration.source.identity);
|
|
53
63
|
}
|
|
54
|
-
/** @returns
|
|
64
|
+
/** @returns 已应用/候选版本、执行阻断及当前作用域的最近失败和拒绝。 */
|
|
55
65
|
state() {
|
|
56
66
|
if (this.candidate !== undefined && this.candidate.revision !== this.registration.source.revision())
|
|
57
67
|
this.candidate = undefined;
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
if (this.lifetime.signal.aborted || this.rejection?.revision !== this.registration.source.revision())
|
|
69
|
+
this.rejection = undefined;
|
|
70
|
+
return { lastRejection: this.rejection?.value ?? null, blocked: this.admissionFailure !== undefined,
|
|
71
|
+
blockingFailure: this.admissionFailure === undefined ? null : { code: this.admissionFailure.code }, current: this.blockedVersion ?? publicVersion(this.selected.template, this.selected.sha256),
|
|
72
|
+
pending: this.candidate === undefined ? null : publicVersion(this.candidate.template, this.candidate.sha256),
|
|
73
|
+
failure: this.admissionFailure === undefined ? this.failure : { code: this.admissionFailure.code, message: this.admissionFailure.message } };
|
|
60
74
|
}
|
|
61
75
|
/**
|
|
62
76
|
* 从 journal 已提交的摘要定位只读缓存,不从 server 的无分配回答推导降级。
|
|
@@ -66,14 +80,54 @@ export class WorkspaceAgentConfigSourceOwner {
|
|
|
66
80
|
* @returns 对应完整模板;缺失已安装缓存时拒绝继续。
|
|
67
81
|
*/
|
|
68
82
|
async recover(digest, version, sha256) {
|
|
83
|
+
const revision = this.registration.source.revision();
|
|
84
|
+
try {
|
|
85
|
+
const template = await this.recoverExecution(digest, version, sha256);
|
|
86
|
+
if (this.rejection?.revision === revision && this.rejection.value.version === template.manifest.pluginVersion)
|
|
87
|
+
this.rejection = undefined;
|
|
88
|
+
return template;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (digest !== undefined && version !== undefined)
|
|
92
|
+
this.blockedVersion = {
|
|
93
|
+
...publicVersion(this.registration.template, sha256 ?? null), version, templateDigest: digest,
|
|
94
|
+
};
|
|
95
|
+
const failure = new AgentConfigError(this.configError(error).code, 'blocked');
|
|
96
|
+
const previous = this.admissionFailure;
|
|
97
|
+
this.admissionFailure = failure;
|
|
98
|
+
this.rememberRejection(failure, version, revision, sha256 ?? undefined);
|
|
99
|
+
if (previous?.code !== failure.code)
|
|
100
|
+
this.reportFailure(failure);
|
|
101
|
+
throw failure;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async recoverExecution(digest, version, sha256) {
|
|
105
|
+
const restored = await this.restoredTemplate(digest, version, sha256);
|
|
106
|
+
this.assertCompatible(restored.range, restored.runtimeApiRange);
|
|
107
|
+
await this.registration.source.validate?.(restored.template, this.lifetime.signal);
|
|
108
|
+
this.selected = { template: restored.template, sha256: restored.template.agentConfigSha256 ?? null };
|
|
109
|
+
this.blockedVersion = undefined;
|
|
110
|
+
this.admissionFailure = undefined;
|
|
111
|
+
if (this.candidate?.sha256 === this.selected.sha256)
|
|
112
|
+
this.candidate = undefined;
|
|
113
|
+
return restored.template;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Load a complete old/new tree only for physical swap recovery; this grants no execution permission.
|
|
117
|
+
* @param digest - Journal template digest.
|
|
118
|
+
* @param version - Journal release version, when recorded.
|
|
119
|
+
* @param sha256 - Journal package digest, absent for builtin.
|
|
120
|
+
* @returns Physically verified template; consumers must subsequently call recover for execution validation.
|
|
121
|
+
*/
|
|
122
|
+
async recoverPhysical(digest, version, sha256) {
|
|
123
|
+
return (await this.restoredTemplate(digest, version, sha256)).template;
|
|
124
|
+
}
|
|
125
|
+
async restoredTemplate(digest, version, sha256) {
|
|
69
126
|
if (digest === undefined || sha256 === undefined || sha256 === null) {
|
|
70
|
-
|
|
71
|
-
return
|
|
127
|
+
const template = this.registration.template;
|
|
128
|
+
return { template, range: template.agentConfigReceipt?.hostAgentConfigApiRange,
|
|
129
|
+
...(template.agentConfigReceipt?.runtimeApiRange === undefined ? {} : { runtimeApiRange: template.agentConfigReceipt.runtimeApiRange }) };
|
|
72
130
|
}
|
|
73
|
-
if (digest === this.selected.template.manifest.digest
|
|
74
|
-
&& (version === undefined || version === this.selected.template.manifest.pluginVersion)
|
|
75
|
-
&& sha256 === this.selected.sha256)
|
|
76
|
-
return this.selected.template;
|
|
77
131
|
if (!/^[0-9a-f]{64}$/u.test(sha256))
|
|
78
132
|
throw new AgentConfigError('AGENT_CONFIG_INVALID');
|
|
79
133
|
const fs = createNodeFs();
|
|
@@ -86,16 +140,44 @@ export class WorkspaceAgentConfigSourceOwner {
|
|
|
86
140
|
if (manifest.digest !== digest || version !== undefined && receipt.version !== version
|
|
87
141
|
|| manifest.pluginVersion !== receipt.version)
|
|
88
142
|
throw new AgentConfigError('AGENT_CONFIG_INVALID');
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
143
|
+
const { bundleId, workspaceKey, title } = this.registration.template;
|
|
144
|
+
return { template: { bundleId, workspaceKey, title, sourceRoot: join(directory, receipt.version), manifest,
|
|
145
|
+
agentConfigSha256: receipt.sha256 }, range: receipt.hostAgentConfigApiRange,
|
|
146
|
+
...(receipt.runtimeApiRange === undefined ? {} : { runtimeApiRange: receipt.runtimeApiRange }) };
|
|
147
|
+
}
|
|
148
|
+
assertCompatible(range, runtimeApiRange) {
|
|
149
|
+
if (range !== undefined && !semver.satisfies(HOST_AGENT_CONFIG_API_VERSION, range, { includePrerelease: true }))
|
|
150
|
+
throw new AgentConfigError('AGENT_CONFIG_INCOMPATIBLE');
|
|
151
|
+
const runtime = this.registration.source.runtimeApiVersion;
|
|
152
|
+
if (runtimeApiRange !== undefined && (runtime === undefined || !semver.satisfies(runtime, runtimeApiRange, { includePrerelease: true })))
|
|
153
|
+
throw new AgentConfigError('AGENT_CONFIG_INCOMPATIBLE');
|
|
154
|
+
}
|
|
155
|
+
configError(error) {
|
|
156
|
+
return error instanceof AgentConfigError ? error : new AgentConfigError(error instanceof WorkspaceBundleError
|
|
157
|
+
&& error.code === 'WORKSPACE_BUNDLE_DIGEST_MISMATCH' ? 'AGENT_CONFIG_DIGEST_MISMATCH' : 'AGENT_CONFIG_INVALID');
|
|
158
|
+
}
|
|
159
|
+
reportFailure(error) {
|
|
160
|
+
try {
|
|
161
|
+
this.report(error);
|
|
162
|
+
}
|
|
163
|
+
catch (reportError) {
|
|
164
|
+
// stderr remains available if the Cordis reporter has already been released.
|
|
165
|
+
process.stderr.write(`${error.code}: ${error.message}\n`);
|
|
166
|
+
}
|
|
94
167
|
}
|
|
95
|
-
/**
|
|
96
|
-
start() {
|
|
168
|
+
/** @param restore - Restore this identity's committed journal before starting reports or queries; omission is for an owner without a journal. */
|
|
169
|
+
start(restore = async () => { }) {
|
|
170
|
+
this.startup = restore().then(() => {
|
|
171
|
+
if (!this.lifetime.signal.aborted)
|
|
172
|
+
this.observation = this.registration.source.observation?.(() => this.state());
|
|
173
|
+
});
|
|
97
174
|
const tick = async () => {
|
|
98
|
-
|
|
175
|
+
try {
|
|
176
|
+
await this.refresh();
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
this.reportFailure(this.configError(error));
|
|
180
|
+
}
|
|
99
181
|
if (!this.lifetime.signal.aborted)
|
|
100
182
|
this.timer = setTimeout(() => { void tick(); }, this.spec.agentConfigPollIntervalMs);
|
|
101
183
|
};
|
|
@@ -103,6 +185,9 @@ export class WorkspaceAgentConfigSourceOwner {
|
|
|
103
185
|
}
|
|
104
186
|
/** @returns 本次查询和完整候选下载结束;失败通过 state 与 report 保留可观察诊断。 */
|
|
105
187
|
refresh() {
|
|
188
|
+
return this.startup.then(() => this.refreshReady());
|
|
189
|
+
}
|
|
190
|
+
refreshReady() {
|
|
106
191
|
if (this.lifetime.signal.aborted)
|
|
107
192
|
return Promise.resolve();
|
|
108
193
|
const revision = this.registration.source.revision();
|
|
@@ -118,13 +203,22 @@ export class WorkspaceAgentConfigSourceOwner {
|
|
|
118
203
|
const abort = new AbortController();
|
|
119
204
|
this.requestAbort = abort;
|
|
120
205
|
this.requestRevision = revision;
|
|
121
|
-
const
|
|
206
|
+
const attempt = {};
|
|
207
|
+
const run = this.fetchCandidate(revision, abort.signal, attempt).then(async () => {
|
|
208
|
+
if (this.lifetime.signal.aborted || abort.signal.aborted || revision !== this.registration.source.revision())
|
|
209
|
+
return;
|
|
210
|
+
if (this.admissionFailure !== undefined && this.candidate !== undefined)
|
|
211
|
+
await this.recoverIdle();
|
|
212
|
+
if (!this.lifetime.signal.aborted)
|
|
213
|
+
await this.observation?.refresh();
|
|
214
|
+
}).catch((error) => {
|
|
122
215
|
if (this.lifetime.signal.aborted || abort.signal.aborted || revision !== this.registration.source.revision())
|
|
123
216
|
return;
|
|
124
217
|
this.candidate = undefined;
|
|
125
|
-
const failure = error
|
|
218
|
+
const failure = this.configError(error);
|
|
126
219
|
this.failure = { code: failure.code, message: failure.message };
|
|
127
|
-
this.
|
|
220
|
+
this.rememberRejection(failure, attempt.version, revision, attempt.sha256);
|
|
221
|
+
this.reportFailure(failure);
|
|
128
222
|
}).finally(() => { this.inFlight = undefined; this.requestAbort = undefined; this.requestRevision = undefined; });
|
|
129
223
|
this.inFlight = run;
|
|
130
224
|
return run;
|
|
@@ -133,8 +227,31 @@ export class WorkspaceAgentConfigSourceOwner {
|
|
|
133
227
|
template() {
|
|
134
228
|
if (this.candidate !== undefined && this.candidate.revision !== this.registration.source.revision())
|
|
135
229
|
this.candidate = undefined;
|
|
230
|
+
if (this.candidate === undefined && this.admissionFailure !== undefined)
|
|
231
|
+
throw this.admissionFailure;
|
|
136
232
|
return this.candidate?.template ?? this.selected.template;
|
|
137
233
|
}
|
|
234
|
+
/** @throws AgentConfigError while committed content is blocked, including when another lease remains active. */
|
|
235
|
+
assertExecutionAllowed() {
|
|
236
|
+
if (this.admissionFailure !== undefined)
|
|
237
|
+
throw this.admissionFailure;
|
|
238
|
+
}
|
|
239
|
+
/** @param template - Pending template. @returns After compatibility and consumer validation, without selecting it. */
|
|
240
|
+
async prepare(template) {
|
|
241
|
+
try {
|
|
242
|
+
await this.prepareValidated(template);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
throw this.configError(error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async prepareValidated(template) {
|
|
249
|
+
this.assertApplicable(template);
|
|
250
|
+
const restored = await this.restoredTemplate(template.manifest.digest, template.manifest.pluginVersion, journalAgentConfigSha256(template, this.registration.template));
|
|
251
|
+
this.assertCompatible(restored.range, restored.runtimeApiRange);
|
|
252
|
+
await this.registration.source.validate?.(restored.template, this.lifetime.signal);
|
|
253
|
+
this.assertApplicable(template);
|
|
254
|
+
}
|
|
138
255
|
/** @param template - 即将整体替换的候选。 @throws 来源代次已经改变或已撤销时拒绝应用。 */
|
|
139
256
|
assertApplicable(template) {
|
|
140
257
|
const revision = this.preparedRevisions.get(template);
|
|
@@ -143,7 +260,7 @@ export class WorkspaceAgentConfigSourceOwner {
|
|
|
143
260
|
}
|
|
144
261
|
/** @param template - 已通过 Workspace 整体替换提交的模板。 */
|
|
145
262
|
async committed(template) {
|
|
146
|
-
await this.recover(template.manifest.digest, template.manifest.pluginVersion, template.
|
|
263
|
+
await this.recover(template.manifest.digest, template.manifest.pluginVersion, journalAgentConfigSha256(template, this.registration.template));
|
|
147
264
|
}
|
|
148
265
|
/**
|
|
149
266
|
* 清除未提交候选,报告整体替换失败;已提交状态由 journal 恢复结果决定。
|
|
@@ -153,18 +270,20 @@ export class WorkspaceAgentConfigSourceOwner {
|
|
|
153
270
|
failedApply(template, cause) {
|
|
154
271
|
if (this.candidate?.template === template)
|
|
155
272
|
this.candidate = undefined;
|
|
156
|
-
const error = cause instanceof AgentConfigError
|
|
157
|
-
? cause : new AgentConfigError('AGENT_CONFIG_APPLY_FAILED');
|
|
273
|
+
const error = cause instanceof AgentConfigError ? cause : new AgentConfigError('AGENT_CONFIG_APPLY_FAILED');
|
|
158
274
|
this.failure = { code: error.code, message: error.message };
|
|
159
|
-
this.
|
|
275
|
+
const revision = this.preparedRevisions.get(template) ?? this.registration.source.revision();
|
|
276
|
+
this.rememberRejection(error, template.manifest.pluginVersion, revision, template.agentConfigSha256);
|
|
277
|
+
this.reportFailure(error);
|
|
160
278
|
}
|
|
161
279
|
/** 撤销查询、清除后续定时器,并等待下载和目录写入完成。 */
|
|
162
280
|
async dispose() {
|
|
163
281
|
this.lifetime.abort();
|
|
282
|
+
this.rejection = undefined;
|
|
164
283
|
this.candidate = undefined;
|
|
165
284
|
if (this.timer !== undefined)
|
|
166
285
|
clearTimeout(this.timer);
|
|
167
|
-
await this.inFlight;
|
|
286
|
+
await Promise.all([this.startup, this.inFlight, this.observation?.dispose()]);
|
|
168
287
|
this.candidate = undefined;
|
|
169
288
|
}
|
|
170
289
|
versionDirectory(version, sha256) {
|
|
@@ -173,14 +292,25 @@ export class WorkspaceAgentConfigSourceOwner {
|
|
|
173
292
|
throw new AgentConfigError('AGENT_CONFIG_INVALID');
|
|
174
293
|
return join(this.root, sha256);
|
|
175
294
|
}
|
|
176
|
-
|
|
295
|
+
rememberRejection(error, version, revision, sha256) {
|
|
296
|
+
if (this.lifetime.signal.aborted || revision !== this.registration.source.revision())
|
|
297
|
+
return;
|
|
298
|
+
if (error.code === 'AGENT_CONFIG_SCOPE_CHANGED') {
|
|
299
|
+
this.rejection = undefined;
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (error.code === 'AGENT_CONFIG_QUERY_FAILED')
|
|
303
|
+
return;
|
|
304
|
+
this.rejection = { revision, value: { code: error.code, ...(version === undefined ? {} : { version }), ...(sha256 === undefined ? {} : { sha256 }) } };
|
|
305
|
+
}
|
|
306
|
+
async fetchCandidate(revision, requestSignal, attempt) {
|
|
177
307
|
const signal = AbortSignal.any([this.lifetime.signal, requestSignal, AbortSignal.timeout(this.spec.agentConfigRequestTimeoutMs)]);
|
|
178
308
|
let value;
|
|
179
309
|
try {
|
|
180
310
|
value = await this.registration.source.query(signal);
|
|
181
311
|
}
|
|
182
312
|
catch (error) {
|
|
183
|
-
throw new AgentConfigError('AGENT_CONFIG_QUERY_FAILED');
|
|
313
|
+
throw error instanceof AgentConfigError ? error : new AgentConfigError('AGENT_CONFIG_QUERY_FAILED');
|
|
184
314
|
}
|
|
185
315
|
const assignment = AssignmentSchema.safeParse(value);
|
|
186
316
|
if (!assignment.success || assignment.data.workspaceKind !== this.registration.template.bundleId)
|
|
@@ -188,14 +318,22 @@ export class WorkspaceAgentConfigSourceOwner {
|
|
|
188
318
|
if (assignment.data.source === 'builtin') {
|
|
189
319
|
this.candidate = undefined;
|
|
190
320
|
this.failure = null;
|
|
321
|
+
this.rejection = undefined;
|
|
191
322
|
return;
|
|
192
323
|
}
|
|
193
324
|
const descriptor = assignment.data.agentConfig;
|
|
325
|
+
attempt.version = descriptor.version;
|
|
326
|
+
attempt.sha256 = descriptor.sha256;
|
|
327
|
+
if (this.rejection?.value.version !== descriptor.version || this.rejection?.value.sha256 !== descriptor.sha256)
|
|
328
|
+
this.rejection = undefined;
|
|
194
329
|
if (descriptor.workspaceKind !== assignment.data.workspaceKind)
|
|
195
330
|
throw new AgentConfigError('AGENT_CONFIG_INVALID');
|
|
196
|
-
|
|
331
|
+
this.assertCompatible(descriptor.hostAgentConfigApiRange, descriptor.runtimeApiRange);
|
|
332
|
+
// Blocked journal content remains current even when the registered template has a newer digest.
|
|
333
|
+
if (descriptor.sha256 === (this.blockedVersion?.sha256 ?? this.selected.sha256)) {
|
|
197
334
|
this.candidate = undefined;
|
|
198
335
|
this.failure = null;
|
|
336
|
+
this.rejection = undefined;
|
|
199
337
|
return;
|
|
200
338
|
}
|
|
201
339
|
if (descriptor.sizeBytes > this.spec.agentConfigMaxDownloadBytes)
|
|
@@ -260,9 +398,14 @@ export class WorkspaceAgentConfigSourceOwner {
|
|
|
260
398
|
throw new AgentConfigError('AGENT_CONFIG_DIGEST_MISMATCH');
|
|
261
399
|
if (signal.aborted || revision !== this.registration.source.revision())
|
|
262
400
|
return;
|
|
263
|
-
const
|
|
401
|
+
const { bundleId, workspaceKey, title } = this.registration.template;
|
|
402
|
+
const template = { bundleId, workspaceKey, title, sourceRoot: tree, manifest, agentConfigSha256: descriptor.sha256 };
|
|
403
|
+
await this.registration.source.validate?.(template, signal);
|
|
404
|
+
if (signal.aborted || revision !== this.registration.source.revision())
|
|
405
|
+
return;
|
|
264
406
|
this.preparedRevisions.set(template, revision);
|
|
265
407
|
this.candidate = { template, sha256: descriptor.sha256, revision };
|
|
266
408
|
this.failure = null;
|
|
409
|
+
this.rejection = undefined;
|
|
267
410
|
}
|
|
268
411
|
}
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
/** Workspace Agent 配置版本来源和 turn 记录的 Host 接口。 */
|
|
2
2
|
import type { WorkspaceBundleIdentity, WorkspaceTemplate } from './types.ts';
|
|
3
3
|
import type { AgentConfigErrorCode } from './agent-config-format.ts';
|
|
4
|
+
import type { WorkspaceAgentConfigObservation } from './agent-config-observation.ts';
|
|
5
|
+
import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types';
|
|
4
6
|
/** 调用方负责通过自身鉴权能力查询 server,不向此服务交出凭据。 */
|
|
5
7
|
export interface WorkspaceAgentConfigSource {
|
|
6
8
|
readonly identity: WorkspaceBundleIdentity;
|
|
9
|
+
/** Fixed consumer runtime API, when this module accepts descriptors with runtime requirements. */
|
|
10
|
+
readonly runtimeApiVersion?: string;
|
|
11
|
+
/** Optional authenticated module reporter; its lifetime is owned by this source registration. */
|
|
12
|
+
readonly observation?: (read: () => WorkspaceAgentConfigState) => WorkspaceAgentConfigObservation;
|
|
7
13
|
/** @returns 随登录、租户或其他分配身份改变的进程内代次。 */
|
|
8
14
|
revision(): number;
|
|
9
15
|
/** @param signal - 源卸载或请求超时取消。 @returns #86 的 builtin / registry 分配。 */
|
|
10
16
|
query(signal: AbortSignal): Promise<unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Consumer validation before candidate publication, adoption and restored execution; omission keeps generic archive checks.
|
|
19
|
+
* @param template - Verified template within this source's controlled tree.
|
|
20
|
+
* @param signal - Source disposal or request deadline.
|
|
21
|
+
* @throws AgentConfigError for invalid consumer metadata or bytes; no content may execute after failure.
|
|
22
|
+
*/
|
|
23
|
+
validate?(template: WorkspaceTemplate, signal: AbortSignal): void | Promise<void>;
|
|
11
24
|
}
|
|
12
25
|
/** 下一个 turn 可使用的完整 Agent 配置版本,路径指向已经校验的不可变文件。 */
|
|
13
26
|
export interface WorkspaceAgentConfigVersion {
|
|
@@ -18,14 +31,34 @@ export interface WorkspaceAgentConfigVersion {
|
|
|
18
31
|
readonly templateDigest: string;
|
|
19
32
|
readonly sourceRoot: string;
|
|
20
33
|
}
|
|
34
|
+
/** Verified content held by one activity; each retained handle owns a separate release. */
|
|
35
|
+
export interface WorkspaceAgentConfigHandle extends WorkspaceAgentConfigVersion {
|
|
36
|
+
readonly workspaceId: WorkspaceId;
|
|
37
|
+
/** @returns another synchronous lease of these exact bytes, including after the parent activity ends. @throws WorkspaceBundleError when this handle was released or the Provider is unloading. */
|
|
38
|
+
retain(): WorkspaceAgentConfigHandle;
|
|
39
|
+
/** Release this activity once; retained consumers keep the content leased. */
|
|
40
|
+
release(): void;
|
|
41
|
+
}
|
|
21
42
|
/** 内容更新的当前可观察状态;错误只包含固定诊断。 */
|
|
22
43
|
export interface WorkspaceAgentConfigState {
|
|
44
|
+
/** True when committed content has failed execution validation; current is diagnostic only until compatible replacement. */
|
|
45
|
+
readonly blocked: boolean;
|
|
46
|
+
/** Execution refusal for current content; candidate and query failures cannot clear it. */
|
|
47
|
+
readonly blockingFailure: {
|
|
48
|
+
readonly code: AgentConfigErrorCode;
|
|
49
|
+
} | null;
|
|
23
50
|
readonly current: WorkspaceAgentConfigVersion;
|
|
24
51
|
readonly pending: WorkspaceAgentConfigVersion | null;
|
|
25
52
|
readonly failure: {
|
|
26
53
|
readonly code: AgentConfigErrorCode;
|
|
27
54
|
readonly message: string;
|
|
28
55
|
} | null;
|
|
56
|
+
/** Latest same-scope content refusal; query/scope failures are excluded. Null means no applicable refusal. */
|
|
57
|
+
readonly lastRejection: {
|
|
58
|
+
readonly code: AgentConfigErrorCode;
|
|
59
|
+
readonly version?: string;
|
|
60
|
+
readonly sha256?: string;
|
|
61
|
+
} | null;
|
|
29
62
|
}
|
|
30
63
|
/** 注册时固定的内置版本;源查询失败不能使它不可用。 */
|
|
31
64
|
export interface WorkspaceAgentConfigRegistration {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { WorkspaceTemplate } from './types.ts';
|
|
3
|
+
/** Public metadata of the exact published gzip installed as a bundled template. */
|
|
4
|
+
export declare const BundledAgentConfigReceiptSchema: z.ZodObject<{
|
|
5
|
+
workspaceKind: z.ZodString;
|
|
6
|
+
version: z.ZodString;
|
|
7
|
+
sha256: z.ZodString;
|
|
8
|
+
sizeBytes: z.ZodNumber;
|
|
9
|
+
hostAgentConfigApiRange: z.ZodString;
|
|
10
|
+
runtimeApiRange: z.ZodOptional<z.ZodString>;
|
|
11
|
+
formatVersion: z.ZodLiteral<1>;
|
|
12
|
+
templateDigest: z.ZodString;
|
|
13
|
+
}, z.core.$strict>;
|
|
14
|
+
/** Parsed identity and compatibility of a published bundled template. */
|
|
15
|
+
export type BundledAgentConfigReceipt = z.infer<typeof BundledAgentConfigReceiptSchema>;
|
|
16
|
+
/**
|
|
17
|
+
* Attach the adjacent publication receipt to a verified packaged template.
|
|
18
|
+
* @param template - Template whose file catalog has already been verified.
|
|
19
|
+
* @returns The original template when no receipt exists, otherwise its published identity and receipt.
|
|
20
|
+
* @throws AgentConfigError when an existing receipt is unreadable, invalid or identifies different content.
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadBundledAgentConfigSync(template: WorkspaceTemplate): WorkspaceTemplate;
|
|
23
|
+
/**
|
|
24
|
+
* Identify downloaded content in journals while registered content follows the installation.
|
|
25
|
+
* @param template - Selected template, including a possible downloaded copy.
|
|
26
|
+
* @param registered - This identity's exact registered object, or undefined during recovery without a registration.
|
|
27
|
+
* @returns Null for the registered object or a template without a package digest; otherwise the package digest.
|
|
28
|
+
*/
|
|
29
|
+
export declare function journalAgentConfigSha256(template: WorkspaceTemplate, registered: WorkspaceTemplate | undefined): string | null;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Published package receipts beside shipped templates, separate from model-visible files. */
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { AgentConfigDescriptorSchema, AgentConfigError, AgentConfigSha256 } from "./agent-config-format.js";
|
|
6
|
+
/** Public metadata of the exact published gzip installed as a bundled template. */
|
|
7
|
+
export const BundledAgentConfigReceiptSchema = AgentConfigDescriptorSchema.omit({ downloadUrl: true, releaseNotes: true }).extend({
|
|
8
|
+
formatVersion: z.literal(1),
|
|
9
|
+
templateDigest: AgentConfigSha256,
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Attach the adjacent publication receipt to a verified packaged template.
|
|
13
|
+
* @param template - Template whose file catalog has already been verified.
|
|
14
|
+
* @returns The original template when no receipt exists, otherwise its published identity and receipt.
|
|
15
|
+
* @throws AgentConfigError when an existing receipt is unreadable, invalid or identifies different content.
|
|
16
|
+
*/
|
|
17
|
+
export function loadBundledAgentConfigSync(template) {
|
|
18
|
+
let bytes;
|
|
19
|
+
try {
|
|
20
|
+
bytes = readFileSync(join(dirname(template.sourceRoot), `${basename(template.sourceRoot)}.agent-config.json`));
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error.code === 'ENOENT')
|
|
24
|
+
return template;
|
|
25
|
+
throw new AgentConfigError('AGENT_CONFIG_INVALID');
|
|
26
|
+
}
|
|
27
|
+
let receipt;
|
|
28
|
+
try {
|
|
29
|
+
receipt = BundledAgentConfigReceiptSchema.parse(JSON.parse(bytes.toString('utf8')));
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw new AgentConfigError('AGENT_CONFIG_INVALID');
|
|
33
|
+
}
|
|
34
|
+
if (receipt.workspaceKind !== template.bundleId || receipt.version !== template.manifest.pluginVersion
|
|
35
|
+
|| receipt.templateDigest !== template.manifest.digest)
|
|
36
|
+
throw new AgentConfigError('AGENT_CONFIG_INVALID');
|
|
37
|
+
return { ...template, agentConfigSha256: receipt.sha256, agentConfigReceipt: receipt };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Identify downloaded content in journals while registered content follows the installation.
|
|
41
|
+
* @param template - Selected template, including a possible downloaded copy.
|
|
42
|
+
* @param registered - This identity's exact registered object, or undefined during recovery without a registration.
|
|
43
|
+
* @returns Null for the registered object or a template without a package digest; otherwise the package digest.
|
|
44
|
+
*/
|
|
45
|
+
export function journalAgentConfigSha256(template, registered) {
|
|
46
|
+
return template === registered ? null : template.agentConfigSha256 ?? null;
|
|
47
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/** `WorkspaceBundles` Service Definition: Host-only template registration and stable Workspace materialize. */
|
|
2
|
+
import { type LocalHostReplacement } from './local-host-replacement.ts';
|
|
3
|
+
import { type LocalHostIdentity } from './local-host-identity.ts';
|
|
2
4
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
3
5
|
import type { Workspace, WorkspaceId } from '@deepseek-ai/dsh-workspace/types';
|
|
4
6
|
import type { WorkspaceBundleIdentity, WorkspaceBundleRecord, WorkspaceTemplate } from './types.ts';
|
|
5
|
-
import type { WorkspaceAgentConfigSource, WorkspaceAgentConfigState } from './agent-config-types.ts';
|
|
7
|
+
import type { WorkspaceAgentConfigHandle, WorkspaceAgentConfigSource, WorkspaceAgentConfigState } from './agent-config-types.ts';
|
|
6
8
|
declare module '@deepseek-ai/cordis' {
|
|
7
9
|
interface Context {
|
|
8
10
|
/** Bundled Workspace templates and their stable materialize. */
|
|
@@ -22,8 +24,12 @@ declare module '@deepseek-ai/cordis' {
|
|
|
22
24
|
* replace its tree as a whole. Registration is an effect; the returned disposer forbids later acquire.
|
|
23
25
|
*/
|
|
24
26
|
export declare abstract class WorkspaceBundles extends Service {
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
+
/** One member-level fence shared by every local module. */
|
|
28
|
+
readonly localHostReplacement: LocalHostReplacement;
|
|
29
|
+
/** One persisted local Host identity per server and member, published by the module that owns the installation identity. */
|
|
30
|
+
readonly localHostIdentity: LocalHostIdentity;
|
|
31
|
+
/** @param ctx - context owning this Provider. @param replacementPath - durable fence file, or null for an in-memory provider. @param identityPath - durable shared identity file, or null for an in-memory provider. */
|
|
32
|
+
constructor(ctx: Context, replacementPath?: string | null, identityPath?: string | null);
|
|
27
33
|
/**
|
|
28
34
|
* Make a template available for {@link ensure} and {@link acquire}.
|
|
29
35
|
* @param template - verified catalog and source root.
|
|
@@ -36,7 +42,7 @@ export declare abstract class WorkspaceBundles extends Service {
|
|
|
36
42
|
* @returns 撤销轮询并等待下载停止的 disposer。
|
|
37
43
|
*/
|
|
38
44
|
abstract registerAgentConfigSource(source: WorkspaceAgentConfigSource): () => Promise<void>;
|
|
39
|
-
/** @param identity - 内置 Workspace 身份。 @returns
|
|
45
|
+
/** @param identity - 内置 Workspace 身份。 @returns 已安装版本、候选、执行阻断、固定诊断与同作用域最近拒绝;blocked 时 current 只供诊断,未注册来源时为 undefined。 */
|
|
40
46
|
abstract agentConfig(identity: WorkspaceBundleIdentity): WorkspaceAgentConfigState | undefined;
|
|
41
47
|
/** @param identity - 内置 Workspace 身份。 @returns 该来源本次查询与下载完成,失败保留在 agentConfig 中。 */
|
|
42
48
|
abstract refreshAgentConfig(identity: WorkspaceBundleIdentity): Promise<void>;
|
|
@@ -45,6 +51,7 @@ export declare abstract class WorkspaceBundles extends Service {
|
|
|
45
51
|
* Unchanged trees reuse the existing Workspace id and path.
|
|
46
52
|
* @param identity - branded pair.
|
|
47
53
|
* @returns the Workspace after the tree is wholly old or wholly new.
|
|
54
|
+
* @throws AgentConfigError when committed remote content cannot execute; only a compatible validated candidate may repair it.
|
|
48
55
|
*/
|
|
49
56
|
abstract ensure(identity: WorkspaceBundleIdentity): Promise<Workspace>;
|
|
50
57
|
/**
|
|
@@ -53,6 +60,13 @@ export declare abstract class WorkspaceBundles extends Service {
|
|
|
53
60
|
* @returns disposer of the lease.
|
|
54
61
|
*/
|
|
55
62
|
abstract acquire(workspaceId: WorkspaceId): Promise<() => void>;
|
|
63
|
+
/**
|
|
64
|
+
* Adopt and verify content, then lease its exact identity before another adoption can run.
|
|
65
|
+
* @param identity - registered bundled Workspace.
|
|
66
|
+
* @returns a handle whose immutable source root and version match the committed Workspace; release after all activity and persistence finish.
|
|
67
|
+
* @throws WorkspaceBundleError or AgentConfigError when registration, content, scope or compatibility validation fails; no lease is returned.
|
|
68
|
+
*/
|
|
69
|
+
abstract acquireAgentConfig(identity: WorkspaceBundleIdentity): Promise<WorkspaceAgentConfigHandle>;
|
|
56
70
|
/**
|
|
57
71
|
* Read the durable product record when one exists.
|
|
58
72
|
* @param identity - branded pair.
|
package/lib/types/definition.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
/** `WorkspaceBundles` Service Definition: Host-only template registration and stable Workspace materialize. */
|
|
2
|
+
import { LocalHostReplacementState } from "./local-host-replacement.js";
|
|
3
|
+
import { LocalHostIdentityState } from "./local-host-identity.js";
|
|
2
4
|
import { Service } from '@deepseek-ai/cordis';
|
|
3
5
|
/**
|
|
4
6
|
* Host-only capability: register a packaged template, materialize a stable hidden Workspace, and
|
|
5
7
|
* replace its tree as a whole. Registration is an effect; the returned disposer forbids later acquire.
|
|
6
8
|
*/
|
|
7
9
|
export class WorkspaceBundles extends Service {
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
+
/** One member-level fence shared by every local module. */
|
|
11
|
+
localHostReplacement;
|
|
12
|
+
/** One persisted local Host identity per server and member, published by the module that owns the installation identity. */
|
|
13
|
+
localHostIdentity;
|
|
14
|
+
/** @param ctx - context owning this Provider. @param replacementPath - durable fence file, or null for an in-memory provider. @param identityPath - durable shared identity file, or null for an in-memory provider. */
|
|
15
|
+
constructor(ctx, replacementPath = null, identityPath = null) {
|
|
10
16
|
super(ctx, 'workspaceBundles');
|
|
17
|
+
this.localHostReplacement = new LocalHostReplacementState(replacementPath);
|
|
18
|
+
this.localHostIdentity = new LocalHostIdentityState(identityPath);
|
|
11
19
|
}
|
|
12
20
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -15,6 +15,14 @@ export type * from './types.ts';
|
|
|
15
15
|
export type * from './agent-config-types.ts';
|
|
16
16
|
export type { WorkspaceAgentConfigVersionEvent } from './session-events.ts';
|
|
17
17
|
export { recordWorkspaceAgentConfigVersion } from './session-events.ts';
|
|
18
|
-
export { HOST_AGENT_CONFIG_API_VERSION, AgentConfigError, agentConfigSha256, decodeAgentConfig, AgentConfigDescriptorSchema } from './agent-config-format.ts';
|
|
18
|
+
export { HOST_AGENT_CONFIG_API_VERSION, AgentConfigError, agentConfigSha256, decodeAgentConfig, AgentConfigDescriptorSchema, isAgentConfigVersion } from './agent-config-format.ts';
|
|
19
19
|
export type { AgentConfigDescriptor, AgentConfigErrorCode } from './agent-config-format.ts';
|
|
20
|
+
export { BundledAgentConfigReceiptSchema, loadBundledAgentConfigSync } from './bundled-agent-config.ts';
|
|
21
|
+
export type { BundledAgentConfigReceipt } from './bundled-agent-config.ts';
|
|
20
22
|
export default GestaltWorkspaceBundles;
|
|
23
|
+
export { getModuleAgentConfigAssignment } from './agent-config-contract/agent-configs.ts';
|
|
24
|
+
export { agentConfigHostObservation, sandboxAgentConfigHostObservation } from './agent-config-observation.ts';
|
|
25
|
+
export type { WorkspaceAgentConfigObservation, WorkspaceAgentConfigObservationOptions, WorkspaceAgentConfigHostId, WorkspaceAgentConfigSandbox } from './agent-config-observation.ts';
|
|
26
|
+
export type { LocalHostReplacement, LocalHostMember } from './local-host-replacement.ts';
|
|
27
|
+
export { LocalHostIdentityLoadError, LocalHostIdentityWriteError, sharedLocalHostId } from './local-host-identity.ts';
|
|
28
|
+
export type { LocalHostIdentity, LocalHostIdentityScope, LocalHostIdentitySources, LocalHostIdentityValue } from './local-host-identity.ts';
|
package/lib/types/index.js
CHANGED
|
@@ -11,5 +11,9 @@ export { encodeBundleSegment, bundledDirectory, assertTemplateRelativePath, iden
|
|
|
11
11
|
export { createNodeFs, crashableFs } from "./filesystem.js";
|
|
12
12
|
export { GestaltWorkspaceBundles } from "./provider.js";
|
|
13
13
|
export { recordWorkspaceAgentConfigVersion } from "./session-events.js";
|
|
14
|
-
export { HOST_AGENT_CONFIG_API_VERSION, AgentConfigError, agentConfigSha256, decodeAgentConfig, AgentConfigDescriptorSchema } from "./agent-config-format.js";
|
|
14
|
+
export { HOST_AGENT_CONFIG_API_VERSION, AgentConfigError, agentConfigSha256, decodeAgentConfig, AgentConfigDescriptorSchema, isAgentConfigVersion } from "./agent-config-format.js";
|
|
15
|
+
export { BundledAgentConfigReceiptSchema, loadBundledAgentConfigSync } from "./bundled-agent-config.js";
|
|
15
16
|
export default GestaltWorkspaceBundles;
|
|
17
|
+
export { getModuleAgentConfigAssignment } from "./agent-config-contract/agent-configs.js";
|
|
18
|
+
export { agentConfigHostObservation, sandboxAgentConfigHostObservation } from "./agent-config-observation.js";
|
|
19
|
+
export { LocalHostIdentityLoadError, LocalHostIdentityWriteError, sharedLocalHostId } from "./local-host-identity.js";
|