evolcore 0.0.17 → 0.0.19
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 +51 -0
- package/bin/codex-managed-hook.mjs +16 -7
- package/bin/install-codex-managed-hooks.mjs +4 -2
- package/dist/agents/claude-runner.js +113 -24
- package/dist/agents/codex-app-server-client.js +6 -1
- package/dist/agents/codex-runner.js +43 -24
- package/dist/agents/ecagent-runner.js +39 -10
- package/dist/agents/gemini-runner.js +90 -19
- package/dist/aun/aid/store.js +36 -0
- package/dist/aun/msg/p2p.js +20 -8
- package/dist/channels/aun.js +159 -21
- package/dist/cli/agent-command.js +67 -6
- package/dist/cli/agent.js +26 -0
- package/dist/cli/command-log.js +23 -4
- package/dist/cli/daemon-commands.js +82 -14
- package/dist/cli/init.js +21 -5
- package/dist/cli/restart-monitor.js +13 -6
- package/dist/cli/watch-logs.js +2 -2
- package/dist/config/builtin-roles.js +5 -1
- package/dist/config/role-ranks.js +4 -0
- package/dist/core/audit/event-key.js +29 -0
- package/dist/core/audit/log-integrity.js +13 -3
- package/dist/core/auth/auth-gateway.js +14 -18
- package/dist/core/auth/authorization-audit.js +110 -3
- package/dist/core/auth/authorization-denial.js +17 -0
- package/dist/core/auth/operation-authorizer.js +143 -18
- package/dist/core/auth/operation-catalog.js +21 -5
- package/dist/core/bootstrap-messages.js +11 -6
- package/dist/core/bootstrap-service.js +26 -4
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/command/agent-control.js +25 -16
- package/dist/core/command/command-handler.js +50 -4
- package/dist/core/command/group-menu.js +1 -1
- package/dist/core/command/menu-catalog.js +32 -7
- package/dist/core/command/menu-handler.js +59 -23
- package/dist/core/command/menu-protocol.js +196 -0
- package/dist/core/command/slash-gate.js +14 -5
- package/dist/core/command/slash-handler.js +81 -99
- package/dist/core/data-migration.js +10 -4
- package/dist/core/event-catalog.js +18 -0
- package/dist/core/message/message-bridge.js +66 -8
- package/dist/core/message/response-engine.js +147 -10
- package/dist/core/permission/ec-command-parser.js +148 -22
- package/dist/core/permission/sandbox-runtime.js +79 -13
- package/dist/core/permission/tool-policy.js +19 -7
- package/dist/index.js +357 -48
- package/dist/ipc.js +81 -5
- package/dist/paths.js +0 -3
- package/dist/utils/atomic-write.js +45 -11
- package/dist/utils/logger.js +27 -0
- package/dist/utils/windows-autostart.js +740 -83
- package/ecagent/dist/harness/agent-harness.d.ts +1 -1
- package/ecagent/dist/harness/agent-harness.js +6 -4
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/group-rules.md +2 -1
- package/kits/docs/identity/ROLE_DETAIL.md +3 -1
- package/kits/docs/path-registry.md +1 -1
- package/kits/eck_manifest.json +25 -16
- package/kits/rules/01-overview.md +5 -5
- package/kits/rules/02-navigation.md +2 -2
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/04-relation.md +4 -4
- package/kits/rules/05-venue.md +5 -5
- package/kits/templates/bootstrap-welcome.md +3 -1
- package/kits/templates/system-fragments/bootstrap.md +17 -9
- package/package.json +1 -1
|
@@ -459,11 +459,30 @@ function isWithin(root, candidate) {
|
|
|
459
459
|
const relative = path.relative(root, candidate);
|
|
460
460
|
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
461
461
|
}
|
|
462
|
-
function
|
|
462
|
+
export function resolveEcagentToolPath(projectPath, candidate, forWrite = false, bootstrapAgentMdPath) {
|
|
463
463
|
const root = fs.realpathSync(projectPath);
|
|
464
464
|
const resolved = path.resolve(root, candidate || '.');
|
|
465
|
-
|
|
465
|
+
const allowedBootstrapFile = bootstrapAgentMdPath
|
|
466
|
+
? path.resolve(bootstrapAgentMdPath)
|
|
467
|
+
: undefined;
|
|
468
|
+
const isBootstrapAgentMd = allowedBootstrapFile === resolved;
|
|
469
|
+
if (!isWithin(root, resolved) && !isBootstrapAgentMd) {
|
|
466
470
|
throw new Error(`Path is outside the project: ${candidate}`);
|
|
471
|
+
}
|
|
472
|
+
if (isBootstrapAgentMd) {
|
|
473
|
+
const parent = path.dirname(resolved);
|
|
474
|
+
const canonicalParent = fs.realpathSync(parent);
|
|
475
|
+
if (!isWithin(parent, canonicalParent) || !isWithin(canonicalParent, parent)) {
|
|
476
|
+
throw new Error(`Bootstrap agent.md parent resolves outside its declared path: ${candidate}`);
|
|
477
|
+
}
|
|
478
|
+
if (fs.existsSync(resolved)) {
|
|
479
|
+
const canonicalFile = fs.realpathSync(resolved);
|
|
480
|
+
if (!isWithin(resolved, canonicalFile) || !isWithin(canonicalFile, resolved)) {
|
|
481
|
+
throw new Error(`Bootstrap agent.md resolves outside its declared path: ${candidate}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return resolved;
|
|
485
|
+
}
|
|
467
486
|
const existing = forWrite ? path.dirname(resolved) : resolved;
|
|
468
487
|
let cursor = existing;
|
|
469
488
|
while (!fs.existsSync(cursor) && cursor !== path.dirname(cursor))
|
|
@@ -522,7 +541,7 @@ async function walkFiles(root, signal) {
|
|
|
522
541
|
function textResult(text, details = {}) {
|
|
523
542
|
return { content: [{ type: 'text', text: truncate(text) }], details };
|
|
524
543
|
}
|
|
525
|
-
function createTools(env, projectPath, runtimeEnv) {
|
|
544
|
+
function createTools(env, projectPath, runtimeEnv, bootstrapAgentMdPath) {
|
|
526
545
|
const read = {
|
|
527
546
|
name: 'Read', label: 'Read', description: 'Read a UTF-8 text file from the project.',
|
|
528
547
|
parameters: Type.Object({
|
|
@@ -531,7 +550,7 @@ function createTools(env, projectPath, runtimeEnv) {
|
|
|
531
550
|
limit: Type.Optional(Type.Number({ minimum: 1 })),
|
|
532
551
|
}),
|
|
533
552
|
async execute(_id, params, signal) {
|
|
534
|
-
const file =
|
|
553
|
+
const file = resolveEcagentToolPath(projectPath, params.file_path, false, bootstrapAgentMdPath);
|
|
535
554
|
const stat = await fs.promises.stat(file);
|
|
536
555
|
if (stat.size > MAX_READ_BYTES)
|
|
537
556
|
throw new Error(`File exceeds ${MAX_READ_BYTES} byte read limit`);
|
|
@@ -548,7 +567,7 @@ function createTools(env, projectPath, runtimeEnv) {
|
|
|
548
567
|
name: 'Glob', label: 'Glob', description: 'Find project files matching a glob pattern.',
|
|
549
568
|
parameters: Type.Object({ pattern: Type.String(), path: Type.Optional(Type.String()) }),
|
|
550
569
|
async execute(_id, params, signal) {
|
|
551
|
-
const root =
|
|
570
|
+
const root = resolveEcagentToolPath(projectPath, params.path ?? '.');
|
|
552
571
|
const matcher = globRegex(params.pattern.replaceAll('\\', '/'));
|
|
553
572
|
const files = (await walkFiles(root, signal))
|
|
554
573
|
.map(file => path.relative(root, file).replaceAll(path.sep, '/'))
|
|
@@ -564,7 +583,7 @@ function createTools(env, projectPath, runtimeEnv) {
|
|
|
564
583
|
case_insensitive: Type.Optional(Type.Boolean()), literal: Type.Optional(Type.Boolean()),
|
|
565
584
|
}),
|
|
566
585
|
async execute(_id, params, signal) {
|
|
567
|
-
const root =
|
|
586
|
+
const root = resolveEcagentToolPath(projectPath, params.path ?? '.');
|
|
568
587
|
const files = (await fs.promises.stat(root)).isFile() ? [root] : await walkFiles(root, signal);
|
|
569
588
|
const fileMatcher = params.glob ? globRegex(params.glob.replaceAll('\\', '/')) : undefined;
|
|
570
589
|
const flags = params.case_insensitive ? 'i' : '';
|
|
@@ -597,7 +616,7 @@ function createTools(env, projectPath, runtimeEnv) {
|
|
|
597
616
|
name: 'Write', label: 'Write', description: 'Create or completely overwrite a project file.',
|
|
598
617
|
parameters: Type.Object({ file_path: Type.String(), content: Type.String() }), executionMode: 'sequential',
|
|
599
618
|
async execute(_id, params, signal) {
|
|
600
|
-
const file =
|
|
619
|
+
const file = resolveEcagentToolPath(projectPath, params.file_path, true, bootstrapAgentMdPath);
|
|
601
620
|
await fs.promises.mkdir(path.dirname(file), { recursive: true });
|
|
602
621
|
await fs.promises.writeFile(file, params.content, { encoding: 'utf8', signal });
|
|
603
622
|
return textResult(`Wrote ${Buffer.byteLength(params.content)} bytes to ${path.relative(projectPath, file)}`, { file });
|
|
@@ -610,7 +629,7 @@ function createTools(env, projectPath, runtimeEnv) {
|
|
|
610
629
|
replace_all: Type.Optional(Type.Boolean()),
|
|
611
630
|
}), executionMode: 'sequential',
|
|
612
631
|
async execute(_id, params, signal) {
|
|
613
|
-
const file =
|
|
632
|
+
const file = resolveEcagentToolPath(projectPath, params.file_path, false, bootstrapAgentMdPath);
|
|
614
633
|
const value = await fs.promises.readFile(file, { encoding: 'utf8', signal });
|
|
615
634
|
if (!params.old_string)
|
|
616
635
|
throw new Error('old_string must not be empty');
|
|
@@ -809,7 +828,7 @@ export class EcagentRunner {
|
|
|
809
828
|
models,
|
|
810
829
|
model,
|
|
811
830
|
thinkingLevel: normalizeThinkingLevel(effort),
|
|
812
|
-
tools: createTools(nodeEnv, request.projectPath, request.runtimeEnv),
|
|
831
|
+
tools: createTools(nodeEnv, request.projectPath, request.runtimeEnv, request.modelOverride?.bootstrapAgentMdPath),
|
|
813
832
|
systemPrompt: [DEFAULT_SYSTEM_PROMPT, request.systemPromptAppend].filter(Boolean).join('\n\n'),
|
|
814
833
|
streamOptions: {
|
|
815
834
|
timeoutMs: this.config.timeoutMs,
|
|
@@ -823,7 +842,15 @@ export class EcagentRunner {
|
|
|
823
842
|
runtime.harness = undefined;
|
|
824
843
|
throw new Error('ecagent run interrupted');
|
|
825
844
|
}
|
|
826
|
-
harness.on('tool_call',
|
|
845
|
+
harness.on('tool_call', async (event, signal) => {
|
|
846
|
+
const pauseController = this.permissionContexts.get(request.sessionId)?.pauseController;
|
|
847
|
+
if (pauseController) {
|
|
848
|
+
const pauseResult = await pauseController.waitAtToolBoundary(signal);
|
|
849
|
+
if (pauseResult === 'cancelled')
|
|
850
|
+
return { block: true, reason: '工具调用已取消' };
|
|
851
|
+
}
|
|
852
|
+
return this.authorizeTool(request.sessionId, request.projectPath, permissionMode, event.toolName, event.input);
|
|
853
|
+
});
|
|
827
854
|
const unsubscribe = harness.subscribe(event => this.mapHarnessEvent(queue, event, stats, model, effort));
|
|
828
855
|
queue.push({ type: 'state_changed', state: 'running' });
|
|
829
856
|
queue.push({ type: 'status', subtype: 'started', message: `ecagent is using ${modelId}` });
|
|
@@ -1043,6 +1070,7 @@ export class EcagentRunner {
|
|
|
1043
1070
|
return decision.matched && decision.decision === 'deny' ? { block: true, reason: '危险操作未获人工批准' } : {};
|
|
1044
1071
|
}
|
|
1045
1072
|
async interrupt(sessionKey) {
|
|
1073
|
+
this.permissionContexts.get(sessionKey)?.pauseController?.cancel();
|
|
1046
1074
|
this.permissionGateway?.cancelAll(sessionKey, 'interrupted');
|
|
1047
1075
|
const harness = this.runtimes.get(sessionKey)?.harness;
|
|
1048
1076
|
if (harness) {
|
|
@@ -1058,6 +1086,7 @@ export class EcagentRunner {
|
|
|
1058
1086
|
void harness.steer(text).catch(error => logger.warn('[EcagentRunner] Failed to steer active run:', error));
|
|
1059
1087
|
}
|
|
1060
1088
|
async closeSession(sessionId) {
|
|
1089
|
+
this.permissionContexts.get(sessionId)?.pauseController?.cancel();
|
|
1061
1090
|
await this.interrupt(sessionId).catch(() => { });
|
|
1062
1091
|
this.runtimes.delete(sessionId);
|
|
1063
1092
|
this.activeStreams.delete(sessionId);
|
|
@@ -129,44 +129,99 @@ const GEMINI_L_CLASS_PATTERNS = [
|
|
|
129
129
|
const GEMINI_L_CLASS_WRITE_TOOLS = [
|
|
130
130
|
'write_file', 'replace', 'delete_file', 'move_file', 'rename_file',
|
|
131
131
|
];
|
|
132
|
+
const GEMINI_BOOTSTRAP_FILE_TOOLS = ['write_file', 'replace'];
|
|
133
|
+
const GEMINI_BOOTSTRAP_LIFECYCLE_TOOLS = ['update_topic', 'complete_task'];
|
|
132
134
|
const GEMINI_EC_COMMAND_PATTERNS = [
|
|
133
|
-
String.raw
|
|
135
|
+
String.raw `\x00"command":"[ ]*ec[ ]*"\x00`,
|
|
134
136
|
// This is only a shell-process boundary. It deliberately does not inspect
|
|
135
137
|
// subcommands or arguments. Shell composition remains excluded because the
|
|
136
138
|
// Gemini policy exception applies to the whole run_shell_command call.
|
|
137
|
-
|
|
139
|
+
// stableStringify JSON-escapes control characters. Reject those escapes
|
|
140
|
+
// explicitly, while retaining valid `\\`/`\"` escapes used by paths and
|
|
141
|
+
// quoted arguments.
|
|
142
|
+
String.raw `\x00"command":"[ ]*ec[ ]+(?:[^"\x00;&|$()<>\x60\r\n\\]|\\(?:\\|"))*"\x00`,
|
|
138
143
|
];
|
|
139
|
-
export function resolveGeminiPermissionProfile(value) {
|
|
144
|
+
export function resolveGeminiPermissionProfile(value, bootstrap = false) {
|
|
140
145
|
const normalized = normalizePermissionMode(value);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
const profile = normalized.mode === 'auto'
|
|
147
|
+
? { mode: 'auto', approvalMode: 'auto_edit' }
|
|
148
|
+
: normalized.mode === 'request' || normalized.mode === 'bypass'
|
|
149
|
+
? { mode: 'readonly', approvalMode: 'plan', degradedFrom: normalized.mode }
|
|
150
|
+
: { mode: 'readonly', approvalMode: 'plan' };
|
|
151
|
+
// Bootstrap has its own deny-by-default Admin policy. auto_edit is required
|
|
152
|
+
// so Gemini exposes write_file/replace, while that policy still limits them
|
|
153
|
+
// to the single declared agent.md path.
|
|
154
|
+
return bootstrap ? { ...profile, approvalMode: 'auto_edit' } : profile;
|
|
155
|
+
}
|
|
156
|
+
function escapeGeminiArgsRegex(value) {
|
|
157
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
158
|
+
}
|
|
159
|
+
function normalizeGeminiBootstrapAgentMdPath(candidate) {
|
|
160
|
+
const filePath = path.resolve(candidate);
|
|
161
|
+
const aidDir = path.dirname(filePath);
|
|
162
|
+
const aid = path.basename(aidDir);
|
|
163
|
+
const aidsDirName = path.basename(path.dirname(aidDir));
|
|
164
|
+
if (path.basename(filePath).toLowerCase() !== 'agent.md'
|
|
165
|
+
|| aidsDirName.toLowerCase() !== 'aids'
|
|
166
|
+
|| !/^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?){2,}$/.test(aid)) {
|
|
167
|
+
throw new Error(`Invalid Gemini bootstrap agent.md path: ${candidate}`);
|
|
168
|
+
}
|
|
169
|
+
return { filePath, aid };
|
|
170
|
+
}
|
|
171
|
+
function geminiJsonFieldEqualsPattern(fields, value) {
|
|
172
|
+
const encodedValue = JSON.stringify(value);
|
|
173
|
+
return `\\x00"(?:${fields.join('|')})":${escapeGeminiArgsRegex(encodedValue)}\\x00`;
|
|
174
|
+
}
|
|
175
|
+
function geminiExactShellCommandPattern(command) {
|
|
176
|
+
// Gemini stableStringify wraps the top-level NUL-delimited field in `{}`.
|
|
177
|
+
// Matching the whole object also rejects extra shell options such as
|
|
178
|
+
// is_background or additional_permissions during bootstrap.
|
|
179
|
+
return `^\\{\\x00"command":"[ ]*${escapeGeminiArgsRegex(command)}[ ]*"\\x00\\}$`;
|
|
147
180
|
}
|
|
148
181
|
function appendGeminiPolicyRule(lines, toolName, decision, priority, argsPattern) {
|
|
149
182
|
lines.push('[[rule]]');
|
|
150
183
|
lines.push(`toolName = ${Array.isArray(toolName) ? JSON.stringify(toolName) : JSON.stringify(toolName)}`);
|
|
151
184
|
if (argsPattern)
|
|
152
|
-
lines.push(`argsPattern =
|
|
185
|
+
lines.push(`argsPattern = ${JSON.stringify(argsPattern)}`);
|
|
153
186
|
lines.push(`decision = "${decision}"`);
|
|
154
187
|
lines.push(`priority = ${priority}`, '');
|
|
155
188
|
}
|
|
156
|
-
export function buildGeminiAdminPolicy(profile) {
|
|
189
|
+
export function buildGeminiAdminPolicy(profile, bootstrapAgentMdPath) {
|
|
157
190
|
const lines = [];
|
|
191
|
+
if (bootstrapAgentMdPath) {
|
|
192
|
+
const bootstrap = normalizeGeminiBootstrapAgentMdPath(bootstrapAgentMdPath);
|
|
193
|
+
// Preserve the global protected-path boundary even if a malformed internal
|
|
194
|
+
// caller ever supplies a protected file as the bootstrap target.
|
|
195
|
+
for (const pattern of GEMINI_H_CLASS_PATTERNS) {
|
|
196
|
+
appendGeminiPolicyRule(lines, '*', 'deny', 998, pattern);
|
|
197
|
+
}
|
|
198
|
+
for (const pattern of GEMINI_L_CLASS_PATTERNS) {
|
|
199
|
+
appendGeminiPolicyRule(lines, GEMINI_L_CLASS_WRITE_TOOLS, 'deny', 997, pattern);
|
|
200
|
+
}
|
|
201
|
+
const readPathPattern = geminiJsonFieldEqualsPattern(['file_path'], bootstrap.filePath);
|
|
202
|
+
const writePathPattern = geminiJsonFieldEqualsPattern(['file_path'], bootstrap.filePath);
|
|
203
|
+
appendGeminiPolicyRule(lines, 'read_file', 'allow', 990, readPathPattern);
|
|
204
|
+
appendGeminiPolicyRule(lines, GEMINI_BOOTSTRAP_FILE_TOOLS, 'allow', 990, writePathPattern);
|
|
205
|
+
appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 990, geminiExactShellCommandPattern(`ec aid agentmd put ${bootstrap.aid}`));
|
|
206
|
+
appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 990, geminiExactShellCommandPattern(`ec agent ready ${bootstrap.aid}`));
|
|
207
|
+
// These are Gemini's non-I/O task bookkeeping tools. They do not expose
|
|
208
|
+
// project files, shell, network, MCP, or user-interaction capabilities.
|
|
209
|
+
appendGeminiPolicyRule(lines, GEMINI_BOOTSTRAP_LIFECYCLE_TOOLS, 'allow', 950);
|
|
210
|
+
appendGeminiPolicyRule(lines, '*', 'deny', 900);
|
|
211
|
+
return lines.join('\n');
|
|
212
|
+
}
|
|
158
213
|
// EC/daemon owns all command semantics and authorization. The Runner only
|
|
159
214
|
// admits a single EC process through the otherwise mode-specific shell
|
|
160
215
|
// policy. Keep this above path denies so `--file` is not reinterpreted here.
|
|
161
216
|
for (const pattern of GEMINI_EC_COMMAND_PATTERNS) {
|
|
162
|
-
appendGeminiPolicyRule(lines, 'run_shell_command', 'allow',
|
|
217
|
+
appendGeminiPolicyRule(lines, 'run_shell_command', 'allow', 999, pattern);
|
|
163
218
|
}
|
|
164
219
|
// H-class protection is mode-independent and applies to built-in and MCP tools.
|
|
165
220
|
for (const pattern of GEMINI_H_CLASS_PATTERNS) {
|
|
166
|
-
appendGeminiPolicyRule(lines, '*', 'deny',
|
|
221
|
+
appendGeminiPolicyRule(lines, '*', 'deny', 998, pattern);
|
|
167
222
|
}
|
|
168
223
|
for (const pattern of GEMINI_L_CLASS_PATTERNS) {
|
|
169
|
-
appendGeminiPolicyRule(lines, GEMINI_L_CLASS_WRITE_TOOLS, 'deny',
|
|
224
|
+
appendGeminiPolicyRule(lines, GEMINI_L_CLASS_WRITE_TOOLS, 'deny', 997, pattern);
|
|
170
225
|
}
|
|
171
226
|
if (profile.mode === 'readonly') {
|
|
172
227
|
appendGeminiPolicyRule(lines, GEMINI_READONLY_TOOLS, 'allow', 950);
|
|
@@ -277,7 +332,10 @@ export class GeminiRunner {
|
|
|
277
332
|
let geminiSessionId = initialAgentSessionId || this.activeSessions.get(sessionId);
|
|
278
333
|
// per-call 权限模式/模型:优先 override,缺省回落实例级(多会话并发互不污染)
|
|
279
334
|
const requestedPermissionMode = modelOverride?.permissionMode || this.currentMode;
|
|
280
|
-
const
|
|
335
|
+
const bootstrapAgentMdPath = modelOverride?.bootstrapAgentMdPath
|
|
336
|
+
? normalizeGeminiBootstrapAgentMdPath(modelOverride.bootstrapAgentMdPath).filePath
|
|
337
|
+
: undefined;
|
|
338
|
+
const permissionProfile = resolveGeminiPermissionProfile(requestedPermissionMode, !!bootstrapAgentMdPath);
|
|
281
339
|
const callModel = modelOverride?.model || this.model;
|
|
282
340
|
const runtimeDir = runtimeEnv?.EVOLCORE_SESSION_RUNTIME_DIR;
|
|
283
341
|
const managedTmpDir = runtimeDir ?? process.env.TMPDIR;
|
|
@@ -288,7 +346,12 @@ export class GeminiRunner {
|
|
|
288
346
|
fs.mkdirSync(geminiHome, { recursive: true, mode: 0o700 });
|
|
289
347
|
const sandboxProbe = buildBubblewrapCommand(this.resolved.cliPath, [], {
|
|
290
348
|
projectPath,
|
|
291
|
-
writablePaths: [
|
|
349
|
+
writablePaths: [
|
|
350
|
+
geminiHome,
|
|
351
|
+
...(runtimeDir ? [runtimeDir] : []),
|
|
352
|
+
...(runtimeLockDir ? [runtimeLockDir] : []),
|
|
353
|
+
...(bootstrapAgentMdPath ? [bootstrapAgentMdPath] : []),
|
|
354
|
+
],
|
|
292
355
|
});
|
|
293
356
|
if (!sandboxProbe && workspaceContainsHClassPaths(projectPath)) {
|
|
294
357
|
throw new Error('Gemini 缺少可用的路径级隔离运行时,且项目覆盖 EvolCore 受保护根;已拒绝启动本轮任务');
|
|
@@ -309,7 +372,7 @@ export class GeminiRunner {
|
|
|
309
372
|
throw new Error('Gemini 系统级 admin policy 会忽略 EvolCore 的临时安全策略,已拒绝启动本轮任务');
|
|
310
373
|
}
|
|
311
374
|
const policyPath = path.join(managedTmpDir, `evolcore-gemini-permission-${crypto.randomUUID()}.toml`);
|
|
312
|
-
fs.writeFileSync(policyPath, buildGeminiAdminPolicy(permissionProfile), { mode: 0o600, flag: 'wx' });
|
|
375
|
+
fs.writeFileSync(policyPath, buildGeminiAdminPolicy(permissionProfile, bootstrapAgentMdPath), { mode: 0o600, flag: 'wx' });
|
|
313
376
|
tempFiles.push(policyPath);
|
|
314
377
|
args.push(...buildGeminiPermissionArgs(permissionProfile, policyPath, !!sandboxProbe));
|
|
315
378
|
}
|
|
@@ -331,9 +394,12 @@ export class GeminiRunner {
|
|
|
331
394
|
args.push('--output-format', 'stream-json');
|
|
332
395
|
args.push('-m', callModel);
|
|
333
396
|
// Permission mode
|
|
334
|
-
if (permissionProfile.degradedFrom) {
|
|
397
|
+
if (permissionProfile.degradedFrom && !bootstrapAgentMdPath) {
|
|
335
398
|
logger.warn(`[GeminiRunner] permission mode ${permissionProfile.degradedFrom} is unavailable in headless mode; degraded to readonly`);
|
|
336
399
|
}
|
|
400
|
+
else if (bootstrapAgentMdPath) {
|
|
401
|
+
logger.info('[GeminiRunner] using bootstrap-only tool policy for the declared agent.md');
|
|
402
|
+
}
|
|
337
403
|
// Resume session
|
|
338
404
|
if (geminiSessionId) {
|
|
339
405
|
args.push('-r', geminiSessionId);
|
|
@@ -342,7 +408,12 @@ export class GeminiRunner {
|
|
|
342
408
|
const env = this.buildAgentEnv(sessionId, runtimeEnv);
|
|
343
409
|
const sandboxedCommand = buildBubblewrapCommand(this.resolved.cliPath, args, {
|
|
344
410
|
projectPath,
|
|
345
|
-
writablePaths: [
|
|
411
|
+
writablePaths: [
|
|
412
|
+
geminiHome,
|
|
413
|
+
...(runtimeDir ? [runtimeDir] : []),
|
|
414
|
+
...(runtimeLockDir ? [runtimeLockDir] : []),
|
|
415
|
+
...(bootstrapAgentMdPath ? [bootstrapAgentMdPath] : []),
|
|
416
|
+
],
|
|
346
417
|
readonlyPaths: tempFiles,
|
|
347
418
|
});
|
|
348
419
|
const child = spawn(sandboxedCommand?.command ?? this.resolved.cliPath, sandboxedCommand?.args ?? args, {
|
package/dist/aun/aid/store.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { AUN_KEYSTORE_ENCRYPTION_SEED } from './encryption-seed-policy.js';
|
|
4
|
+
// fastaun uses maxPages=1 for the background tail -> forward handoff. That is
|
|
5
|
+
// useful as a latency guard, but it can leave a group backlog when no later
|
|
6
|
+
// push arrives to trigger the next page. Bound the daemon recovery pull to ten
|
|
7
|
+
// pages so a large backlog is drained without monopolizing the sync loop.
|
|
8
|
+
const FULL_GROUP_PULL_MAX_PAGES = 10;
|
|
9
|
+
const GROUP_PULL_PAGINATION_PATCH = Symbol('evolcore.groupPullPaginationPatch');
|
|
4
10
|
/**
|
|
5
11
|
* Slot 命名约定(基于 fastaun 0.4.3 隔离键语义)。
|
|
6
12
|
*
|
|
@@ -68,6 +74,36 @@ export async function loadClient(store, aid) {
|
|
|
68
74
|
const { AUNClient } = await import('@agentunion/fastaun');
|
|
69
75
|
return new AUNClient(loadAid(store, aid));
|
|
70
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Enable bounded multi-page group pulls for the long-lived daemon client.
|
|
79
|
+
*
|
|
80
|
+
* The SDK's background realtime recovery path intentionally passes
|
|
81
|
+
* `maxPages=1`; for a daemon this can strand a backlog after a tail sync.
|
|
82
|
+
* This adapter only expands that sentinel value and leaves all other pull
|
|
83
|
+
* options unchanged. It is isolated to the client that explicitly opts in.
|
|
84
|
+
*/
|
|
85
|
+
export function enableFullGroupPullPagination(client) {
|
|
86
|
+
const target = client;
|
|
87
|
+
if (target[GROUP_PULL_PAGINATION_PATCH])
|
|
88
|
+
return false;
|
|
89
|
+
const pull = target._pullGroupV2;
|
|
90
|
+
if (typeof pull !== 'function')
|
|
91
|
+
return false;
|
|
92
|
+
const wrappedPull = function (groupId, afterSeq, limit, opts) {
|
|
93
|
+
const expandedOpts = opts?.maxPages === 1
|
|
94
|
+
? { ...opts, maxPages: FULL_GROUP_PULL_MAX_PAGES }
|
|
95
|
+
: opts;
|
|
96
|
+
return pull.call(this, groupId, afterSeq, limit, expandedOpts);
|
|
97
|
+
};
|
|
98
|
+
Object.defineProperty(target, GROUP_PULL_PAGINATION_PATCH, {
|
|
99
|
+
configurable: false,
|
|
100
|
+
enumerable: false,
|
|
101
|
+
value: true,
|
|
102
|
+
writable: false,
|
|
103
|
+
});
|
|
104
|
+
target._pullGroupV2 = wrappedPull;
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
71
107
|
/** 加载本地 AID 值对象(用于离线签名/验签,无需连接)。load 失败抛 AidLoadError。 */
|
|
72
108
|
export function loadAid(store, aid) {
|
|
73
109
|
const r = store.load(aid);
|
package/dist/aun/msg/p2p.js
CHANGED
|
@@ -14,22 +14,34 @@ import { normalizeAunMentionEntries } from './mention-schema.js';
|
|
|
14
14
|
const DAEMON_SEND_TIMEOUT_GRACE_MS = 10_000;
|
|
15
15
|
const DEFAULT_IDLE_TIMEOUT_MS = 120_000;
|
|
16
16
|
const MAX_TIMER_MS = 2_147_483_647;
|
|
17
|
+
/**
|
|
18
|
+
* Read process settings through the config facade. A delegated CLI can run
|
|
19
|
+
* inside a sandbox where daemon.json is intentionally unreadable; sending a
|
|
20
|
+
* message must still use the protocol defaults instead of surfacing EACCES.
|
|
21
|
+
*/
|
|
22
|
+
function readDaemonConfigForSend() {
|
|
23
|
+
try {
|
|
24
|
+
return loadDaemonConfig();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
17
30
|
export function daemonTaskSendIpcTimeoutMs(file = false) {
|
|
18
31
|
let idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
// Use the ConfigStore read path so managed tasks never open daemon.json
|
|
33
|
+
// directly. The daemon-owned config reader handles missing, invalid, and
|
|
34
|
+
// protected files without leaking a filesystem access error into IMRenderer.
|
|
35
|
+
const seconds = readDaemonConfigForSend().idleMonitor?.timeout;
|
|
36
|
+
if (typeof seconds === 'number' && Number.isFinite(seconds) && seconds > 0) {
|
|
37
|
+
idleTimeoutMs = seconds * 1000;
|
|
25
38
|
}
|
|
26
|
-
catch { }
|
|
27
39
|
return Math.min(MAX_TIMER_MS, Math.max(file ? 120_000 : 5_000, idleTimeoutMs + DAEMON_SEND_TIMEOUT_GRACE_MS));
|
|
28
40
|
}
|
|
29
41
|
export function resolveAunMessageEncrypt(explicit) {
|
|
30
42
|
if (typeof explicit === 'boolean')
|
|
31
43
|
return explicit;
|
|
32
|
-
return
|
|
44
|
+
return readDaemonConfigForSend().aun?.defaultEncrypt ?? true;
|
|
33
45
|
}
|
|
34
46
|
function buildSimplePayload(body) {
|
|
35
47
|
switch (body.mode) {
|