evolcore 0.0.18 → 0.0.20
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 +39 -0
- package/README.md +2 -0
- package/bin/install-codex-managed-hooks.mjs +61 -0
- package/dist/agents/claude-runner.js +4 -3
- package/dist/agents/codex-runner.js +25 -20
- package/dist/agents/ecagent-runner.js +3 -2
- package/dist/aun/aid/agentmd.js +7 -0
- package/dist/aun/msg/group.js +14 -3
- package/dist/aun/msg/p2p.js +21 -11
- package/dist/aun/outbox.js +144 -19
- package/dist/channels/aun.js +621 -211
- package/dist/cli/daemon-commands.js +41 -8
- package/dist/cli/index.js +1 -0
- package/dist/cli/init.js +55 -15
- package/dist/cli/restart-monitor.js +3 -3
- package/dist/config/aun-gateway-config.js +2 -0
- package/dist/config/config-manager.js +92 -8
- package/dist/config/config-operation-service.js +1 -2
- package/dist/config/gateway-config.js +9 -7
- package/dist/config/lifecycle.js +16 -5
- package/dist/config-store.js +13 -6
- package/dist/core/auth/authorization-audit.js +5 -2
- package/dist/core/bootstrap-messages.js +2 -2
- package/dist/core/bootstrap-service.js +21 -36
- package/dist/core/channel-loader.js +0 -2
- package/dist/core/data-migration.js +10 -4
- package/dist/core/evolagent.js +5 -4
- package/dist/core/message/message-bridge.js +6 -11
- package/dist/core/message/response-engine.js +62 -6
- package/dist/core/permission/ec-command-parser.js +203 -24
- package/dist/core/permission/sandbox-runtime.js +46 -12
- package/dist/core/permission/tool-policy.js +116 -47
- package/dist/core/relation/peer-identity.js +18 -0
- package/dist/eck/kit-renderer.js +17 -8
- package/dist/index.js +30 -19
- package/dist/ipc.js +6 -1
- package/dist/paths.js +0 -3
- package/dist/utils/stats.js +52 -18
- package/dist/utils/welcome.js +2 -2
- package/kits/docs/path-registry.md +1 -1
- package/kits/rules/01-overview.md +1 -1
- package/kits/rules/02-navigation.md +2 -2
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.10.json +2 -1
- package/kits/schemas/agent-config.schema.11.json +408 -0
- package/kits/schemas/daemon.schema.5.json +136 -0
- package/kits/schemas/defaults.schema.5.json +107 -0
- package/package.json +2 -1
- package/dist/core/message/pause-controller.js +0 -53
package/dist/aun/outbox.js
CHANGED
|
@@ -3,6 +3,7 @@ import { agentOutboxPath } from '../paths.js';
|
|
|
3
3
|
import { atomicRead, atomicWrite } from '../utils/atomic-write.js';
|
|
4
4
|
import { isDeliveryTarget, isDeliveryTargetForChannel, sameDeliveryTarget } from '../core/message/message-utils.js';
|
|
5
5
|
const MAX_ENTRIES_PER_AID = 20;
|
|
6
|
+
const MAX_TERMINAL_ENTRIES_PER_AID = 100;
|
|
6
7
|
const DEFAULT_TTL = 300_000; // 5 minutes
|
|
7
8
|
function outboxFile(aid) {
|
|
8
9
|
return agentOutboxPath(aid);
|
|
@@ -13,6 +14,11 @@ function generateId() {
|
|
|
13
14
|
return `out-${ts}-${rand}`;
|
|
14
15
|
}
|
|
15
16
|
function isExpired(entry) {
|
|
17
|
+
// Terminal failures are retained as durable diagnostics. They are excluded
|
|
18
|
+
// from `load()`/`hasPending()` and must not disappear merely because the
|
|
19
|
+
// original delivery TTL elapsed.
|
|
20
|
+
if (entry.terminal)
|
|
21
|
+
return false;
|
|
16
22
|
if (entry.critical)
|
|
17
23
|
return false;
|
|
18
24
|
return Date.now() - entry.ts > entry.ttl;
|
|
@@ -41,11 +47,22 @@ function readEntries(aid) {
|
|
|
41
47
|
}
|
|
42
48
|
function writeEntries(aid, entries) {
|
|
43
49
|
const file = outboxFile(aid);
|
|
44
|
-
const
|
|
45
|
-
|
|
50
|
+
const boundedEntries = trimTerminalEntries(entries);
|
|
51
|
+
const content = boundedEntries.length > 0
|
|
52
|
+
? boundedEntries.map(e => JSON.stringify(e)).join('\n') + '\n'
|
|
46
53
|
: '';
|
|
47
54
|
atomicWrite(file, content);
|
|
48
55
|
}
|
|
56
|
+
function trimTerminalEntries(entries) {
|
|
57
|
+
const terminalEntries = entries.filter(entry => !!entry.terminal);
|
|
58
|
+
if (terminalEntries.length <= MAX_TERMINAL_ENTRIES_PER_AID)
|
|
59
|
+
return entries;
|
|
60
|
+
const retainedTerminalIds = new Set(terminalEntries
|
|
61
|
+
.sort((left, right) => (left.terminal?.at ?? left.ts) - (right.terminal?.at ?? right.ts))
|
|
62
|
+
.slice(-MAX_TERMINAL_ENTRIES_PER_AID)
|
|
63
|
+
.map(entry => entry.id));
|
|
64
|
+
return entries.filter(entry => !entry.terminal || retainedTerminalIds.has(entry.id));
|
|
65
|
+
}
|
|
49
66
|
export function enqueue(aid, opts) {
|
|
50
67
|
const delivery = opts.delivery;
|
|
51
68
|
if (!isDeliveryForChannel(delivery, opts.channelId)) {
|
|
@@ -54,6 +71,9 @@ export function enqueue(aid, opts) {
|
|
|
54
71
|
}
|
|
55
72
|
let existingEntries = readEntries(aid);
|
|
56
73
|
if (opts.dedupeKey) {
|
|
74
|
+
// Terminal results are decisions for this logical operation. Reusing the
|
|
75
|
+
// same route must not silently turn a permanent rejection into a retry;
|
|
76
|
+
// an explicit route correction is the only case that replaces it.
|
|
57
77
|
const existing = existingEntries.find(entry => entry.dedupeKey === opts.dedupeKey && !isExpired(entry));
|
|
58
78
|
if (existing
|
|
59
79
|
&& existing.channelId === opts.channelId
|
|
@@ -86,10 +106,12 @@ export function enqueue(aid, opts) {
|
|
|
86
106
|
ttl: opts.ttl ?? DEFAULT_TTL,
|
|
87
107
|
postSend: opts.postSend,
|
|
88
108
|
};
|
|
89
|
-
// Enforce
|
|
109
|
+
// Enforce the pending-entry cap without allowing historical terminal
|
|
110
|
+
// diagnostics to evict live messages. Terminal records are bounded
|
|
111
|
+
// separately so a permanently failing target cannot grow the file forever.
|
|
90
112
|
let entries = existingEntries;
|
|
91
|
-
if (entries.filter(candidate => !candidate.critical).length >= MAX_ENTRIES_PER_AID) {
|
|
92
|
-
const dropIndex = entries.findIndex(candidate => !candidate.critical);
|
|
113
|
+
if (entries.filter(candidate => !candidate.critical && !candidate.terminal).length >= MAX_ENTRIES_PER_AID) {
|
|
114
|
+
const dropIndex = entries.findIndex(candidate => !candidate.critical && !candidate.terminal);
|
|
93
115
|
if (dropIndex >= 0)
|
|
94
116
|
entries.splice(dropIndex, 1);
|
|
95
117
|
entries = [...entries, entry];
|
|
@@ -104,6 +126,16 @@ export function remove(aid, id) {
|
|
|
104
126
|
const entries = readEntries(aid).filter(e => e.id !== id);
|
|
105
127
|
writeEntries(aid, entries);
|
|
106
128
|
}
|
|
129
|
+
/** Remove an entry only if it still points at the route that was submitted. */
|
|
130
|
+
export function removeIfRouteMatches(aid, submitted) {
|
|
131
|
+
const entries = readEntries(aid);
|
|
132
|
+
const index = entries.findIndex(entry => entry.id === submitted.id);
|
|
133
|
+
if (index < 0 || !hasSamePersistedRoute(entries[index], submitted))
|
|
134
|
+
return false;
|
|
135
|
+
entries.splice(index, 1);
|
|
136
|
+
writeEntries(aid, entries);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
107
139
|
/** Replace an existing entry without consuming another outbox slot. */
|
|
108
140
|
export function replace(aid, entry) {
|
|
109
141
|
const entries = readEntries(aid);
|
|
@@ -114,6 +146,30 @@ export function replace(aid, entry) {
|
|
|
114
146
|
writeEntries(aid, entries);
|
|
115
147
|
return true;
|
|
116
148
|
}
|
|
149
|
+
/** Replace an entry only while its submitted route is still current. */
|
|
150
|
+
export function replaceIfRouteMatches(aid, submitted, replacement) {
|
|
151
|
+
const entries = readEntries(aid);
|
|
152
|
+
const index = entries.findIndex(candidate => candidate.id === submitted.id);
|
|
153
|
+
if (index < 0)
|
|
154
|
+
return 'missing';
|
|
155
|
+
if (!hasSamePersistedRoute(entries[index], submitted))
|
|
156
|
+
return 'route-changed';
|
|
157
|
+
entries[index] = replacement;
|
|
158
|
+
writeEntries(aid, entries);
|
|
159
|
+
return 'replaced';
|
|
160
|
+
}
|
|
161
|
+
/** Update only the delivery receipt while preserving concurrent entry changes. */
|
|
162
|
+
export function updateDeliveryReceiptIfRouteMatches(aid, submitted, receipt) {
|
|
163
|
+
const entries = readEntries(aid);
|
|
164
|
+
const index = entries.findIndex(candidate => candidate.id === submitted.id);
|
|
165
|
+
if (index < 0)
|
|
166
|
+
return 'missing';
|
|
167
|
+
if (!hasSamePersistedRoute(entries[index], submitted))
|
|
168
|
+
return 'route-changed';
|
|
169
|
+
entries[index].deliveryReceipt = receipt;
|
|
170
|
+
writeEntries(aid, entries);
|
|
171
|
+
return 'replaced';
|
|
172
|
+
}
|
|
117
173
|
/** Remove durable interaction cards that were cancelled before delivery. */
|
|
118
174
|
export function removeInteractionCards(aid, requestId) {
|
|
119
175
|
const entries = readEntries(aid);
|
|
@@ -125,10 +181,13 @@ export function removeInteractionCards(aid, requestId) {
|
|
|
125
181
|
return removed;
|
|
126
182
|
}
|
|
127
183
|
export function load(aid) {
|
|
128
|
-
return readEntries(aid).filter(e => !isExpired(e));
|
|
184
|
+
return readEntries(aid).filter(e => !e.terminal && !isExpired(e));
|
|
129
185
|
}
|
|
130
|
-
export function findByDedupeKey(aid, dedupeKey) {
|
|
131
|
-
|
|
186
|
+
export function findByDedupeKey(aid, dedupeKey, opts = {}) {
|
|
187
|
+
const entries = opts.includeTerminal
|
|
188
|
+
? readEntries(aid).filter(entry => !isExpired(entry))
|
|
189
|
+
: load(aid);
|
|
190
|
+
return entries.find(entry => entry.dedupeKey === dedupeKey);
|
|
132
191
|
}
|
|
133
192
|
export function cleanup(aid) {
|
|
134
193
|
const all = readEntries(aid);
|
|
@@ -138,6 +197,19 @@ export function cleanup(aid) {
|
|
|
138
197
|
writeEntries(aid, valid);
|
|
139
198
|
return removed;
|
|
140
199
|
}
|
|
200
|
+
/** Mark a durable entry terminal while keeping its reason for diagnostics. */
|
|
201
|
+
export function markTerminal(aid, id, failure, submitted) {
|
|
202
|
+
const entries = readEntries(aid);
|
|
203
|
+
const entry = entries.find(candidate => candidate.id === id);
|
|
204
|
+
if (!entry || (submitted && !hasSamePersistedRoute(entry, submitted)))
|
|
205
|
+
return false;
|
|
206
|
+
entry.lastError = failure.error ?? 'permanent send failure';
|
|
207
|
+
if (failure.code !== undefined)
|
|
208
|
+
entry.lastErrorCode = failure.code;
|
|
209
|
+
entry.terminal = { at: Date.now(), error: entry.lastError, ...(failure.code !== undefined ? { code: failure.code } : {}) };
|
|
210
|
+
writeEntries(aid, entries);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
141
213
|
// Each caller gets its own pass over the current disk state. This prevents
|
|
142
214
|
// duplicate sends while allowing a later drain trigger to pick up entries
|
|
143
215
|
// enqueued during an active pass.
|
|
@@ -145,14 +217,25 @@ const activeDrainTails = new Map();
|
|
|
145
217
|
function hasRouteForChannel(entry) {
|
|
146
218
|
return isDeliveryForChannel(entry.delivery, entry.channelId);
|
|
147
219
|
}
|
|
220
|
+
function hasSamePersistedRoute(left, right) {
|
|
221
|
+
if (left.channelId !== right.channelId)
|
|
222
|
+
return false;
|
|
223
|
+
if (left.delivery === undefined && right.delivery === undefined)
|
|
224
|
+
return true;
|
|
225
|
+
return isDeliveryTarget(left.delivery)
|
|
226
|
+
&& isDeliveryTarget(right.delivery)
|
|
227
|
+
&& sameDeliveryTarget(left.delivery, right.delivery);
|
|
228
|
+
}
|
|
148
229
|
async function drainOnce(aid, sender) {
|
|
149
|
-
const entries = readEntries(aid);
|
|
230
|
+
const entries = readEntries(aid).filter(entry => !entry.terminal);
|
|
150
231
|
if (entries.length === 0)
|
|
151
232
|
return { sent: 0, expired: 0, failed: 0 };
|
|
152
233
|
const drainedIds = new Set(entries.map(e => e.id));
|
|
234
|
+
const drainedById = new Map(entries.map(entry => [entry.id, entry]));
|
|
153
235
|
let sent = 0;
|
|
154
236
|
let expired = 0;
|
|
155
237
|
let failed = 0;
|
|
238
|
+
let permanent = 0;
|
|
156
239
|
const remaining = [];
|
|
157
240
|
for (const entry of entries) {
|
|
158
241
|
if (isExpired(entry)) {
|
|
@@ -161,20 +244,48 @@ async function drainOnce(aid, sender) {
|
|
|
161
244
|
}
|
|
162
245
|
if (!hasRouteForChannel(entry)) {
|
|
163
246
|
// A route is security-sensitive metadata. An entry whose groupId does
|
|
164
|
-
// not match its address cannot be repaired from channelId
|
|
165
|
-
// instead of retrying the same malformed
|
|
166
|
-
|
|
247
|
+
// not match its address cannot be repaired from channelId. Keep a
|
|
248
|
+
// terminal diagnostic record instead of retrying the same malformed
|
|
249
|
+
// send forever or silently deleting evidence of the failure.
|
|
250
|
+
permanent++;
|
|
251
|
+
entry.lastError = 'outbox entry has no valid delivery route';
|
|
252
|
+
entry.lastErrorCode = 'AUN_OUTBOUND_ROUTE_REQUIRED';
|
|
253
|
+
entry.terminal = {
|
|
254
|
+
at: Date.now(),
|
|
255
|
+
error: entry.lastError,
|
|
256
|
+
code: entry.lastErrorCode,
|
|
257
|
+
};
|
|
258
|
+
remaining.push(entry);
|
|
167
259
|
continue;
|
|
168
260
|
}
|
|
169
261
|
try {
|
|
170
262
|
entry.attempts = (entry.attempts ?? 0) + 1;
|
|
171
|
-
const
|
|
172
|
-
|
|
263
|
+
const raw = await sender(entry);
|
|
264
|
+
const result = typeof raw === 'boolean'
|
|
265
|
+
? { status: raw ? 'sent' : 'retry' }
|
|
266
|
+
: raw;
|
|
267
|
+
if (result.status === 'sent') {
|
|
173
268
|
sent++;
|
|
174
269
|
}
|
|
270
|
+
else if (result.status === 'permanent') {
|
|
271
|
+
permanent++;
|
|
272
|
+
entry.lastError = result.error ?? 'permanent send failure';
|
|
273
|
+
if (result.code !== undefined)
|
|
274
|
+
entry.lastErrorCode = result.code;
|
|
275
|
+
entry.terminal = {
|
|
276
|
+
at: Date.now(),
|
|
277
|
+
error: entry.lastError,
|
|
278
|
+
...(result.code !== undefined ? { code: result.code } : {}),
|
|
279
|
+
};
|
|
280
|
+
// Keep a terminal record for diagnostics, but exclude it from all
|
|
281
|
+
// pending-send views so the timer cannot submit it again.
|
|
282
|
+
remaining.push(entry);
|
|
283
|
+
}
|
|
175
284
|
else {
|
|
176
285
|
failed++;
|
|
177
|
-
entry.lastError = 'sender returned false';
|
|
286
|
+
entry.lastError = result.error ?? 'sender returned false';
|
|
287
|
+
if (result.code !== undefined)
|
|
288
|
+
entry.lastErrorCode = result.code;
|
|
178
289
|
remaining.push(entry);
|
|
179
290
|
}
|
|
180
291
|
}
|
|
@@ -190,17 +301,31 @@ async function drainOnce(aid, sender) {
|
|
|
190
301
|
if (!drainedIds.has(entry.id))
|
|
191
302
|
return [entry];
|
|
192
303
|
const failedEntry = failedById.get(entry.id);
|
|
193
|
-
if (!failedEntry)
|
|
194
|
-
|
|
304
|
+
if (!failedEntry) {
|
|
305
|
+
const submittedEntry = drainedById.get(entry.id);
|
|
306
|
+
return submittedEntry && !hasSamePersistedRoute(entry, submittedEntry)
|
|
307
|
+
? [entry]
|
|
308
|
+
: [];
|
|
309
|
+
}
|
|
310
|
+
// A failure belongs to the route that was actually submitted. If another
|
|
311
|
+
// writer corrected that route while the RPC was in flight, preserve the
|
|
312
|
+
// corrected entry as pending instead of terminating it with a stale
|
|
313
|
+
// target's permanent error.
|
|
314
|
+
if (!hasSamePersistedRoute(entry, failedEntry))
|
|
315
|
+
return [entry];
|
|
316
|
+
if (entry.terminal && !failedEntry.terminal)
|
|
317
|
+
return [entry];
|
|
195
318
|
return [{
|
|
196
319
|
...failedEntry,
|
|
197
320
|
...entry,
|
|
198
321
|
attempts: failedEntry.attempts,
|
|
199
322
|
lastError: failedEntry.lastError,
|
|
323
|
+
lastErrorCode: failedEntry.lastErrorCode,
|
|
324
|
+
...(failedEntry.terminal ? { terminal: failedEntry.terminal } : {}),
|
|
200
325
|
}];
|
|
201
326
|
});
|
|
202
327
|
writeEntries(aid, retained);
|
|
203
|
-
return { sent, expired, failed };
|
|
328
|
+
return { sent, expired, failed, ...(permanent > 0 ? { permanent } : {}) };
|
|
204
329
|
}
|
|
205
330
|
export async function drain(aid, sender) {
|
|
206
331
|
const previous = activeDrainTails.get(aid);
|
|
@@ -220,7 +345,7 @@ export async function drain(aid, sender) {
|
|
|
220
345
|
}
|
|
221
346
|
}
|
|
222
347
|
export function hasPending(aid) {
|
|
223
|
-
return readEntries(aid).some(entry => !isExpired(entry));
|
|
348
|
+
return readEntries(aid).some(entry => !entry.terminal && !isExpired(entry));
|
|
224
349
|
}
|
|
225
350
|
/**
|
|
226
351
|
* 当前 outbox 中待发送条目数。用于诊断发送管线堵塞:depth 持续增长说明
|