evolcore 0.0.20 → 0.0.22
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 +65 -0
- package/README.md +58 -9
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +393 -108
- package/dist/agents/codex-app-server-client.js +41 -7
- package/dist/agents/codex-runner.js +1292 -220
- package/dist/agents/ecagent-runner.js +171 -61
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +59 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +72 -6
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +5 -0
- package/dist/aun/outbox.js +189 -80
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +618 -123
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +98 -123
- package/dist/cli/init.js +27 -15
- package/dist/cli/task-context.js +50 -0
- package/dist/cli/trigger-command.js +14 -5
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +19 -5
- package/dist/config/config-manager.js +167 -22
- package/dist/config/contact-book-store.js +25 -3
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +20 -9
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +151 -9
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +43 -1
- package/dist/core/auth/auth-gateway.js +41 -4
- package/dist/core/auth/authorization-audit.js +216 -8
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +99 -27
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +311 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +288 -80
- package/dist/core/command/menu-protocol.js +1 -1
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +377 -36
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +37 -0
- package/dist/core/evolagent.js +4 -0
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +33 -3
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +90 -87
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +184 -12
- package/dist/core/message/message-log.js +47 -7
- package/dist/core/message/message-queue.js +227 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +658 -109
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +627 -69
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +159 -1
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +618 -23
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +329 -30
- package/dist/core/session/session-renew.js +37 -13
- package/dist/core/session/session-turn-coordinator.js +16 -5
- package/dist/eck/kit-renderer.js +1 -1
- package/dist/index.js +316 -50
- package/dist/ipc.js +459 -29
- package/dist/paths.js +82 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +70 -7
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +13 -3
- package/dist/trigger/scheduler.js +20 -3
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +30 -6
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +3 -3
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/group-collaboration.md +251 -0
- package/kits/docs/evolcore/group-rules.md +1 -19
- package/kits/docs/evolcore/group.md +3 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/docs/evolcore/trigger.md +6 -3
- package/kits/docs/prompt-loading-architecture.md +6 -0
- package/kits/eck_message_manifest.json +6 -6
- package/kits/schemas/_meta.json +7 -4
- package/kits/schemas/agent-config.schema.11.json +13 -0
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/schemas/daemon.schema.5.json +0 -1
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +15 -3
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +6 -3
- package/skills/eclink/SKILL.md +2 -0
- package/dist/config/aun-gateway-config.js +0 -2
|
@@ -14,13 +14,15 @@ import fs from 'fs';
|
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import os from 'os';
|
|
16
16
|
import crypto from 'crypto';
|
|
17
|
+
import { hasTrustedFullAccessContext } from './runner-types.js';
|
|
17
18
|
import { resolveGoogleConfig } from './baseagent.js';
|
|
18
19
|
import { commandExists } from '../utils/cross-platform.js';
|
|
19
20
|
import { GeminiSessionFileAdapter } from '../core/session/adapters/gemini-session-file-adapter.js';
|
|
20
21
|
import { logger } from '../utils/logger.js';
|
|
21
|
-
import { normalizePermissionMode } from '../core/permission/mode.js';
|
|
22
|
-
import { workspaceContainsHClassPaths } from '../core/protected-paths.js';
|
|
22
|
+
import { normalizeExecutionPermissionMode, normalizePermissionMode } from '../core/permission/mode.js';
|
|
23
|
+
import { workspaceContainsHClassPaths } from '../core/permission/protected-paths.js';
|
|
23
24
|
import { buildBubblewrapCommand } from '../core/permission/sandbox-runtime.js';
|
|
25
|
+
import { getManagedTaskTempDir } from '../cli/task-context.js';
|
|
24
26
|
// Strip ANSI escape codes from Gemini CLI text output.
|
|
25
27
|
// Gemini embeds raw terminal colors from tool stdout (e.g. vitest, npm)
|
|
26
28
|
// into its assistant text, unlike Claude SDK which strips them internally.
|
|
@@ -142,6 +144,8 @@ const GEMINI_EC_COMMAND_PATTERNS = [
|
|
|
142
144
|
String.raw `\x00"command":"[ ]*ec[ ]+(?:[^"\x00;&|$()<>\x60\r\n\\]|\\(?:\\|"))*"\x00`,
|
|
143
145
|
];
|
|
144
146
|
export function resolveGeminiPermissionProfile(value, bootstrap = false) {
|
|
147
|
+
if (value === 'fullaccess')
|
|
148
|
+
return { mode: 'fullaccess', approvalMode: 'yolo' };
|
|
145
149
|
const normalized = normalizePermissionMode(value);
|
|
146
150
|
const profile = normalized.mode === 'auto'
|
|
147
151
|
? { mode: 'auto', approvalMode: 'auto_edit' }
|
|
@@ -238,6 +242,12 @@ export function buildGeminiAdminPolicy(profile, bootstrapAgentMdPath) {
|
|
|
238
242
|
return lines.join('\n');
|
|
239
243
|
}
|
|
240
244
|
export function buildGeminiPermissionArgs(profile, policyPath, externallySandboxed = false) {
|
|
245
|
+
if (profile.mode === 'fullaccess') {
|
|
246
|
+
// Absence of --sandbox is not enough: ~/.gemini/settings.json may enable
|
|
247
|
+
// it again. An explicit CLI false keeps this per-call host profile
|
|
248
|
+
// authoritative over user/project settings.
|
|
249
|
+
return ['--sandbox=false', `--approval-mode=${profile.approvalMode}`];
|
|
250
|
+
}
|
|
241
251
|
return [
|
|
242
252
|
'--admin-policy', policyPath,
|
|
243
253
|
...(!externallySandboxed ? ['--sandbox'] : []),
|
|
@@ -269,7 +279,7 @@ export function hasStandardGeminiAdminPolicy() {
|
|
|
269
279
|
// ── Gemini Runner ──
|
|
270
280
|
export class GeminiRunner {
|
|
271
281
|
name = 'gemini';
|
|
272
|
-
capabilities = { clear: true, compact: false, fork: false, forkAtTurn: false, askUserQuestion: false, planApproval: false, fileRewind: 'unsupported' };
|
|
282
|
+
capabilities = { fullaccess: true, clear: true, compact: false, fork: false, forkAtTurn: false, askUserQuestion: false, planApproval: false, fileRewind: 'unsupported' };
|
|
273
283
|
resolved;
|
|
274
284
|
model;
|
|
275
285
|
activeProcesses = new Map();
|
|
@@ -329,22 +339,39 @@ export class GeminiRunner {
|
|
|
329
339
|
}
|
|
330
340
|
// ── Core: runQuery ──
|
|
331
341
|
async runQuery(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
|
|
332
|
-
|
|
342
|
+
const topicBinding = modelOverride?.backend?.kind === 'topic';
|
|
343
|
+
if (topicBinding && (!modelOverride?.turn || modelOverride.turn.sessionId !== sessionId || !this.onSessionIdUpdate)) {
|
|
344
|
+
const error = new Error('topic backend run requires an active TurnLease and binding callback');
|
|
345
|
+
error.code = 'TOPIC_TURN_LEASE_REQUIRED';
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
const topicAgentSessionId = topicBinding ? modelOverride?.backend?.agentSessionId ?? null : undefined;
|
|
349
|
+
const cachedSessionId = topicBinding
|
|
350
|
+
? (topicAgentSessionId && this.activeSessions.get(sessionId) === topicAgentSessionId ? topicAgentSessionId : undefined)
|
|
351
|
+
: this.activeSessions.get(sessionId);
|
|
352
|
+
let geminiSessionId = topicBinding
|
|
353
|
+
? (topicAgentSessionId || undefined)
|
|
354
|
+
: initialAgentSessionId || cachedSessionId;
|
|
333
355
|
// per-call 权限模式/模型:优先 override,缺省回落实例级(多会话并发互不污染)
|
|
334
356
|
const requestedPermissionMode = modelOverride?.permissionMode || this.currentMode;
|
|
357
|
+
const executionPermissionMode = normalizeExecutionPermissionMode(requestedPermissionMode);
|
|
358
|
+
if (executionPermissionMode === 'fullaccess' && !hasTrustedFullAccessContext(this.permissionContexts.get(sessionId))) {
|
|
359
|
+
throw new Error('Gemini fullaccess requires a trusted per-call execution authorization');
|
|
360
|
+
}
|
|
335
361
|
const bootstrapAgentMdPath = modelOverride?.bootstrapAgentMdPath
|
|
336
362
|
? normalizeGeminiBootstrapAgentMdPath(modelOverride.bootstrapAgentMdPath).filePath
|
|
337
363
|
: undefined;
|
|
338
364
|
const permissionProfile = resolveGeminiPermissionProfile(requestedPermissionMode, !!bootstrapAgentMdPath);
|
|
339
365
|
const callModel = modelOverride?.model || this.model;
|
|
340
|
-
const
|
|
366
|
+
const managedTaskTmpDir = getManagedTaskTempDir(runtimeEnv);
|
|
367
|
+
const runtimeDir = managedTaskTmpDir ?? runtimeEnv?.EVOLCORE_SESSION_RUNTIME_DIR;
|
|
341
368
|
const managedTmpDir = runtimeDir ?? process.env.TMPDIR;
|
|
342
369
|
if (!managedTmpDir || !path.isAbsolute(managedTmpDir))
|
|
343
370
|
throw new Error('managed TMPDIR is unset or not absolute');
|
|
344
371
|
const runtimeLockDir = runtimeEnv?.EVOLCORE_RUNTIME_LOCK_DIR;
|
|
345
372
|
const geminiHome = path.join(os.homedir(), '.gemini');
|
|
346
373
|
fs.mkdirSync(geminiHome, { recursive: true, mode: 0o700 });
|
|
347
|
-
const sandboxProbe = buildBubblewrapCommand(this.resolved.cliPath, [], {
|
|
374
|
+
const sandboxProbe = executionPermissionMode === 'fullaccess' ? undefined : buildBubblewrapCommand(this.resolved.cliPath, [], {
|
|
348
375
|
projectPath,
|
|
349
376
|
writablePaths: [
|
|
350
377
|
geminiHome,
|
|
@@ -353,7 +380,7 @@ export class GeminiRunner {
|
|
|
353
380
|
...(bootstrapAgentMdPath ? [bootstrapAgentMdPath] : []),
|
|
354
381
|
],
|
|
355
382
|
});
|
|
356
|
-
if (!sandboxProbe && workspaceContainsHClassPaths(projectPath)) {
|
|
383
|
+
if (executionPermissionMode !== 'fullaccess' && !sandboxProbe && workspaceContainsHClassPaths(projectPath)) {
|
|
357
384
|
throw new Error('Gemini 缺少可用的路径级隔离运行时,且项目覆盖 EvolCore 受保护根;已拒绝启动本轮任务');
|
|
358
385
|
}
|
|
359
386
|
// Build CLI args
|
|
@@ -369,12 +396,20 @@ export class GeminiRunner {
|
|
|
369
396
|
const tempFiles = [];
|
|
370
397
|
{
|
|
371
398
|
if (hasStandardGeminiAdminPolicy()) {
|
|
399
|
+
if (executionPermissionMode === 'fullaccess') {
|
|
400
|
+
throw new Error('Gemini 系统级 admin policy 无法被本次调用可靠关闭;已拒绝以不完整的 fullaccess profile 启动');
|
|
401
|
+
}
|
|
372
402
|
throw new Error('Gemini 系统级 admin policy 会忽略 EvolCore 的临时安全策略,已拒绝启动本轮任务');
|
|
373
403
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
404
|
+
if (executionPermissionMode === 'fullaccess') {
|
|
405
|
+
args.push(...buildGeminiPermissionArgs(permissionProfile, '', false));
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
const policyPath = path.join(managedTmpDir, `evolcore-gemini-permission-${crypto.randomUUID()}.toml`);
|
|
409
|
+
fs.writeFileSync(policyPath, buildGeminiAdminPolicy(permissionProfile, bootstrapAgentMdPath), { mode: 0o600, flag: 'wx' });
|
|
410
|
+
tempFiles.push(policyPath);
|
|
411
|
+
args.push(...buildGeminiPermissionArgs(permissionProfile, policyPath, !!sandboxProbe));
|
|
412
|
+
}
|
|
378
413
|
}
|
|
379
414
|
if (images?.length) {
|
|
380
415
|
const tmpDir = managedTmpDir;
|
|
@@ -406,7 +441,7 @@ export class GeminiRunner {
|
|
|
406
441
|
}
|
|
407
442
|
// Spawn subprocess
|
|
408
443
|
const env = this.buildAgentEnv(sessionId, runtimeEnv);
|
|
409
|
-
const sandboxedCommand = buildBubblewrapCommand(this.resolved.cliPath, args, {
|
|
444
|
+
const sandboxedCommand = executionPermissionMode === 'fullaccess' ? undefined : buildBubblewrapCommand(this.resolved.cliPath, args, {
|
|
410
445
|
projectPath,
|
|
411
446
|
writablePaths: [
|
|
412
447
|
geminiHome,
|
|
@@ -429,10 +464,10 @@ export class GeminiRunner {
|
|
|
429
464
|
if (msg)
|
|
430
465
|
logger.debug(`[GeminiRunner:stderr] ${msg}`);
|
|
431
466
|
});
|
|
432
|
-
return this.transformStream(child, sessionId, tempFiles);
|
|
467
|
+
return this.transformStream(child, sessionId, tempFiles, modelOverride?.turn);
|
|
433
468
|
}
|
|
434
469
|
// ── Event stream transformation ──
|
|
435
|
-
async *transformStream(child, sessionId, tempFiles) {
|
|
470
|
+
async *transformStream(child, sessionId, tempFiles, turn) {
|
|
436
471
|
const pendingToolNames = new Map(); // toolId → toolName
|
|
437
472
|
const recordedDeniedToolIds = new Set();
|
|
438
473
|
const startTime = Date.now();
|
|
@@ -506,8 +541,19 @@ export class GeminiRunner {
|
|
|
506
541
|
// Extract session_id from init event
|
|
507
542
|
const geminiId = event.session_id;
|
|
508
543
|
if (geminiId) {
|
|
544
|
+
const result = this.onSessionIdUpdate
|
|
545
|
+
? (turn === undefined
|
|
546
|
+
? await this.onSessionIdUpdate(sessionId, geminiId)
|
|
547
|
+
: await this.onSessionIdUpdate(sessionId, geminiId, { turn }))
|
|
548
|
+
: 'legacy_updated';
|
|
549
|
+
const accepted = result === undefined || result === 'activated' || result === 'already_active' || result === 'legacy_updated';
|
|
550
|
+
if (!accepted) {
|
|
551
|
+
child.kill('SIGTERM');
|
|
552
|
+
const error = new Error(`backend discovery rejected: ${result}`);
|
|
553
|
+
error.code = 'TOPIC_BACKEND_DISCOVERY_REJECTED';
|
|
554
|
+
throw error;
|
|
555
|
+
}
|
|
509
556
|
this.activeSessions.set(sessionId, geminiId);
|
|
510
|
-
this.onSessionIdUpdate?.(sessionId, geminiId);
|
|
511
557
|
yield { type: 'session_id', sessionId: geminiId };
|
|
512
558
|
}
|
|
513
559
|
break;
|
|
@@ -619,7 +665,9 @@ export class GeminiRunner {
|
|
|
619
665
|
}
|
|
620
666
|
finally {
|
|
621
667
|
rl.close();
|
|
622
|
-
this.activeProcesses.
|
|
668
|
+
if (this.activeProcesses.get(sessionId) === child) {
|
|
669
|
+
this.activeProcesses.delete(sessionId);
|
|
670
|
+
}
|
|
623
671
|
// Kill process if still running
|
|
624
672
|
if (!child.killed && !processExited) {
|
|
625
673
|
child.kill('SIGTERM');
|
|
@@ -652,6 +700,12 @@ export class GeminiRunner {
|
|
|
652
700
|
toolName,
|
|
653
701
|
...(requestId ? { requestId } : {}),
|
|
654
702
|
policy: context.approvalRouting?.approverPolicy ?? 'requester',
|
|
703
|
+
policyCode: 'permission_mode_denied',
|
|
704
|
+
decisionSource: 'policy',
|
|
705
|
+
agentAid: context.selfAid,
|
|
706
|
+
agentName: context.agentName,
|
|
707
|
+
sessionId,
|
|
708
|
+
permissionMode: context.permissionMode,
|
|
655
709
|
summary: `${toolName} runtime authorization denied by Gemini headless policy`,
|
|
656
710
|
effect: 'operation_skipped',
|
|
657
711
|
});
|
|
@@ -679,19 +733,65 @@ export class GeminiRunner {
|
|
|
679
733
|
}
|
|
680
734
|
// ── Session commands ──
|
|
681
735
|
updateSessionId(sessionId, agentSessionId) {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
736
|
+
const previousId = this.activeSessions.get(sessionId);
|
|
737
|
+
const applyLocal = () => {
|
|
738
|
+
const currentId = this.activeSessions.get(sessionId);
|
|
739
|
+
if (currentId !== previousId && !(previousId === undefined && currentId === undefined))
|
|
740
|
+
return;
|
|
741
|
+
if (agentSessionId) {
|
|
742
|
+
this.activeSessions.set(sessionId, agentSessionId);
|
|
743
|
+
}
|
|
744
|
+
else if (this.activeSessions.get(sessionId) === previousId) {
|
|
745
|
+
this.activeSessions.delete(sessionId);
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
if (!this.onSessionIdUpdate) {
|
|
749
|
+
applyLocal();
|
|
750
|
+
return;
|
|
687
751
|
}
|
|
688
|
-
this.onSessionIdUpdate
|
|
752
|
+
void Promise.resolve(this.onSessionIdUpdate(sessionId, agentSessionId)).then(result => {
|
|
753
|
+
if (result === undefined || result === 'activated' || result === 'already_active' || result === 'legacy_updated' || !agentSessionId)
|
|
754
|
+
applyLocal();
|
|
755
|
+
}).catch(error => {
|
|
756
|
+
logger.warn(`[GeminiRunner] session binding callback failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
757
|
+
if (!agentSessionId)
|
|
758
|
+
applyLocal();
|
|
759
|
+
});
|
|
689
760
|
}
|
|
690
761
|
async closeSession(sessionId) {
|
|
691
|
-
this.activeSessions.
|
|
692
|
-
this.activeStreams.
|
|
693
|
-
this.
|
|
694
|
-
this.
|
|
762
|
+
const capturedId = this.activeSessions.get(sessionId);
|
|
763
|
+
const capturedStream = this.activeStreams.get(sessionId);
|
|
764
|
+
const capturedPermissionContext = this.permissionContexts.get(sessionId);
|
|
765
|
+
const child = this.activeProcesses.get(sessionId);
|
|
766
|
+
if (child && child.exitCode === null && child.signalCode === null) {
|
|
767
|
+
await new Promise(resolve => {
|
|
768
|
+
let settled = false;
|
|
769
|
+
const finish = () => {
|
|
770
|
+
if (settled)
|
|
771
|
+
return;
|
|
772
|
+
settled = true;
|
|
773
|
+
clearTimeout(killTimer);
|
|
774
|
+
child.off('exit', finish);
|
|
775
|
+
child.off('error', finish);
|
|
776
|
+
resolve();
|
|
777
|
+
};
|
|
778
|
+
child.once('exit', finish);
|
|
779
|
+
child.once('error', finish);
|
|
780
|
+
child.kill('SIGTERM');
|
|
781
|
+
const killTimer = setTimeout(() => {
|
|
782
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
783
|
+
child.kill('SIGKILL');
|
|
784
|
+
}, 3_000);
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
if (this.activeSessions.get(sessionId) === capturedId)
|
|
788
|
+
this.activeSessions.delete(sessionId);
|
|
789
|
+
if (this.activeStreams.get(sessionId) === capturedStream)
|
|
790
|
+
this.activeStreams.delete(sessionId);
|
|
791
|
+
if (!child || this.activeProcesses.get(sessionId) === child)
|
|
792
|
+
this.activeProcesses.delete(sessionId);
|
|
793
|
+
if (this.permissionContexts.get(sessionId) === capturedPermissionContext)
|
|
794
|
+
this.permissionContexts.delete(sessionId);
|
|
695
795
|
}
|
|
696
796
|
resolveSessionFile(agentSessionId, projectPath) {
|
|
697
797
|
const adapter = new GeminiSessionFileAdapter();
|
|
@@ -702,12 +802,12 @@ export class GeminiRunner {
|
|
|
702
802
|
this.activeSessions.delete(sessionId);
|
|
703
803
|
return true;
|
|
704
804
|
}
|
|
705
|
-
async compactSession(_sessionId, _agentSessionId, _projectPath) {
|
|
805
|
+
async compactSession(_sessionId, _agentSessionId, _projectPath, _modelOverride) {
|
|
706
806
|
logger.info('[GeminiRunner] Compact not supported, Gemini CLI handles context internally');
|
|
707
|
-
return false;
|
|
807
|
+
return { ok: false, code: 'unsupported', message: 'Gemini CLI handles context internally', durationMs: 0 };
|
|
708
808
|
}
|
|
709
|
-
async compact(_sessionId, _agentSessionId, _projectPath) {
|
|
710
|
-
return this.compactSession(_sessionId, _agentSessionId, _projectPath);
|
|
809
|
+
async compact(_sessionId, _agentSessionId, _projectPath, modelOverride) {
|
|
810
|
+
return this.compactSession(_sessionId, _agentSessionId, _projectPath, modelOverride);
|
|
711
811
|
}
|
|
712
812
|
setCompactStartCallback(_callback) { }
|
|
713
813
|
// ── Cleanup ──
|
|
@@ -53,3 +53,28 @@ export function buildModelRequestHeaders(input) {
|
|
|
53
53
|
export function _resetRequestIdentityWarningsForTests() {
|
|
54
54
|
warnedReservedHeaders.clear();
|
|
55
55
|
}
|
|
56
|
+
/** Preserve existing query parameters and replace configured names deterministically. */
|
|
57
|
+
export function appendRequestQueryParams(url, queryParams) {
|
|
58
|
+
if (!queryParams || Object.keys(queryParams).length === 0)
|
|
59
|
+
return url;
|
|
60
|
+
const parsed = new URL(url);
|
|
61
|
+
for (const [name, value] of Object.entries(queryParams))
|
|
62
|
+
parsed.searchParams.set(name, value);
|
|
63
|
+
return parsed.toString();
|
|
64
|
+
}
|
|
65
|
+
/** Append an API resource to the URL pathname without corrupting an existing query string. */
|
|
66
|
+
export function appendRequestPath(baseUrl, resourcePath, queryParams) {
|
|
67
|
+
const parsed = new URL(baseUrl);
|
|
68
|
+
const basePath = parsed.pathname.replace(/\/+$/u, '');
|
|
69
|
+
const resource = resourcePath.replace(/^\/+|\/+$/gu, '');
|
|
70
|
+
parsed.pathname = `${basePath}/${resource}`;
|
|
71
|
+
for (const [name, value] of Object.entries(queryParams ?? {}))
|
|
72
|
+
parsed.searchParams.set(name, value);
|
|
73
|
+
return parsed.toString();
|
|
74
|
+
}
|
|
75
|
+
/** Append a resource below /v1 unless the configured pathname already ends there. */
|
|
76
|
+
export function appendV1RequestPath(baseUrl, resourcePath, queryParams) {
|
|
77
|
+
const pathname = new URL(baseUrl).pathname.replace(/\/+$/u, '');
|
|
78
|
+
const resource = resourcePath.replace(/^\/+|\/+$/gu, '').replace(/^v1\/+/, '');
|
|
79
|
+
return appendRequestPath(baseUrl, pathname.endsWith('/v1') ? resource : `v1/${resource}`, queryParams);
|
|
80
|
+
}
|
|
@@ -12,6 +12,25 @@ export class BaseagentRunnerUnavailableError extends Error {
|
|
|
12
12
|
this.name = 'BaseagentRunnerUnavailableError';
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
export function hasTrustedFullAccessContext(context) {
|
|
16
|
+
const authorization = context?.executionPermission;
|
|
17
|
+
if (!authorization
|
|
18
|
+
|| authorization.permissionMode !== 'fullaccess'
|
|
19
|
+
|| authorization.processRole !== 'fullaccess-run'
|
|
20
|
+
|| authorization.dataScope !== 'daemon')
|
|
21
|
+
return false;
|
|
22
|
+
if (authorization.source === 'fullaccess-command') {
|
|
23
|
+
return typeof authorization.authorizedBy === 'string' && authorization.authorizedBy.length > 0;
|
|
24
|
+
}
|
|
25
|
+
return authorization.source === 'trigger'
|
|
26
|
+
&& typeof authorization.authorizedBy === 'string' && authorization.authorizedBy.length > 0
|
|
27
|
+
&& typeof authorization.triggerId === 'string' && authorization.triggerId.length > 0
|
|
28
|
+
&& typeof authorization.runId === 'string' && authorization.runId.length > 0
|
|
29
|
+
// Trigger fullaccess is authorized for one scheduler attempt. Requiring
|
|
30
|
+
// its stable attempt identity prevents a partial trigger envelope from
|
|
31
|
+
// being mistaken for a runnable privileged invocation.
|
|
32
|
+
&& typeof authorization.attemptId === 'string' && authorization.attemptId.length > 0;
|
|
33
|
+
}
|
|
15
34
|
// ── 类型守卫 ──
|
|
16
35
|
export function hasModelSwitcher(agent) {
|
|
17
36
|
return typeof agent.setModel === 'function' && typeof agent.listModels === 'function';
|
package/dist/aun/aid/agentmd.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { parseDocument } from 'yaml';
|
|
3
4
|
import { getAidStore, loadAid, SLOT } from './store.js';
|
|
4
5
|
import { agentMdPath, aidLocalDir, resolveRoot } from '../../paths.js';
|
|
6
|
+
export const DEFAULT_AGENT_DESCRIPTION = 'EvolCore AI agent.';
|
|
5
7
|
const displayNameCache = new Map();
|
|
6
8
|
const displayNamePending = new Map();
|
|
7
9
|
const displayNameRevision = new Map();
|
|
@@ -82,9 +84,10 @@ export function invalidateAgentDisplayName(aid) {
|
|
|
82
84
|
displayNameRevision.set(normalizedAid, (displayNameRevision.get(normalizedAid) ?? 0) + 1);
|
|
83
85
|
}
|
|
84
86
|
export function buildInitialAgentMd(opts) {
|
|
85
|
-
const agentName = opts.aid.split('.')[0];
|
|
87
|
+
const agentName = opts.name?.trim() || opts.aid.split('.')[0];
|
|
86
88
|
const agentType = opts.type || 'ai';
|
|
87
|
-
|
|
89
|
+
const description = opts.description?.trim() || DEFAULT_AGENT_DESCRIPTION;
|
|
90
|
+
return `---\naid: ${yamlDoubleQuote(opts.aid)}\nname: ${yamlDoubleQuote(agentName)}\ntype: ${yamlDoubleQuote(agentType)}\nversion: "1.0.0"\ndescription: ${yamlDoubleQuote(description)}\ntags:\n - evolcore\n---\n`;
|
|
88
91
|
}
|
|
89
92
|
function ensureTrailingNewline(content) {
|
|
90
93
|
return content.endsWith('\n') ? content : `${content}\n`;
|
|
@@ -98,6 +101,56 @@ function lineEnding(line, fallback) {
|
|
|
98
101
|
function yamlDoubleQuote(value) {
|
|
99
102
|
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
100
103
|
}
|
|
104
|
+
/** Validate the gateway-facing agent.md frontmatter before signing or upload. */
|
|
105
|
+
export function validateAgentMdFrontmatter(content, expectedAid) {
|
|
106
|
+
const errors = [];
|
|
107
|
+
const payload = stripAgentMdSignature(content);
|
|
108
|
+
if (Buffer.byteLength(payload, 'utf8') > 4 * 1024) {
|
|
109
|
+
errors.push('agent.md must not exceed 4KB before the signature block');
|
|
110
|
+
}
|
|
111
|
+
const frontmatter = payload.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1];
|
|
112
|
+
if (!frontmatter)
|
|
113
|
+
return { valid: false, errors: [...errors, 'agent.md has no YAML frontmatter'] };
|
|
114
|
+
const document = parseDocument(frontmatter);
|
|
115
|
+
if (document.errors.length > 0) {
|
|
116
|
+
return {
|
|
117
|
+
valid: false,
|
|
118
|
+
errors: [...errors, ...document.errors.map(error => `invalid YAML frontmatter: ${error.message}`)],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const fields = document.toJS();
|
|
122
|
+
if (!fields || typeof fields !== 'object' || Array.isArray(fields)) {
|
|
123
|
+
return { valid: false, errors: [...errors, 'agent.md frontmatter must be a mapping'] };
|
|
124
|
+
}
|
|
125
|
+
const values = fields;
|
|
126
|
+
const requiredString = (key) => {
|
|
127
|
+
const value = values[key];
|
|
128
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
129
|
+
errors.push(`agent.md frontmatter ${key} must be a non-empty string`);
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
return value.trim();
|
|
133
|
+
};
|
|
134
|
+
const aid = requiredString('aid');
|
|
135
|
+
requiredString('name');
|
|
136
|
+
requiredString('type');
|
|
137
|
+
const version = requiredString('version');
|
|
138
|
+
const description = requiredString('description');
|
|
139
|
+
if (expectedAid && aid && aid !== expectedAid) {
|
|
140
|
+
errors.push(`agent.md frontmatter aid must match upload target ${expectedAid}`);
|
|
141
|
+
}
|
|
142
|
+
if (version && !/^\d+\.\d+\.\d+$/.test(version)) {
|
|
143
|
+
errors.push('agent.md frontmatter version must use major.minor.patch format');
|
|
144
|
+
}
|
|
145
|
+
if (description && description.length > 100) {
|
|
146
|
+
errors.push('agent.md frontmatter description must be at most 100 characters');
|
|
147
|
+
}
|
|
148
|
+
if (values.tags !== undefined && (!Array.isArray(values.tags)
|
|
149
|
+
|| values.tags.some(tag => typeof tag !== 'string' || !tag.trim()))) {
|
|
150
|
+
errors.push('agent.md frontmatter tags must be an array of non-empty strings');
|
|
151
|
+
}
|
|
152
|
+
return { valid: errors.length === 0, errors };
|
|
153
|
+
}
|
|
101
154
|
/**
|
|
102
155
|
* Remove the SDK-generated trailing signature block before modifying agent.md.
|
|
103
156
|
* Only a block at EOF is stripped, so body text that mentions AUN-SIGNATURE is preserved.
|
|
@@ -217,6 +270,10 @@ export async function agentmdGet(aid, opts) {
|
|
|
217
270
|
* its own LocalTokenStore + AuthFlow internally, so no AUNClient/authenticate needed.
|
|
218
271
|
*/
|
|
219
272
|
export async function agentmdPut(content, opts) {
|
|
273
|
+
const validation = validateAgentMdFrontmatter(content, opts.aid);
|
|
274
|
+
if (!validation.valid) {
|
|
275
|
+
throw new Error(`invalid agent.md frontmatter: ${validation.errors.join('; ')}`);
|
|
276
|
+
}
|
|
220
277
|
const aunPath = opts.aunPath ?? resolveRoot();
|
|
221
278
|
const store = opts.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
|
|
222
279
|
const ownStore = !opts.store;
|
package/dist/aun/aid/identity.js
CHANGED
|
@@ -111,7 +111,10 @@ export async function verifySignAbility(aid, opts) {
|
|
|
111
111
|
const code = e instanceof AidLoadError ? e.code : 'LOAD_FAILED';
|
|
112
112
|
return { ok: false, reason: `load failed: ${code} ${String(e?.message || e).slice(0, 100)}` };
|
|
113
113
|
}
|
|
114
|
-
|
|
114
|
+
// signAgentMd validates the same frontmatter contract as a published agent.md.
|
|
115
|
+
// Keep the probe minimal, but make it a valid document so self-checks do not
|
|
116
|
+
// reject otherwise healthy identities before exercising sign + verify.
|
|
117
|
+
const probe = `---\naid: "${aid}"\nname: "probe"\ntype: "ai"\nversion: "1.0.0"\ndescription: "Local signing self-check"\ntags:\n - evolcore\n---\n`;
|
|
115
118
|
const signRes = aidObj.signAgentMd(probe);
|
|
116
119
|
if (!signRes.ok) {
|
|
117
120
|
return { ok: false, reason: `sign failed: ${String(signRes.error?.message || signRes.error?.code).slice(0, 120)}` };
|
package/dist/aun/aid/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { isValidAid, aidList, aidListVerified, aidCreate, aidShow, aidDelete, aidLookup, verifySignAbility, probePkiRecoverability, appendAidLifecycle, readAidLifecycle } from './identity.js';
|
|
2
|
-
export { buildInitialAgentMd, agentmdGet, agentmdPut, agentmdSync, invalidateAgentDisplayName, parseAgentDisplayName, refreshAgentDisplayName, resolveAgentDisplayName, stripAgentMdSignature, updateAgentMdFrontmatterAvatar, updateAgentMdFrontmatterName, } from './agentmd.js';
|
|
2
|
+
export { DEFAULT_AGENT_DESCRIPTION, buildInitialAgentMd, validateAgentMdFrontmatter, agentmdGet, agentmdPut, agentmdSync, invalidateAgentDisplayName, parseAgentDisplayName, refreshAgentDisplayName, resolveAgentDisplayName, stripAgentMdSignature, updateAgentMdFrontmatterAvatar, updateAgentMdFrontmatterName, } from './agentmd.js';
|
|
3
3
|
export { MIN_AUN_CORE_SDK, AUN_CORE_SDK_PKG, isAunSdkVersionOk, resolveAunCoreSdkPkg, ensureAunSdk, isAunSdkReady, downloadCaRoot, suppressSdkLogs, } from './client.js';
|
|
4
4
|
export { getAidStore, loadClient, loadAid, AidLoadError, SLOT } from './store.js';
|
package/dist/aun/msg/group.js
CHANGED
|
@@ -15,6 +15,21 @@ import { resolvePaths } from '../../paths.js';
|
|
|
15
15
|
import { AGENT_DELEGATION_TOKEN_ENV } from '../../core/auth/agent-delegation.js';
|
|
16
16
|
import { normalizeAunMentionEntries } from './mention-schema.js';
|
|
17
17
|
import { downloadGroupFsBytes } from '../group-fs-download.js';
|
|
18
|
+
/** Map a group directory probe failure to a stable Menu protocol code. */
|
|
19
|
+
export function groupDirectoryErrorCode(rawCode, error) {
|
|
20
|
+
if (rawCode === -33001 || rawCode === -33005 || rawCode === -33006 || rawCode === '-33001' || rawCode === '-33005' || rawCode === '-33006')
|
|
21
|
+
return 'NOT_FOUND';
|
|
22
|
+
if (rawCode === -32004 || rawCode === 403 || rawCode === 4030 || rawCode === '-32004' || rawCode === '403' || rawCode === '4030')
|
|
23
|
+
return 'PERMISSION_DENIED';
|
|
24
|
+
if (rawCode === -32001 || rawCode === -32003 || rawCode === 4001 || rawCode === 4010 || rawCode === '-32001' || rawCode === '-32003' || rawCode === '4001' || rawCode === '4010')
|
|
25
|
+
return 'UNAUTHORIZED';
|
|
26
|
+
if (rawCode === 'TEMPORARILY_UNAVAILABLE')
|
|
27
|
+
return 'TEMPORARILY_UNAVAILABLE';
|
|
28
|
+
const message = String(error ?? '').toLowerCase();
|
|
29
|
+
if (message.includes('websocket connect') || message.includes('websocket disconnected') || message.includes('temporarily unavailable') || message.includes('gateway unavailable'))
|
|
30
|
+
return 'TEMPORARILY_UNAVAILABLE';
|
|
31
|
+
return 'TEMPORARILY_UNAVAILABLE';
|
|
32
|
+
}
|
|
18
33
|
function buildGroupPayload(body) {
|
|
19
34
|
return body.mode === 'text' ? { type: 'text', text: body.text } : { ...body.payload };
|
|
20
35
|
}
|
|
@@ -312,9 +327,35 @@ export async function groupCreate(args) {
|
|
|
312
327
|
catch { }
|
|
313
328
|
}
|
|
314
329
|
}
|
|
330
|
+
// Menu pages commonly ask for several fields from the same group at once. Keep
|
|
331
|
+
// concurrent probes on one short connection so a burst does not create several
|
|
332
|
+
// independent WebSocket handshakes for the same AID/group/slot.
|
|
333
|
+
const groupInfoInflight = new Map();
|
|
315
334
|
export async function groupInfo(args) {
|
|
316
|
-
const
|
|
335
|
+
const key = JSON.stringify([
|
|
336
|
+
args.from,
|
|
337
|
+
args.groupId,
|
|
338
|
+
args.aunPath ?? null,
|
|
339
|
+
args.slotId ?? SLOT.cli,
|
|
340
|
+
[...new Set(args.required ?? [])].sort(),
|
|
341
|
+
]);
|
|
342
|
+
const existing = groupInfoInflight.get(key);
|
|
343
|
+
if (existing)
|
|
344
|
+
return existing;
|
|
345
|
+
const request = groupInfoUncached(args);
|
|
346
|
+
groupInfoInflight.set(key, request);
|
|
317
347
|
try {
|
|
348
|
+
return await request;
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
if (groupInfoInflight.get(key) === request)
|
|
352
|
+
groupInfoInflight.delete(key);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async function groupInfoUncached(args) {
|
|
356
|
+
let conn;
|
|
357
|
+
try {
|
|
358
|
+
conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
318
359
|
const params = { group_id: args.groupId };
|
|
319
360
|
if (args.required?.length)
|
|
320
361
|
params.required = args.required;
|
|
@@ -324,10 +365,17 @@ export async function groupInfo(args) {
|
|
|
324
365
|
return { ok: true, found, ...(found ? { group: normalizeGroupInfo(raw) } : {}) };
|
|
325
366
|
}
|
|
326
367
|
catch (e) {
|
|
327
|
-
return formatRpcError(e);
|
|
368
|
+
return formatRpcError(e, { transientConnection: true });
|
|
328
369
|
}
|
|
329
370
|
finally {
|
|
330
|
-
|
|
371
|
+
if (conn) {
|
|
372
|
+
try {
|
|
373
|
+
await conn.close();
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
// Cleanup failures must not replace the structured query result.
|
|
377
|
+
}
|
|
378
|
+
}
|
|
331
379
|
}
|
|
332
380
|
}
|
|
333
381
|
export async function groupList(args) {
|
|
@@ -1258,11 +1306,29 @@ function normalizeGroupInfo(value) {
|
|
|
1258
1306
|
name: String(value?.name ?? groupId),
|
|
1259
1307
|
};
|
|
1260
1308
|
}
|
|
1261
|
-
function formatRpcError(e) {
|
|
1309
|
+
function formatRpcError(e, options = {}) {
|
|
1262
1310
|
if (e?.code !== undefined && e?.message !== undefined) {
|
|
1263
|
-
return {
|
|
1311
|
+
return {
|
|
1312
|
+
ok: false,
|
|
1313
|
+
error: String(e.message),
|
|
1314
|
+
code: options.transientConnection && isTransientRpcError(e) ? 'TEMPORARILY_UNAVAILABLE' : e.code,
|
|
1315
|
+
};
|
|
1264
1316
|
}
|
|
1265
|
-
return {
|
|
1317
|
+
return {
|
|
1318
|
+
ok: false,
|
|
1319
|
+
error: String(e?.message ?? e),
|
|
1320
|
+
...(options.transientConnection && isTransientRpcError(e) ? { code: 'TEMPORARILY_UNAVAILABLE' } : {}),
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
function isTransientRpcError(error) {
|
|
1324
|
+
const value = error;
|
|
1325
|
+
const name = String(value?.name ?? '');
|
|
1326
|
+
const message = String(value?.message ?? error ?? '').toLowerCase();
|
|
1327
|
+
return name === 'ConnectionError'
|
|
1328
|
+
|| name === 'TimeoutError'
|
|
1329
|
+
|| message.includes('websocket connect timeout')
|
|
1330
|
+
|| message.includes('websocket connect failed')
|
|
1331
|
+
|| message.includes('websocket disconnected');
|
|
1266
1332
|
}
|
|
1267
1333
|
function normalizeRecord(value) {
|
|
1268
1334
|
return value && typeof value === 'object' ? value : { value };
|