agentlas 0.7.0 → 0.9.2
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/CHANGELOG.md +199 -0
- package/README.md +161 -18
- package/bin/agentlas.cjs +8 -8
- package/engine/agentlas-core-harness.cjs +212 -0
- package/engine/agentlas-desktop-loadout.cjs +527 -0
- package/engine/agentlas-doctor.cjs +1 -1
- package/engine/agentlas-experience-exchange.cjs +835 -85
- package/engine/agentlas-experience-intake.cjs +444 -0
- package/engine/agentlas-experience-mcp.cjs +580 -18
- package/engine/agentlas-i18n.cjs +10 -10
- package/engine/agentlas-input.cjs +5 -4
- package/engine/agentlas-mcp-env.cjs +219 -0
- package/engine/agentlas-mcp-wrapper.cjs +51 -0
- package/engine/agentlas-memory-governance.cjs +1029 -0
- package/engine/agentlas-native-host.cjs +129 -39
- package/engine/agentlas-parity.cjs +339 -154
- package/engine/agentlas-repl.cjs +306 -31
- package/engine/agentlas-workforce.cjs +2991 -0
- package/engine/agentlas-workload-routing.cjs +523 -0
- package/engine/agentlas.cjs +1619 -234
- package/engine/bootstrap-schema.sql +1 -1
- package/engine/experience-taxonomy-v1.json +49 -0
- package/package.json +8 -4
- package/scripts/gen-bootstrap-schema.sh +0 -23
- package/test/bootstrap-race.cjs +0 -47
- package/test/capture-runtime-guard.cjs +0 -122
- package/test/cloud-asset-restore.cjs +0 -423
- package/test/cloud-cas-client.cjs +0 -333
- package/test/cloud-owner-restore.cjs +0 -183
- package/test/cloud-runtime-paths.cjs +0 -40
- package/test/cloud-save-publish.cjs +0 -487
- package/test/credential-env-regression.cjs +0 -52
- package/test/engine-hardening-regression.cjs +0 -74
- package/test/experience-exchange-contract.cjs +0 -569
- package/test/experience-mcp-contract.cjs +0 -391
- package/test/fixtures/portable-experience-bundle-v1-golden.json +0 -124
- package/test/login-loopback-security.cjs +0 -115
- package/test/mcp-config-isolation.cjs +0 -36
- package/test/permission-mapping.cjs +0 -180
- package/test/route-regression.cjs +0 -357
- package/test/run-api-regression.cjs +0 -322
- package/test/runtime-env-protection.cjs +0 -89
- package/test/semver-precedence.cjs +0 -39
- package/test/smoke.sh +0 -93
- package/test/sqlite-driver-probe.cjs +0 -22
- package/test/terminal-ui-regression.cjs +0 -477
- package/test/timeout-regression.cjs +0 -218
- package/test/tool-workspace-boundary.cjs +0 -165
- package/test/update-safety.cjs +0 -376
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
const assert = require("node:assert/strict");
|
|
5
|
-
const fs = require("node:fs");
|
|
6
|
-
const http = require("node:http");
|
|
7
|
-
const os = require("node:os");
|
|
8
|
-
const path = require("node:path");
|
|
9
|
-
|
|
10
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "agentlas-cloud-cas-client-"));
|
|
11
|
-
process.env.AGENTLAS_USER_DATA_DIR = path.join(tempDir, "user-data");
|
|
12
|
-
process.env.AGENTLAS_SESSION = "cas-owner-session";
|
|
13
|
-
|
|
14
|
-
const {
|
|
15
|
-
deleteCloudAgentCli,
|
|
16
|
-
packageCloudAgentCli,
|
|
17
|
-
readCloudAssetStateCli,
|
|
18
|
-
} = require("../engine/agentlas.cjs");
|
|
19
|
-
|
|
20
|
-
function writePrivateAgent(root, title = "CAS Agent") {
|
|
21
|
-
fs.mkdirSync(root, { recursive: true });
|
|
22
|
-
fs.writeFileSync(path.join(root, "AGENTS.md"), `# ${title}\n\nCAS_TEST_ENTRY\n`, "utf8");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function writePublicAgent(root, title = "CAS Public Agent") {
|
|
26
|
-
writePrivateAgent(root, title);
|
|
27
|
-
fs.mkdirSync(path.join(root, ".agentlas"), { recursive: true });
|
|
28
|
-
fs.writeFileSync(path.join(root, ".agentlas", "routing-card.json"), JSON.stringify({
|
|
29
|
-
schemaVersion: "routing-card/2.0",
|
|
30
|
-
id: title.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
|
|
31
|
-
type: "agent",
|
|
32
|
-
name: title,
|
|
33
|
-
summary: "Exercises conditional multi-host Cloud writes.",
|
|
34
|
-
capabilities: ["cloud_cas_testing"],
|
|
35
|
-
routing_status: "routing_ready",
|
|
36
|
-
}, null, 2) + "\n");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function listen(server) {
|
|
40
|
-
return new Promise((resolve, reject) => {
|
|
41
|
-
server.once("error", reject);
|
|
42
|
-
server.listen(0, "127.0.0.1", () => resolve(server.address()));
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function close(server) {
|
|
47
|
-
return new Promise((resolve) => server.close(resolve));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function descriptorFor(body, sequence, cloudId) {
|
|
51
|
-
const scope = body.visibility === "marketplace" ? "hub-public" : "owner-private";
|
|
52
|
-
const revision = `rev-${sequence}-${body.manifest.packageHash.slice(0, 16)}`;
|
|
53
|
-
return {
|
|
54
|
-
cloudId,
|
|
55
|
-
slug: body.manifest.slug,
|
|
56
|
-
scope,
|
|
57
|
-
packageHash: body.manifest.packageHash,
|
|
58
|
-
packageHashVersion: body.manifest.packageHashVersion,
|
|
59
|
-
revision,
|
|
60
|
-
etag: `"${revision}"`,
|
|
61
|
-
updatedAt: new Date().toISOString(),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function sendJson(response, status, body, headers = {}) {
|
|
66
|
-
response.writeHead(status, { "content-type": "application/json", "cache-control": "no-store", ...headers });
|
|
67
|
-
response.end(JSON.stringify(body));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
(async () => {
|
|
71
|
-
const assets = new Map();
|
|
72
|
-
const requests = [];
|
|
73
|
-
let sequence = 0;
|
|
74
|
-
let cloudSequence = 0;
|
|
75
|
-
const server = http.createServer((request, response) => {
|
|
76
|
-
const url = new URL(request.url, "http://127.0.0.1");
|
|
77
|
-
const chunks = [];
|
|
78
|
-
request.on("data", (chunk) => chunks.push(chunk));
|
|
79
|
-
request.on("end", () => {
|
|
80
|
-
const body = chunks.length ? JSON.parse(Buffer.concat(chunks).toString("utf8")) : null;
|
|
81
|
-
requests.push({ method: request.method, url, headers: { ...request.headers }, body });
|
|
82
|
-
if (request.method === "POST") {
|
|
83
|
-
const scope = body.visibility === "marketplace" ? "hub-public" : "owner-private";
|
|
84
|
-
const key = `${scope}:${body.manifest.slug}`;
|
|
85
|
-
const current = assets.get(key) || null;
|
|
86
|
-
if (body.manifest.slug === "maintenance-agent") {
|
|
87
|
-
sendJson(response, 503, {
|
|
88
|
-
code: "cloud_mutations_maintenance",
|
|
89
|
-
retryable: true,
|
|
90
|
-
error: "Cloud mutations are temporarily disabled.",
|
|
91
|
-
}, { "retry-after": "60" });
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (body.manifest.slug === "upgrade-required-agent") {
|
|
95
|
-
sendJson(response, 428, {
|
|
96
|
-
code: "client_upgrade_required",
|
|
97
|
-
current: current || descriptorFor(body, ++sequence, `cloud-test-${++cloudSequence}`),
|
|
98
|
-
});
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const requested = { slug: body.manifest.slug, scope, cloudId: request.headers["x-agentlas-cloud-id"] };
|
|
102
|
-
const conflict = (reason) => sendJson(response, 412, {
|
|
103
|
-
code: "cloud_agent_revision_conflict",
|
|
104
|
-
conflict: { reason, requested },
|
|
105
|
-
current,
|
|
106
|
-
}, current ? { etag: current.etag } : {});
|
|
107
|
-
if (request.headers["if-none-match"] === "*") {
|
|
108
|
-
if (current) return conflict("already_exists");
|
|
109
|
-
const created = descriptorFor(body, ++sequence, `cloud-test-${++cloudSequence}`);
|
|
110
|
-
assets.set(key, created);
|
|
111
|
-
sendJson(response, 200, {
|
|
112
|
-
schema: "agentlas.agent_cloud.registration.v1",
|
|
113
|
-
operation: "created",
|
|
114
|
-
source: scope === "hub-public" ? "hub" : "agent-cloud",
|
|
115
|
-
visibility: scope === "hub-public" ? "marketplace" : "owner-private",
|
|
116
|
-
scope,
|
|
117
|
-
owner: true,
|
|
118
|
-
publicHubPublished: scope === "hub-public",
|
|
119
|
-
...created,
|
|
120
|
-
url: `http://agentlas.test/${created.slug}`,
|
|
121
|
-
marketplaceUrl: scope === "hub-public" ? `http://agentlas.test/p/${created.slug}` : undefined,
|
|
122
|
-
registeredAt: created.updatedAt,
|
|
123
|
-
savedAt: created.updatedAt,
|
|
124
|
-
dryRun: false,
|
|
125
|
-
billing: { modelCallsPaidBy: "none", platformModelCalls: 0 },
|
|
126
|
-
}, { etag: body.manifest.slug === "bad-etag-agent" ? '"wrong-revision"' : created.etag });
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (!current) return conflict("missing_target");
|
|
130
|
-
if (
|
|
131
|
-
request.headers["if-match"] !== current.etag ||
|
|
132
|
-
request.headers["x-agentlas-cloud-id"] !== current.cloudId
|
|
133
|
-
) return conflict("revision_mismatch");
|
|
134
|
-
const unchanged = current.packageHash === body.manifest.packageHash && current.packageHashVersion === body.manifest.packageHashVersion;
|
|
135
|
-
const next = unchanged ? current : descriptorFor(body, ++sequence, current.cloudId);
|
|
136
|
-
assets.set(key, next);
|
|
137
|
-
sendJson(response, 200, {
|
|
138
|
-
schema: "agentlas.agent_cloud.registration.v1",
|
|
139
|
-
operation: unchanged ? "unchanged" : "updated",
|
|
140
|
-
source: scope === "hub-public" ? "hub" : "agent-cloud",
|
|
141
|
-
visibility: scope === "hub-public" ? "marketplace" : "owner-private",
|
|
142
|
-
scope,
|
|
143
|
-
owner: true,
|
|
144
|
-
publicHubPublished: scope === "hub-public",
|
|
145
|
-
...next,
|
|
146
|
-
url: `http://agentlas.test/${next.slug}`,
|
|
147
|
-
marketplaceUrl: scope === "hub-public" ? `http://agentlas.test/p/${next.slug}` : undefined,
|
|
148
|
-
registeredAt: next.updatedAt,
|
|
149
|
-
savedAt: next.updatedAt,
|
|
150
|
-
dryRun: false,
|
|
151
|
-
billing: { modelCallsPaidBy: "none", platformModelCalls: 0 },
|
|
152
|
-
}, { etag: next.etag });
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (request.method === "DELETE") {
|
|
157
|
-
const slug = url.searchParams.get("slug");
|
|
158
|
-
const scope = url.searchParams.get("scope");
|
|
159
|
-
const cloudId = url.searchParams.get("cloudId");
|
|
160
|
-
const key = `${scope}:${slug}`;
|
|
161
|
-
const current = assets.get(key) || null;
|
|
162
|
-
if (
|
|
163
|
-
!current || current.cloudId !== cloudId ||
|
|
164
|
-
request.headers["if-match"] !== current.etag ||
|
|
165
|
-
request.headers["x-agentlas-cloud-id"] !== current.cloudId
|
|
166
|
-
) {
|
|
167
|
-
sendJson(response, 412, {
|
|
168
|
-
code: "cloud_agent_revision_conflict",
|
|
169
|
-
conflict: { reason: "revision_mismatch", requested: { slug, scope, cloudId } },
|
|
170
|
-
current,
|
|
171
|
-
}, current ? { etag: current.etag } : {});
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
assets.delete(key);
|
|
175
|
-
sendJson(response, 200, {
|
|
176
|
-
schema: "agentlas.agent_cloud.delete.v1",
|
|
177
|
-
ok: true,
|
|
178
|
-
source: scope === "hub-public" ? "hub" : "agent-cloud",
|
|
179
|
-
visibility: scope === "hub-public" ? "marketplace" : "owner-private",
|
|
180
|
-
...current,
|
|
181
|
-
...(scope === "hub-public"
|
|
182
|
-
? { operation: "unpublished", unpublishedAt: new Date().toISOString() }
|
|
183
|
-
: { deletedAt: new Date().toISOString() }),
|
|
184
|
-
}, { etag: current.etag });
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
sendJson(response, 405, { error: "method_not_allowed" });
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
const address = await listen(server);
|
|
193
|
-
process.env.AGENTLAS_WEB_BASE_URL = `http://127.0.0.1:${address.port}`;
|
|
194
|
-
|
|
195
|
-
const updateRoot = path.join(tempDir, "update-agent");
|
|
196
|
-
writePrivateAgent(updateRoot, "Update Agent");
|
|
197
|
-
const created = await packageCloudAgentCli(null, updateRoot, { slug: "update-agent", dryRun: false, llmReview: false });
|
|
198
|
-
assert.equal(created.registration.operation, "created");
|
|
199
|
-
const createRequest = requests.at(-1);
|
|
200
|
-
assert.equal(createRequest.headers["if-none-match"], "*");
|
|
201
|
-
assert.equal(createRequest.headers["if-match"], undefined);
|
|
202
|
-
const createdMarker = JSON.parse(fs.readFileSync(path.join(updateRoot, ".agentlas-cloud-package.json"), "utf8"));
|
|
203
|
-
assert.equal(createdMarker.cloudAssets["owner-private"].revision, created.registration.revision);
|
|
204
|
-
|
|
205
|
-
fs.appendFileSync(path.join(updateRoot, "AGENTS.md"), "UPDATED_LOCALLY\n");
|
|
206
|
-
const updated = await packageCloudAgentCli(null, updateRoot, { slug: "update-agent", dryRun: false, llmReview: false });
|
|
207
|
-
assert.equal(updated.registration.operation, "updated");
|
|
208
|
-
const updateRequest = requests.at(-1);
|
|
209
|
-
assert.equal(updateRequest.headers["if-match"], created.registration.etag);
|
|
210
|
-
assert.equal(updateRequest.headers["x-agentlas-cloud-id"], created.registration.cloudId);
|
|
211
|
-
assert.equal(updateRequest.headers["if-none-match"], undefined);
|
|
212
|
-
|
|
213
|
-
const updateKey = "owner-private:update-agent";
|
|
214
|
-
const remote = { ...assets.get(updateKey) };
|
|
215
|
-
remote.revision = `rev-remote-${Date.now()}`;
|
|
216
|
-
remote.etag = `"${remote.revision}"`;
|
|
217
|
-
remote.packageHash = "f".repeat(64);
|
|
218
|
-
remote.updatedAt = new Date(Date.now() + 1000).toISOString();
|
|
219
|
-
assets.set(updateKey, remote);
|
|
220
|
-
fs.appendFileSync(path.join(updateRoot, "AGENTS.md"), "STALE_LOCAL_CHANGE\n");
|
|
221
|
-
await assert.rejects(
|
|
222
|
-
packageCloudAgentCli(null, updateRoot, { slug: "update-agent", dryRun: false, llmReview: false }),
|
|
223
|
-
(error) => error.code === "cloud_agent_revision_conflict" && /다른 PC/.test(error.message),
|
|
224
|
-
);
|
|
225
|
-
const staleMarker = JSON.parse(fs.readFileSync(path.join(updateRoot, ".agentlas-cloud-package.json"), "utf8"));
|
|
226
|
-
assert.equal(staleMarker.cloudAssets["owner-private"].revision, updated.registration.revision, "412 must never adopt the server revision automatically");
|
|
227
|
-
assert.equal(readCloudAssetStateCli().assets[updateKey].descriptor.revision, updated.registration.revision);
|
|
228
|
-
|
|
229
|
-
const foreignRoot = path.join(tempDir, "foreign-agent");
|
|
230
|
-
writePrivateAgent(foreignRoot, "Foreign Agent");
|
|
231
|
-
const foreignBody = {
|
|
232
|
-
visibility: "private-link",
|
|
233
|
-
manifest: {
|
|
234
|
-
slug: "foreign-agent",
|
|
235
|
-
packageHash: "e".repeat(64),
|
|
236
|
-
packageHashVersion: "path-sha256-executable-v2",
|
|
237
|
-
},
|
|
238
|
-
};
|
|
239
|
-
assets.set("owner-private:foreign-agent", descriptorFor(foreignBody, ++sequence, `cloud-test-${++cloudSequence}`));
|
|
240
|
-
await assert.rejects(
|
|
241
|
-
packageCloudAgentCli(null, foreignRoot, { slug: "foreign-agent", dryRun: false, llmReview: false }),
|
|
242
|
-
(error) => error.code === "cloud_agent_revision_conflict",
|
|
243
|
-
);
|
|
244
|
-
assert.equal(fs.existsSync(path.join(foreignRoot, ".agentlas-cloud-package.json")), false);
|
|
245
|
-
assert.equal(requests.at(-1).headers["if-none-match"], "*");
|
|
246
|
-
|
|
247
|
-
const upgradeRoot = path.join(tempDir, "upgrade-required-agent");
|
|
248
|
-
writePrivateAgent(upgradeRoot, "Upgrade Required Agent");
|
|
249
|
-
await assert.rejects(
|
|
250
|
-
packageCloudAgentCli(null, upgradeRoot, { slug: "upgrade-required-agent", dryRun: false, llmReview: false }),
|
|
251
|
-
(error) => error.code === "client_upgrade_required" && /자동 복사하지 않습니다/.test(error.message),
|
|
252
|
-
);
|
|
253
|
-
assert.equal(fs.existsSync(path.join(upgradeRoot, ".agentlas-cloud-package.json")), false);
|
|
254
|
-
|
|
255
|
-
const maintenanceRoot = path.join(tempDir, "maintenance-agent");
|
|
256
|
-
writePrivateAgent(maintenanceRoot, "Maintenance Agent");
|
|
257
|
-
await assert.rejects(
|
|
258
|
-
packageCloudAgentCli(null, maintenanceRoot, { slug: "maintenance-agent", dryRun: false, llmReview: false }),
|
|
259
|
-
(error) => error.code === "cloud_mutations_maintenance" && /60초.*읽기·목록·복원/.test(error.message),
|
|
260
|
-
);
|
|
261
|
-
assert.equal(fs.existsSync(path.join(maintenanceRoot, ".agentlas-cloud-package.json")), false);
|
|
262
|
-
|
|
263
|
-
const badEtagRoot = path.join(tempDir, "bad-etag-agent");
|
|
264
|
-
writePrivateAgent(badEtagRoot, "Bad ETag Agent");
|
|
265
|
-
await assert.rejects(
|
|
266
|
-
packageCloudAgentCli(null, badEtagRoot, { slug: "bad-etag-agent", dryRun: false, llmReview: false }),
|
|
267
|
-
/invalid or mismatched registration receipt/,
|
|
268
|
-
);
|
|
269
|
-
assert.equal(fs.existsSync(path.join(badEtagRoot, ".agentlas-cloud-package.json")), false);
|
|
270
|
-
|
|
271
|
-
const deleteRoot = path.join(tempDir, "delete-agent");
|
|
272
|
-
writePrivateAgent(deleteRoot, "Delete Agent");
|
|
273
|
-
const beforeDelete = await packageCloudAgentCli(null, deleteRoot, { slug: "delete-agent", dryRun: false, llmReview: false });
|
|
274
|
-
const deleted = await deleteCloudAgentCli("delete-agent", { scope: "owner-private" });
|
|
275
|
-
assert.equal(deleted.revision, beforeDelete.registration.revision);
|
|
276
|
-
const deleteRequest = requests.at(-1);
|
|
277
|
-
assert.equal(deleteRequest.method, "DELETE");
|
|
278
|
-
assert.equal(deleteRequest.url.searchParams.get("scope"), "owner-private");
|
|
279
|
-
assert.equal(deleteRequest.url.searchParams.get("cloudId"), beforeDelete.registration.cloudId);
|
|
280
|
-
assert.equal(deleteRequest.headers["if-match"], beforeDelete.registration.etag);
|
|
281
|
-
const markerAfterDelete = JSON.parse(fs.readFileSync(path.join(deleteRoot, ".agentlas-cloud-package.json"), "utf8"));
|
|
282
|
-
assert.equal(markerAfterDelete.cloudAssets["owner-private"], undefined);
|
|
283
|
-
const recreated = await packageCloudAgentCli(null, deleteRoot, { slug: "delete-agent", dryRun: false, llmReview: false });
|
|
284
|
-
assert.equal(recreated.registration.operation, "created");
|
|
285
|
-
assert.notEqual(recreated.registration.cloudId, beforeDelete.registration.cloudId);
|
|
286
|
-
assert.equal(requests.at(-1).headers["if-none-match"], "*", "delete→recreate must not reuse a deleted revision");
|
|
287
|
-
|
|
288
|
-
const staleDeleteRoot = path.join(tempDir, "stale-delete-agent");
|
|
289
|
-
writePrivateAgent(staleDeleteRoot, "Stale Delete Agent");
|
|
290
|
-
const staleDeleteBase = await packageCloudAgentCli(null, staleDeleteRoot, { slug: "stale-delete-agent", dryRun: false, llmReview: false });
|
|
291
|
-
const staleDeleteKey = "owner-private:stale-delete-agent";
|
|
292
|
-
const remotelyUpdatedDelete = { ...assets.get(staleDeleteKey) };
|
|
293
|
-
remotelyUpdatedDelete.revision = `rev-remote-delete-${Date.now()}`;
|
|
294
|
-
remotelyUpdatedDelete.etag = `"${remotelyUpdatedDelete.revision}"`;
|
|
295
|
-
remotelyUpdatedDelete.updatedAt = new Date(Date.now() + 1000).toISOString();
|
|
296
|
-
assets.set(staleDeleteKey, remotelyUpdatedDelete);
|
|
297
|
-
await assert.rejects(
|
|
298
|
-
deleteCloudAgentCli("stale-delete-agent", { scope: "owner-private" }),
|
|
299
|
-
(error) => error.code === "cloud_agent_revision_conflict" && /다른 PC/.test(error.message),
|
|
300
|
-
);
|
|
301
|
-
const staleDeleteMarker = JSON.parse(fs.readFileSync(path.join(staleDeleteRoot, ".agentlas-cloud-package.json"), "utf8"));
|
|
302
|
-
assert.equal(staleDeleteMarker.cloudAssets["owner-private"].revision, staleDeleteBase.registration.revision);
|
|
303
|
-
assert.equal(readCloudAssetStateCli().assets[staleDeleteKey].descriptor.revision, staleDeleteBase.registration.revision);
|
|
304
|
-
|
|
305
|
-
const dualRoot = path.join(tempDir, "dual-scope-agent");
|
|
306
|
-
writePublicAgent(dualRoot, "Dual Scope Agent");
|
|
307
|
-
const dualPrivate = await packageCloudAgentCli(null, dualRoot, { slug: "dual-scope-agent", dryRun: false, llmReview: false });
|
|
308
|
-
const dualPublic = await packageCloudAgentCli(null, dualRoot, { slug: "dual-scope-agent", visibility: "marketplace", dryRun: false, llmReview: false });
|
|
309
|
-
const dualMarker = JSON.parse(fs.readFileSync(path.join(dualRoot, ".agentlas-cloud-package.json"), "utf8"));
|
|
310
|
-
assert.equal(dualMarker.cloudAssets["owner-private"].revision, dualPrivate.registration.revision);
|
|
311
|
-
assert.equal(dualMarker.cloudAssets["hub-public"].revision, dualPublic.registration.revision);
|
|
312
|
-
await assert.rejects(
|
|
313
|
-
deleteCloudAgentCli("dual-scope-agent"),
|
|
314
|
-
/multiple scopes/,
|
|
315
|
-
"same slug in private/public scopes must require an exact scope",
|
|
316
|
-
);
|
|
317
|
-
await deleteCloudAgentCli("dual-scope-agent", { scope: "owner-private" });
|
|
318
|
-
const dualAfterDelete = JSON.parse(fs.readFileSync(path.join(dualRoot, ".agentlas-cloud-package.json"), "utf8"));
|
|
319
|
-
assert.equal(dualAfterDelete.cloudAssets["owner-private"], undefined);
|
|
320
|
-
assert.equal(dualAfterDelete.cloudAssets["hub-public"].revision, dualPublic.registration.revision);
|
|
321
|
-
const unpublished = await deleteCloudAgentCli("dual-scope-agent", { scope: "hub-public" });
|
|
322
|
-
assert.equal(unpublished.operation, "unpublished");
|
|
323
|
-
assert.ok(Number.isFinite(Date.parse(unpublished.unpublishedAt)));
|
|
324
|
-
|
|
325
|
-
console.log("cloud multi-host CAS client: PASS");
|
|
326
|
-
} finally {
|
|
327
|
-
await close(server).catch(() => {});
|
|
328
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
329
|
-
}
|
|
330
|
-
})().catch((error) => {
|
|
331
|
-
console.error(error);
|
|
332
|
-
process.exitCode = 1;
|
|
333
|
-
});
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
const assert = require("node:assert/strict");
|
|
5
|
-
const crypto = require("node:crypto");
|
|
6
|
-
const fs = require("node:fs");
|
|
7
|
-
const http = require("node:http");
|
|
8
|
-
const os = require("node:os");
|
|
9
|
-
const path = require("node:path");
|
|
10
|
-
const { execFile } = require("node:child_process");
|
|
11
|
-
const { promisify } = require("node:util");
|
|
12
|
-
|
|
13
|
-
const execFileAsync = promisify(execFile);
|
|
14
|
-
|
|
15
|
-
function cloudFile(filePath, content) {
|
|
16
|
-
const bytes = Buffer.from(content, "utf8");
|
|
17
|
-
return {
|
|
18
|
-
path: filePath,
|
|
19
|
-
bytes: bytes.length,
|
|
20
|
-
sha256: crypto.createHash("sha256").update(bytes).digest("hex"),
|
|
21
|
-
contentBase64: bytes.toString("base64"),
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function packageHash(files) {
|
|
26
|
-
const hash = crypto.createHash("sha256");
|
|
27
|
-
for (const file of [...files].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))) {
|
|
28
|
-
hash.update(file.path);
|
|
29
|
-
hash.update("\0");
|
|
30
|
-
hash.update(file.sha256);
|
|
31
|
-
hash.update("\0");
|
|
32
|
-
}
|
|
33
|
-
return hash.digest("hex");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function listen(server) {
|
|
37
|
-
return new Promise((resolve, reject) => {
|
|
38
|
-
server.once("error", reject);
|
|
39
|
-
server.listen(0, "127.0.0.1", () => resolve(server.address()));
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function close(server) {
|
|
44
|
-
return new Promise((resolve) => server.close(resolve));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function main() {
|
|
48
|
-
const userData = fs.mkdtempSync(path.join(os.tmpdir(), "agentlas-owner-cloud-"));
|
|
49
|
-
const files = [cloudFile("AGENTS.md", "# Owned Agent\n"), cloudFile("skills/core/SKILL.md", "# Core\n")];
|
|
50
|
-
const aggregate = packageHash(files);
|
|
51
|
-
const updatedAt = "2026-07-11T00:00:00.000Z";
|
|
52
|
-
const descriptorFor = (slug) => ({
|
|
53
|
-
cloudId: `cloud-${slug}`,
|
|
54
|
-
slug,
|
|
55
|
-
scope: "owner-private",
|
|
56
|
-
packageHash: aggregate,
|
|
57
|
-
packageHashVersion: "path-sha256-v1",
|
|
58
|
-
revision: `rev-${slug}-001`,
|
|
59
|
-
etag: `"rev-${slug}-001"`,
|
|
60
|
-
updatedAt,
|
|
61
|
-
});
|
|
62
|
-
const calls = [];
|
|
63
|
-
const server = http.createServer((request, response) => {
|
|
64
|
-
let raw = "";
|
|
65
|
-
request.setEncoding("utf8");
|
|
66
|
-
request.on("data", (chunk) => { raw += chunk; });
|
|
67
|
-
request.on("end", () => {
|
|
68
|
-
const body = JSON.parse(raw || "{}");
|
|
69
|
-
const name = body?.params?.name;
|
|
70
|
-
const requestedSlug = body?.params?.arguments?.slug;
|
|
71
|
-
calls.push({ name, cookie: request.headers.cookie || "" });
|
|
72
|
-
let result;
|
|
73
|
-
if (name === "cargo.search_agents") {
|
|
74
|
-
result = {
|
|
75
|
-
schema: "agentlas.agent_cloud.search.v1",
|
|
76
|
-
source: "cloud",
|
|
77
|
-
status: "ok",
|
|
78
|
-
count: 1,
|
|
79
|
-
total: 1,
|
|
80
|
-
results: [{ ...descriptorFor("owned-agent"), name: "Owned Agent", entityKind: "agent" }],
|
|
81
|
-
};
|
|
82
|
-
} else if (name === "cargo.restore_package") {
|
|
83
|
-
const responseSlug = requestedSlug === "cross-slug" ? "different-agent" : (requestedSlug || "owned-agent");
|
|
84
|
-
const descriptor = descriptorFor(responseSlug);
|
|
85
|
-
result = {
|
|
86
|
-
schema: "agentlas.agent_cloud.restore.v1",
|
|
87
|
-
source: "cloud",
|
|
88
|
-
owner: true,
|
|
89
|
-
...descriptor,
|
|
90
|
-
name: "Owned Agent",
|
|
91
|
-
cloudPackage: {
|
|
92
|
-
...descriptor,
|
|
93
|
-
etag: undefined,
|
|
94
|
-
agentKind: "agent",
|
|
95
|
-
fileCount: files.length,
|
|
96
|
-
totalBytes: files.reduce((sum, file) => sum + file.bytes, 0),
|
|
97
|
-
files,
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
if (requestedSlug === "mismatched-envelope") result.packageHash = "0".repeat(64);
|
|
101
|
-
} else if (name === "marketplace.get_manifest") {
|
|
102
|
-
result = {
|
|
103
|
-
slug: "call-only-agent",
|
|
104
|
-
name: "Call Only Agent",
|
|
105
|
-
delivery: {
|
|
106
|
-
mode: "call_only",
|
|
107
|
-
sourceDownload: false,
|
|
108
|
-
runtimeTool: "agentlas.get_runtime_bundle",
|
|
109
|
-
runtimeVersion: aggregate,
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
} else {
|
|
113
|
-
response.writeHead(400, { "content-type": "application/json" });
|
|
114
|
-
response.end(JSON.stringify({ error: { message: `unexpected tool: ${name}` } }));
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
response.writeHead(200, { "content-type": "application/json" });
|
|
118
|
-
response.end(JSON.stringify({ result }));
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
const address = await listen(server);
|
|
124
|
-
const env = {
|
|
125
|
-
...process.env,
|
|
126
|
-
AGENTLAS_USER_DATA_DIR: userData,
|
|
127
|
-
AGENTLAS_MCP_BASE_URL: `http://127.0.0.1:${address.port}/api/mcp/v1`,
|
|
128
|
-
AGENTLAS_SESSION: "owner-session-fixture",
|
|
129
|
-
};
|
|
130
|
-
const bin = path.join(__dirname, "..", "bin", "agentlas.cjs");
|
|
131
|
-
|
|
132
|
-
const listed = await execFileAsync(process.execPath, [bin, "cloud", "list"], { env });
|
|
133
|
-
assert.match(listed.stdout, /owned-agent/);
|
|
134
|
-
const observedState = JSON.parse(fs.readFileSync(path.join(userData, "cloud-asset-state.v1.json"), "utf8"));
|
|
135
|
-
assert.equal(observedState.assets["owner-private:owned-agent"].descriptor.revision, "rev-owned-agent-001");
|
|
136
|
-
|
|
137
|
-
const restored = await execFileAsync(process.execPath, [bin, "cloud", "restore", "owned-agent", "--json"], { env });
|
|
138
|
-
const receipt = JSON.parse(restored.stdout);
|
|
139
|
-
assert.equal(receipt.source, "cloud");
|
|
140
|
-
assert.equal(receipt.packageHash, aggregate);
|
|
141
|
-
assert.equal(receipt.packageHashVersion, "path-sha256-v1");
|
|
142
|
-
assert.equal(receipt.revision, "rev-owned-agent-001");
|
|
143
|
-
assert.equal(receipt.etag, '"rev-owned-agent-001"');
|
|
144
|
-
const installRoot = path.join(userData, "cloud-agent-installs", "owned-agent");
|
|
145
|
-
assert.equal(fs.readFileSync(path.join(installRoot, "AGENTS.md"), "utf8"), "# Owned Agent\n");
|
|
146
|
-
const marker = JSON.parse(fs.readFileSync(path.join(installRoot, ".agentlas-cloud-package.json"), "utf8"));
|
|
147
|
-
assert.equal(marker.packageHash, aggregate);
|
|
148
|
-
assert.equal(marker.revision, "rev-owned-agent-001");
|
|
149
|
-
assert.equal(marker.cloudAssets["owner-private"].cloudId, "cloud-owned-agent");
|
|
150
|
-
await assert.rejects(
|
|
151
|
-
execFileAsync(process.execPath, [bin, "cloud", "restore", "cross-slug", "--json"], { env }),
|
|
152
|
-
(error) => /restore_slug_mismatch/.test(String(error.stderr || error)),
|
|
153
|
-
);
|
|
154
|
-
await assert.rejects(
|
|
155
|
-
execFileAsync(process.execPath, [bin, "cloud", "restore", "mismatched-envelope", "--json"], { env }),
|
|
156
|
-
(error) => /invalid_restore_contract/.test(String(error.stderr || error)),
|
|
157
|
-
);
|
|
158
|
-
assert.equal(fs.existsSync(path.join(userData, "cloud-agent-installs", "cross-slug")), false);
|
|
159
|
-
assert.equal(fs.existsSync(path.join(userData, "cloud-agent-installs", "mismatched-envelope")), false);
|
|
160
|
-
await assert.rejects(
|
|
161
|
-
execFileAsync(process.execPath, [bin, "install", "call-only-agent"], { env }),
|
|
162
|
-
(error) => /call-only.*agentlas call call-only-agent/s.test(String(error.stderr || error)),
|
|
163
|
-
"direct install must route invoke-only assets to the runtime call path",
|
|
164
|
-
);
|
|
165
|
-
assert.deepEqual(calls.map((call) => call.name), [
|
|
166
|
-
"cargo.search_agents",
|
|
167
|
-
"cargo.restore_package",
|
|
168
|
-
"cargo.restore_package",
|
|
169
|
-
"cargo.restore_package",
|
|
170
|
-
"marketplace.get_manifest",
|
|
171
|
-
]);
|
|
172
|
-
assert.ok(calls.every((call) => call.cookie === "agentlas_session=owner-session-fixture"));
|
|
173
|
-
console.log("cloud owner list/restore: PASS");
|
|
174
|
-
} finally {
|
|
175
|
-
await close(server);
|
|
176
|
-
fs.rmSync(userData, { recursive: true, force: true });
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
main().catch((error) => {
|
|
181
|
-
console.error(error);
|
|
182
|
-
process.exitCode = 1;
|
|
183
|
-
});
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
const assert = require("node:assert/strict");
|
|
5
|
-
const fs = require("node:fs");
|
|
6
|
-
const os = require("node:os");
|
|
7
|
-
const path = require("node:path");
|
|
8
|
-
const { buildManifest, matches, normalizeRequestedPath, readAgentFile } = require("../engine/agentlas-cloud-runtime.cjs");
|
|
9
|
-
|
|
10
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "agentlas-cloud-paths-"));
|
|
11
|
-
try {
|
|
12
|
-
fs.mkdirSync(path.join(root, "skills", "nested", "docs"), { recursive: true });
|
|
13
|
-
fs.mkdirSync(path.join(root, "skills", "nested", "secrets"), { recursive: true });
|
|
14
|
-
fs.mkdirSync(path.join(root, "secrets"), { recursive: true });
|
|
15
|
-
fs.writeFileSync(path.join(root, "skills", "nested", "docs", "guide.md"), "safe guide\n");
|
|
16
|
-
fs.writeFileSync(path.join(root, "skills", "nested", "secrets", "key.md"), "password=fixture_secret_12345678901234567890\n");
|
|
17
|
-
fs.writeFileSync(path.join(root, "secrets", "root.md"), "blocked\n");
|
|
18
|
-
fs.writeFileSync(
|
|
19
|
-
path.join(root, "agentlas.json"),
|
|
20
|
-
JSON.stringify({
|
|
21
|
-
entry: "skills/nested/docs/guide.md",
|
|
22
|
-
allowRead: ["skills/**"],
|
|
23
|
-
denyRead: ["**/secrets/**"],
|
|
24
|
-
}),
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
assert.equal(matches("skills/nested/docs/guide.md", "skills/**"), true);
|
|
28
|
-
assert.equal(matches("secrets/root.md", "**/secrets/**"), true);
|
|
29
|
-
assert.equal(matches("skills/nested/secrets/key.md", "**/secrets/**"), true);
|
|
30
|
-
assert.equal(readAgentFile(root, "skills/nested/docs/guide.md").status, "allowed");
|
|
31
|
-
assert.equal(readAgentFile(root, "secrets/root.md").status, "denied");
|
|
32
|
-
assert.equal(readAgentFile(root, "skills/nested/secrets/key.md").status, "denied");
|
|
33
|
-
assert.equal(readAgentFile(root, "../outside.md").status, "denied");
|
|
34
|
-
assert.equal(readAgentFile(root, "skills/../../outside.md").status, "denied");
|
|
35
|
-
assert.equal(normalizeRequestedPath("skills\\nested\\docs\\guide.md"), "skills/nested/docs/guide.md");
|
|
36
|
-
assert.equal(buildManifest(root).createdBy, "agentlas-terminal-setup-wizard");
|
|
37
|
-
console.log("cloud runtime glob/path containment: PASS");
|
|
38
|
-
} finally {
|
|
39
|
-
fs.rmSync(root, { recursive: true, force: true });
|
|
40
|
-
}
|