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
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Successful-run -> private Operational Experience candidate bridge.
|
|
5
|
+
*
|
|
6
|
+
* Storage boundary:
|
|
7
|
+
* - Run/evidence and value-free intake decisions use the existing shared
|
|
8
|
+
* `run_events` ledger.
|
|
9
|
+
* - Candidates use the existing Portable Experience exchange store.
|
|
10
|
+
* - Preferences never enter Operational Experience; they remain local Taste
|
|
11
|
+
* observations referencing curated Memory, with no copied preference text.
|
|
12
|
+
*/
|
|
13
|
+
const crypto = require("node:crypto");
|
|
14
|
+
const exchange = require("./agentlas-experience-exchange.cjs");
|
|
15
|
+
|
|
16
|
+
const RUN_RECEIPT_SCHEMA = "agentlas.run-receipt.v1";
|
|
17
|
+
const INTAKE_POLICY_VERSION = "agentlas-terminal-operational-intake.v1";
|
|
18
|
+
const ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@-]{2,255}$/;
|
|
19
|
+
const HASH_RE = /^sha256:[0-9a-f]{64}$/;
|
|
20
|
+
const OPERATIONAL_KINDS = new Set(["procedure", "decision", "risk"]);
|
|
21
|
+
|
|
22
|
+
function canonicalHash(value) {
|
|
23
|
+
return exchange.canonicalHash(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function digestHex(...parts) {
|
|
27
|
+
const hash = crypto.createHash("sha256");
|
|
28
|
+
for (const part of parts) hash.update(String(part ?? "")).update("\0");
|
|
29
|
+
return hash.digest("hex");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function opaqueId(prefix, ...parts) {
|
|
33
|
+
return `${prefix}:${digestHex(...parts).slice(0, 32)}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function cleanId(value, fallback) {
|
|
37
|
+
const text = String(value || "").trim();
|
|
38
|
+
return ID_RE.test(text) ? text : fallback;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function codePointSlice(value, max) {
|
|
42
|
+
return Array.from(String(value || "").normalize("NFC").trim()).slice(0, max).join("");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function runtimeEnvironment(input = {}) {
|
|
46
|
+
const platform = String(input.os || process.platform).toLowerCase();
|
|
47
|
+
const arch = String(input.arch || process.arch).toLowerCase();
|
|
48
|
+
const runtime = String(input.runtime || "terminal").toLowerCase().replace(/[^a-z0-9._-]/g, "-").slice(0, 64);
|
|
49
|
+
const osName = platform === "darwin" || platform === "macos"
|
|
50
|
+
? "macos"
|
|
51
|
+
: platform === "win32" || platform === "windows"
|
|
52
|
+
? "windows"
|
|
53
|
+
: platform === "linux" ? "linux" : "unknown";
|
|
54
|
+
const archName = arch === "arm64" || arch === "aarch64" ? "arm64" : arch === "x64" || arch === "x86_64" ? "x64" : "unknown";
|
|
55
|
+
if (osName === "unknown" || archName === "unknown" || !/^[a-z0-9][a-z0-9._-]{1,63}$/.test(runtime)) return null;
|
|
56
|
+
const constraints = [
|
|
57
|
+
`agentlas.env.v1/os/${osName}`,
|
|
58
|
+
`agentlas.env.v1/arch/${archName}`,
|
|
59
|
+
`agentlas.env.v1/runtime/${runtime}`,
|
|
60
|
+
];
|
|
61
|
+
return {
|
|
62
|
+
runtime,
|
|
63
|
+
os: osName,
|
|
64
|
+
arch: archName,
|
|
65
|
+
constraints,
|
|
66
|
+
fingerprintHash: canonicalHash({ runtime, os: osName, arch: archName }),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createRunReceipt(input) {
|
|
71
|
+
const environment = runtimeEnvironment(input.environment);
|
|
72
|
+
if (!environment) throw new Error("RunReceipt requires a canonical runtime environment.");
|
|
73
|
+
const taskSignature = exchange.canonicalSourceTaskId(input.taskSignature);
|
|
74
|
+
if (!taskSignature) throw new Error("RunReceipt requires one canonical task signature.");
|
|
75
|
+
if (!input.exactBase || !ID_RE.test(String(input.exactBase.agentReleaseId || ""))) {
|
|
76
|
+
throw new Error("RunReceipt requires an exact agent definition release.");
|
|
77
|
+
}
|
|
78
|
+
const runId = cleanId(input.runId, opaqueId("run", input.exactBase.agentReleaseId, taskSignature, input.createdAt));
|
|
79
|
+
const idempotencyKey = cleanId(input.idempotencyKey, opaqueId("run-key", runId));
|
|
80
|
+
const createdAt = input.createdAt || new Date().toISOString();
|
|
81
|
+
const provider = cleanId(input.model?.provider, "terminal-runtime");
|
|
82
|
+
const modelId = cleanId(input.model?.modelId, provider);
|
|
83
|
+
const outcome = ["succeeded", "partial", "failed", "cancelled"].includes(input.outcome?.status)
|
|
84
|
+
? input.outcome.status
|
|
85
|
+
: "failed";
|
|
86
|
+
const metrics = input.metrics || {};
|
|
87
|
+
const promptTokens = Math.max(0, Math.trunc(Number(metrics.promptTokens) || 0));
|
|
88
|
+
const completionTokens = Math.max(0, Math.trunc(Number(metrics.completionTokens) || 0));
|
|
89
|
+
const totalTokens = Math.max(
|
|
90
|
+
promptTokens + completionTokens,
|
|
91
|
+
Math.max(0, Math.trunc(Number(metrics.totalTokens) || 0)),
|
|
92
|
+
);
|
|
93
|
+
const draft = {
|
|
94
|
+
schemaVersion: RUN_RECEIPT_SCHEMA,
|
|
95
|
+
kind: "agentlas-run-receipt",
|
|
96
|
+
receiptId: "pending:run-receipt",
|
|
97
|
+
idempotencyKey,
|
|
98
|
+
runId,
|
|
99
|
+
agentDefinitionReleaseId: input.exactBase.agentReleaseId,
|
|
100
|
+
experiencePackReleaseId: input.experiencePackReleaseId && ID_RE.test(input.experiencePackReleaseId) ? input.experiencePackReleaseId : null,
|
|
101
|
+
variantId: null,
|
|
102
|
+
taskSignature: {
|
|
103
|
+
kind: taskSignature,
|
|
104
|
+
hash: canonicalHash({ kind: taskSignature, locale: input.locale || "und" }),
|
|
105
|
+
locale: String(input.locale || "und").slice(0, 20),
|
|
106
|
+
},
|
|
107
|
+
environment: {
|
|
108
|
+
runtime: environment.runtime,
|
|
109
|
+
os: environment.os,
|
|
110
|
+
arch: environment.arch,
|
|
111
|
+
fingerprintHash: environment.fingerprintHash,
|
|
112
|
+
},
|
|
113
|
+
resources: {
|
|
114
|
+
mcp: (input.mcp || []).slice(0, 64).flatMap((item) => {
|
|
115
|
+
const catalogId = cleanId(item?.catalogId, null);
|
|
116
|
+
if (!catalogId) return [];
|
|
117
|
+
const status = ["recommended", "approved", "connected", "skipped", "missing-key", "failed", "degraded"].includes(item.status)
|
|
118
|
+
? item.status
|
|
119
|
+
: "approved";
|
|
120
|
+
return [{ catalogId, status, resolvedVersion: item.resolvedVersion || null, fallbackFor: item.fallbackFor || null }];
|
|
121
|
+
}),
|
|
122
|
+
skills: [],
|
|
123
|
+
model: { provider, modelId },
|
|
124
|
+
},
|
|
125
|
+
outcome: { status: outcome, failureCode: input.outcome?.failureCode ? codePointSlice(input.outcome.failureCode, 120) : null },
|
|
126
|
+
verification: { verdict: "unverified", method: "none", verifierRef: null, evidenceRefs: [] },
|
|
127
|
+
metricsEligible: false,
|
|
128
|
+
metrics: {
|
|
129
|
+
promptTokens,
|
|
130
|
+
completionTokens,
|
|
131
|
+
totalTokens,
|
|
132
|
+
durationMs: Math.max(0, Math.trunc(Number(metrics.durationMs) || 0)),
|
|
133
|
+
retryCount: Math.max(0, Math.trunc(Number(metrics.retryCount) || 0)),
|
|
134
|
+
},
|
|
135
|
+
sideEffects: { occurred: input.sideEffects?.occurred === true, adverse: input.sideEffects?.adverse === true, evidenceRefs: [] },
|
|
136
|
+
privacy: { rawPromptIncluded: false, rawTranscriptIncluded: false, rawLocalPathsIncluded: false, credentialValuesIncluded: false },
|
|
137
|
+
createdAt,
|
|
138
|
+
signature: null,
|
|
139
|
+
};
|
|
140
|
+
const receiptId = opaqueId("run-receipt", idempotencyKey, input.exactBase.agentReleaseId, taskSignature);
|
|
141
|
+
// Core public RunReceipt hashes the canonical payload without receiptHash or
|
|
142
|
+
// the optional transport signature. Keeping signature:null out of the hash
|
|
143
|
+
// preserves Web/Desktop verification parity.
|
|
144
|
+
const { signature: _signature, ...hashPayload } = { ...draft, receiptId };
|
|
145
|
+
const receiptHash = canonicalHash(hashPayload);
|
|
146
|
+
return { ...draft, receiptId, receiptHash };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function validateRunReceipt(receipt) {
|
|
150
|
+
const required = [
|
|
151
|
+
"schemaVersion", "kind", "receiptId", "idempotencyKey", "receiptHash", "runId",
|
|
152
|
+
"agentDefinitionReleaseId", "experiencePackReleaseId", "variantId", "taskSignature",
|
|
153
|
+
"environment", "resources", "outcome", "verification", "metricsEligible", "metrics",
|
|
154
|
+
"sideEffects", "privacy", "createdAt", "signature",
|
|
155
|
+
];
|
|
156
|
+
if (!receipt || Object.keys(receipt).some((key) => !required.includes(key)) || required.some((key) => !(key in receipt))) {
|
|
157
|
+
throw new Error("Terminal RunReceipt shape drifted from Core v1.");
|
|
158
|
+
}
|
|
159
|
+
if (receipt.schemaVersion !== RUN_RECEIPT_SCHEMA || receipt.kind !== "agentlas-run-receipt") throw new Error("Terminal RunReceipt schema is invalid.");
|
|
160
|
+
for (const key of ["receiptId", "idempotencyKey", "runId", "agentDefinitionReleaseId"]) if (!ID_RE.test(String(receipt[key] || ""))) throw new Error(`RunReceipt ${key} is invalid.`);
|
|
161
|
+
if (!HASH_RE.test(receipt.receiptHash) || !HASH_RE.test(receipt.taskSignature?.hash) || !HASH_RE.test(receipt.environment?.fingerprintHash)) throw new Error("RunReceipt hash is invalid.");
|
|
162
|
+
const { receiptHash: claimedHash, signature: _signature, ...hashPayload } = receipt;
|
|
163
|
+
if (claimedHash !== canonicalHash(hashPayload)) throw new Error("RunReceipt hash does not match the Core canonical payload.");
|
|
164
|
+
if (receipt.privacy?.rawPromptIncluded !== false || receipt.privacy?.rawTranscriptIncluded !== false || receipt.privacy?.rawLocalPathsIncluded !== false || receipt.privacy?.credentialValuesIncluded !== false) {
|
|
165
|
+
throw new Error("RunReceipt privacy flags must all be false.");
|
|
166
|
+
}
|
|
167
|
+
if (receipt.signature !== null) throw new Error("Portable RunReceipt signature must be null.");
|
|
168
|
+
return receipt;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function tableExists(db, name) {
|
|
172
|
+
try { return Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(name)); }
|
|
173
|
+
catch { return false; }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function appendRunEvent(db, input) {
|
|
177
|
+
if (!db || !tableExists(db, "run_events")) return false;
|
|
178
|
+
const id = input.id;
|
|
179
|
+
if (db.prepare("SELECT 1 FROM run_events WHERE id=?").get(id)) return false;
|
|
180
|
+
const seqRow = db.prepare("SELECT COALESCE(MAX(seq), -1) + 1 AS seq FROM run_events WHERE run_id=?").get(input.runId);
|
|
181
|
+
db.prepare(
|
|
182
|
+
"INSERT OR IGNORE INTO run_events (id,run_id,seq,ts,kind,chat_id,automation_id,node_id,agent_id,payload_json) VALUES (?,?,?,?,?,NULL,NULL,NULL,?,?)",
|
|
183
|
+
).run(id, input.runId, Number(seqRow?.seq || 0), input.ts, input.kind, input.agentId || null, JSON.stringify(input.payload));
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function persistRunReceipt(db, receipt, agentId) {
|
|
188
|
+
validateRunReceipt(receipt);
|
|
189
|
+
appendRunEvent(db, {
|
|
190
|
+
id: opaqueId("event", "experience-run-receipt", receipt.receiptId),
|
|
191
|
+
runId: receipt.runId,
|
|
192
|
+
ts: receipt.createdAt,
|
|
193
|
+
kind: "experience-run-receipt",
|
|
194
|
+
agentId,
|
|
195
|
+
payload: receipt,
|
|
196
|
+
});
|
|
197
|
+
return receipt;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function recordIntakeDecision(db, input) {
|
|
201
|
+
const sourceHash = digestHex(INTAKE_POLICY_VERSION, input.agentId, input.memoryId || "none", input.exactBase?.agentReleaseId || "none", input.environmentKey || "none");
|
|
202
|
+
appendRunEvent(db, {
|
|
203
|
+
id: opaqueId("event", "experience-intake", sourceHash),
|
|
204
|
+
runId: input.runId,
|
|
205
|
+
ts: input.ts,
|
|
206
|
+
kind: input.kind || "experience-intake-decision",
|
|
207
|
+
agentId: input.agentId,
|
|
208
|
+
payload: {
|
|
209
|
+
schemaVersion: "agentlas.terminal-experience-intake-receipt.v1",
|
|
210
|
+
sourceMemoryRefHash: `sha256:${sourceHash}`,
|
|
211
|
+
status: input.status,
|
|
212
|
+
reasonCodes: [...new Set(input.reasonCodes || [])].sort(),
|
|
213
|
+
candidateId: input.candidateId || null,
|
|
214
|
+
bundleId: input.bundleId || null,
|
|
215
|
+
exactAgentReleaseId: input.exactBase?.agentReleaseId || null,
|
|
216
|
+
environmentKey: input.environmentKey || null,
|
|
217
|
+
privacy: { sourceContentIncluded: false, rawPromptIncluded: false, rawTranscriptIncluded: false },
|
|
218
|
+
networkUsed: false,
|
|
219
|
+
published: false,
|
|
220
|
+
attached: false,
|
|
221
|
+
promoted: false,
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function existingCandidate(userDataDir, itemId) {
|
|
227
|
+
const state = exchange.loadExchangeState(userDataDir);
|
|
228
|
+
for (const row of state.bundles) {
|
|
229
|
+
try {
|
|
230
|
+
const validation = exchange.readStoredBundle(userDataDir, row.bundleId);
|
|
231
|
+
if (validation.bundle.items.some((item) => item.experienceItemId === itemId)) return { row, validation };
|
|
232
|
+
} catch { /* corrupted unrelated rows are surfaced by list/inspect; intake continues fail-closed */ }
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function candidateType(memoryKind) {
|
|
238
|
+
return memoryKind === "risk" ? "warning" : "procedure";
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function buildCandidateBundle(input) {
|
|
242
|
+
const summary = codePointSlice(input.memory.content, 320);
|
|
243
|
+
const scopeHash = exchange.projectScopeHash(input.cwd);
|
|
244
|
+
const packKey = digestHex(INTAKE_POLICY_VERSION, input.agentId, input.exactBase.agentDefinitionId, input.exactBase.agentReleaseId, scopeHash, ...input.environment.constraints);
|
|
245
|
+
const candidateKey = digestHex(packKey, input.memory.id, summary, ...input.taskSignatures);
|
|
246
|
+
const experiencePackId = `exp:${packKey.slice(0, 32)}`;
|
|
247
|
+
const releaseId = `experience-release:${candidateKey.slice(0, 32)}`;
|
|
248
|
+
const itemId = `experience-item:${candidateKey.slice(0, 32)}`;
|
|
249
|
+
const createdAt = input.receipt.createdAt;
|
|
250
|
+
const item = {
|
|
251
|
+
schemaVersion: "agentlas.experience-item.v1",
|
|
252
|
+
kind: "agentlas-experience-item",
|
|
253
|
+
experienceItemId: itemId,
|
|
254
|
+
experiencePackId,
|
|
255
|
+
experiencePackReleaseId: releaseId,
|
|
256
|
+
type: candidateType(input.memory.kind),
|
|
257
|
+
summary,
|
|
258
|
+
instructions: [summary],
|
|
259
|
+
taskSignatures: [...new Set(input.taskSignatures)].sort(),
|
|
260
|
+
environmentConstraints: [...input.environment.constraints],
|
|
261
|
+
evidenceReceiptIds: [input.receipt.receiptId],
|
|
262
|
+
supersedesItemIds: [],
|
|
263
|
+
confidence: input.memory.confidence === "high" ? 0.85 : input.memory.confidence === "low" ? 0.4 : 0.65,
|
|
264
|
+
status: "candidate",
|
|
265
|
+
privacyScope: "private",
|
|
266
|
+
createdAt,
|
|
267
|
+
};
|
|
268
|
+
const bundle = {
|
|
269
|
+
schemaVersion: exchange.BUNDLE_SCHEMA,
|
|
270
|
+
kind: "agentlas-experience-bundle",
|
|
271
|
+
bundleId: "exb_" + "0".repeat(48),
|
|
272
|
+
bundleHash: `sha256:${"0".repeat(64)}`,
|
|
273
|
+
requestedVisibility: "private",
|
|
274
|
+
pack: {
|
|
275
|
+
schemaVersion: "agentlas.experience-pack.v1",
|
|
276
|
+
kind: "agentlas-experience-pack",
|
|
277
|
+
experiencePackId,
|
|
278
|
+
releaseId,
|
|
279
|
+
ownerRef: "owner:local-terminal",
|
|
280
|
+
version: "0.0.1",
|
|
281
|
+
baseCompatibility: {
|
|
282
|
+
agentDefinitionId: input.exactBase.agentDefinitionId,
|
|
283
|
+
compatibleBaseReleaseIds: [input.exactBase.agentReleaseId],
|
|
284
|
+
},
|
|
285
|
+
itemIds: [itemId],
|
|
286
|
+
evidenceReceiptIds: [input.receipt.receiptId],
|
|
287
|
+
mcpRequirements: [],
|
|
288
|
+
containsBasePackageMaterial: false,
|
|
289
|
+
contentHash: `sha256:${"0".repeat(64)}`,
|
|
290
|
+
visibility: "private",
|
|
291
|
+
status: "draft",
|
|
292
|
+
createdAt,
|
|
293
|
+
},
|
|
294
|
+
items: [item],
|
|
295
|
+
sourceAttestations: [],
|
|
296
|
+
privacy: {
|
|
297
|
+
basePackageMaterialIncluded: false,
|
|
298
|
+
rawPromptIncluded: false,
|
|
299
|
+
rawTranscriptIncluded: false,
|
|
300
|
+
rawLocalPathsIncluded: false,
|
|
301
|
+
credentialValuesIncluded: false,
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
bundle.pack.contentHash = exchange.experiencePackContentHash(bundle);
|
|
305
|
+
bundle.bundleHash = exchange.experienceBundleHash(bundle);
|
|
306
|
+
bundle.bundleId = exchange.experienceBundleId(bundle);
|
|
307
|
+
return { validation: exchange.validateExperienceBundle(bundle), itemId };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function captureOperationalCandidate(input) {
|
|
311
|
+
const issues = exchange.portableExperienceSafetyIssues(input.memory.content);
|
|
312
|
+
const sensitivity = String(input.memory.sensitivity || "internal").trim().toLowerCase();
|
|
313
|
+
if (!["internal", "public"].includes(sensitivity)) issues.push("sensitive-memory");
|
|
314
|
+
if (input.memory.scope === "user_identity") issues.push("user-specific-memory-scope");
|
|
315
|
+
if (issues.length) return { status: "blocked", reasonCodes: [...new Set(issues)].sort() };
|
|
316
|
+
const tasks = exchange.deriveCanonicalTaskClasses(input.taskHint, {
|
|
317
|
+
declaredTaskClasses: input.taskSignatures,
|
|
318
|
+
}).taskIds;
|
|
319
|
+
if (!tasks.length) return { status: "skipped", reasonCodes: ["task-taxonomy-unavailable"] };
|
|
320
|
+
const { validation, itemId } = buildCandidateBundle({ ...input, taskSignatures: tasks });
|
|
321
|
+
const existing = existingCandidate(input.userDataDir, itemId);
|
|
322
|
+
if (existing) return {
|
|
323
|
+
status: "existing",
|
|
324
|
+
reasonCodes: ["idempotent-existing-candidate"],
|
|
325
|
+
candidateId: itemId,
|
|
326
|
+
bundleId: existing.row.bundleId,
|
|
327
|
+
row: existing.row,
|
|
328
|
+
};
|
|
329
|
+
const row = exchange.saveLocalBundle(input.userDataDir, validation, { cwd: input.cwd });
|
|
330
|
+
return { status: "candidate-created", reasonCodes: [], candidateId: itemId, bundleId: row.bundleId, row };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function finalizeAgentExecution(input) {
|
|
334
|
+
const now = input.createdAt || new Date().toISOString();
|
|
335
|
+
const result = {
|
|
336
|
+
receipt: null,
|
|
337
|
+
candidates: [],
|
|
338
|
+
blocked: 0,
|
|
339
|
+
skipped: 0,
|
|
340
|
+
tasteObservations: 0,
|
|
341
|
+
networkUsed: false,
|
|
342
|
+
published: false,
|
|
343
|
+
attached: false,
|
|
344
|
+
promoted: false,
|
|
345
|
+
};
|
|
346
|
+
if (!input.agent?.id || !input.exactBase?.agentDefinitionId || !input.exactBase?.agentReleaseId) return result;
|
|
347
|
+
const environment = runtimeEnvironment(input.environment);
|
|
348
|
+
const taskResolution = exchange.deriveCanonicalTaskClasses(input.taskHint, { declaredTaskClasses: input.taskSignatures });
|
|
349
|
+
if (!environment || !taskResolution.taskIds.length) return result;
|
|
350
|
+
const receipt = createRunReceipt({
|
|
351
|
+
runId: input.runId,
|
|
352
|
+
idempotencyKey: input.idempotencyKey,
|
|
353
|
+
exactBase: input.exactBase,
|
|
354
|
+
experiencePackReleaseId: input.experiencePackReleaseId,
|
|
355
|
+
taskSignature: taskResolution.taskIds[0],
|
|
356
|
+
environment,
|
|
357
|
+
model: input.model,
|
|
358
|
+
mcp: input.mcp,
|
|
359
|
+
outcome: input.outcome,
|
|
360
|
+
metrics: input.metrics,
|
|
361
|
+
sideEffects: input.sideEffects,
|
|
362
|
+
locale: input.locale,
|
|
363
|
+
createdAt: now,
|
|
364
|
+
});
|
|
365
|
+
persistRunReceipt(input.db, receipt, input.agent.id);
|
|
366
|
+
result.receipt = receipt;
|
|
367
|
+
if (receipt.outcome.status !== "succeeded") return result;
|
|
368
|
+
|
|
369
|
+
const memories = Array.isArray(input.curatedMemories) ? input.curatedMemories : [];
|
|
370
|
+
for (const memory of memories) {
|
|
371
|
+
if (!memory?.id || !memory.kind) continue;
|
|
372
|
+
if (memory.kind === "preference") {
|
|
373
|
+
recordIntakeDecision(input.db, {
|
|
374
|
+
runId: receipt.runId,
|
|
375
|
+
ts: now,
|
|
376
|
+
kind: "taste-draft-observation",
|
|
377
|
+
agentId: input.agent.id,
|
|
378
|
+
memoryId: memory.id,
|
|
379
|
+
exactBase: input.exactBase,
|
|
380
|
+
environmentKey: environment.fingerprintHash,
|
|
381
|
+
status: "local-observation",
|
|
382
|
+
reasonCodes: ["preference-private-taste-only", "pairwise-evidence-required"],
|
|
383
|
+
});
|
|
384
|
+
result.tasteObservations += 1;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (!OPERATIONAL_KINDS.has(memory.kind)) {
|
|
388
|
+
recordIntakeDecision(input.db, {
|
|
389
|
+
runId: receipt.runId, ts: now, agentId: input.agent.id, memoryId: memory.id,
|
|
390
|
+
exactBase: input.exactBase, environmentKey: environment.fingerprintHash,
|
|
391
|
+
status: "skipped", reasonCodes: ["non-operational-memory-kind"],
|
|
392
|
+
});
|
|
393
|
+
result.skipped += 1;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
const captured = captureOperationalCandidate({
|
|
397
|
+
userDataDir: input.userDataDir,
|
|
398
|
+
cwd: input.cwd,
|
|
399
|
+
agentId: input.agent.id,
|
|
400
|
+
exactBase: input.exactBase,
|
|
401
|
+
environment,
|
|
402
|
+
memory,
|
|
403
|
+
receipt,
|
|
404
|
+
taskHint: input.taskHint,
|
|
405
|
+
taskSignatures: taskResolution.taskIds,
|
|
406
|
+
});
|
|
407
|
+
recordIntakeDecision(input.db, {
|
|
408
|
+
runId: receipt.runId,
|
|
409
|
+
ts: now,
|
|
410
|
+
agentId: input.agent.id,
|
|
411
|
+
memoryId: memory.id,
|
|
412
|
+
exactBase: input.exactBase,
|
|
413
|
+
environmentKey: environment.fingerprintHash,
|
|
414
|
+
status: captured.status,
|
|
415
|
+
reasonCodes: captured.reasonCodes,
|
|
416
|
+
candidateId: captured.candidateId,
|
|
417
|
+
bundleId: captured.bundleId,
|
|
418
|
+
});
|
|
419
|
+
if (captured.status === "blocked") result.blocked += 1;
|
|
420
|
+
else if (captured.status === "skipped") result.skipped += 1;
|
|
421
|
+
else result.candidates.push(captured);
|
|
422
|
+
}
|
|
423
|
+
return result;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function listRunEvents(db, kind, agentId = null) {
|
|
427
|
+
if (!db || !tableExists(db, "run_events")) return [];
|
|
428
|
+
const rows = agentId
|
|
429
|
+
? db.prepare("SELECT payload_json FROM run_events WHERE kind=? AND agent_id=? ORDER BY ts DESC,id ASC").all(kind, agentId)
|
|
430
|
+
: db.prepare("SELECT payload_json FROM run_events WHERE kind=? ORDER BY ts DESC,id ASC").all(kind);
|
|
431
|
+
return rows.flatMap((row) => { try { return [JSON.parse(row.payload_json)]; } catch { return []; } });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
module.exports = {
|
|
435
|
+
RUN_RECEIPT_SCHEMA,
|
|
436
|
+
INTAKE_POLICY_VERSION,
|
|
437
|
+
runtimeEnvironment,
|
|
438
|
+
createRunReceipt,
|
|
439
|
+
validateRunReceipt,
|
|
440
|
+
persistRunReceipt,
|
|
441
|
+
finalizeAgentExecution,
|
|
442
|
+
captureOperationalCandidate,
|
|
443
|
+
listRunEvents,
|
|
444
|
+
};
|