evolcore 0.0.31 → 0.0.32
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 +11 -0
- package/dist/agents/codex-runner.js +28 -5
- package/dist/agents/ecagent-runner.js +4 -0
- package/dist/agents/gemini-runner.js +4 -0
- package/dist/aun/aid/store.js +0 -81
- package/dist/aun/group/fs-download.js +1 -10
- package/dist/aun/msg/managed-operation.js +130 -32
- package/dist/aun/service-proxy.js +12 -18
- package/dist/aun/storage/index.js +29 -0
- package/dist/channels/aun.js +10 -135
- package/dist/cli/agent-command.js +6 -7
- package/dist/cli/agent-create.js +43 -24
- package/dist/cli/agent.js +15 -69
- package/dist/cli/aun-commands.js +149 -5
- package/dist/cli/ctl-command.js +1 -2
- package/dist/cli/daemon-commands.js +1 -1
- package/dist/cli/diag.js +2 -2
- package/dist/cli/handoff-command.js +15 -5
- package/dist/cli/memory-command.js +85 -11
- package/dist/cli/model.js +14 -138
- package/dist/cli/queue-command.js +46 -0
- package/dist/cli/response.js +5 -54
- package/dist/cli/skill-command.js +9 -3
- package/dist/cli/stats.js +10 -4
- package/dist/cli/task-command.js +1 -0
- package/dist/cli/task-context.js +15 -0
- package/dist/cli/trigger-command.js +73 -49
- package/dist/config/builtin-roles.js +8 -7
- package/dist/core/agent-ipc-registration.js +65 -22
- package/dist/core/auth/agent-delegation.js +17 -1
- package/dist/core/auth/delegation-authorization.js +1 -0
- package/dist/core/auth/operation-catalog.js +11 -3
- package/dist/core/command/command-handler.js +164 -21
- package/dist/core/command/diag-service.js +5 -1
- package/dist/core/handoff/ipc-registration.js +38 -0
- package/dist/core/ipc-registration.js +71 -7
- package/dist/core/message/aun-ipc-sender.js +48 -4
- package/dist/core/message/message-queue.js +131 -3
- package/dist/core/message/response-engine.js +30 -2
- package/dist/core/permission/sandbox-runtime.js +14 -1
- package/dist/core/task/task-recovery-service.js +2 -0
- package/dist/core/task/task-resume-operation.js +11 -0
- package/dist/index.js +2 -2
- package/dist/ipc.js +146 -8
- package/dist/trigger/authorization.js +2 -1
- package/dist/trigger/ipc-service.js +49 -35
- package/dist/trigger/script-sandbox.js +58 -77
- package/kits/docs/AUTHORING.md +1 -1
- package/kits/docs/channels/aun.md +8 -0
- package/kits/docs/evolcore/INDEX.md +2 -2
- package/kits/docs/evolcore/task.md +2 -0
- package/kits/docs/evolcore/trigger.md +2 -2
- package/kits/rules/01-overview.md +0 -9
- package/kits/rules/06-channel.md +7 -1
- package/kits/templates/system-fragments/commands.md +1 -1
- package/package.json +1 -1
|
@@ -21,6 +21,20 @@ export class MessageQueue {
|
|
|
21
21
|
interruptBarriers = new Map();
|
|
22
22
|
interruptFailures = new Map();
|
|
23
23
|
activeTerminalReasons = new Map();
|
|
24
|
+
/**
|
|
25
|
+
* 允许「未提交输入」回队的 key(仅新消息到达触发的中断)。
|
|
26
|
+
* 与 activeTerminalReasons 分开:后者还承载 stop/timeout/priority 抢占等
|
|
27
|
+
* 语义,那些场景绝不能复活旧输入。
|
|
28
|
+
*/
|
|
29
|
+
requeueableInterrupts = new Set();
|
|
30
|
+
/**
|
|
31
|
+
* queueKey → 触发本次 new_message 中断的消息。
|
|
32
|
+
*
|
|
33
|
+
* 回队前用它把「打断消息」从 pending 中排除:只有当队列里没有其它等待项时
|
|
34
|
+
* 才回队。否则重放旧输入会插到那些等待项(如被 defer 的群聊消息)前面,
|
|
35
|
+
* 打乱既有调度顺序。
|
|
36
|
+
*/
|
|
37
|
+
interruptingMessages = new Map();
|
|
24
38
|
eventBus;
|
|
25
39
|
recentMessageIds = new Set();
|
|
26
40
|
DEDUP_WINDOW = 60_000; // 1 分钟窗口
|
|
@@ -108,6 +122,19 @@ export class MessageQueue {
|
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
});
|
|
125
|
+
subscribe.call(eventBus, 'input_accepted', event => {
|
|
126
|
+
if (!('inputId' in event) || !event.inputId)
|
|
127
|
+
return;
|
|
128
|
+
const inputEvent = event;
|
|
129
|
+
// inputId 格式为 sessionId 或 sessionId:xxx,取第一段匹配
|
|
130
|
+
const sessionId = inputEvent.inputId.split(':')[0];
|
|
131
|
+
for (const [queueKey, state] of this.activeStates) {
|
|
132
|
+
if (this.matchesSession(queueKey, sessionId)) {
|
|
133
|
+
state.inputSubmitted = true;
|
|
134
|
+
logger.debug(`[MessageQueue] input_accepted for ${queueKey}, taskId=${state.taskId}, generation=${state.generation}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
111
138
|
subscribe.call(eventBus, 'task:completed', event => {
|
|
112
139
|
if ('sessionId' in event && event.sessionId)
|
|
113
140
|
this.markActiveTerminal(event.sessionId, 'completed');
|
|
@@ -124,6 +151,37 @@ export class MessageQueue {
|
|
|
124
151
|
this.activeTerminalReasons.set(queueKey, reason);
|
|
125
152
|
}
|
|
126
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* 显式允许/禁止该 key 的 active 批次在中断后回队。
|
|
156
|
+
*
|
|
157
|
+
* 只有「新消息到达触发的中断」才置 true:那种场景下旧输入尚未提交给模型,
|
|
158
|
+
* 用户也确实期望两条一起被看到。stop / 优先级抢占 / 超时等一律不允许复活。
|
|
159
|
+
*/
|
|
160
|
+
markRequeueable(queueKey, requeueable) {
|
|
161
|
+
if (requeueable)
|
|
162
|
+
this.requeueableInterrupts.add(queueKey);
|
|
163
|
+
else
|
|
164
|
+
this.requeueableInterrupts.delete(queueKey);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 标记 active 批次的输入已真正提交给模型(runner 发出 input_accepted 后调用)。
|
|
168
|
+
*
|
|
169
|
+
* 这是 pre-submit 回队的关键判据:只有「未提交」的 active 才能在 new_message
|
|
170
|
+
* 中断后安全放回队列;已提交的批次若回队,会在 provider session 里产生重复用户轮次。
|
|
171
|
+
*/
|
|
172
|
+
markInputSubmitted(sessionId, taskId) {
|
|
173
|
+
for (const [queueKey, state] of this.activeStates) {
|
|
174
|
+
if (!this.matchesSession(queueKey, sessionId))
|
|
175
|
+
continue;
|
|
176
|
+
// 同一 session 可能有多个 queueKey(不同 agent/project),taskId 用来精确定位。
|
|
177
|
+
if (taskId !== undefined && state.taskId !== undefined && state.taskId !== taskId)
|
|
178
|
+
continue;
|
|
179
|
+
state.inputSubmitted = true;
|
|
180
|
+
state.submittedMessageIds = Array.from(state.messageIds);
|
|
181
|
+
logger.debug(`[Queue] input submitted: key=${queueKey} task=${taskId ?? '<none>'} ids=${state.submittedMessageIds.join(',')}`);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
127
185
|
/**
|
|
128
186
|
* 注册一次性消息拦截器:下一条来自 sessionKey 的消息不入队、不触发 interrupt,
|
|
129
187
|
* 直接传给 handler。用于 AskUserQuestion 的"手动输入"场景。
|
|
@@ -1082,7 +1140,12 @@ export class MessageQueue {
|
|
|
1082
1140
|
activeState?.peerId === message.peerId &&
|
|
1083
1141
|
!this.hasOtherPeerQueued(queueKey, message.peerId);
|
|
1084
1142
|
if (interruptSingle || interruptGroupSamePeer) {
|
|
1085
|
-
|
|
1143
|
+
const inputSubmitted = activeState?.inputSubmitted ?? false;
|
|
1144
|
+
logger.info(`[Queue] new_message interrupt: key=${queueKey} samePeer=${interruptGroupSamePeer} taskId=${activeState?.taskId ?? '<none>'} generation=${activeState?.generation ?? '<none>'} inputSubmitted=${inputSubmitted} activeMsg=${Array.from(activeState?.messageIds ?? []).join(',')}`);
|
|
1145
|
+
// 新消息到达才允许 pre-submit 回队:旧输入尚未提交给模型,
|
|
1146
|
+
// 且用户期望它与刚到的消息一起被看到。
|
|
1147
|
+
this.markRequeueable(queueKey, true);
|
|
1148
|
+
this.interruptingMessages.set(queueKey, message);
|
|
1086
1149
|
this.eventBus?.publish({
|
|
1087
1150
|
type: 'task:interrupted',
|
|
1088
1151
|
sessionId: sessionKey,
|
|
@@ -1218,7 +1281,7 @@ export class MessageQueue {
|
|
|
1218
1281
|
this.activeBatches.set(queueKey, activeItem);
|
|
1219
1282
|
this.processingStartTime.set(queueKey, Date.now());
|
|
1220
1283
|
this.persistQueuesImmediate();
|
|
1221
|
-
logger.info(`[Queue] processing batch: key=${queueKey} items=${rawItems.length} pending=${queue.length} rawPending=${this.queueRawMessageCount(queue)} batchRole=${merged.message.batchRole ?? '<mixed>'} peer=${activeState.peerId ?? '<multi>'} msg=${merged.message.messageId ?? '<none>'}`);
|
|
1284
|
+
logger.info(`[Queue] processing batch: key=${queueKey} items=${rawItems.length} pending=${queue.length} rawPending=${this.queueRawMessageCount(queue)} batchRole=${merged.message.batchRole ?? '<mixed>'} peer=${activeState.peerId ?? '<multi>'} msg=${merged.message.messageId ?? '<none>'} sourceIds=${Array.from(activeMessageIds).join(',')}`);
|
|
1222
1285
|
try {
|
|
1223
1286
|
await this.handler(merged.message);
|
|
1224
1287
|
logger.debug(`[Queue] Message processed successfully`);
|
|
@@ -1230,10 +1293,17 @@ export class MessageQueue {
|
|
|
1230
1293
|
}
|
|
1231
1294
|
finally {
|
|
1232
1295
|
if (!this.shuttingDown) {
|
|
1296
|
+
const terminalReason = this.activeTerminalReasons.get(queueKey);
|
|
1297
|
+
const disposition = this.disposeInterruptedBatch(queueKey, activeState, rawParts, merged.message.causation);
|
|
1298
|
+
if (disposition !== 'none') {
|
|
1299
|
+
logger.info(`[Queue] batch disposition: key=${queueKey} reason=${terminalReason ?? '<completed>'} disposition=${disposition}`);
|
|
1300
|
+
}
|
|
1233
1301
|
this.activeBatches.delete(queueKey);
|
|
1234
1302
|
this.processingStartTime.delete(queueKey);
|
|
1235
1303
|
this.activeStates.delete(queueKey);
|
|
1236
1304
|
this.activeTerminalReasons.delete(queueKey);
|
|
1305
|
+
this.requeueableInterrupts.delete(queueKey);
|
|
1306
|
+
this.interruptingMessages.delete(queueKey);
|
|
1237
1307
|
this.persistQueuesImmediate();
|
|
1238
1308
|
}
|
|
1239
1309
|
}
|
|
@@ -1244,6 +1314,63 @@ export class MessageQueue {
|
|
|
1244
1314
|
}
|
|
1245
1315
|
}
|
|
1246
1316
|
}
|
|
1317
|
+
/**
|
|
1318
|
+
* 决定被中断的 active 批次的下落。
|
|
1319
|
+
*
|
|
1320
|
+
* 只有同时满足两个条件才回队:
|
|
1321
|
+
* 1) 该 key 被显式标记为「新消息到达」(markRequeueable),而不是 stop/超时/优先级抢占;
|
|
1322
|
+
* 2) 该批次的输入尚未提交给模型(未收到 input_accepted / 未产出输出)。
|
|
1323
|
+
*
|
|
1324
|
+
* 已提交的批次若回队,会在 provider session 里产生重复用户轮次;
|
|
1325
|
+
* stop/timeout/recalled 是用户或系统的明确意图,绝不复活;
|
|
1326
|
+
* daemon_restart 由启动恢复流程处理,这里不重复入队。
|
|
1327
|
+
*
|
|
1328
|
+
* 回队到物理队首并保留原始 messageId/receivedAt,下一次 dequeueGreedy 会把它
|
|
1329
|
+
* 与期间到达的 pending 消息一起合并,等价于「未提交的旧输入 + 新输入」同批提交。
|
|
1330
|
+
*/
|
|
1331
|
+
disposeInterruptedBatch(queueKey, activeState, rawParts, causation) {
|
|
1332
|
+
if (!this.requeueableInterrupts.has(queueKey))
|
|
1333
|
+
return 'none';
|
|
1334
|
+
if (!this.interruptCallback
|
|
1335
|
+
|| this.activeTerminalReasons.get(queueKey) !== 'new_message')
|
|
1336
|
+
return 'none';
|
|
1337
|
+
if (rawParts.length === 0)
|
|
1338
|
+
return 'none';
|
|
1339
|
+
// 已提交:不能重放,交由 provider/session 侧的既有用户轮次保证上下文完整。
|
|
1340
|
+
if (activeState.inputSubmitted)
|
|
1341
|
+
return 'input_already_submitted';
|
|
1342
|
+
const queue = this.queues.get(queueKey);
|
|
1343
|
+
if (!queue) {
|
|
1344
|
+
logger.warn(`[Queue] pre-submit requeue skipped: queue gone key=${queueKey}`);
|
|
1345
|
+
return 'none';
|
|
1346
|
+
}
|
|
1347
|
+
const requeued = rawParts.map(part => this.itemFromPart(part));
|
|
1348
|
+
// 只有当队列里除打断消息外没有其它等待项时才回队。
|
|
1349
|
+
// 若已有其它 pending(例如被 defer 的群聊消息),说明旧输入本可在当前轮
|
|
1350
|
+
// 处理完毕,回队会插入一个与它们无关的额外轮次并打乱既有调度顺序;
|
|
1351
|
+
// 那种情况下保持原有中断行为。
|
|
1352
|
+
const interrupting = this.interruptingMessages.get(queueKey);
|
|
1353
|
+
const pendingWithoutInterrupt = interrupting
|
|
1354
|
+
? queue.filter(item => item.message.messageId !== interrupting.messageId)
|
|
1355
|
+
: queue;
|
|
1356
|
+
if (pendingWithoutInterrupt.length > 0) {
|
|
1357
|
+
logger.info(`[Queue] pre-submit requeue skipped: key=${queueKey} other pending=${pendingWithoutInterrupt.length}`);
|
|
1358
|
+
return 'none';
|
|
1359
|
+
}
|
|
1360
|
+
queue.unshift(...requeued);
|
|
1361
|
+
// 按物理顺序重建逻辑队列,避免把旧输入追加到新消息之后,导致 FIFO
|
|
1362
|
+
// 在下一次 reorderPhysical 时颠倒回队消息与打断消息的顺序。
|
|
1363
|
+
// 与 enqueue 路径保持同一条件,避免把 owner-inject/interrupt 消息写进逻辑队列。
|
|
1364
|
+
this.logicalQueue.clear(queueKey);
|
|
1365
|
+
for (const item of queue) {
|
|
1366
|
+
if (item.message.source !== 'owner-inject' && item.message.attentionPolicy !== 'interrupt') {
|
|
1367
|
+
this.logicalQueue.enqueue(queueKey, item.message);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
logger.info(`[Queue] requeued pre-submit batch: key=${queueKey} ids=${requeued
|
|
1371
|
+
.flatMap(item => this.messageIdsFor(item)).join(',')} causation=${causation?.traceId ?? '<none>'}`);
|
|
1372
|
+
return 'requeued_pre_submit';
|
|
1373
|
+
}
|
|
1247
1374
|
/**
|
|
1248
1375
|
* 贪心弹出队首可合并消息。
|
|
1249
1376
|
* 群聊入队携带 role,此时取出当前所有群聊 pending 并按时间线合并;
|
|
@@ -1337,7 +1464,8 @@ export class MessageQueue {
|
|
|
1337
1464
|
&& a.channelKey === b.channelKey
|
|
1338
1465
|
&& a.channelType === b.channelType
|
|
1339
1466
|
&& a.chatType === b.chatType
|
|
1340
|
-
&& a.peerKey === b.peerKey
|
|
1467
|
+
&& a.peerKey === b.peerKey
|
|
1468
|
+
&& a.approvalInteractionPolicy === b.approvalInteractionPolicy;
|
|
1341
1469
|
}
|
|
1342
1470
|
firstTimestamp(item) {
|
|
1343
1471
|
const parts = this.partsOf(item);
|
|
@@ -1893,6 +1893,20 @@ export class ResponseEngine {
|
|
|
1893
1893
|
peerKey: formatPeerKey(channelType, chatType === 'group' ? conversationId : actorId),
|
|
1894
1894
|
};
|
|
1895
1895
|
}
|
|
1896
|
+
resolveTaskApprovalInteractionPolicy(message) {
|
|
1897
|
+
if (message.taskRecovery) {
|
|
1898
|
+
const policy = message.taskRecovery.approvalInteractionPolicy;
|
|
1899
|
+
if (policy === 'interactive' || policy === 'deny')
|
|
1900
|
+
return policy;
|
|
1901
|
+
return message.taskRecovery.automatic ? 'deny' : 'interactive';
|
|
1902
|
+
}
|
|
1903
|
+
if (message.source === 'handoff' && message.handoffDelivery?.direction === 'origin') {
|
|
1904
|
+
return message.handoffDelivery.originAuthorization?.approvalInteractionPolicy === 'interactive'
|
|
1905
|
+
? 'interactive'
|
|
1906
|
+
: 'deny';
|
|
1907
|
+
}
|
|
1908
|
+
return message.source === 'trigger' ? 'deny' : 'interactive';
|
|
1909
|
+
}
|
|
1896
1910
|
resolveHandoffOriginExecutionIdentity(message, selfAid) {
|
|
1897
1911
|
const authorization = message.handoffDelivery?.originAuthorization;
|
|
1898
1912
|
if (message.source !== 'handoff'
|
|
@@ -2701,6 +2715,7 @@ export class ResponseEngine {
|
|
|
2701
2715
|
const triggerExecutionIdentity = this.resolveTriggerExecutionIdentity(message, selfAid);
|
|
2702
2716
|
const authorizationIdentity = triggerExecutionIdentity
|
|
2703
2717
|
?? this.resolveHandoffOriginExecutionIdentity(message, selfAid);
|
|
2718
|
+
const approvalInteractionPolicy = this.resolveTaskApprovalInteractionPolicy(message);
|
|
2704
2719
|
const configChannelType = authorizationIdentity?.channelType ?? currentChannelType;
|
|
2705
2720
|
const configChatType = authorizationIdentity
|
|
2706
2721
|
? (message.triggerMeta?.originChatType ?? message.handoffDelivery?.originAuthorization?.chatType ?? 'private')
|
|
@@ -3122,6 +3137,7 @@ export class ResponseEngine {
|
|
|
3122
3137
|
: undefined;
|
|
3123
3138
|
turnLease = await this.turnCoordinator.begin(session, taskId, inputId, configActorId ? {
|
|
3124
3139
|
peerId: configActorId,
|
|
3140
|
+
approvalInteractionPolicy,
|
|
3125
3141
|
encrypted: message.encrypted ?? message.replyContext?.metadata?.encrypted,
|
|
3126
3142
|
baseagent: session.baseagent,
|
|
3127
3143
|
agentSessionId: session.agentSessionId,
|
|
@@ -3439,7 +3455,7 @@ export class ResponseEngine {
|
|
|
3439
3455
|
peerKey: authPeerKey,
|
|
3440
3456
|
causation: taskCausation,
|
|
3441
3457
|
approvalRouting,
|
|
3442
|
-
approvalInteractionPolicy
|
|
3458
|
+
approvalInteractionPolicy,
|
|
3443
3459
|
permissionMode: effectivePermissionMode,
|
|
3444
3460
|
recordExecutionAnomaly,
|
|
3445
3461
|
pauseController: taskPauseController,
|
|
@@ -4253,6 +4269,7 @@ export class ResponseEngine {
|
|
|
4253
4269
|
const taskRuntimeContext = {
|
|
4254
4270
|
taskId,
|
|
4255
4271
|
sessionId: session.id,
|
|
4272
|
+
projectPath: path.resolve(session.projectPath),
|
|
4256
4273
|
messageId: message.messageId,
|
|
4257
4274
|
baseagent: normalizedBaseagent.canonical,
|
|
4258
4275
|
channel: configChannelType,
|
|
@@ -4300,7 +4317,9 @@ export class ResponseEngine {
|
|
|
4300
4317
|
selfAid,
|
|
4301
4318
|
peerKey,
|
|
4302
4319
|
issuedRole: peerRole,
|
|
4320
|
+
executionSource: fullAccessAuthorization?.source ?? (message.source === 'trigger' ? 'trigger' : 'user'),
|
|
4303
4321
|
permissionMode: effectivePermissionMode,
|
|
4322
|
+
approvalInteractionPolicy,
|
|
4304
4323
|
...(sessionRuntimeDir ? { sessionRuntimeDir } : {}),
|
|
4305
4324
|
...(fullAccessAuthorization ? { executionIdentity: fullAccessAuthorization } : {}),
|
|
4306
4325
|
});
|
|
@@ -5932,14 +5951,23 @@ export class ResponseEngine {
|
|
|
5932
5951
|
if (event.type === 'input_accepted') {
|
|
5933
5952
|
inputAccepted = !turnLease || event.inputId === turnLease.inputId;
|
|
5934
5953
|
logger.debug(`[ResponseEngine] Input accepted: session=${session.id} input=${event.inputId}`);
|
|
5954
|
+
// 通知队列:本批次输入已真正提交给模型,此后不可回队重放。
|
|
5955
|
+
this.messageQueue?.markInputSubmitted?.(session.id, turnLease?.taskId);
|
|
5935
5956
|
continue;
|
|
5936
5957
|
}
|
|
5937
5958
|
if (event.type === 'text') {
|
|
5938
|
-
|
|
5959
|
+
const hasText = !!event.text.trim();
|
|
5960
|
+
// 模型已产出文本 ⇒ 输入必然已被消费。覆盖不发 input_accepted 的 runner
|
|
5961
|
+
// (codex/ecagent/gemini),避免它们在 pre-submit 回队时重复用户轮次。
|
|
5962
|
+
if (hasText)
|
|
5963
|
+
this.messageQueue?.markInputSubmitted?.(session.id, turnLease?.taskId);
|
|
5964
|
+
if (hasText)
|
|
5939
5965
|
textCount++;
|
|
5940
5966
|
assistantUuid = event.assistantUuid ?? assistantUuid;
|
|
5941
5967
|
}
|
|
5942
5968
|
else if (event.type === 'tool_use') {
|
|
5969
|
+
// 产生工具调用同样证明输入已被消费,且此时重放会重复发起副作用。
|
|
5970
|
+
this.messageQueue?.markInputSubmitted?.(session.id, turnLease?.taskId);
|
|
5943
5971
|
toolUseCount++;
|
|
5944
5972
|
assistantUuid = event.assistantUuid ?? assistantUuid;
|
|
5945
5973
|
if (event.callId) {
|
|
@@ -563,6 +563,14 @@ function appendLClassReadOnlyBinds(args, root, options = {}) {
|
|
|
563
563
|
args.push('--ro-bind', target.path, target.path);
|
|
564
564
|
}
|
|
565
565
|
}
|
|
566
|
+
function appendLClassMasks(args, root, options = {}) {
|
|
567
|
+
for (const target of getExistingLClassReadOnlyTargets(root, options)) {
|
|
568
|
+
if (target.kind === 'directory')
|
|
569
|
+
pushMaskedDirectory(args, target.path);
|
|
570
|
+
else
|
|
571
|
+
pushMaskedFile(args, target.path);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
566
574
|
export function ensureCodexManagedMountpoint(systemConfigDirectory = '/etc/codex', effectiveUid = typeof process.getuid === 'function' ? process.getuid() : undefined) {
|
|
567
575
|
let systemConfigStat;
|
|
568
576
|
try {
|
|
@@ -691,7 +699,12 @@ export function buildBubblewrapCommand(executable, executableArgs, options) {
|
|
|
691
699
|
pushMaskedFile(args, resolved);
|
|
692
700
|
}
|
|
693
701
|
}
|
|
694
|
-
|
|
702
|
+
if (options.denyLClassRead) {
|
|
703
|
+
appendLClassMasks(args, root, options);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
appendLClassReadOnlyBinds(args, root, options);
|
|
707
|
+
}
|
|
695
708
|
appendHClassMasks(args, root, true, options);
|
|
696
709
|
if (options.exposeDaemonIpc !== false)
|
|
697
710
|
appendDaemonIpcSocket(args, root);
|
|
@@ -133,6 +133,8 @@ export class TaskRecoveryService {
|
|
|
133
133
|
agentSessionId: session.agentSessionId, baseagent: session.baseagent,
|
|
134
134
|
automatic: request.automatic === true,
|
|
135
135
|
origin,
|
|
136
|
+
approvalInteractionPolicy: context?.approvalInteractionPolicy
|
|
137
|
+
?? (request.automatic ? 'deny' : 'interactive'),
|
|
136
138
|
};
|
|
137
139
|
const replyContext = replyContextFromSession(session);
|
|
138
140
|
const message = {
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
/** IPC has authenticated the transport; this enforces the Agent/task boundary. */
|
|
2
2
|
export async function executeTaskResume(request, deps) {
|
|
3
|
+
if (request.argv) {
|
|
4
|
+
const argv = request.argv;
|
|
5
|
+
if (argv[0] !== 'task' || argv[1] !== 'resume' || !argv[2]?.trim() || argv[2].startsWith('-')
|
|
6
|
+
|| !(argv.length === 3 || (argv.length === 5 && argv[3] === '--format' && argv[4] === 'json'))) {
|
|
7
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: 'Invalid task resume argv' };
|
|
8
|
+
}
|
|
9
|
+
request = { ...request, targetSessionId: argv[2].trim() };
|
|
10
|
+
}
|
|
11
|
+
else if (request.sessionId) {
|
|
12
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'Managed task resume requires argv' };
|
|
13
|
+
}
|
|
3
14
|
const authorization = request.sessionId
|
|
4
15
|
? await deps.authorizeManaged({
|
|
5
16
|
sessionId: request.sessionId,
|
package/dist/index.js
CHANGED
|
@@ -640,11 +640,11 @@ async function main() {
|
|
|
640
640
|
getAgentRuntimeError: () => agentRuntimeError,
|
|
641
641
|
daemonAid: daemonCfg.aid,
|
|
642
642
|
getControlAidState: () => controlChannel?.getAidState(),
|
|
643
|
-
}), async (
|
|
643
|
+
}), async (argv, sessionId, delegationToken, delegationCommandHash) => {
|
|
644
644
|
const delegation = agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
|
|
645
645
|
if (!delegation.ok)
|
|
646
646
|
return { ok: false, code: delegation.code, error: delegation.reason };
|
|
647
|
-
return cmdHandler.
|
|
647
|
+
return cmdHandler.handleManagedCliOperation(argv, sessionId, delegation.grant);
|
|
648
648
|
});
|
|
649
649
|
ipcServer.setDaemonRuntimeEpoch(agentDelegationRegistry.getRuntimeEpoch());
|
|
650
650
|
const { reloadHooks, reloadCoordinator } = createAgentReloadRuntime({
|
package/dist/ipc.js
CHANGED
|
@@ -21,6 +21,7 @@ import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
|
|
|
21
21
|
import { remoteAccessService } from './core/auth/remote-access-store.js';
|
|
22
22
|
import { setModelCatalogDaemonOwner } from './core/model/model-catalog.js';
|
|
23
23
|
import { RUNTIME_ENVIRONMENT_NAMES, setRuntimeEnvironmentOverride } from './config/runtime-env.js';
|
|
24
|
+
import { MAIN_CLI_BIN } from './product.js';
|
|
24
25
|
const isWindows = process.platform === 'win32';
|
|
25
26
|
const isNamedPipe = (p) => isWindows && p.toLowerCase().startsWith('\\\\.\\pipe\\');
|
|
26
27
|
const CPU_SAMPLE_INTERVAL_MS = 1000;
|
|
@@ -75,6 +76,16 @@ const LOCAL_CONTROL_COMMANDS = new Set([
|
|
|
75
76
|
'remote-auth.redeem',
|
|
76
77
|
'remote-auth.validate',
|
|
77
78
|
]);
|
|
79
|
+
const MEMORY_COMMAND_ENDPOINTS = new Set([
|
|
80
|
+
'memory.note', 'memory.note.update', 'memory.note.drop', 'memory.note.list',
|
|
81
|
+
'memory.create', 'memory.update', 'memory.archive', 'memory.demote', 'memory.consolidate', 'memory.feedback',
|
|
82
|
+
]);
|
|
83
|
+
const DELEGATED_ARGV_ENDPOINTS = new Set([
|
|
84
|
+
'agent.op', 'config.op', 'contact.op', 'aid.op', 'group.op', 'msg.op', 'fs.op',
|
|
85
|
+
'ctl', 'diag', 'menu-client.request', 'task.resume', 'queue.op', 'storage.auth', 'storage.upload',
|
|
86
|
+
'trigger.op', 'local-command-context', 'evolagent.bootstrapComplete', ...MEMORY_COMMAND_ENDPOINTS,
|
|
87
|
+
'handoff-return', 'handoff-recover-return', 'handoff-status', 'handoff-list', 'handoff-trace',
|
|
88
|
+
]);
|
|
78
89
|
let ipcQuerySequence = 0;
|
|
79
90
|
function ipcLogToken(value, fallback = 'unknown') {
|
|
80
91
|
const token = String(value ?? '').replace(/[^A-Za-z0-9_.:-]/g, '_').slice(0, 128);
|
|
@@ -925,6 +936,75 @@ export class IpcServer {
|
|
|
925
936
|
error: 'The task delegation token belongs to a previous daemon runtime; start a new task authorization and do not retry this command in the current task',
|
|
926
937
|
};
|
|
927
938
|
}
|
|
939
|
+
if (typeof cmd.delegationToken === 'string'
|
|
940
|
+
&& (DELEGATED_ARGV_ENDPOINTS.has(cmd.type) || (cmd.type === 'aun-msg-send' && cmd.argv !== undefined))) {
|
|
941
|
+
if (!Array.isArray(cmd.argv) || cmd.argv.some(value => typeof value !== 'string')) {
|
|
942
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'Invalid command argv' };
|
|
943
|
+
}
|
|
944
|
+
const expectedHash = hashDelegatedCommandArgv([
|
|
945
|
+
MAIN_CLI_BIN,
|
|
946
|
+
...(cmd.type === 'menu-client.request' ? ['menu-client'] : []),
|
|
947
|
+
...cmd.argv,
|
|
948
|
+
]);
|
|
949
|
+
if (!expectedHash || cmd.delegationCommandHash !== expectedHash) {
|
|
950
|
+
return { ok: false, code: 'INVALID_DELEGATION', error: 'Command hash does not match the submitted argv' };
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (cmd.argv !== undefined && ['handoff-status', 'handoff-list', 'handoff-trace'].includes(cmd.type)) {
|
|
954
|
+
try {
|
|
955
|
+
const { parseList, parseTrace } = await import('./cli/handoff-command.js');
|
|
956
|
+
if (!Array.isArray(cmd.argv) || cmd.argv[0] !== 'handoff' || `handoff-${cmd.argv[1]}` !== cmd.type) {
|
|
957
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'Handoff endpoint does not match argv' };
|
|
958
|
+
}
|
|
959
|
+
if (cmd.type === 'handoff-list') {
|
|
960
|
+
const parsed = parseList(cmd.argv.slice(2));
|
|
961
|
+
cmd = { ...cmd, agent: parsed.agent, state: parsed.state, filterSessionId: parsed.sessionId, limit: parsed.limit };
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
const parsed = parseTrace(cmd.argv.slice(2));
|
|
965
|
+
if (cmd.type === 'handoff-status' && cmd.argv.length !== 3)
|
|
966
|
+
throw new Error('Invalid handoff status arguments');
|
|
967
|
+
cmd = { ...cmd, agent: parsed.agent, handoffId: parsed.handoffId, limit: parsed.limit };
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
catch (error) {
|
|
971
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: error instanceof Error ? error.message : String(error) };
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (MEMORY_COMMAND_ENDPOINTS.has(cmd.type)) {
|
|
975
|
+
if (typeof cmd.sessionId !== 'string' || !cmd.sessionId)
|
|
976
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'missing sessionId' };
|
|
977
|
+
if (cmd.debug === true) {
|
|
978
|
+
if (cmd.delegationToken || !this.hasValidControlToken(cmd.controlToken)) {
|
|
979
|
+
return { ok: false, code: 'INVALID_CONTROL_TOKEN', error: 'Memory debug requires a local control token, not task delegation' };
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
else {
|
|
983
|
+
if (!this.taskRuntimeContextAuthorizer)
|
|
984
|
+
return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Memory authorization is not configured' };
|
|
985
|
+
if (!Array.isArray(cmd.argv) || cmd.argv.some((value) => typeof value !== 'string') || cmd.argv.includes('--debug')) {
|
|
986
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'Managed memory requires argv without --debug' };
|
|
987
|
+
}
|
|
988
|
+
const authorization = this.taskRuntimeContextAuthorizer({
|
|
989
|
+
sessionId: cmd.sessionId, delegationToken: cmd.delegationToken,
|
|
990
|
+
delegationCommandHash: cmd.delegationCommandHash, consumeApproval: true,
|
|
991
|
+
});
|
|
992
|
+
if (!authorization.ok)
|
|
993
|
+
return authorization;
|
|
994
|
+
}
|
|
995
|
+
if (cmd.argv !== undefined) {
|
|
996
|
+
try {
|
|
997
|
+
const { resolveMemoryCommandArgv } = await import('./cli/memory-command.js');
|
|
998
|
+
const operation = resolveMemoryCommandArgv(cmd.argv);
|
|
999
|
+
if (operation.type !== cmd.type)
|
|
1000
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'Memory endpoint does not match argv' };
|
|
1001
|
+
cmd = { ...operation, sessionId: cmd.sessionId, debug: cmd.debug === true };
|
|
1002
|
+
}
|
|
1003
|
+
catch (error) {
|
|
1004
|
+
return { ok: false, code: 'INVALID_ARGUMENT', error: error instanceof Error ? error.message : String(error) };
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
928
1008
|
switch (cmd.type) {
|
|
929
1009
|
case 'diag':
|
|
930
1010
|
return this.diagExecutor ? this.diagExecutor(cmd) : { ok: false, code: 'NOT_CONFIGURED', message: '诊断入口未配置' };
|
|
@@ -1168,6 +1248,7 @@ export class IpcServer {
|
|
|
1168
1248
|
sessionId: cmd.sessionId,
|
|
1169
1249
|
delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
|
|
1170
1250
|
delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
|
|
1251
|
+
argv: Array.isArray(cmd.argv) ? cmd.argv : undefined,
|
|
1171
1252
|
});
|
|
1172
1253
|
}
|
|
1173
1254
|
case 'status.auth': {
|
|
@@ -1191,6 +1272,7 @@ export class IpcServer {
|
|
|
1191
1272
|
return { ok: false, code: 'INVALID_REQUEST', error: 'invalid storage operation' };
|
|
1192
1273
|
}
|
|
1193
1274
|
return await this.storageAuthorizationExecutor({
|
|
1275
|
+
argv: cmd.argv,
|
|
1194
1276
|
sessionId: cmd.sessionId,
|
|
1195
1277
|
aid: cmd.aid,
|
|
1196
1278
|
operation: cmd.operation,
|
|
@@ -1208,6 +1290,7 @@ export class IpcServer {
|
|
|
1208
1290
|
return { ok: false, code: 'INVALID_REQUEST', error: 'missing sessionId, aid, localFile, or remotePath' };
|
|
1209
1291
|
}
|
|
1210
1292
|
return await this.storageUploadExecutor({
|
|
1293
|
+
argv: cmd.argv,
|
|
1211
1294
|
sessionId: cmd.sessionId,
|
|
1212
1295
|
aid: cmd.aid,
|
|
1213
1296
|
localFile: cmd.localFile,
|
|
@@ -1235,9 +1318,10 @@ export class IpcServer {
|
|
|
1235
1318
|
}
|
|
1236
1319
|
}
|
|
1237
1320
|
case 'task.resume': {
|
|
1238
|
-
const allowed = new Set(['type', 'targetSessionId', 'sessionId', 'delegationToken',
|
|
1321
|
+
const allowed = new Set(['type', 'argv', 'targetSessionId', 'sessionId', 'delegationToken',
|
|
1239
1322
|
'delegationCommandHash', 'controlToken', 'daemonRuntimeEpoch', 'requestId', 'id']);
|
|
1240
1323
|
if (Object.keys(cmd).some(key => !allowed.has(key))
|
|
1324
|
+
|| (cmd.argv !== undefined && (!Array.isArray(cmd.argv) || cmd.argv.some((value) => typeof value !== 'string')))
|
|
1241
1325
|
|| typeof cmd.targetSessionId !== 'string' || !cmd.targetSessionId.trim()
|
|
1242
1326
|
|| (cmd.sessionId !== undefined && (typeof cmd.sessionId !== 'string' || !cmd.sessionId.trim()))) {
|
|
1243
1327
|
return { ok: false, code: 'INVALID_REQUEST', error: 'task.resume accepts only a target session and caller credentials' };
|
|
@@ -1250,6 +1334,7 @@ export class IpcServer {
|
|
|
1250
1334
|
try {
|
|
1251
1335
|
return await this.taskResumeExecutor({
|
|
1252
1336
|
targetSessionId: cmd.targetSessionId,
|
|
1337
|
+
argv: Array.isArray(cmd.argv) ? cmd.argv : undefined,
|
|
1253
1338
|
sessionId: cmd.sessionId,
|
|
1254
1339
|
delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
|
|
1255
1340
|
delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
|
|
@@ -1260,6 +1345,34 @@ export class IpcServer {
|
|
|
1260
1345
|
return { ok: false, code: 'TASK_RESUME_FAILED', error: error instanceof Error ? error.message : String(error) };
|
|
1261
1346
|
}
|
|
1262
1347
|
}
|
|
1348
|
+
case 'local-command-context': {
|
|
1349
|
+
if (!Array.isArray(cmd.argv) || cmd.argv.some((value) => typeof value !== 'string')
|
|
1350
|
+
|| !['skill', 'stats'].includes(cmd.argv[0]) || typeof cmd.sessionId !== 'string' || !cmd.sessionId
|
|
1351
|
+
|| typeof cmd.delegationToken !== 'string' || !cmd.delegationToken) {
|
|
1352
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'Local command requires argv and task credentials' };
|
|
1353
|
+
}
|
|
1354
|
+
if (!this.taskRuntimeContextAuthorizer || !this.taskRuntimeContextProvider) {
|
|
1355
|
+
return { ok: false, code: 'DELEGATION_REQUIRED', error: 'Command authorization is unavailable' };
|
|
1356
|
+
}
|
|
1357
|
+
const authorization = this.taskRuntimeContextAuthorizer({
|
|
1358
|
+
sessionId: cmd.sessionId, delegationToken: cmd.delegationToken,
|
|
1359
|
+
delegationCommandHash: cmd.delegationCommandHash, consumeApproval: true,
|
|
1360
|
+
});
|
|
1361
|
+
if (!authorization.ok)
|
|
1362
|
+
return authorization;
|
|
1363
|
+
const context = this.taskRuntimeContextProvider({ sessionId: cmd.sessionId });
|
|
1364
|
+
if (!context?.selfAid || !context.peerRole || context.peerRole === 'none') {
|
|
1365
|
+
return { ok: false, code: 'NOT_ALLOWED', error: 'Current task identity and role are unavailable' };
|
|
1366
|
+
}
|
|
1367
|
+
if (cmd.argv[0] === 'stats' && cmd.argv.some((value) => /^--(?:sql|rebuild)(?:=|$)/.test(value))) {
|
|
1368
|
+
return { ok: false, code: 'NOT_ALLOWED', error: 'Managed stats cannot use --sql or --rebuild' };
|
|
1369
|
+
}
|
|
1370
|
+
if (cmd.argv[0] === 'skill' && context.permissionMode === 'readonly'
|
|
1371
|
+
&& ['create', 'install', 'remove', 'enable', 'disable'].includes(cmd.argv[1])) {
|
|
1372
|
+
return { ok: false, code: 'NOT_ALLOWED', error: 'Readonly tasks cannot modify skills' };
|
|
1373
|
+
}
|
|
1374
|
+
return { ok: true, context };
|
|
1375
|
+
}
|
|
1263
1376
|
case 'task-runtime-context': {
|
|
1264
1377
|
if (!this.taskRuntimeContextProvider)
|
|
1265
1378
|
return { ok: false, error: 'task runtime context not configured' };
|
|
@@ -1279,6 +1392,15 @@ export class IpcServer {
|
|
|
1279
1392
|
return { ok: true, context };
|
|
1280
1393
|
}
|
|
1281
1394
|
case 'aun-msg-send': {
|
|
1395
|
+
if (cmd.argv !== undefined) {
|
|
1396
|
+
if (!this.aunMsgSender)
|
|
1397
|
+
return { ok: false, code: 'NOT_CONFIGURED', error: 'AUN sender is not configured' };
|
|
1398
|
+
return this.aunMsgSender({
|
|
1399
|
+
aid: '', to: '', scope: 'msg', argv: cmd.argv,
|
|
1400
|
+
originSessionId: cmd.originSessionId, originMessageId: cmd.originMessageId,
|
|
1401
|
+
delegationToken: cmd.delegationToken, delegationCommandHash: cmd.delegationCommandHash,
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1282
1404
|
if (cmd.handoffContext !== undefined && (typeof cmd.handoffContext !== 'string'
|
|
1283
1405
|
|| !cmd.handoffContext.trim() || cmd.handoffContext.length > 64 * 1024 || cmd.scope === 'group')) {
|
|
1284
1406
|
return { ok: false, code: 'INVALID_HANDOFF_CONTEXT', error: 'Private handoff context must contain 1–65536 characters' };
|
|
@@ -1356,6 +1478,7 @@ export class IpcServer {
|
|
|
1356
1478
|
return { ok: false, code: 'HANDOFF_RETURN_CONTENT_REQUIRED', error: 'missing content' };
|
|
1357
1479
|
try {
|
|
1358
1480
|
return await this.handoffReturnExecutor({
|
|
1481
|
+
argv: cmd.argv,
|
|
1359
1482
|
sessionId: typeof cmd.sessionId === 'string' ? cmd.sessionId : undefined,
|
|
1360
1483
|
handoffId: typeof cmd.handoffId === 'string' ? cmd.handoffId : undefined,
|
|
1361
1484
|
content: cmd.content,
|
|
@@ -1378,6 +1501,7 @@ export class IpcServer {
|
|
|
1378
1501
|
return { ok: false, code: 'HANDOFF_RETURN_CONTENT_REQUIRED', error: 'missing content' };
|
|
1379
1502
|
try {
|
|
1380
1503
|
return await this.handoffRecoverReturnExecutor({
|
|
1504
|
+
argv: cmd.argv,
|
|
1381
1505
|
sessionId: cmd.sessionId,
|
|
1382
1506
|
handoffId: cmd.handoffId,
|
|
1383
1507
|
content: cmd.content,
|
|
@@ -1444,10 +1568,13 @@ export class IpcServer {
|
|
|
1444
1568
|
case 'ctl': {
|
|
1445
1569
|
if (!this.commandExecutor)
|
|
1446
1570
|
return { ok: false, error: 'ctl not configured' };
|
|
1447
|
-
const {
|
|
1448
|
-
if (!
|
|
1449
|
-
return { ok: false, error: 'missing
|
|
1450
|
-
|
|
1571
|
+
const { sessionId, argv } = cmd;
|
|
1572
|
+
if (!sessionId || !Array.isArray(argv))
|
|
1573
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
|
|
1574
|
+
if (argv.some(value => typeof value !== 'string')) {
|
|
1575
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'Invalid command argv' };
|
|
1576
|
+
}
|
|
1577
|
+
return await this.commandExecutor(argv, sessionId, typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
|
|
1451
1578
|
}
|
|
1452
1579
|
case 'memory.note': {
|
|
1453
1580
|
const { sessionId, slug, content, tags, speakers } = cmd;
|
|
@@ -1864,11 +1991,11 @@ export class IpcServer {
|
|
|
1864
1991
|
}
|
|
1865
1992
|
return await this.agentOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
|
|
1866
1993
|
}
|
|
1994
|
+
case 'trigger.op':
|
|
1867
1995
|
case 'trigger.list':
|
|
1868
1996
|
case 'trigger.show':
|
|
1869
1997
|
case 'trigger.history':
|
|
1870
1998
|
case 'trigger.eventCatalog':
|
|
1871
|
-
case 'trigger.authorizeLocalFiles':
|
|
1872
1999
|
case 'trigger.create':
|
|
1873
2000
|
case 'trigger.update':
|
|
1874
2001
|
case 'trigger.setEnabled':
|
|
@@ -1917,6 +2044,17 @@ export class IpcServer {
|
|
|
1917
2044
|
const aid = cmd.aid;
|
|
1918
2045
|
if (!aid || typeof aid !== 'string')
|
|
1919
2046
|
return { ok: false, error: 'missing aid' };
|
|
2047
|
+
if (cmd.argv !== undefined) {
|
|
2048
|
+
if (!Array.isArray(cmd.argv) || cmd.argv.some((value) => typeof value !== 'string')) {
|
|
2049
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'Invalid command argv' };
|
|
2050
|
+
}
|
|
2051
|
+
const argv = cmd.argv;
|
|
2052
|
+
const validFormat = argv.length === 3
|
|
2053
|
+
|| (argv.length === 5 && argv[3] === '--format' && argv[4] === 'json');
|
|
2054
|
+
if (argv[0] !== 'agent' || argv[1] !== 'ready' || argv[2] !== aid || !validFormat) {
|
|
2055
|
+
return { ok: false, code: 'INVALID_REQUEST', error: 'agent ready endpoint does not match argv' };
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
1920
2058
|
if (!this.bootstrapCompleteExecutor)
|
|
1921
2059
|
return { ok: false, error: 'bootstrap complete executor not configured' };
|
|
1922
2060
|
try {
|
|
@@ -2333,7 +2471,7 @@ export async function ipcQuery(socketPath, cmd, timeoutMs = 3000, options = {})
|
|
|
2333
2471
|
function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
|
|
2334
2472
|
let authenticatedCmd = cmd;
|
|
2335
2473
|
if (!process.env.EVOLCORE_SESSION_ID
|
|
2336
|
-
&& requiresLocalControlToken(cmd)
|
|
2474
|
+
&& (requiresLocalControlToken(cmd) || (MEMORY_COMMAND_ENDPOINTS.has(cmd.type) && cmd.debug === true))
|
|
2337
2475
|
&& typeof cmd.controlToken !== 'string') {
|
|
2338
2476
|
try {
|
|
2339
2477
|
const controlToken = fs.readFileSync(resolvePaths().controlToken, 'utf8').trim();
|
|
@@ -2345,7 +2483,7 @@ function ipcQueryOnce(socketPath, cmd, timeoutMs = 3000) {
|
|
|
2345
2483
|
const requestArgv = Array.isArray(authenticatedCmd.argv)
|
|
2346
2484
|
&& authenticatedCmd.argv.every((value) => typeof value === 'string')
|
|
2347
2485
|
? [
|
|
2348
|
-
|
|
2486
|
+
MAIN_CLI_BIN,
|
|
2349
2487
|
...(authenticatedCmd.type === 'menu-client.request' ? ['menu-client'] : []),
|
|
2350
2488
|
...authenticatedCmd.argv,
|
|
2351
2489
|
]
|
|
@@ -247,7 +247,7 @@ export function authorizeTriggerExecution(definition, dependencies) {
|
|
|
247
247
|
}
|
|
248
248
|
const permissionDecision = constrainRuntimePermissionMode({
|
|
249
249
|
selfAid: definition.agentAid,
|
|
250
|
-
role,
|
|
250
|
+
role: typeof role === 'string' ? role : undefined,
|
|
251
251
|
requestedValue: definition.execution.permissionMode,
|
|
252
252
|
});
|
|
253
253
|
if (definition.execution.permissionMode && permissionDecision.constrained) {
|
|
@@ -260,6 +260,7 @@ export function authorizeTriggerExecution(definition, dependencies) {
|
|
|
260
260
|
allowed: true,
|
|
261
261
|
scope: privilegedReason && hasDaemonTriggerAuthority(subject) ? 'daemon' : 'agent',
|
|
262
262
|
permissionMode: permissionDecision.effectiveValue,
|
|
263
|
+
role: role ?? undefined,
|
|
263
264
|
};
|
|
264
265
|
}
|
|
265
266
|
export async function authenticatedTriggerActor(dependencies, agentAid, actorSessionId, delegationToken, delegationCommandHash, controlToken, contextOnly = false) {
|