evolcore 0.0.21 → 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 +43 -0
- package/bin/codex-managed-hook.mjs +3 -0
- package/bin/install-codex-managed-hooks.mjs +3 -1
- package/dist/agents/claude-runner.js +14 -0
- package/dist/agents/codex-app-server-client.js +31 -5
- package/dist/agents/codex-runner.js +926 -121
- package/dist/aun/outbox.js +7 -0
- package/dist/channels/aun.js +209 -35
- package/dist/cli/daemon-commands.js +29 -8
- package/dist/cli/task-context.js +4 -0
- package/dist/cli/trigger-command.js +13 -4
- package/dist/config/config-field-policy.js +3 -0
- package/dist/config/config-manager.js +32 -5
- package/dist/config/contact-book-store.js +25 -3
- package/dist/core/auth/agent-delegation.js +12 -0
- package/dist/core/auth/auth-gateway.js +8 -0
- package/dist/core/auth/authorization-audit.js +66 -6
- package/dist/core/bootstrap-messages.js +8 -0
- package/dist/core/bootstrap-service.js +93 -25
- package/dist/core/command/command-handler.js +21 -0
- package/dist/core/command/menu-handler.js +9 -0
- package/dist/core/command/menu-protocol.js +1 -1
- package/dist/core/command/slash-handler.js +41 -18
- package/dist/core/data-migration.js +11 -1
- package/dist/core/event-catalog.js +32 -0
- package/dist/core/handoff/runtime.js +23 -3
- package/dist/core/message/im-renderer.js +7 -3
- package/dist/core/message/message-bridge.js +60 -2
- package/dist/core/message/message-log.js +33 -0
- package/dist/core/message/message-queue.js +21 -0
- package/dist/core/message/response-engine.js +172 -41
- package/dist/core/permission/ec-command-parser.js +272 -70
- package/dist/core/permission/protected-paths.js +11 -10
- package/dist/core/permission/tool-error-code.js +12 -0
- package/dist/core/permission/tool-policy.js +46 -5
- package/dist/core/session/session-manager.js +30 -0
- package/dist/core/session/session-renew.js +18 -1
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/index.js +64 -5
- package/dist/ipc.js +97 -17
- package/dist/paths.js +18 -0
- package/dist/response-system/engines/v1/proactive-flow.js +7 -2
- package/dist/stats/price-resolver.js +4 -0
- package/dist/trigger/feedback.js +14 -2
- package/dist/trigger/parser.js +10 -1
- package/dist/trigger/scheduler.js +20 -3
- package/dist/utils/logger.js +9 -4
- package/dist/utils/tool-summary.js +59 -0
- package/dist/utils/windows-shell-trust.js +201 -0
- package/kits/docs/evolcore/INDEX.md +2 -2
- package/kits/docs/evolcore/agent-create.md +146 -0
- package/kits/docs/evolcore/agent.md +6 -0
- 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/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 +3 -2
- package/kits/schemas/agent-config.schema.12.json +427 -0
- package/kits/templates/message-fragments/item.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +2 -1
- package/kits/templates/system-fragments/commands.md +2 -2
- package/package.json +2 -2
package/dist/trigger/feedback.js
CHANGED
|
@@ -251,12 +251,24 @@ export class TriggerFeedbackDispatcher {
|
|
|
251
251
|
};
|
|
252
252
|
}
|
|
253
253
|
if (event?.type === 'trigger:skipped') {
|
|
254
|
+
const interruption = event.executionState === 'interrupted' && event.decisionSource === 'infrastructure'
|
|
255
|
+
? {
|
|
256
|
+
reasonCode: event.reasonCode ?? event.reason,
|
|
257
|
+
decisionSource: event.decisionSource,
|
|
258
|
+
executionState: event.executionState,
|
|
259
|
+
...(event.generation !== undefined ? { generation: event.generation } : {}),
|
|
260
|
+
...(event.currentGeneration !== undefined ? { currentGeneration: event.currentGeneration } : {}),
|
|
261
|
+
}
|
|
262
|
+
: undefined;
|
|
254
263
|
return {
|
|
255
|
-
status: 'failed',
|
|
264
|
+
status: interruption ? 'skipped' : 'failed',
|
|
256
265
|
reason: event.reason,
|
|
266
|
+
...(interruption ? { interruption } : {}),
|
|
257
267
|
feedback,
|
|
258
268
|
effects: [this.inboundEffect('success', target, session.id, startedAt)],
|
|
259
|
-
error:
|
|
269
|
+
error: interruption
|
|
270
|
+
? null
|
|
271
|
+
: { code: event.reason || 'target_session_skipped', message: event.reason || 'target session skipped' },
|
|
260
272
|
};
|
|
261
273
|
}
|
|
262
274
|
return {
|
package/dist/trigger/parser.js
CHANGED
|
@@ -2,6 +2,7 @@ import { CronExpressionParser } from 'cron-parser';
|
|
|
2
2
|
const TRIGGER_UPDATE_FLAGS = new Set([
|
|
3
3
|
'once', 'delay', 'at', 'cron', 'every', 'event', 'tz',
|
|
4
4
|
'prompt', 'name',
|
|
5
|
+
'script-file',
|
|
5
6
|
'model', 'effort', 'permission',
|
|
6
7
|
'max-runs', 'max-duration', 'concurrency', 'missed-policy',
|
|
7
8
|
]);
|
|
@@ -552,7 +553,7 @@ export function parseTriggerUpdateArgv(nameOrId, args, opts = {}) {
|
|
|
552
553
|
const parsedFlags = flagsFromArgv(args, 'update');
|
|
553
554
|
if (!parsedFlags.ok)
|
|
554
555
|
return { ok: false, error: withPromptFileHint(parsedFlags.error, args, opts) };
|
|
555
|
-
const parsed = parseTriggerUpdateFlags(nameOrId, parsedFlags.flags, opts);
|
|
556
|
+
const parsed = parseTriggerUpdateFlags(nameOrId, parsedFlags.flags, { ...opts, allowScriptFile: true });
|
|
556
557
|
return parsed.ok ? parsed : { ok: false, error: withPromptFileHint(parsed.error, args, opts) };
|
|
557
558
|
}
|
|
558
559
|
function flagsFromArgv(args, command) {
|
|
@@ -585,6 +586,9 @@ function parseTriggerUpdateFlags(nameOrId, flags, opts = {}) {
|
|
|
585
586
|
if (!TRIGGER_UPDATE_FLAGS.has(flag))
|
|
586
587
|
return { ok: false, error: `update 不支持参数 --${flag}` };
|
|
587
588
|
}
|
|
589
|
+
if (flags.has('script-file') && !opts.allowScriptFile) {
|
|
590
|
+
return { ok: false, error: '--script-file 仅支持 ec trigger update CLI;请通过受控 CLI 上传脚本文件' };
|
|
591
|
+
}
|
|
588
592
|
const parsed = commonParsed(flags, { update: true, ...opts });
|
|
589
593
|
if (!parsed.ok)
|
|
590
594
|
return parsed;
|
|
@@ -607,6 +611,11 @@ function parseTriggerUpdateFlags(nameOrId, flags, opts = {}) {
|
|
|
607
611
|
update.name = value.name;
|
|
608
612
|
if (value.prompt !== undefined)
|
|
609
613
|
update.prompt = value.prompt;
|
|
614
|
+
const scriptFile = optionalFlagString(flags, 'script-file');
|
|
615
|
+
if ('error' in scriptFile)
|
|
616
|
+
return { ok: false, error: scriptFile.error };
|
|
617
|
+
if (scriptFile.value !== undefined)
|
|
618
|
+
update.scriptFile = scriptFile.value;
|
|
610
619
|
if (value.model !== undefined)
|
|
611
620
|
update.model = value.model;
|
|
612
621
|
if (value.effort !== undefined)
|
|
@@ -120,12 +120,12 @@ export class TriggerRuntimeScheduler {
|
|
|
120
120
|
}
|
|
121
121
|
listItems(definitions) {
|
|
122
122
|
const latestRuns = this.audit.latest(definitions.map(definition => definition.id));
|
|
123
|
-
return definitions.map(definition => this.buildListItem(definition, latestRuns.get(definition.id)));
|
|
123
|
+
return definitions.map(definition => this.buildListItem(definition, latestRuns.get(definition.id), this.stats(definition.id)));
|
|
124
124
|
}
|
|
125
125
|
listItem(definition) {
|
|
126
|
-
return this.buildListItem(definition, this.audit.recent(definition.id, 1)[0]);
|
|
126
|
+
return this.buildListItem(definition, this.audit.recent(definition.id, 1)[0], this.stats(definition.id));
|
|
127
127
|
}
|
|
128
|
-
buildListItem(definition, lastRun) {
|
|
128
|
+
buildListItem(definition, lastRun, stats = this.stats(definition.id)) {
|
|
129
129
|
const schedule = this.state.readSchedule(definition.id);
|
|
130
130
|
return {
|
|
131
131
|
id: definition.id,
|
|
@@ -139,6 +139,10 @@ export class TriggerRuntimeScheduler {
|
|
|
139
139
|
...(definition.feedback.target ? { target: definition.feedback.target } : {}),
|
|
140
140
|
},
|
|
141
141
|
...(schedule ? { nextFireAt: schedule.nextFireAt } : {}),
|
|
142
|
+
fireCount: stats.fireCount,
|
|
143
|
+
failCount: stats.failCount,
|
|
144
|
+
...(stats.lastFiredAt !== undefined ? { lastFiredAt: stats.lastFiredAt } : {}),
|
|
145
|
+
...(stats.lastResult ? { lastResult: stats.lastResult } : {}),
|
|
142
146
|
...(lastRun ? { lastStatus: lastRun.status, lastFinishedAt: lastRun.finishedAt } : {}),
|
|
143
147
|
updatedAt: definition.updatedAt,
|
|
144
148
|
};
|
|
@@ -627,6 +631,7 @@ export class TriggerRuntimeScheduler {
|
|
|
627
631
|
effects: feedbackResult.effects,
|
|
628
632
|
error: feedbackResult.error,
|
|
629
633
|
conflictRunId: feedbackResult.conflictRunId,
|
|
634
|
+
interruption: feedbackResult.interruption,
|
|
630
635
|
causation,
|
|
631
636
|
});
|
|
632
637
|
if (!payload.dryRun) {
|
|
@@ -1147,6 +1152,7 @@ export class TriggerRuntimeScheduler {
|
|
|
1147
1152
|
anomalies,
|
|
1148
1153
|
effects: feedbackResult.effects,
|
|
1149
1154
|
error: feedbackResult.error,
|
|
1155
|
+
interruption: feedbackResult.interruption,
|
|
1150
1156
|
causation,
|
|
1151
1157
|
});
|
|
1152
1158
|
this.audit.write(audit);
|
|
@@ -1469,6 +1475,7 @@ export class TriggerRuntimeScheduler {
|
|
|
1469
1475
|
status: input.status,
|
|
1470
1476
|
reason: input.reason,
|
|
1471
1477
|
conflictRunId: input.conflictRunId,
|
|
1478
|
+
...(input.interruption ? { interruption: input.interruption } : {}),
|
|
1472
1479
|
...(activeRun?.executionSessionId ? { executionSessionId: activeRun.executionSessionId } : {}),
|
|
1473
1480
|
...(attempts?.length ? { attempts } : {}),
|
|
1474
1481
|
...(finalAttempt ? { finalAttempt: finalAttempt.attempt } : {}),
|
|
@@ -1783,6 +1790,7 @@ export class TriggerRuntimeScheduler {
|
|
|
1783
1790
|
type: 'trigger:skipped',
|
|
1784
1791
|
...base,
|
|
1785
1792
|
reason: audit.reason ?? 'skipped',
|
|
1793
|
+
...(audit.interruption ?? {}),
|
|
1786
1794
|
});
|
|
1787
1795
|
}
|
|
1788
1796
|
}
|
|
@@ -1900,6 +1908,15 @@ function errorReply(runId, durationMs, reason, text) {
|
|
|
1900
1908
|
}
|
|
1901
1909
|
function attemptSummaryFromExecution(attempt, attemptId, execution) {
|
|
1902
1910
|
if (execution.feedbackResult) {
|
|
1911
|
+
if (execution.feedbackResult.interruption) {
|
|
1912
|
+
return {
|
|
1913
|
+
status: 'interrupted',
|
|
1914
|
+
error: {
|
|
1915
|
+
code: execution.feedbackResult.interruption.reasonCode,
|
|
1916
|
+
message: execution.feedbackResult.reason ?? execution.feedbackResult.interruption.reasonCode,
|
|
1917
|
+
},
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1903
1920
|
const failed = execution.feedbackResult.status !== 'completed';
|
|
1904
1921
|
return {
|
|
1905
1922
|
status: failed ? 'failed' : 'completed',
|
package/dist/utils/logger.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import { resolvePaths } from '../paths.js';
|
|
3
3
|
import { LogWriter } from './log-writer.js';
|
|
4
|
-
import { classifyToolErrorCode } from '../core/permission/tool-error-code.js';
|
|
4
|
+
import { classifyToolErrorCode, normalizeToolErrorCode } from '../core/permission/tool-error-code.js';
|
|
5
5
|
import { normalizePermissionMode } from '../core/permission/mode.js';
|
|
6
6
|
import { buildToolLifecycleEventKey } from '../core/audit/event-key.js';
|
|
7
7
|
let currentLevel = process.env.LOG_LEVEL || 'INFO';
|
|
@@ -95,8 +95,8 @@ export function normalizeStructuredLog(data) {
|
|
|
95
95
|
const isToolUse = eventType === 'tool:use' || eventType === 'tool_use';
|
|
96
96
|
const isError = data.isError ?? data.is_error ?? (data.ok === false ? true : undefined)
|
|
97
97
|
?? nested?.isError ?? nested?.is_error ?? (nested?.ok === false ? true : undefined);
|
|
98
|
-
const
|
|
99
|
-
|
|
98
|
+
const decisionSource = data.decisionSource ?? data.decision_source
|
|
99
|
+
?? nested?.decisionSource ?? nested?.decision_source;
|
|
100
100
|
const decision = data.decision
|
|
101
101
|
?? data.status
|
|
102
102
|
?? (isToolResult && isError === true ? 'error' : undefined)
|
|
@@ -113,8 +113,11 @@ export function normalizeStructuredLog(data) {
|
|
|
113
113
|
?? nested?.errorCode
|
|
114
114
|
?? nested?.error_code;
|
|
115
115
|
const errorCode = isToolResult && isError === true
|
|
116
|
-
? classifyToolErrorCode({ errorCode: explicitErrorCode,
|
|
116
|
+
? classifyToolErrorCode({ errorCode: explicitErrorCode, decisionSource })
|
|
117
117
|
: explicitErrorCode;
|
|
118
|
+
const classificationMissing = isToolResult && isError === true
|
|
119
|
+
&& !normalizeToolErrorCode(explicitErrorCode)
|
|
120
|
+
&& decisionSource !== 'policy' && decisionSource !== 'approval';
|
|
118
121
|
const lifecycleRecord = isToolUse || isToolResult;
|
|
119
122
|
const lifecycleEventKey = lifecycleRecord
|
|
120
123
|
? (eventKey ?? buildToolLifecycleEventKey({
|
|
@@ -137,10 +140,12 @@ export function normalizeStructuredLog(data) {
|
|
|
137
140
|
...(agentName ? { agentName } : lifecycleRecord ? { agentName: 'unknown' } : {}),
|
|
138
141
|
...(permissionMode ? { permissionMode } : lifecycleRecord ? { permissionMode: 'unknown' } : {}),
|
|
139
142
|
...(toolName ? { toolName } : {}),
|
|
143
|
+
...(decisionSource ? { decisionSource } : {}),
|
|
140
144
|
...(decision ? { decision } : {}),
|
|
141
145
|
...(executed !== undefined ? { executed } : {}),
|
|
142
146
|
...(executionState ? { executionState } : {}),
|
|
143
147
|
...(errorCode ? { errorCode } : {}),
|
|
148
|
+
...(classificationMissing ? { classificationMissing: true } : {}),
|
|
144
149
|
...(missingContextFields.length > 0 ? { contextMissing: missingContextFields } : {}),
|
|
145
150
|
};
|
|
146
151
|
}
|
|
@@ -5,6 +5,65 @@
|
|
|
5
5
|
* Edit 工具的摘要为 diff 风格预览(支持 old/new_string 与 unified diff 两种输入)。
|
|
6
6
|
*/
|
|
7
7
|
import fs from 'fs';
|
|
8
|
+
import { parseLiteralShellArgv, resolveCodexShellCarrierString, } from '../core/permission/ec-command-parser.js';
|
|
9
|
+
const DISPLAY_POSIX_SHELLS = new Set(['bash', 'sh']);
|
|
10
|
+
const DISPLAY_POWERSHELLS = new Set(['pwsh', 'pwsh.exe', 'powershell', 'powershell.exe']);
|
|
11
|
+
const DISPLAY_CMD_SHELLS = new Set(['cmd', 'cmd.exe']);
|
|
12
|
+
const DISPLAY_CMD_INERT_SWITCHES = new Set(['/d', '/s', '/q', '/a', '/u']);
|
|
13
|
+
function executableBasename(value) {
|
|
14
|
+
if (typeof value !== 'string')
|
|
15
|
+
return '';
|
|
16
|
+
return value.replaceAll('\\', '/').split('/').at(-1)?.toLowerCase() ?? '';
|
|
17
|
+
}
|
|
18
|
+
/** Display-only fallback for valid Codex carriers installed outside standard paths. */
|
|
19
|
+
function summarizeDisplayCarrierArgv(argv) {
|
|
20
|
+
if (argv.length === 3) {
|
|
21
|
+
const executable = executableBasename(argv[0]);
|
|
22
|
+
const switchName = argv[1]?.toLowerCase();
|
|
23
|
+
if (DISPLAY_POSIX_SHELLS.has(executable) && switchName === '-lc')
|
|
24
|
+
return argv[2];
|
|
25
|
+
if (DISPLAY_POWERSHELLS.has(executable) && switchName === '-command')
|
|
26
|
+
return argv[2];
|
|
27
|
+
}
|
|
28
|
+
if (argv.length >= 3 && DISPLAY_CMD_SHELLS.has(executableBasename(argv[0]))) {
|
|
29
|
+
const executeIndex = argv.slice(1, -1).findIndex(value => value.toLowerCase() === '/c');
|
|
30
|
+
if (executeIndex >= 0 && executeIndex + 1 === argv.length - 2) {
|
|
31
|
+
const switches = argv.slice(1, executeIndex + 1);
|
|
32
|
+
if (switches.every(value => DISPLAY_CMD_INERT_SWITCHES.has(value.toLowerCase()))) {
|
|
33
|
+
return argv.at(-1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/** Remove Codex app-server's fixed shell carrier from display-only arguments. */
|
|
40
|
+
function displayShellCommand(command) {
|
|
41
|
+
if (typeof command !== 'string' || !command)
|
|
42
|
+
return undefined;
|
|
43
|
+
const parsedArgv = parseLiteralShellArgv(command);
|
|
44
|
+
const display = parsedArgv ? summarizeDisplayCarrierArgv(parsedArgv) : undefined;
|
|
45
|
+
if (display !== undefined)
|
|
46
|
+
return display;
|
|
47
|
+
// The nested PowerShell body is not POSIX shell syntax and can contain
|
|
48
|
+
// quotes, variables, redirects, and statement composition. Use the strict
|
|
49
|
+
// carrier parser only for PowerShell commands that the argv projection
|
|
50
|
+
// cannot parse; ordinary `bash -c` commands must remain unchanged.
|
|
51
|
+
const carrier = resolveCodexShellCarrierString(command);
|
|
52
|
+
return carrier?.dialect === 'powershell' ? carrier.command : undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Build the tool arguments projected into activity/thought payloads.
|
|
56
|
+
* Execution, permission checks, and audit logging keep using the original
|
|
57
|
+
* input; only the human-facing Shell command is de-wrapped here.
|
|
58
|
+
*/
|
|
59
|
+
export function toolInputForDisplay(toolName, input) {
|
|
60
|
+
if (!input || toolName !== 'Shell')
|
|
61
|
+
return input;
|
|
62
|
+
const command = displayShellCommand(input.command);
|
|
63
|
+
if (!command || command === input.command)
|
|
64
|
+
return input;
|
|
65
|
+
return { ...input, command };
|
|
66
|
+
}
|
|
8
67
|
/**
|
|
9
68
|
* 工具输入摘要(提取工具调用的可读描述,供权限审批和消息展示使用)
|
|
10
69
|
*/
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
const MAX_CACHE_ENTRIES = 128;
|
|
5
|
+
const signatureCache = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* The signer name is checked only after Authenticode reports a trusted chain.
|
|
8
|
+
* Keep this list deliberately small: an unsigned or unknown Bash distribution
|
|
9
|
+
* must not enter the privileged EC command path.
|
|
10
|
+
*/
|
|
11
|
+
const MICROSOFT_PUBLISHER_RE = /(?:^|[,;]\s*)CN=Microsoft (?:Corporation|Windows(?: [^,;]+)?)(?:[,;]|$)/i;
|
|
12
|
+
const MICROSOFT_ORG_RE = /(?:^|[,;]\s*)O=Microsoft Corporation(?:[,;]|$)/i;
|
|
13
|
+
const BASH_PUBLISHER_RES = [
|
|
14
|
+
/(?:^|[,;]\s*)(?:CN|O)=Git for Windows(?:[,;]|$)/i,
|
|
15
|
+
/(?:^|[,;]\s*)(?:CN|O)=The Git Development Community(?:[,;]|$)/i,
|
|
16
|
+
// Git for Windows binaries are currently signed by project maintainer
|
|
17
|
+
// Johannes Schindelin rather than a certificate named after the project.
|
|
18
|
+
/(?:^|[,;]\s*)(?:CN|O)=Johannes Schindelin(?:[,;]|$)/i,
|
|
19
|
+
/(?:^|[,;]\s*)(?:CN|O)=Red Hat,? Inc\.(?:[,;]|$)/i,
|
|
20
|
+
/(?:^|[,;]\s*)(?:CN|O)=MSYS2(?:[,;]|$)/i,
|
|
21
|
+
];
|
|
22
|
+
function isValidStatus(status) {
|
|
23
|
+
return typeof status === 'string' && status.trim().toLowerCase() === 'valid';
|
|
24
|
+
}
|
|
25
|
+
/** Pure signature policy, separately testable without a Windows host. */
|
|
26
|
+
export function isTrustedShellSignature(kind, evidence) {
|
|
27
|
+
if (!isValidStatus(evidence.status) || typeof evidence.subject !== 'string')
|
|
28
|
+
return false;
|
|
29
|
+
const subject = evidence.subject.trim();
|
|
30
|
+
if (kind === 'cmd' || kind === 'powershell') {
|
|
31
|
+
return MICROSOFT_PUBLISHER_RE.test(subject) && MICROSOFT_ORG_RE.test(subject);
|
|
32
|
+
}
|
|
33
|
+
return (MICROSOFT_PUBLISHER_RE.test(subject) && MICROSOFT_ORG_RE.test(subject))
|
|
34
|
+
|| BASH_PUBLISHER_RES.some(pattern => pattern.test(subject));
|
|
35
|
+
}
|
|
36
|
+
function windowsSystemRoot() {
|
|
37
|
+
return process.env.SystemRoot || process.env.WINDIR || 'C:\\Windows';
|
|
38
|
+
}
|
|
39
|
+
function systemPowerShellPath() {
|
|
40
|
+
return path.win32.join(windowsSystemRoot(), 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
41
|
+
}
|
|
42
|
+
function systemPowerShellModulePath() {
|
|
43
|
+
return path.win32.join(windowsSystemRoot(), 'System32', 'WindowsPowerShell', 'v1.0', 'Modules');
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Windows PowerShell 5.1 must load its own Security module. A caller's
|
|
47
|
+
* PSModulePath can put PowerShell 7's module ahead of it; that module is not
|
|
48
|
+
* compatible with 5.1 and makes Get-AuthenticodeSignature fail to load.
|
|
49
|
+
*/
|
|
50
|
+
function systemPowerShellEnvironment() {
|
|
51
|
+
const env = { ...process.env };
|
|
52
|
+
for (const key of Object.keys(env)) {
|
|
53
|
+
if (key.toLowerCase() === 'psmodulepath')
|
|
54
|
+
delete env[key];
|
|
55
|
+
}
|
|
56
|
+
env.PSModulePath = systemPowerShellModulePath();
|
|
57
|
+
return env;
|
|
58
|
+
}
|
|
59
|
+
function systemWherePath() {
|
|
60
|
+
return path.win32.join(windowsSystemRoot(), 'System32', 'where.exe');
|
|
61
|
+
}
|
|
62
|
+
function isAbsoluteWindowsPath(value) {
|
|
63
|
+
return path.win32.isAbsolute(value) || /^\\\\[^\\/]+[\\/][^\\/]+/.test(value);
|
|
64
|
+
}
|
|
65
|
+
function resolveBareShellExecutable(executable, kind) {
|
|
66
|
+
const name = executable.toLowerCase().replace(/\.exe$/i, '');
|
|
67
|
+
if (kind === 'cmd' && name === 'cmd') {
|
|
68
|
+
return path.win32.join(windowsSystemRoot(), 'System32', 'cmd.exe');
|
|
69
|
+
}
|
|
70
|
+
if (kind === 'powershell' && name === 'powershell') {
|
|
71
|
+
return systemPowerShellPath();
|
|
72
|
+
}
|
|
73
|
+
const result = spawnSync(systemWherePath(), [`${name}.exe`], {
|
|
74
|
+
encoding: 'utf8',
|
|
75
|
+
timeout: 3000,
|
|
76
|
+
windowsHide: true,
|
|
77
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
78
|
+
});
|
|
79
|
+
if (result.status !== 0)
|
|
80
|
+
return undefined;
|
|
81
|
+
const first = String(result.stdout || '')
|
|
82
|
+
.split(/\r?\n/)
|
|
83
|
+
.map(line => line.trim())
|
|
84
|
+
.find(Boolean);
|
|
85
|
+
return first || undefined;
|
|
86
|
+
}
|
|
87
|
+
function resolveShellPath(executable, kind) {
|
|
88
|
+
if (!executable)
|
|
89
|
+
return undefined;
|
|
90
|
+
// Codex can keep its POSIX carrier spelling on Windows (for example
|
|
91
|
+
// `/bin/bash`). It is a logical shell name, not a Windows root-relative
|
|
92
|
+
// filesystem path, so resolve it through the trusted Windows PATH lookup.
|
|
93
|
+
if (executable.startsWith('/') && !executable.startsWith('//')) {
|
|
94
|
+
const basename = path.posix.basename(executable);
|
|
95
|
+
return resolveBareShellExecutable(basename, kind);
|
|
96
|
+
}
|
|
97
|
+
if (isAbsoluteWindowsPath(executable))
|
|
98
|
+
return executable;
|
|
99
|
+
return resolveBareShellExecutable(executable, kind);
|
|
100
|
+
}
|
|
101
|
+
function isAppExecutionAliasPath(value) {
|
|
102
|
+
return /[\\/]AppData[\\/]Local[\\/]Microsoft[\\/]WindowsApps[\\/]/i.test(value);
|
|
103
|
+
}
|
|
104
|
+
function shellQuote(value) {
|
|
105
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
106
|
+
}
|
|
107
|
+
function probeAuthenticode(filePath) {
|
|
108
|
+
const script = [
|
|
109
|
+
'$ErrorActionPreference = "Stop"',
|
|
110
|
+
`$p = ${shellQuote(filePath)}`,
|
|
111
|
+
'$item = Get-Item -LiteralPath $p -Force',
|
|
112
|
+
'if ($item.PSIsContainer) { exit 17 }',
|
|
113
|
+
'$sig = Get-AuthenticodeSignature -LiteralPath $item.FullName',
|
|
114
|
+
'[pscustomobject]@{',
|
|
115
|
+
' status = [string]$sig.Status',
|
|
116
|
+
' subject = if ($sig.SignerCertificate) { [string]$sig.SignerCertificate.Subject } else { "" }',
|
|
117
|
+
' issuer = if ($sig.SignerCertificate) { [string]$sig.SignerCertificate.Issuer } else { "" }',
|
|
118
|
+
'} | ConvertTo-Json -Compress',
|
|
119
|
+
].join('\n');
|
|
120
|
+
const result = spawnSync(systemPowerShellPath(), [
|
|
121
|
+
'-NoLogo', '-NoProfile', '-NonInteractive', '-Command', script,
|
|
122
|
+
], {
|
|
123
|
+
encoding: 'utf8',
|
|
124
|
+
env: systemPowerShellEnvironment(),
|
|
125
|
+
timeout: 5000,
|
|
126
|
+
windowsHide: true,
|
|
127
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
128
|
+
});
|
|
129
|
+
if (result.status !== 0 || result.error || !String(result.stdout || '').trim())
|
|
130
|
+
return undefined;
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(String(result.stdout));
|
|
133
|
+
return {
|
|
134
|
+
status: typeof parsed.status === 'string' ? parsed.status : undefined,
|
|
135
|
+
subject: typeof parsed.subject === 'string' ? parsed.subject : undefined,
|
|
136
|
+
issuer: typeof parsed.issuer === 'string' ? parsed.issuer : undefined,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function cacheKey(filePath) {
|
|
144
|
+
try {
|
|
145
|
+
return fs.realpathSync.native(filePath).toLowerCase();
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return filePath.replaceAll('\\', '/').toLowerCase();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Verify the concrete executable selected by Codex. Non-Windows callers keep
|
|
153
|
+
* the existing parser behavior; only Windows enters the Authenticode path.
|
|
154
|
+
*/
|
|
155
|
+
export function isTrustedWindowsShellExecutable(executable, kind) {
|
|
156
|
+
if (process.platform !== 'win32')
|
|
157
|
+
return true;
|
|
158
|
+
const resolved = resolveShellPath(executable, kind);
|
|
159
|
+
if (!resolved)
|
|
160
|
+
return false;
|
|
161
|
+
let finalPath = resolved;
|
|
162
|
+
try {
|
|
163
|
+
finalPath = fs.realpathSync.native(resolved);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Keep the original path for the signature probe; the stat/probe below
|
|
167
|
+
// will fail closed if the executable is not accessible.
|
|
168
|
+
}
|
|
169
|
+
// If realpath could not escape an App Execution Alias, do not authenticate
|
|
170
|
+
// the alias stub itself. A resolved WindowsApps package binary is allowed
|
|
171
|
+
// and is checked below like any other signed executable.
|
|
172
|
+
if (isAppExecutionAliasPath(finalPath))
|
|
173
|
+
return false;
|
|
174
|
+
let stat;
|
|
175
|
+
try {
|
|
176
|
+
stat = fs.statSync(finalPath);
|
|
177
|
+
if (!stat.isFile())
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
const key = cacheKey(finalPath);
|
|
184
|
+
const previous = signatureCache.get(key);
|
|
185
|
+
if (previous && previous.size === stat.size && previous.mtimeMs === stat.mtimeMs) {
|
|
186
|
+
return previous.trusted;
|
|
187
|
+
}
|
|
188
|
+
const trusted = isTrustedShellSignature(kind, probeAuthenticode(finalPath) ?? {});
|
|
189
|
+
signatureCache.set(key, { size: stat.size, mtimeMs: stat.mtimeMs, trusted });
|
|
190
|
+
while (signatureCache.size > MAX_CACHE_ENTRIES) {
|
|
191
|
+
const oldest = signatureCache.keys().next().value;
|
|
192
|
+
if (!oldest)
|
|
193
|
+
break;
|
|
194
|
+
signatureCache.delete(oldest);
|
|
195
|
+
}
|
|
196
|
+
return trusted;
|
|
197
|
+
}
|
|
198
|
+
/** Test hook: clear cached signature decisions after a fixture is replaced. */
|
|
199
|
+
export function clearWindowsShellTrustCache() {
|
|
200
|
+
signatureCache.clear();
|
|
201
|
+
}
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
| 命令集 | 用途 | 触发词 | 适用场景 | 文档 |
|
|
30
30
|
|--------|------|--------|----------|------|
|
|
31
31
|
| `ec msg` | 私聊收发消息 | 回复/发消息/拉取/撤回/查在线 | 有对端(peerId) | `msg.md` |
|
|
32
|
-
| `ec group` | 群聊收发与群管理 |
|
|
32
|
+
| `ec group` | 群聊收发与群管理 | 群发/建群/邀请/踢人/退群/群成员/角色/封禁/规则/协作 | 群聊(groupId) | `group.md`;完整协作流程见 `group-collaboration.md` |
|
|
33
33
|
| `ec group rules` | 群规则文件发布与上下文注入 | 群规则/工作流程/职责分工/rules.md/发布规则 | AUN 群聊(groupId) | `group-rules.md` |
|
|
34
34
|
| `ec aid` | AID 身份管理 | 身份/证书/名片/探测对端 | 任意有渠道场景 | `aid.md` |
|
|
35
35
|
| `ec fs` | AUN 文件系统统一入口 | 上传/下载/看文件/列目录/删文件/配额/群空间 | 任意有渠道场景 | `fs.md` |
|
|
36
36
|
| `ec storage` | 文件存储底层调试入口 | storage 调试/旧命令/底层上传下载 | 任意有渠道场景 | `storage.md` |
|
|
37
|
-
| `ec agent` | EvolAgent 生命周期 | 创建/启停/热重载/改配置 | 管理员(owner/admin) | `agent.md` |
|
|
37
|
+
| `ec agent` | EvolAgent 生命周期 | 创建/启停/热重载/改配置 | 管理员(owner/admin) | `agent.md`;完整创建与 Bootstrap 见 `agent-create.md` |
|
|
38
38
|
| `ec contact` | Contact Book 与访问控制 | 申请添加、拉黑/解除拉黑、查拉黑 | Agent 当前私聊可申请添加;管理写操作按角色限制 | `contact.md` |
|
|
39
39
|
| `ec rpc` | 底层 AUN RPC(逃生通道) | 直接调协议方法 | 高级/兜底 | `rpc.md` |
|
|
40
40
|
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# 创建 EvolAgent:从创建到 Bootstrap 完成
|
|
2
|
+
|
|
3
|
+
本手册用于创建一个能正常工作的 EvolAgent。创建者负责创建和观察状态;新 Agent 在自己的 Bootstrap 会话中完成首次设定。不要把创建流水线完成误认为 Agent 已激活。
|
|
4
|
+
|
|
5
|
+
## 生命周期与完成标准
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
ec agent new
|
|
9
|
+
↓
|
|
10
|
+
created
|
|
11
|
+
↓ channel 可以联系 Owner,或 Owner 首次入站
|
|
12
|
+
bootstrapping
|
|
13
|
+
↓ 新 Agent 发布 agent.md,并执行 ec agent ready
|
|
14
|
+
active
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
只有 `lifecycle=active` 才表示 Agent 已完成创建流程并可正常处理普通任务。
|
|
18
|
+
|
|
19
|
+
## 1. 创建者创建 Agent
|
|
20
|
+
|
|
21
|
+
在独立终端可以使用交互模式:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
ec agent new helper.agentid.pub
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
在 EvolCore 托管 Agent 会话等自动化环境中,必须使用非交互模式:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
ec agent new helper.agentid.pub --non-interactive \
|
|
31
|
+
--project /home/user/helper-project \
|
|
32
|
+
--baseagent codex \
|
|
33
|
+
--owner owner.agentid.pub \
|
|
34
|
+
--name "Helper" \
|
|
35
|
+
--description "协助处理项目开发任务"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
建议先用 dry-run 检查最终参数:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
ec agent new helper.agentid.pub --non-interactive \
|
|
42
|
+
--project /home/user/helper-project \
|
|
43
|
+
--baseagent codex \
|
|
44
|
+
--owner owner.agentid.pub \
|
|
45
|
+
--name "Helper" \
|
|
46
|
+
--description "协助处理项目开发任务" \
|
|
47
|
+
--dry-run --format json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
创建时会注册 AID、写入 Agent 配置、生成并尝试发布初始 `agent.md`,然后尝试热加载。配置写盘时 `lifecycle` 初始为 `created`;channel 很快触发 Bootstrap 时,查询结果也可能已经是 `bootstrapping`:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
ec agent get helper.agentid.pub lifecycle
|
|
54
|
+
ec agent show helper.agentid.pub
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
如果是 menu 创建,`accepted=true` 只表示请求已受理。继续轮询 `createProgress.status` 直到 `ready` 或 `failed`。这里的 `createProgress.status=ready` **只表示后台创建流水线结束,不等于 `lifecycle=active`**。
|
|
58
|
+
|
|
59
|
+
## 2. 触发 Bootstrap
|
|
60
|
+
|
|
61
|
+
当任一已启用 channel 可以联系 Owner 时,系统会向 Owner 发送固定的 Bootstrap 首条消息,并把生命周期从 `created` 切换为 `bootstrapping`。
|
|
62
|
+
|
|
63
|
+
部分 channel 不能在连接时主动定位 Owner。这种情况下 Agent 会保持 `created`;Owner 需要先在该 channel 向新 Agent 发送一条消息,系统获得回复上下文后再启动 Bootstrap。
|
|
64
|
+
|
|
65
|
+
`created` 状态下 Base Agent 不处理普通消息。`bootstrapping` 状态下只加载 Bootstrap 专属提示词,不加载正常的 ECK 规则、人格、记忆或命令能力卡。
|
|
66
|
+
|
|
67
|
+
## 3. Owner 与新 Agent 确认名片信息
|
|
68
|
+
|
|
69
|
+
以下步骤发生在**新 Agent 自己的 Bootstrap 会话**中,不由创建者代替执行。
|
|
70
|
+
|
|
71
|
+
新 Agent 先读取系统注入路径指向的本地 `agent.md`,把其中格式有效的字段作为现有候选值:
|
|
72
|
+
|
|
73
|
+
- `name`:简短清晰的显示名
|
|
74
|
+
- `description`:一句到两句话描述职责或定位
|
|
75
|
+
- `tags`:3 到 6 个非空标签
|
|
76
|
+
|
|
77
|
+
交互规则:
|
|
78
|
+
|
|
79
|
+
1. 已有有效值时,新 Agent 简短回显并询问保持还是修改,不要求 Owner 重新输入。
|
|
80
|
+
2. 空值、占位值或格式错误的字段按缺失处理;`tags` 不足 3 个或超过 6 个也不合格。
|
|
81
|
+
3. 只有 Owner 明确提供、修改、选择或确认的内容才可作为最终值。不得从普通任务、示例文字或顺带提及的内容中推断。
|
|
82
|
+
4. 信息缺失、冲突或含义不清时,只追问对应部分,保留此前已明确的内容。
|
|
83
|
+
5. 三项齐全后,新 Agent 必须回显整组最终值,并再次请求 Owner 明确确认。
|
|
84
|
+
|
|
85
|
+
例如,Owner 可以回复:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
名称用 Helper;简介是“协助维护和开发项目”;标签用 coding、maintenance、typescript。请按这些值设置。
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
新 Agent 应回显这三个最终值并等待确认。Owner 再明确回复“确认”后,才能修改和发布名片。
|
|
92
|
+
|
|
93
|
+
Bootstrap 完成前,新 Agent 不处理普通任务,也不执行与本流程无关的工具或命令。如果 Owner 要求跳过、先工作或同时交办普通任务,新 Agent 应说明必须先完成首次设定,并继续确认缺失字段。
|
|
94
|
+
|
|
95
|
+
## 4. 新 Agent 修改并发布自己的 agent.md
|
|
96
|
+
|
|
97
|
+
Owner 明确确认最终值后,新 Agent 才能编辑系统提示词中 `agentMdPath` 指向的本地文件,并且:
|
|
98
|
+
|
|
99
|
+
- 只修改 YAML frontmatter 中的 `name`、`description`、`tags`。
|
|
100
|
+
- 保留其他 frontmatter 字段、Markdown 正文和文件结构。
|
|
101
|
+
- 修改后检查 YAML 合法,且三个字段满足上述格式要求。
|
|
102
|
+
|
|
103
|
+
然后由新 Agent 使用自己的 AID 签名并发布:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
ec aid agentmd put helper.agentid.pub
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
只有命令明确成功后才能继续。若发布失败,不得执行 `ready`;应向 Owner 说明具体错误,修正后再继续,不要盲目重复。
|
|
110
|
+
|
|
111
|
+
## 5. 新 Agent 完成 Bootstrap
|
|
112
|
+
|
|
113
|
+
发布成功后,新 Agent 执行整个 Bootstrap 的最后一个操作:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
ec agent ready helper.agentid.pub
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
托管任务只能对自身 AID 执行 `ready`,不能替其他 Agent 完成 Bootstrap。只有处于 `bootstrapping` 时,`ready` 才会完成状态切换;仍为 `created` 时会失败,已经是 `active` 时重复调用则保持 `active`。
|
|
120
|
+
|
|
121
|
+
`ready` 成功后,系统会:
|
|
122
|
+
|
|
123
|
+
1. 将 `lifecycle` 切换为 `active`。
|
|
124
|
+
2. 结束当前 Bootstrap 上下文。
|
|
125
|
+
3. 创建全新的主会话。
|
|
126
|
+
4. 发送正式欢迎消息。
|
|
127
|
+
|
|
128
|
+
新 Agent 在 `ready` 成功后不要从旧 Bootstrap 上下文再输出一段完成说明,也不要继续处理先前夹带的普通任务;后续工作从新主会话开始。
|
|
129
|
+
|
|
130
|
+
## 6. 创建者验收
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
ec agent get helper.agentid.pub lifecycle
|
|
134
|
+
# 应输出 active
|
|
135
|
+
|
|
136
|
+
ec agent show helper.agentid.pub
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
验收标准:
|
|
140
|
+
|
|
141
|
+
- `lifecycle` 为 `active`。
|
|
142
|
+
- `agent.md` 已成功签名发布。
|
|
143
|
+
- 系统已创建新的主会话并发送正式欢迎消息。
|
|
144
|
+
- 新 Agent 能在新主会话中处理普通任务。
|
|
145
|
+
|
|
146
|
+
如果仍为 `created`,检查 channel 是否已能联系 Owner,必要时由 Owner 主动发送首条消息。如果仍为 `bootstrapping`,回到新 Agent 的 Bootstrap 会话完成字段确认、名片发布和 `ready`,不要由创建者直接绕过引导。
|
|
@@ -53,6 +53,12 @@ ec agent delete <aid> [--purge]
|
|
|
53
53
|
- `--dry-run` 只输出最终创建计划,不注册 AID、不写配置、不上传 `agent.md`。
|
|
54
54
|
- `--force` 覆盖已有 `config.json`;AID 密钥保留。
|
|
55
55
|
|
|
56
|
+
## 创建 Agent
|
|
57
|
+
|
|
58
|
+
`ec agent new` 成功不代表新 Agent 已能处理普通任务。新 Agent 还必须完成 Bootstrap,并由自身执行 `ec agent ready <aid>` 进入 `active`。
|
|
59
|
+
|
|
60
|
+
完整的创建、Bootstrap、名片发布和验收示例见:`$KITS_DOCS/evolcore/agent-create.md`。
|
|
61
|
+
|
|
56
62
|
## 头像更新
|
|
57
63
|
|
|
58
64
|
头像上传通过 menu 协议暴露,不是 `ec agent` CLI 子命令:
|