evolcore 0.0.39 → 0.0.40
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 +49 -0
- package/README.md +31 -20
- package/bin/ec-safe-output.js +14 -2
- package/dist/agents/baseagent-availability.js +1 -0
- package/dist/agents/claude-runner.js +32 -46
- package/dist/agents/codex-runner.js +51 -27
- package/dist/agents/ecagent-runner.js +18 -7
- package/dist/agents/gemini-runner.js +1 -2
- package/dist/agents/runner-types.js +28 -6
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +23 -98
- package/dist/aun/aid/ownership-operation.js +1 -3
- package/dist/aun/group/invite-service.js +6 -0
- package/dist/aun/msg/p2p.js +8 -12
- package/dist/channels/aun.js +66 -48
- package/dist/channels/dingtalk.js +8 -2
- package/dist/channels/feishu.js +6 -1
- package/dist/channels/wecom.js +7 -3
- package/dist/cli/agent-create.js +23 -4
- package/dist/cli/aid-bind-command.js +4 -1
- package/dist/cli/daemon-commands.js +242 -17
- package/dist/cli/index.js +25 -13
- package/dist/cli/memory-command.js +33 -14
- package/dist/cli/restart-monitor.js +35 -13
- package/dist/cli/task-context.js +1 -0
- package/dist/config/config-field-policy.js +1 -0
- package/dist/contracts/ecweb-service-protocol.js +44 -0
- package/dist/core/agent-runtime.js +6 -0
- package/dist/core/auth/agent-delegation.js +7 -4
- package/dist/core/auth/auth-gateway.js +3 -1
- package/dist/core/auth/authorization-audit.js +5 -1
- package/dist/core/auth/debug-context.js +5 -1
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/command/command-handler.js +7 -1
- package/dist/core/command/menu-catalog.js +48 -12
- package/dist/core/command/menu-handler.js +72 -4
- package/dist/core/command/menu-protocol.js +20 -1
- package/dist/core/command/slash-gate.js +17 -3
- package/dist/core/command/slash-handler.js +90 -7
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/interaction-router.js +2 -1
- package/dist/core/ipc-registration.js +26 -7
- package/dist/core/memory/batch-meta.js +2 -0
- package/dist/core/memory/consolidation-context.js +42 -51
- package/dist/core/memory/consolidation-ops-records.js +39 -7
- package/dist/core/memory/consolidation-trigger.js +71 -64
- package/dist/core/memory/consolidation.js +226 -20
- package/dist/core/memory/memory-writer.js +160 -34
- package/dist/core/memory/relation-idle-monitor.js +287 -0
- package/dist/core/memory/relation-sweep.js +206 -0
- package/dist/core/message/aun-ipc-sender.js +5 -0
- package/dist/core/message/message-bridge.js +64 -55
- package/dist/core/message/message-log.js +17 -0
- package/dist/core/message/message-queue.js +6 -0
- package/dist/core/message/outbound.js +70 -0
- package/dist/core/message/peer-mode.js +7 -2
- package/dist/core/message/response-engine.js +341 -73
- package/dist/core/ownership/owner-resolver.js +384 -178
- package/dist/core/permission/ec-command-parser.js +19 -3
- package/dist/core/permission/tool-policy.js +7 -7
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/session/session-manager.js +25 -5
- package/dist/core/system-session/memory.js +3 -18
- package/dist/ecweb/basic-settings-service.js +709 -0
- package/dist/ecweb/providers-service.js +172 -0
- package/dist/index.js +92 -4
- package/dist/ipc.js +78 -11
- package/dist/stats/db.js +33 -0
- package/dist/stats/index.js +2 -2
- package/dist/stats/query.js +246 -0
- package/dist/stats/writer.js +41 -1
- package/dist/trigger/application-service.js +12 -0
- package/dist/trigger/authorization.js +2 -2
- package/dist/trigger/history.js +36 -0
- package/dist/trigger/scheduler.js +3 -0
- package/dist/utils/ecweb-supervisor.js +31 -10
- package/dist/utils/error-utils.js +8 -4
- package/dist/utils/evolcore-version.js +3 -2
- package/dist/utils/host-info.js +52 -0
- package/ecagent/dist/harness/agent-harness.js +4 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/agent.md +1 -1
- package/kits/docs/evolcore/aid.md +29 -12
- package/kits/docs/evolcore/config.md +9 -7
- package/kits/docs/evolcore/memory-architecture.md +3 -4
- package/kits/docs/evolcore/memory-consolidation.md +28 -7
- package/kits/docs/evolcore/memory-tags.md +1 -1
- package/kits/docs/evolcore/memory.md +1 -1
- package/kits/docs/evolcore/menu-client.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +6 -0
- package/kits/docs/evolcore/msg.md +1 -1
- package/kits/eck_manifest.json +3 -3
- package/kits/rules/04-relation.md +3 -2
- package/kits/schemas/agent-config.schema.13.json +5 -1
- package/kits/templates/memory-consolidation/generalize.md +32 -4
- package/kits/templates/memory-consolidation/notes.md +2 -2
- package/kits/templates/memory-consolidation/promote.md +10 -2
- package/kits/templates/system-fragments/baseagent.md +5 -0
- package/kits/templates/system-fragments/memory.md +12 -3
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/{rules/07-memory-and-thread.md → templates/system-fragments/thread.md} +17 -80
- package/package.json +3 -3
|
@@ -760,8 +760,7 @@ export class GeminiRunner {
|
|
|
760
760
|
if (context?.approvalInteractionPolicy !== 'deny')
|
|
761
761
|
return;
|
|
762
762
|
if (isGeminiUnattendedInteraction(toolName)) {
|
|
763
|
-
|
|
764
|
-
return;
|
|
763
|
+
throw new Error('无人值守任务不能自动回答用户问题');
|
|
765
764
|
}
|
|
766
765
|
const lifecycleRequestId = requestId ?? `gemini-approval-${crypto.randomUUID()}`;
|
|
767
766
|
const auditSummary = `${toolName} runtime authorization denied by Gemini headless policy`;
|
|
@@ -34,7 +34,23 @@ export function parseAsyncUserQuestions(value) {
|
|
|
34
34
|
|| question.options.length > 20 || question.options.some((option) => typeof option !== 'string' || !option.trim() || option.length > 4_096))) {
|
|
35
35
|
throw new Error('Invalid asynchronous question options');
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
if (question.blocking != null && typeof question.blocking !== 'boolean') {
|
|
38
|
+
throw new Error('Invalid asynchronous question blocking flag');
|
|
39
|
+
}
|
|
40
|
+
if (question.recommendedOption != null
|
|
41
|
+
&& (typeof question.recommendedOption !== 'string' || !question.recommendedOption.trim())) {
|
|
42
|
+
throw new Error('Invalid asynchronous question recommended option');
|
|
43
|
+
}
|
|
44
|
+
if (question.recommendedOption != null && question.options
|
|
45
|
+
&& !question.options.includes(question.recommendedOption)) {
|
|
46
|
+
throw new Error('Asynchronous question recommended option is not in options');
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
title: question.title,
|
|
50
|
+
...(question.options ? { options: [...question.options] } : {}),
|
|
51
|
+
blocking: question.blocking !== false,
|
|
52
|
+
...(question.recommendedOption ? { recommendedOption: question.recommendedOption } : {}),
|
|
53
|
+
};
|
|
38
54
|
});
|
|
39
55
|
}
|
|
40
56
|
export function hasTrustedFullAccessContext(context) {
|
|
@@ -44,10 +60,13 @@ export function hasTrustedFullAccessContext(context) {
|
|
|
44
60
|
|| authorization.processRole !== 'fullaccess-run'
|
|
45
61
|
|| authorization.dataScope !== 'daemon')
|
|
46
62
|
return false;
|
|
47
|
-
if (authorization.source === 'fullaccess-command' || authorization.source === 'agent-policy') {
|
|
63
|
+
if (authorization.source === 'fullaccess-command' || authorization.source === 'agent-policy' || authorization.source === 'session-policy') {
|
|
48
64
|
return typeof authorization.authorizedBy === 'string' && authorization.authorizedBy.length > 0
|
|
49
|
-
&& (authorization.source
|
|
50
|
-
|| (typeof authorization.agentAid === 'string'
|
|
65
|
+
&& (authorization.source === 'fullaccess-command'
|
|
66
|
+
|| (typeof authorization.agentAid === 'string'
|
|
67
|
+
&& authorization.agentAid.length > 0
|
|
68
|
+
&& (authorization.source !== 'session-policy'
|
|
69
|
+
|| (typeof authorization.sessionId === 'string' && authorization.sessionId.length > 0))));
|
|
51
70
|
}
|
|
52
71
|
return authorization.source === 'trigger'
|
|
53
72
|
&& typeof authorization.authorizedBy === 'string' && authorization.authorizedBy.length > 0
|
|
@@ -75,10 +94,13 @@ export async function withInterruptDeadline(operation, label, timeoutMs = 5_000)
|
|
|
75
94
|
clearTimeout(timer);
|
|
76
95
|
}
|
|
77
96
|
}
|
|
78
|
-
export function unsettledInterruptError(runner) {
|
|
79
|
-
|
|
97
|
+
export function unsettledInterruptError(runner, cause) {
|
|
98
|
+
const error = Object.assign(new Error(`${runner} 的上一次执行尚未确认停止,本次任务未启动;旧执行结束后可在原会话继续。`), {
|
|
80
99
|
code: 'RUNNER_INTERRUPT_UNCONFIRMED',
|
|
81
100
|
});
|
|
101
|
+
if (cause !== undefined)
|
|
102
|
+
error.cause = cause;
|
|
103
|
+
return error;
|
|
82
104
|
}
|
|
83
105
|
// ── 类型守卫 ──
|
|
84
106
|
export function hasModelSwitcher(agent) {
|
package/dist/aun/aid/client.js
CHANGED
|
@@ -39,7 +39,7 @@ export function suppressSdkLogs() {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
// ==================== SDK package ====================
|
|
42
|
-
export const MIN_AUN_CORE_SDK = [0, 5,
|
|
42
|
+
export const MIN_AUN_CORE_SDK = [0, 5, 25];
|
|
43
43
|
export const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
|
|
44
44
|
function compareVersion(a, min) {
|
|
45
45
|
const parts = a.split('.').map(n => parseInt(n, 10));
|
|
@@ -8,7 +8,6 @@ import { getAidStore, loadClient, SLOT } from './store.js';
|
|
|
8
8
|
import { LocalTokenStore, validateAgentMdDocument, verifyAgentMdUpload, } from '@agentunion/fastaun';
|
|
9
9
|
import { AUN_KEYSTORE_ENCRYPTION_SEED } from './encryption-seed-policy.js';
|
|
10
10
|
import { isValidAid } from './validation.js';
|
|
11
|
-
import { OwnerResolver } from '../../core/ownership/owner-resolver.js';
|
|
12
11
|
import { agentMdPath, resolvePaths } from '../../paths.js';
|
|
13
12
|
import { PeerIdentityCache } from '../../core/relation/peer-identity.js';
|
|
14
13
|
import { agentmdPut, buildInitialAgentMd, invalidateAgentDisplayName, stripAgentMdSignature } from './agentmd.js';
|
|
@@ -331,7 +330,6 @@ export async function submitControlAidRegistration(aid, owner, options = {}) {
|
|
|
331
330
|
let connecting = false;
|
|
332
331
|
let sentOperationIds = new Set();
|
|
333
332
|
let invitationObserver;
|
|
334
|
-
const resolver = new OwnerResolver();
|
|
335
333
|
async function bounded(operation, timeoutMs = deadline - Date.now()) {
|
|
336
334
|
let timer;
|
|
337
335
|
try {
|
|
@@ -346,27 +344,6 @@ export async function submitControlAidRegistration(aid, owner, options = {}) {
|
|
|
346
344
|
clearTimeout(timer);
|
|
347
345
|
}
|
|
348
346
|
}
|
|
349
|
-
function readTask() {
|
|
350
|
-
const raw = records.loadOwnershipTask(aid, operationId)?.task;
|
|
351
|
-
if (!raw)
|
|
352
|
-
return;
|
|
353
|
-
if (raw.operation_id !== operationId || raw.operation !== 'bind' || raw.sub_aid !== aid
|
|
354
|
-
|| raw.owner_aid !== ownerAid || raw.initiator_aid !== aid || raw.new_owner_aid !== undefined
|
|
355
|
-
|| !['pending', 'completed', 'rejected', 'stopped', 'error'].includes(String(raw.status))
|
|
356
|
-
|| typeof raw.step !== 'string' || !Number.isSafeInteger(raw.base_relation_version)
|
|
357
|
-
|| raw.target_relation_version !== Number(raw.base_relation_version) + 1) {
|
|
358
|
-
throw Object.assign(new Error('SDK registration task does not match its original intent'), { code: 'TASK_MISMATCH' });
|
|
359
|
-
}
|
|
360
|
-
task = {
|
|
361
|
-
operation_id: operationId, operation: 'bind', sub_aid: aid, owner_aid: ownerAid,
|
|
362
|
-
base_relation_version: raw.base_relation_version,
|
|
363
|
-
target_relation_version: raw.target_relation_version,
|
|
364
|
-
status: raw.status, step: raw.step,
|
|
365
|
-
...(typeof raw.code === 'string' ? { code: raw.code } : {}),
|
|
366
|
-
...(typeof raw.reason === 'string' ? { reason: raw.reason } : {}),
|
|
367
|
-
...(typeof raw.retryable === 'boolean' ? { retryable: raw.retryable } : {}),
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
347
|
function result(status, error) {
|
|
371
348
|
return { status, ...(operationId ? { operationId } : {}), ...(task ? { task } : {}), ...(error ? { error } : {}) };
|
|
372
349
|
}
|
|
@@ -374,10 +351,8 @@ export async function submitControlAidRegistration(aid, owner, options = {}) {
|
|
|
374
351
|
records = new LocalTokenStore(resolvePaths().root, { encryptionSeed: AUN_KEYSTORE_ENCRYPTION_SEED });
|
|
375
352
|
const intent = records.loadMetadata(aid)?.registration_intent;
|
|
376
353
|
const registrationIntentOwner = typeof intent?.owner === 'string' ? intent.owner.toLowerCase() : undefined;
|
|
377
|
-
// Preserve an existing SDK registration task and let the same SDK client
|
|
378
|
-
// resume it during connect before OwnerResolver checks/binds the subject.
|
|
379
354
|
if (intent && Object.prototype.hasOwnProperty.call(intent, 'owner')) {
|
|
380
|
-
if (!registrationIntentOwner) {
|
|
355
|
+
if (!registrationIntentOwner || !isValidAid(registrationIntentOwner)) {
|
|
381
356
|
return result('unknown', {
|
|
382
357
|
code: 'REGISTRATION_INTENT_PRESENT',
|
|
383
358
|
message: 'Control AID has an invalid SDK registration Owner intent',
|
|
@@ -386,93 +361,44 @@ export async function submitControlAidRegistration(aid, owner, options = {}) {
|
|
|
386
361
|
if (registrationIntentOwner !== ownerAid) {
|
|
387
362
|
return result('failed', { code: 'OWNER_INTENT_CONFLICT', message: 'Control AID was registered for a different Owner' });
|
|
388
363
|
}
|
|
389
|
-
if (typeof intent.operation_id === 'string' && REGISTRATION_OPERATION_ID.test(intent.operation_id)) {
|
|
390
|
-
operationId = intent.operation_id;
|
|
391
|
-
readTask();
|
|
392
|
-
}
|
|
393
|
-
if (!operationId) {
|
|
394
|
-
return result('unknown', {
|
|
395
|
-
code: 'REGISTRATION_INTENT_PRESENT',
|
|
396
|
-
message: 'Control AID has an SDK registration intent without a valid operation ID',
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
364
|
}
|
|
400
365
|
if (options.agentMdError) {
|
|
401
366
|
return result('failed', { code: 'CONTROL_AGENTMD_NOT_READY', message: options.agentMdError });
|
|
402
367
|
}
|
|
403
|
-
store = await bounded(getAidStore({ slotId:
|
|
404
|
-
const ownershipTasks = records.listOwnershipTasks(aid).map(snapshot => snapshot.task);
|
|
405
|
-
const conflictingTask = ownershipTasks.find(candidate => candidate.sub_aid === aid
|
|
406
|
-
&& (candidate.status === 'pending' || candidate.status === 'error' && candidate.retryable === true)
|
|
407
|
-
&& !(candidate.operation === 'bind' && candidate.owner_aid === ownerAid
|
|
408
|
-
&& candidate.initiator_aid === aid && candidate.new_owner_aid === undefined));
|
|
409
|
-
if (conflictingTask) {
|
|
410
|
-
return result('failed', { code: 'BIND_CONFLICT', message: 'A different active ownership task must be resolved first' });
|
|
411
|
-
}
|
|
368
|
+
store = await bounded(getAidStore({ slotId: `ownership-${crypto.randomUUID()}` }));
|
|
412
369
|
client = await bounded(loadClient(store, aid));
|
|
413
370
|
connecting = true;
|
|
414
371
|
sentOperationIds = new Set();
|
|
415
372
|
invitationObserver = observeInvitationSend(client, ownerAid, sentOperationIds);
|
|
416
373
|
await bounded(client.connect({
|
|
417
|
-
connection_kind: '
|
|
374
|
+
connection_kind: 'short', short_ttl_ms: 30_000, auto_reconnect: false, background_sync: false,
|
|
418
375
|
connect_timeout: 10, call_timeout: 10,
|
|
419
376
|
}));
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
377
|
+
task = await bounded(client.bindOwneraid(ownerAid));
|
|
378
|
+
if (task.operation !== 'bind' || task.sub_aid !== aid || task.owner_aid !== ownerAid
|
|
379
|
+
|| task.new_owner_aid !== undefined || !REGISTRATION_OPERATION_ID.test(task.operation_id)
|
|
380
|
+
|| !Number.isSafeInteger(task.base_relation_version)
|
|
381
|
+
|| task.target_relation_version !== task.base_relation_version + 1) {
|
|
382
|
+
throw Object.assign(new Error('SDK returned a binding result for a different subject or Owner'), { code: 'TASK_MISMATCH' });
|
|
383
|
+
}
|
|
384
|
+
operationId = task.operation_id;
|
|
385
|
+
if (task.status === 'completed') {
|
|
386
|
+
let consensus;
|
|
387
|
+
try {
|
|
388
|
+
consensus = await bounded(client.checkOwnershipConsensus(aid));
|
|
425
389
|
}
|
|
426
|
-
|
|
390
|
+
catch (error) {
|
|
427
391
|
return result('unknown', {
|
|
428
|
-
code: '
|
|
429
|
-
message:
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
if (task?.status === 'completed') {
|
|
433
|
-
const consensus = await bounded(client.checkOwnershipConsensus(aid));
|
|
434
|
-
if (consensus.status === 'consensus' && consensus.sub_aid === aid && consensus.owner_aid === ownerAid) {
|
|
435
|
-
return result('already_bound');
|
|
436
|
-
}
|
|
437
|
-
return result('failed', {
|
|
438
|
-
code: 'CONSENSUS_REQUIRED',
|
|
439
|
-
message: 'A completed binding task has no current verified Owner consensus',
|
|
392
|
+
code: 'BIND_SUBMIT_UNKNOWN',
|
|
393
|
+
message: error instanceof Error ? error.message : String(error),
|
|
440
394
|
});
|
|
441
395
|
}
|
|
442
|
-
if (task && (['rejected', 'stopped', 'error'].includes(task.status) || task.code)) {
|
|
443
|
-
return result('failed', { code: task.code ?? 'BIND_SUBMIT_FAILED', message: task.reason ?? `Binding task ${task.status}` });
|
|
444
|
-
}
|
|
445
|
-
if (sentOperationIds.has(operationId))
|
|
446
|
-
return result('submitted');
|
|
447
|
-
return result('unknown', {
|
|
448
|
-
code: 'BIND_SUBMIT_UNKNOWN',
|
|
449
|
-
message: 'SDK is recovering the original registration task; no matching message.send acknowledgement was observed',
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
// Reuse the same service as agent-create, including its fresh consensus
|
|
453
|
-
// check, explicit SDK bind call, and persistent task projection.
|
|
454
|
-
resolver.attachClient(aid, client, true);
|
|
455
|
-
const view = await bounded(resolver.bind(aid, ownerAid));
|
|
456
|
-
if (view.consensusStatus === 'consensus' && view.ownerAid === ownerAid) {
|
|
457
|
-
return result('already_bound');
|
|
458
|
-
}
|
|
459
|
-
const returnedTask = view.task;
|
|
460
|
-
if (!returnedTask || returnedTask.operation !== 'bind' || returnedTask.sub_aid !== aid
|
|
461
|
-
|| returnedTask.owner_aid !== ownerAid || returnedTask.new_owner_aid !== undefined
|
|
462
|
-
|| typeof returnedTask.operation_id !== 'string' || !REGISTRATION_OPERATION_ID.test(returnedTask.operation_id)) {
|
|
463
|
-
throw Object.assign(new Error('SDK returned a binding task for a different subject or Owner'), { code: 'TASK_MISMATCH' });
|
|
464
|
-
}
|
|
465
|
-
operationId = returnedTask.operation_id;
|
|
466
|
-
readTask();
|
|
467
|
-
if (!task) {
|
|
468
|
-
throw Object.assign(new Error('SDK binding task was not persisted'), { code: 'BIND_TASK_NOT_PERSISTED' });
|
|
469
|
-
}
|
|
470
|
-
if (task.status === 'completed') {
|
|
471
|
-
const consensus = await bounded(client.checkOwnershipConsensus(aid));
|
|
472
396
|
if (consensus.status === 'consensus' && consensus.sub_aid === aid && consensus.owner_aid === ownerAid) {
|
|
473
397
|
return result('already_bound');
|
|
474
398
|
}
|
|
475
|
-
|
|
399
|
+
if (sentOperationIds.has(operationId))
|
|
400
|
+
return result('submitted');
|
|
401
|
+
return result('unknown', { code: 'BIND_SUBMIT_UNKNOWN', message: 'SDK completed the local bind without a message.send acknowledgement' });
|
|
476
402
|
}
|
|
477
403
|
if (['rejected', 'stopped', 'error'].includes(task.status)) {
|
|
478
404
|
return result('failed', { code: task.code ?? 'BIND_SUBMIT_FAILED', message: task.reason ?? `Binding task ${task.status}` });
|
|
@@ -489,15 +415,14 @@ export async function submitControlAidRegistration(aid, owner, options = {}) {
|
|
|
489
415
|
const details = error;
|
|
490
416
|
const code = typeof details.stringCode === 'string' ? details.stringCode
|
|
491
417
|
: typeof details.code === 'string' ? details.code : undefined;
|
|
492
|
-
|
|
493
|
-
|
|
418
|
+
operationId ??= sentOperationIds.values().next().value;
|
|
419
|
+
const uncertain = connecting && (code === 'BIND_SUBMIT_TIMEOUT' || !code || !!operationId);
|
|
494
420
|
return result(uncertain ? 'unknown' : 'failed', {
|
|
495
421
|
code: uncertain ? 'BIND_SUBMIT_UNKNOWN' : code ?? 'BIND_SUBMIT_FAILED',
|
|
496
422
|
message: details.message ?? String(error),
|
|
497
423
|
});
|
|
498
424
|
}
|
|
499
425
|
finally {
|
|
500
|
-
resolver.stop();
|
|
501
426
|
invitationObserver?.restore();
|
|
502
427
|
if (client) {
|
|
503
428
|
try {
|
|
@@ -58,15 +58,13 @@ export async function executeOwnershipCommand(command, defaultSubjectAid) {
|
|
|
58
58
|
if (!subjectAid)
|
|
59
59
|
throw new OwnershipError('SUBJECT_REQUIRED', 'No local subject AID is available');
|
|
60
60
|
if (command.action === 'status') {
|
|
61
|
-
ownerResolver.
|
|
62
|
-
return ownerResolver.refresh(subjectAid);
|
|
61
|
+
return ownerResolver.status(subjectAid);
|
|
63
62
|
}
|
|
64
63
|
if (command.action === 'accept' || command.action === 'reject') {
|
|
65
64
|
return ownerResolver.decide(subjectAid, command.operationId, command.action, command.action === 'reject' ? command.reason : undefined);
|
|
66
65
|
}
|
|
67
66
|
logger.info(`[ownership] bind requested subject=${subjectAid} owner=${command.ownerAid}`);
|
|
68
67
|
try {
|
|
69
|
-
ownerResolver.assertLocalSubject(subjectAid);
|
|
70
68
|
const result = await ownerResolver.bind(subjectAid, command.ownerAid);
|
|
71
69
|
const task = result.task;
|
|
72
70
|
const log = task && ['rejected', 'stopped', 'error'].includes(task.status) ? logger.warn : logger.info;
|
|
@@ -372,6 +372,12 @@ export class GroupInviteReviewService {
|
|
|
372
372
|
if (!runtime)
|
|
373
373
|
return Promise.reject(new Error('邀请审核服务未就绪'));
|
|
374
374
|
const task = this.serial(selfAid, async () => {
|
|
375
|
+
if (!this.active(runtime)) {
|
|
376
|
+
runtime.ready = false;
|
|
377
|
+
runtime.failures = 0;
|
|
378
|
+
runtime.nextSync = Date.now() + 15 * 60_000;
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
375
381
|
try {
|
|
376
382
|
await this.sync(runtime);
|
|
377
383
|
runtime.failures = 0;
|
package/dist/aun/msg/p2p.js
CHANGED
|
@@ -10,6 +10,7 @@ import { resolvePaths } from '../../paths.js';
|
|
|
10
10
|
import { ipcQuery } from '../../ipc.js';
|
|
11
11
|
import { readDelegationCredential } from '../../core/auth/agent-delegation.js';
|
|
12
12
|
import { loadDaemonConfig } from '../../config-store.js';
|
|
13
|
+
import { formatPeerKey } from '../../core/relation/peer-identity.js';
|
|
13
14
|
import { normalizeAunMentionEntries, withAunAttention } from './mention-schema.js';
|
|
14
15
|
import { isTopicThreadId } from '../../core/thread/topic-id.js';
|
|
15
16
|
const DAEMON_SEND_TIMEOUT_GRACE_MS = 10_000;
|
|
@@ -60,9 +61,6 @@ function buildSimplePayload(body) {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
|
-
function isRawPayloadBody(body) {
|
|
64
|
-
return body.mode === 'payload';
|
|
65
|
-
}
|
|
66
64
|
function messageLogContent(body) {
|
|
67
65
|
return body.mode === 'text' ? body.text
|
|
68
66
|
: body.mode === 'link' ? `[link] ${body.url}`
|
|
@@ -134,7 +132,7 @@ async function tryDaemonMsgSend(args, payload, runtimeContext, file) {
|
|
|
134
132
|
if (args.slotId || args.aunPath) {
|
|
135
133
|
return { ok: false, code: 'INVALID_DELEGATION', error: 'Task sends cannot override the daemon AUN connection' };
|
|
136
134
|
}
|
|
137
|
-
const thread =
|
|
135
|
+
const thread = msgSendThreadId(args, payload, runtimeContext);
|
|
138
136
|
const response = await ipcQuery(resolvePaths().socket, {
|
|
139
137
|
type: 'aun-msg-send',
|
|
140
138
|
aid: args.from,
|
|
@@ -254,8 +252,7 @@ export async function msgSend(args) {
|
|
|
254
252
|
if (args.body.mode !== 'payload' && Object.prototype.hasOwnProperty.call(payload, 'mentions')) {
|
|
255
253
|
payload.mentions = normalizeAunMentionEntries(payload.mentions);
|
|
256
254
|
}
|
|
257
|
-
|
|
258
|
-
applyRoutingPayloadFields(payload, args, runtimeContext, false);
|
|
255
|
+
applyRoutingPayloadFields(payload, args, runtimeContext, false);
|
|
259
256
|
const daemonResult = await tryDaemonMsgSend(args, payload, runtimeContext);
|
|
260
257
|
if (daemonResult)
|
|
261
258
|
return daemonMsgSendResult(args, payload, runtimeContext, daemonResult);
|
|
@@ -327,7 +324,10 @@ export async function msgSend(args) {
|
|
|
327
324
|
}).effectiveRole;
|
|
328
325
|
}
|
|
329
326
|
catch { /* 角色解析失败时按未知处理(proactive) */ }
|
|
330
|
-
const chatmode = chatmodeForPeerIdentity(peerIdentity, peerRole
|
|
327
|
+
const chatmode = chatmodeForPeerIdentity(peerIdentity, peerRole, {
|
|
328
|
+
self: args.from,
|
|
329
|
+
peerKey: formatPeerKey('aun', args.to),
|
|
330
|
+
});
|
|
331
331
|
// 3. 构建 payload
|
|
332
332
|
if (!payload) {
|
|
333
333
|
payload = buildSimplePayload(args.body);
|
|
@@ -336,11 +336,7 @@ export async function msgSend(args) {
|
|
|
336
336
|
if (args.body.mode !== 'payload')
|
|
337
337
|
payload.chatmode = chatmode;
|
|
338
338
|
// 5. 写入 payload.thread_id/ref_message_id(如果指定)
|
|
339
|
-
|
|
340
|
-
if (!isRawPayloadBody(args.body))
|
|
341
|
-
applyRoutingPayloadFields(payload, args, runtimeContext, false);
|
|
342
|
-
applyRoutingPayloadFields(payload, args, runtimeContext);
|
|
343
|
-
}
|
|
339
|
+
applyRoutingPayloadFields(payload, args, runtimeContext, args.body.mode !== 'payload');
|
|
344
340
|
if (args.body.mode !== 'payload' && Object.prototype.hasOwnProperty.call(payload, 'mentions')) {
|
|
345
341
|
payload.mentions = normalizeAunMentionEntries(payload.mentions);
|
|
346
342
|
}
|
package/dist/channels/aun.js
CHANGED
|
@@ -568,23 +568,18 @@ function setIfDefined(target, key, value) {
|
|
|
568
568
|
if (value !== undefined)
|
|
569
569
|
target[key] = value;
|
|
570
570
|
}
|
|
571
|
-
function
|
|
571
|
+
function rawErrorText(raw) {
|
|
572
572
|
if (typeof raw !== 'string' || !raw.trim())
|
|
573
573
|
return undefined;
|
|
574
574
|
const text = raw.trim();
|
|
575
575
|
const htmlStart = text.toLowerCase().indexOf('<html');
|
|
576
576
|
return htmlStart >= 0 ? text.slice(0, htmlStart).trimEnd() : text;
|
|
577
577
|
}
|
|
578
|
-
function projectedRawError(raw, displayRaw) {
|
|
579
|
-
if (typeof raw !== 'string')
|
|
580
|
-
return raw;
|
|
581
|
-
return raw.trim() ? displayRaw : raw;
|
|
582
|
-
}
|
|
583
578
|
function appendRawErrorText(text, raw) {
|
|
584
|
-
const
|
|
585
|
-
if (!
|
|
579
|
+
const rawText = rawErrorText(raw);
|
|
580
|
+
if (!rawText || text.includes(rawText))
|
|
586
581
|
return text;
|
|
587
|
-
return `${text}\n原因:${
|
|
582
|
+
return `${text}\n原因:${rawText}`;
|
|
588
583
|
}
|
|
589
584
|
/**
|
|
590
585
|
* 把 AUNChannel 投递的 opts 映射成渠道无关的 InboundMessage。
|
|
@@ -4045,6 +4040,9 @@ export class AUNChannel {
|
|
|
4045
4040
|
finalPayload.task_id = context.metadata.taskId;
|
|
4046
4041
|
if (context?.metadata?.chatmode && !finalPayload.chatmode)
|
|
4047
4042
|
finalPayload.chatmode = context.metadata.chatmode;
|
|
4043
|
+
// 结构化/通知类 payload 与文本路径共用同一份诊断字典;缺省不带,sentAt 由发送时刻盖戳。
|
|
4044
|
+
if (context?.debug)
|
|
4045
|
+
finalPayload.debug = { ...context.debug, sentAt: localTimestamp() };
|
|
4048
4046
|
return finalPayload;
|
|
4049
4047
|
}
|
|
4050
4048
|
/**
|
|
@@ -4299,15 +4297,17 @@ export class AUNChannel {
|
|
|
4299
4297
|
const now = Date.now();
|
|
4300
4298
|
const mapTtl = typeof action.expiresAt === 'number' && Number.isFinite(action.expiresAt)
|
|
4301
4299
|
? Math.max(0, action.expiresAt - now)
|
|
4302
|
-
:
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4300
|
+
: undefined;
|
|
4301
|
+
if (mapTtl !== undefined) {
|
|
4302
|
+
const mapTimer = setTimeout(() => {
|
|
4303
|
+
this.cardMessageIdMap.delete(messageId);
|
|
4304
|
+
const ids = this.interactionCardMessageIds.get(action.requestId);
|
|
4305
|
+
ids?.delete(messageId);
|
|
4306
|
+
if (ids && ids.size === 0)
|
|
4307
|
+
this.interactionCardMessageIds.delete(action.requestId);
|
|
4308
|
+
}, mapTtl);
|
|
4309
|
+
mapTimer.unref?.();
|
|
4310
|
+
}
|
|
4311
4311
|
const ownedTimer = setTimeout(() => {
|
|
4312
4312
|
this.ownedCardMsgIds.delete(messageId);
|
|
4313
4313
|
this.ownedCardTopics.delete(messageId);
|
|
@@ -4492,6 +4492,7 @@ export class AUNChannel {
|
|
|
4492
4492
|
channelId,
|
|
4493
4493
|
delivery,
|
|
4494
4494
|
dedupeKey: opts.dedupeKey,
|
|
4495
|
+
critical: opts.critical,
|
|
4495
4496
|
type: 'payload',
|
|
4496
4497
|
contentKind: opts.contentKind,
|
|
4497
4498
|
payload: finalPayload,
|
|
@@ -4865,7 +4866,10 @@ export class AUNChannel {
|
|
|
4865
4866
|
}).effectiveRole;
|
|
4866
4867
|
}
|
|
4867
4868
|
catch { /* 角色解析失败按未知处理 */ }
|
|
4868
|
-
chatmode = chatmodeForPeerIdentity({ isMachine: info.type !== 'human', type: info.type ?? undefined }, peerRole
|
|
4869
|
+
chatmode = chatmodeForPeerIdentity({ isMachine: info.type !== 'human', type: info.type ?? undefined }, peerRole, {
|
|
4870
|
+
self: this.config.aid,
|
|
4871
|
+
peerKey: formatPeerKey('aun', args.to),
|
|
4872
|
+
});
|
|
4869
4873
|
}
|
|
4870
4874
|
if (chatmode && !finalPayload.chatmode)
|
|
4871
4875
|
finalPayload.chatmode = chatmode;
|
|
@@ -6604,26 +6608,36 @@ export class AUNChannelPlugin {
|
|
|
6604
6608
|
}
|
|
6605
6609
|
const taskBase = () => channel.buildTaskPayloadBase(envelope, replyCtx);
|
|
6606
6610
|
switch (payload.kind) {
|
|
6607
|
-
case 'result.text':
|
|
6608
|
-
case 'command.result':
|
|
6609
6611
|
case 'command.error': {
|
|
6612
|
+
const raw = rawErrorText(payload.raw) ?? payload.text;
|
|
6613
|
+
const errorPayload = {
|
|
6614
|
+
type: 'error',
|
|
6615
|
+
...taskBase(),
|
|
6616
|
+
subtype: payload.kind,
|
|
6617
|
+
text: appendRawErrorText(payload.text, raw),
|
|
6618
|
+
message: payload.text,
|
|
6619
|
+
user_message: payload.text,
|
|
6620
|
+
raw,
|
|
6621
|
+
recoverable: true,
|
|
6622
|
+
};
|
|
6623
|
+
for (const key of ['format', 'reason', 'reasonCode', 'nextStep']) {
|
|
6624
|
+
setIfDefined(errorPayload, key, payload[key]);
|
|
6625
|
+
}
|
|
6626
|
+
await channel.sendReliableStructured(channelId, errorPayload, { ...(replyCtx ?? {}), suppressPeerMention: true }, payload.text);
|
|
6627
|
+
return;
|
|
6628
|
+
}
|
|
6629
|
+
case 'result.text':
|
|
6630
|
+
case 'command.result': {
|
|
6610
6631
|
// Group command feedback is control traffic, not model input for
|
|
6611
6632
|
// the other agents in the conversation.
|
|
6612
|
-
if (delivery.chatType === 'group'
|
|
6613
|
-
&& (payload.kind === 'command.result' || payload.kind === 'command.error')) {
|
|
6614
|
-
const isError = payload.kind === 'command.error';
|
|
6633
|
+
if (delivery.chatType === 'group' && payload.kind === 'command.result') {
|
|
6615
6634
|
const feedback = {
|
|
6616
|
-
type:
|
|
6635
|
+
type: 'notice',
|
|
6617
6636
|
...taskBase(),
|
|
6618
6637
|
subtype: payload.kind,
|
|
6619
6638
|
text: payload.text,
|
|
6620
|
-
|
|
6621
|
-
? { message: payload.text, user_message: payload.text, recoverable: true }
|
|
6622
|
-
: { severity: 'info' }),
|
|
6639
|
+
severity: 'info',
|
|
6623
6640
|
};
|
|
6624
|
-
for (const key of ['format', 'reason', 'reasonCode', 'nextStep']) {
|
|
6625
|
-
setIfDefined(feedback, key, payload[key]);
|
|
6626
|
-
}
|
|
6627
6641
|
await channel.sendReliableStructured(channelId, feedback, { ...(replyCtx ?? {}), suppressPeerMention: true }, payload.text);
|
|
6628
6642
|
return;
|
|
6629
6643
|
}
|
|
@@ -6699,35 +6713,29 @@ export class AUNChannelPlugin {
|
|
|
6699
6713
|
return;
|
|
6700
6714
|
}
|
|
6701
6715
|
case 'system.error': {
|
|
6702
|
-
const raw =
|
|
6703
|
-
const projectedRaw = projectedRawError(payload.raw, raw);
|
|
6716
|
+
const raw = rawErrorText(payload.raw) ?? payload.text;
|
|
6704
6717
|
const errorPayload = {
|
|
6705
|
-
type:
|
|
6718
|
+
type: 'error',
|
|
6706
6719
|
...taskBase(),
|
|
6707
6720
|
subtype: payload.subtype,
|
|
6708
|
-
text: payload.
|
|
6709
|
-
|
|
6710
|
-
: appendRawErrorText(payload.text, raw),
|
|
6711
|
-
...(projectedRaw !== undefined ? { raw: projectedRaw } : {}),
|
|
6721
|
+
text: appendRawErrorText(payload.text, raw),
|
|
6722
|
+
raw,
|
|
6712
6723
|
...(payload.reason !== undefined ? { reason: payload.reason } : {}),
|
|
6713
|
-
...(payload.recoverable
|
|
6714
|
-
? { severity: 'warn' }
|
|
6715
|
-
: { recoverable: false }),
|
|
6724
|
+
...(payload.recoverable !== undefined ? { recoverable: payload.recoverable } : {}),
|
|
6716
6725
|
};
|
|
6717
6726
|
await channel.sendReliableStructured(channelId, errorPayload, { ...(replyCtx ?? {}), suppressPeerMention: true }, payload.text);
|
|
6718
6727
|
return;
|
|
6719
6728
|
}
|
|
6720
6729
|
case 'result.error': {
|
|
6721
|
-
const raw =
|
|
6722
|
-
const projectedRaw = projectedRawError(payload.raw, raw);
|
|
6730
|
+
const raw = rawErrorText(payload.raw) ?? payload.text;
|
|
6723
6731
|
const errorPayload = {
|
|
6724
6732
|
type: 'error',
|
|
6725
6733
|
...taskBase(),
|
|
6726
6734
|
text: appendRawErrorText(payload.text, raw),
|
|
6735
|
+
raw,
|
|
6727
6736
|
};
|
|
6728
6737
|
setIfDefined(errorPayload, 'subtype', payload.subtype);
|
|
6729
6738
|
setIfDefined(errorPayload, 'reason', payload.reason);
|
|
6730
|
-
setIfDefined(errorPayload, 'raw', projectedRaw);
|
|
6731
6739
|
await channel.sendReliableStructured(channelId, errorPayload, { ...(replyCtx ?? {}), suppressPeerMention: true }, payload.text);
|
|
6732
6740
|
return;
|
|
6733
6741
|
}
|
|
@@ -6859,9 +6867,10 @@ export class AUNChannelPlugin {
|
|
|
6859
6867
|
case 'interaction': {
|
|
6860
6868
|
const req = payload.interaction;
|
|
6861
6869
|
const cardTtlMs = AUN_INTERACTION_CARD_TTL_MS;
|
|
6870
|
+
const indefiniteBlockingInteraction = req.blocking === true && req.expiresAt === undefined;
|
|
6862
6871
|
const cardExpiresAt = typeof req.expiresAt === 'number' && Number.isFinite(req.expiresAt)
|
|
6863
6872
|
? req.expiresAt
|
|
6864
|
-
: Date.now() + cardTtlMs;
|
|
6873
|
+
: indefiniteBlockingInteraction ? undefined : Date.now() + cardTtlMs;
|
|
6865
6874
|
const toInteractionReceipt = (result) => {
|
|
6866
6875
|
if ((result.status === undefined || result.status === 'sent') && result.messageId) {
|
|
6867
6876
|
return sentReceipt(envelope, [result.messageId], replyCtx?.threadId);
|
|
@@ -6903,15 +6912,21 @@ export class AUNChannelPlugin {
|
|
|
6903
6912
|
const aunCard = withAunAttention({
|
|
6904
6913
|
type: 'action_card',
|
|
6905
6914
|
title: action.title,
|
|
6915
|
+
...(req.blocking === true ? { blocking: true } : {}),
|
|
6916
|
+
...(action.recommendedOption ? { recommended_option: action.recommendedOption } : {}),
|
|
6906
6917
|
actions: action.buttons.map(btn => ({
|
|
6907
|
-
label:
|
|
6918
|
+
label: action.recommendedOption === btn.key ? `💡 ${btn.label}` : btn.label,
|
|
6919
|
+
value: btn.key, style: btn.style ?? 'default', behavior: 'reply',
|
|
6908
6920
|
})),
|
|
6909
6921
|
});
|
|
6910
6922
|
// AUN action_card 以 compose behavior 声明自由文本动作。新版客户端
|
|
6911
6923
|
// 在卡片内采集并确认;旧版客户端可继续从主编辑器采集。两者都结构化
|
|
6912
6924
|
// 回传 action_card_reply{behavior:"compose"},Core 不拦截普通消息队列。
|
|
6913
6925
|
if (action.allowCustomInput) {
|
|
6914
|
-
aunCard.actions.push({
|
|
6926
|
+
aunCard.actions.push({
|
|
6927
|
+
label: '✏️ 手动输入', style: 'default', behavior: 'compose',
|
|
6928
|
+
...(cardExpiresAt !== undefined ? { compose_timeout_ms: Math.max(0, cardExpiresAt - Date.now()) } : {}),
|
|
6929
|
+
});
|
|
6915
6930
|
}
|
|
6916
6931
|
if (action.body) {
|
|
6917
6932
|
aunCard.text = action.body;
|
|
@@ -6928,7 +6943,10 @@ export class AUNChannelPlugin {
|
|
|
6928
6943
|
contentKind: 'card',
|
|
6929
6944
|
context: cardReplyContext,
|
|
6930
6945
|
dedupeKey: req.id.startsWith('gir-') || isAsyncQuestion ? operationId : undefined,
|
|
6931
|
-
|
|
6946
|
+
...(isAsyncQuestion && cardExpiresAt !== undefined
|
|
6947
|
+
? { ttl: Math.max(0, cardExpiresAt - Date.now()) }
|
|
6948
|
+
: {}),
|
|
6949
|
+
...(indefiniteBlockingInteraction ? { critical: true } : {}),
|
|
6932
6950
|
logText: action.title ? `[card] ${action.title}` : '[card]',
|
|
6933
6951
|
postSend: {
|
|
6934
6952
|
type: 'register_interaction_card',
|
|
@@ -88,7 +88,13 @@ export function buildDingtalkCardParamMap(interaction, state, status = '') {
|
|
|
88
88
|
};
|
|
89
89
|
for (let i = 0; i < DINGTALK_CARD_MAX_BUTTONS; i++) {
|
|
90
90
|
const button = interaction.kind.buttons[i];
|
|
91
|
-
map[`button_${i}_text`] = button
|
|
91
|
+
map[`button_${i}_text`] = button
|
|
92
|
+
? (interaction.kind.kind === 'action'
|
|
93
|
+
&& 'key' in button
|
|
94
|
+
&& interaction.kind.recommendedOption === button.key
|
|
95
|
+
? `💡 ${button.label}`
|
|
96
|
+
: button.label)
|
|
97
|
+
: '';
|
|
92
98
|
map[`button_${i}_style`] = button?.style || 'default';
|
|
93
99
|
map[`button_${i}_visible`] = button ? 'true' : 'false';
|
|
94
100
|
map[`button_${i}_disabled`] = state !== 'pending' || !!(button && 'disabled' in button && button.disabled)
|
|
@@ -1005,7 +1011,7 @@ export class DingtalkChannel {
|
|
|
1005
1011
|
continue;
|
|
1006
1012
|
}
|
|
1007
1013
|
const expired = (entry.interaction.expiresAt != null && entry.interaction.expiresAt <= now)
|
|
1008
|
-
|| entry.createdAt <= now - DINGTALK_CARD_PENDING_TTL_MS;
|
|
1014
|
+
|| (entry.interaction.blocking !== true && entry.createdAt <= now - DINGTALK_CARD_PENDING_TTL_MS);
|
|
1009
1015
|
if (!expired)
|
|
1010
1016
|
continue;
|
|
1011
1017
|
entry.invalidated = true;
|
package/dist/channels/feishu.js
CHANGED
|
@@ -1399,7 +1399,12 @@ export function buildCardV2(interaction, opts) {
|
|
|
1399
1399
|
buttons.forEach((btn, idx) => {
|
|
1400
1400
|
const buttonEl = {
|
|
1401
1401
|
tag: 'button',
|
|
1402
|
-
text: {
|
|
1402
|
+
text: {
|
|
1403
|
+
tag: 'plain_text',
|
|
1404
|
+
content: kind.kind === 'action' && kind.recommendedOption === btn.key
|
|
1405
|
+
? `💡 ${btn.label}`
|
|
1406
|
+
: btn.label,
|
|
1407
|
+
},
|
|
1403
1408
|
type: btn.style === 'danger' ? 'danger' : btn.style === 'primary' ? 'primary' : 'default',
|
|
1404
1409
|
action_type: 'form_submit',
|
|
1405
1410
|
name: `btn_${idx}`,
|