evolcore 0.0.11 → 0.0.12
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 +22 -0
- package/bin/codex-managed-hook.mjs +80 -0
- package/dist/agents/claude-runner.js +35 -2
- package/dist/agents/codex-app-server-client.js +120 -14
- package/dist/agents/codex-runner.js +273 -35
- package/dist/agents/ecagent-runner.js +36 -4
- package/dist/agents/gemini-runner.js +1 -0
- package/dist/aun/aid/client.js +11 -21
- package/dist/aun/aid/managed-operation.js +107 -0
- package/dist/aun/msg/group.js +81 -12
- package/dist/aun/msg/managed-operation.js +804 -0
- package/dist/aun/msg/mention-schema.js +20 -0
- package/dist/aun/msg/p2p.js +7 -0
- package/dist/channels/aun.js +288 -108
- package/dist/channels/daemon.js +13 -0
- package/dist/cli/agent-command.js +56 -8
- package/dist/cli/agent.js +170 -6
- package/dist/cli/aun-commands.js +270 -69
- package/dist/cli/bench.js +12 -3
- package/dist/cli/daemon-commands.js +31 -31
- package/dist/cli/fs-command.js +60 -0
- package/dist/cli/handoff-command.js +3 -0
- package/dist/cli/index.js +30 -2
- package/dist/cli/managed-command-guard.js +41 -0
- package/dist/cli/model.js +11 -4
- package/dist/cli/queue-command.js +11 -1
- package/dist/cli/response.js +71 -0
- package/dist/cli/restart-monitor.js +4 -18
- package/dist/cli/trigger-command.js +4 -0
- package/dist/config/builtin-roles.js +4 -0
- package/dist/config/contact-book-store.js +11 -3
- package/dist/config/contact-request-service.js +161 -15
- package/dist/config/mention-mode.js +8 -24
- package/dist/core/auth/agent-delegation.js +0 -1
- package/dist/core/auth/operation-authorizer.js +30 -7
- package/dist/core/auth/operation-catalog.js +187 -25
- package/dist/core/bootstrap-messages.js +18 -0
- package/dist/core/bootstrap-service.js +63 -4
- package/dist/core/capability/providers/claude-capability-provider.js +9 -31
- package/dist/core/capability/providers/codex-capability-provider.js +4 -15
- package/dist/core/capability/skill-discovery.js +55 -0
- package/dist/core/channel-loader.js +1 -1
- package/dist/core/command/cli-intent-parser.js +9 -0
- package/dist/core/command/command-handler.js +467 -6
- package/dist/core/command/connect-menu.js +15 -0
- package/dist/core/command/group-menu.js +4 -4
- package/dist/core/command/menu-catalog.js +366 -0
- package/dist/core/command/menu-handler.js +147 -35
- package/dist/core/command/menu-protocol.js +71 -0
- package/dist/core/command/slash-gate.js +5 -2
- package/dist/core/command/slash-handler.js +41 -21
- package/dist/core/daemon-file-cache.js +2 -1
- package/dist/core/data-migration.js +9 -2
- package/dist/core/evolagent.js +4 -2
- package/dist/core/handoff/dispatcher.js +5 -2
- package/dist/core/handoff/runtime.js +145 -21
- package/dist/core/handoff/store.js +24 -0
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/message/logical-queue-bridge.js +1 -0
- package/dist/core/message/mention-schema.js +126 -0
- package/dist/core/message/message-bridge.js +236 -78
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +1 -0
- package/dist/core/message/response-engine.js +54 -36
- package/dist/core/permission/ec-command-parser.js +156 -10
- package/dist/core/permission/sandbox-runtime.js +22 -1
- package/dist/core/permission/tool-policy.js +73 -30
- package/dist/core/protected-paths.js +7 -2
- package/dist/core/session/session-manager.js +21 -2
- package/dist/eck/manifest-engine.js +39 -13
- package/dist/index.js +384 -98
- package/dist/ipc.js +218 -9
- package/dist/response-system/coordinator.js +6 -4
- package/dist/response-system/engines/v1/proactive-flow.js +26 -8
- package/dist/response-system/modes/single-session/index.js +14 -1
- package/dist/response-system/selector.js +6 -5
- package/dist/trigger/anomaly-store.js +4 -0
- package/dist/trigger/script-executor.js +1 -0
- package/dist/utils/codex-app-server-registry.js +90 -0
- package/dist/utils/codex-cli.js +5 -1
- package/dist/utils/cross-platform.js +15 -0
- package/dist/utils/npm-ops.js +5 -12
- package/dist/utils/tool-summary.js +11 -0
- package/kits/docs/channels/aun.md +1 -1
- package/kits/docs/context-assembly.md +2 -2
- package/kits/docs/evolcore/agent.md +11 -6
- package/kits/docs/evolcore/aid.md +14 -0
- package/kits/docs/prompt-loading-architecture.md +1 -2
- package/kits/docs/venues/group.md +1 -1
- package/kits/eck_manifest.json +0 -12
- package/kits/rules/04-relation.md +1 -1
- package/kits/rules/05-venue.md +2 -2
- package/kits/schemas/single-session.schema.1.json +3 -3
- package/kits/schemas/single-session.schema.2.json +3 -3
- package/kits/templates/roles/admin.json +22 -0
- package/kits/templates/roles/member.json +27 -0
- package/kits/templates/roles/visitor.json +33 -3
- package/kits/templates/system-fragments/bootstrap.md +1 -1
- package/kits/templates/system-fragments/channel.md +1 -1
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +2 -2
- package/dist/core/command/evol-menu-version-gate.js +0 -39
|
@@ -13,13 +13,14 @@ import { buildCodexProtectedFilesystemRules, isSameOrDescendant, resolveProtecte
|
|
|
13
13
|
import { CodexAppServerClient } from './codex-app-server-client.js';
|
|
14
14
|
import { resolveOpenaiConfig } from './baseagent.js';
|
|
15
15
|
import { logger } from '../utils/logger.js';
|
|
16
|
+
import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
16
17
|
import { isRetryableError } from '../utils/error-utils.js';
|
|
17
18
|
import { renderActionAsText } from '../core/interaction-router.js';
|
|
18
19
|
import { buildEnvelope, sendInteractionPayload } from '../core/message/message-utils.js';
|
|
19
20
|
import { resolveCodexCapabilityThreadConfigForProject } from '../core/capability/capability-manager.js';
|
|
20
21
|
import { AGENT_DELEGATION_TOKEN_ENV, hashDelegatedCommandArgv } from '../core/auth/agent-delegation.js';
|
|
21
22
|
import { sanitizeShellExecutionEnvironment } from '../core/permission/shell-environment.js';
|
|
22
|
-
import { classifyEvolcoreShellCommand, parseLiteralShellArgv } from '../core/permission/ec-command-parser.js';
|
|
23
|
+
import { classifyEvolcoreShellCommand, parseLiteralShellArgv, unwrapCodexShellCommandArgv } from '../core/permission/ec-command-parser.js';
|
|
23
24
|
import { compareVersions } from '../utils/npm-ops.js';
|
|
24
25
|
import { resolvePaths, resolveRoot } from '../paths.js';
|
|
25
26
|
import { buildSessionTurnList } from '../core/session/session-turns.js';
|
|
@@ -118,10 +119,9 @@ const CODEX_CATALOG_FALLBACK = [
|
|
|
118
119
|
];
|
|
119
120
|
const CODEX_DELEGATION_CARRIER_METADATA_KEY = '__codexDelegationCarrierV1';
|
|
120
121
|
let codexCatalogCache = null;
|
|
121
|
-
// The permission bridge is version-locked to the
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
export const MIN_CODEX_CLI_VERSION = '0.144.1';
|
|
122
|
+
// The permission bridge is version-locked to the audited approval schema and
|
|
123
|
+
// managed PreToolUse hook behavior used by proactive first-tool enforcement.
|
|
124
|
+
export const MIN_CODEX_CLI_VERSION = '0.147.0';
|
|
125
125
|
export function parseCodexCliVersion(output) {
|
|
126
126
|
const match = output.match(/\b(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\b/);
|
|
127
127
|
return match?.[1] ?? null;
|
|
@@ -223,6 +223,8 @@ export class CodexRunner {
|
|
|
223
223
|
trackedApprovalItems = new Map();
|
|
224
224
|
threadDelegationCarriers = new Map();
|
|
225
225
|
pendingInterrupts = new Map();
|
|
226
|
+
threadOperationLocks = new Map();
|
|
227
|
+
activeOperationReleases = new Map();
|
|
226
228
|
appServerClient = null;
|
|
227
229
|
onSessionIdUpdate;
|
|
228
230
|
onCompactStart;
|
|
@@ -289,15 +291,6 @@ export class CodexRunner {
|
|
|
289
291
|
const argv = parseLiteralShellArgv(command);
|
|
290
292
|
return argv?.[0] === 'ec' ? argv : undefined;
|
|
291
293
|
}
|
|
292
|
-
unwrapCodexShellCommandArgv(argv) {
|
|
293
|
-
if (argv.length !== 3 || argv[1] !== '-lc')
|
|
294
|
-
return undefined;
|
|
295
|
-
const shell = argv[0];
|
|
296
|
-
if (!['bash', '/bin/bash', '/usr/bin/bash', 'sh', '/bin/sh', '/usr/bin/sh'].includes(shell)) {
|
|
297
|
-
return undefined;
|
|
298
|
-
}
|
|
299
|
-
return argv[2];
|
|
300
|
-
}
|
|
301
294
|
approvedEvolcoreCommandArgv(toolInput) {
|
|
302
295
|
const explicitArgv = Array.isArray(toolInput.commandArgv)
|
|
303
296
|
&& toolInput.commandArgv.every(value => typeof value === 'string')
|
|
@@ -306,7 +299,7 @@ export class CodexRunner {
|
|
|
306
299
|
if (explicitArgv?.[0] === 'ec')
|
|
307
300
|
return explicitArgv;
|
|
308
301
|
const explicitWrappedCommand = explicitArgv
|
|
309
|
-
?
|
|
302
|
+
? unwrapCodexShellCommandArgv(explicitArgv)
|
|
310
303
|
: undefined;
|
|
311
304
|
if (explicitWrappedCommand !== undefined) {
|
|
312
305
|
return this.approvedLiteralEvolcoreCommandArgv(explicitWrappedCommand);
|
|
@@ -317,7 +310,7 @@ export class CodexRunner {
|
|
|
317
310
|
return directArgv;
|
|
318
311
|
const wrappedArgv = parseLiteralShellArgv(command);
|
|
319
312
|
const wrappedCommand = wrappedArgv
|
|
320
|
-
?
|
|
313
|
+
? unwrapCodexShellCommandArgv(wrappedArgv)
|
|
321
314
|
: undefined;
|
|
322
315
|
return wrappedCommand === undefined
|
|
323
316
|
? undefined
|
|
@@ -796,6 +789,35 @@ export class CodexRunner {
|
|
|
796
789
|
}
|
|
797
790
|
setSendPrompt(fn) { this.sendPromptFn = fn; }
|
|
798
791
|
setPermissionContext(sessionId, context) { this.permissionContexts.set(sessionId, context); }
|
|
792
|
+
async evaluatePreToolUse(threadId, toolName, toolInput) {
|
|
793
|
+
const sessionKey = this.findSessionKeyByThread(threadId);
|
|
794
|
+
const activeThread = this.activeSessions.get(sessionKey);
|
|
795
|
+
const context = this.permissionContexts.get(sessionKey);
|
|
796
|
+
if (!activeThread || activeThread !== threadId) {
|
|
797
|
+
return { ok: true, applicable: false };
|
|
798
|
+
}
|
|
799
|
+
const proactiveSession = context?.chatmode === 'proactive';
|
|
800
|
+
if (!context || (proactiveSession && !context.preToolUsePolicyHook)) {
|
|
801
|
+
return { ok: false, applicable: true, decision: 'deny', reason: 'EvolCore proactive session policy context is unavailable' };
|
|
802
|
+
}
|
|
803
|
+
// A tracked interactive thread still needs an authenticated, explicit
|
|
804
|
+
// allow so the managed hook can fail closed when no runner matches.
|
|
805
|
+
if (!context.preToolUsePolicyHook)
|
|
806
|
+
return { ok: true, applicable: true, decision: 'allow' };
|
|
807
|
+
const normalizedInput = toolInput && typeof toolInput === 'object' && !Array.isArray(toolInput)
|
|
808
|
+
? toolInput
|
|
809
|
+
: {};
|
|
810
|
+
try {
|
|
811
|
+
const result = context.preToolUsePolicyHook(toolName, normalizedInput);
|
|
812
|
+
if (result?.block) {
|
|
813
|
+
return { ok: true, applicable: true, decision: 'deny', reason: result.reason };
|
|
814
|
+
}
|
|
815
|
+
return { ok: true, applicable: true, decision: 'allow' };
|
|
816
|
+
}
|
|
817
|
+
catch (error) {
|
|
818
|
+
return { ok: false, applicable: true, decision: 'deny', reason: `policy hook failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
819
|
+
}
|
|
820
|
+
}
|
|
799
821
|
setPermissionGateway(gw) { this.permissionGateway = gw; }
|
|
800
822
|
// ── Stream management (needed by MessageProcessor) ──
|
|
801
823
|
registerStream(key, stream) {
|
|
@@ -805,13 +827,135 @@ export class CodexRunner {
|
|
|
805
827
|
this.activeStreams.delete(key);
|
|
806
828
|
this.activeAbortControllers.delete(key);
|
|
807
829
|
this.pendingInterrupts.delete(key);
|
|
830
|
+
const release = this.activeOperationReleases.get(key);
|
|
831
|
+
if (release) {
|
|
832
|
+
this.activeOperationReleases.delete(key);
|
|
833
|
+
release();
|
|
834
|
+
}
|
|
808
835
|
}
|
|
809
836
|
hasActiveStream(key) {
|
|
810
837
|
return this.activeStreams.has(key) || this.activeAbortControllers.has(key) || this.activeTurns.has(key);
|
|
811
838
|
}
|
|
839
|
+
threadOperationKey(threadId, sessionId) {
|
|
840
|
+
// Once a thread id is known it is the shared identity, including when
|
|
841
|
+
// multiple EvolCore sessions happen to address the same Codex thread.
|
|
842
|
+
if (threadId)
|
|
843
|
+
return `thread:${threadId}`;
|
|
844
|
+
if (sessionId)
|
|
845
|
+
return `session:${sessionId}`;
|
|
846
|
+
return 'session:unknown';
|
|
847
|
+
}
|
|
848
|
+
getThreadOperationLock(key) {
|
|
849
|
+
const existing = this.threadOperationLocks.get(key);
|
|
850
|
+
if (existing)
|
|
851
|
+
return existing;
|
|
852
|
+
const lock = { aliases: new Set([key]) };
|
|
853
|
+
this.threadOperationLocks.set(key, lock);
|
|
854
|
+
return lock;
|
|
855
|
+
}
|
|
856
|
+
bindThreadOperationKeys(...keys) {
|
|
857
|
+
const uniqueKeys = [...new Set(keys.filter(Boolean))];
|
|
858
|
+
if (uniqueKeys.length < 2)
|
|
859
|
+
return;
|
|
860
|
+
const locks = uniqueKeys.map(key => this.getThreadOperationLock(key));
|
|
861
|
+
const primary = locks[0];
|
|
862
|
+
for (const other of locks.slice(1)) {
|
|
863
|
+
if (other === primary)
|
|
864
|
+
continue;
|
|
865
|
+
const tails = [primary.tail, other.tail].filter((tail) => !!tail);
|
|
866
|
+
if (tails.length === 1) {
|
|
867
|
+
primary.tail = tails[0];
|
|
868
|
+
}
|
|
869
|
+
else if (tails.length > 1) {
|
|
870
|
+
const merged = Promise.all(tails).then(() => undefined);
|
|
871
|
+
primary.tail = merged;
|
|
872
|
+
merged.then(() => {
|
|
873
|
+
if (primary.tail === merged)
|
|
874
|
+
primary.tail = undefined;
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
for (const alias of other.aliases) {
|
|
878
|
+
primary.aliases.add(alias);
|
|
879
|
+
this.threadOperationLocks.set(alias, primary);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
isActiveThreadOperationAlias(key) {
|
|
884
|
+
if (key.startsWith('session:'))
|
|
885
|
+
return this.activeSessions.has(key.slice('session:'.length));
|
|
886
|
+
if (key.startsWith('thread:')) {
|
|
887
|
+
const threadId = key.slice('thread:'.length);
|
|
888
|
+
for (const activeThreadId of this.activeSessions.values()) {
|
|
889
|
+
if (activeThreadId === threadId)
|
|
890
|
+
return true;
|
|
891
|
+
}
|
|
892
|
+
return false;
|
|
893
|
+
}
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
pruneThreadOperationLocks() {
|
|
897
|
+
for (const [key, lock] of this.threadOperationLocks) {
|
|
898
|
+
if (lock.tail || this.isActiveThreadOperationAlias(key))
|
|
899
|
+
continue;
|
|
900
|
+
this.threadOperationLocks.delete(key);
|
|
901
|
+
lock.aliases.delete(key);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
async acquireThreadOperation(key, operation) {
|
|
905
|
+
const lock = this.getThreadOperationLock(key);
|
|
906
|
+
const previous = lock.tail;
|
|
907
|
+
let releaseCurrent;
|
|
908
|
+
const current = new Promise(resolve => {
|
|
909
|
+
releaseCurrent = resolve;
|
|
910
|
+
});
|
|
911
|
+
lock.tail = current;
|
|
912
|
+
if (previous) {
|
|
913
|
+
logger.info(`[CodexRunner] queued ${operation} behind an active operation: ${key}`);
|
|
914
|
+
await previous;
|
|
915
|
+
}
|
|
916
|
+
let released = false;
|
|
917
|
+
return () => {
|
|
918
|
+
if (released)
|
|
919
|
+
return;
|
|
920
|
+
released = true;
|
|
921
|
+
if (lock.tail === current)
|
|
922
|
+
lock.tail = undefined;
|
|
923
|
+
releaseCurrent();
|
|
924
|
+
this.pruneThreadOperationLocks();
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
async *holdThreadOperation(stream, sessionId, release) {
|
|
928
|
+
try {
|
|
929
|
+
for await (const item of stream)
|
|
930
|
+
yield item;
|
|
931
|
+
}
|
|
932
|
+
finally {
|
|
933
|
+
if (this.activeOperationReleases.get(sessionId) === release) {
|
|
934
|
+
this.activeOperationReleases.delete(sessionId);
|
|
935
|
+
}
|
|
936
|
+
release();
|
|
937
|
+
}
|
|
938
|
+
}
|
|
812
939
|
// ── Core: runQuery ──
|
|
813
940
|
async runQuery(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
|
|
941
|
+
const knownThreadId = initialAgentSessionId || this.activeSessions.get(sessionId);
|
|
942
|
+
const operationKey = this.threadOperationKey(knownThreadId, sessionId);
|
|
943
|
+
const release = await this.acquireThreadOperation(operationKey, 'thread/resume+turn/start');
|
|
944
|
+
try {
|
|
945
|
+
const stream = await this.runQueryLocked(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv);
|
|
946
|
+
this.activeOperationReleases.set(sessionId, release);
|
|
947
|
+
return this.holdThreadOperation(stream, sessionId, release);
|
|
948
|
+
}
|
|
949
|
+
catch (error) {
|
|
950
|
+
release();
|
|
951
|
+
throw error;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
async runQueryLocked(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager, modelOverride, runtimeEnv) {
|
|
814
955
|
let agentSessionId = initialAgentSessionId || this.activeSessions.get(sessionId);
|
|
956
|
+
if (this.permissionContexts.get(sessionId)?.chatmode === 'proactive' && process.platform !== 'linux') {
|
|
957
|
+
throw new Error('Codex proactive managed PreToolUse enforcement currently requires Linux');
|
|
958
|
+
}
|
|
815
959
|
const resumingThread = !!agentSessionId;
|
|
816
960
|
const callModel = modelOverride?.model || this.model;
|
|
817
961
|
const callEffort = modelOverride?.effortMode === 'model_default'
|
|
@@ -893,6 +1037,7 @@ export class CodexRunner {
|
|
|
893
1037
|
}
|
|
894
1038
|
agentSessionId = threadId;
|
|
895
1039
|
this.activeSessions.set(sessionId, threadId);
|
|
1040
|
+
this.bindThreadOperationKeys(`session:${sessionId}`, `thread:${threadId}`);
|
|
896
1041
|
this.threadProjectPaths.set(threadId, path.resolve(projectPath));
|
|
897
1042
|
this.bindExternalToolBoundary(threadId, externalToolConfig);
|
|
898
1043
|
this.onSessionIdUpdate?.(sessionId, threadId);
|
|
@@ -1035,10 +1180,12 @@ export class CodexRunner {
|
|
|
1035
1180
|
}
|
|
1036
1181
|
if (agentSessionId) {
|
|
1037
1182
|
this.activeSessions.set(sessionId, agentSessionId);
|
|
1183
|
+
this.bindThreadOperationKeys(`session:${sessionId}`, `thread:${agentSessionId}`);
|
|
1038
1184
|
}
|
|
1039
1185
|
else {
|
|
1040
1186
|
this.activeSessions.delete(sessionId);
|
|
1041
1187
|
}
|
|
1188
|
+
this.pruneThreadOperationLocks();
|
|
1042
1189
|
this.onSessionIdUpdate?.(sessionId, agentSessionId);
|
|
1043
1190
|
}
|
|
1044
1191
|
async closeSession(sessionId) {
|
|
@@ -1057,6 +1204,7 @@ export class CodexRunner {
|
|
|
1057
1204
|
this.unbindExternalToolBoundary(threadId);
|
|
1058
1205
|
}
|
|
1059
1206
|
this.clearTrackedApprovalItemsForThread(threadId);
|
|
1207
|
+
this.pruneThreadOperationLocks();
|
|
1060
1208
|
}
|
|
1061
1209
|
resolveSessionFile(agentSessionId, _projectPath) {
|
|
1062
1210
|
// Codex session 文件: ~/.codex/sessions/YYYY/MM/DD/rollout-*-{threadId}.jsonl
|
|
@@ -1089,10 +1237,12 @@ export class CodexRunner {
|
|
|
1089
1237
|
this.threadDelegationCarriers.delete(threadId);
|
|
1090
1238
|
this.unbindExternalToolBoundary(threadId);
|
|
1091
1239
|
this.clearTrackedApprovalItemsForThread(threadId);
|
|
1240
|
+
this.pruneThreadOperationLocks();
|
|
1092
1241
|
this.onSessionIdUpdate?.(sessionId, '');
|
|
1093
1242
|
return true;
|
|
1094
1243
|
}
|
|
1095
1244
|
async compactSession(_sessionId, agentSessionId, _projectPath) {
|
|
1245
|
+
const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId, _sessionId), 'compact');
|
|
1096
1246
|
try {
|
|
1097
1247
|
const appServer = this.getAppServerClient();
|
|
1098
1248
|
this.onCompactStart?.(_sessionId);
|
|
@@ -1129,6 +1279,9 @@ export class CodexRunner {
|
|
|
1129
1279
|
logger.error('[CodexRunner] Compact failed:', error);
|
|
1130
1280
|
return false;
|
|
1131
1281
|
}
|
|
1282
|
+
finally {
|
|
1283
|
+
release();
|
|
1284
|
+
}
|
|
1132
1285
|
}
|
|
1133
1286
|
async compact(sessionId, agentSessionId, projectPath) {
|
|
1134
1287
|
return this.compactSession(sessionId, agentSessionId, projectPath);
|
|
@@ -1254,6 +1407,15 @@ export class CodexRunner {
|
|
|
1254
1407
|
return newest?.path;
|
|
1255
1408
|
}
|
|
1256
1409
|
async forkSession(agentSessionId, projectPath, title) {
|
|
1410
|
+
const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId), 'fork');
|
|
1411
|
+
try {
|
|
1412
|
+
return await this.forkSessionLocked(agentSessionId, projectPath, title);
|
|
1413
|
+
}
|
|
1414
|
+
finally {
|
|
1415
|
+
release();
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
async forkSessionLocked(agentSessionId, projectPath, title) {
|
|
1257
1419
|
const sessionKey = this.findSessionKeyByThread(agentSessionId);
|
|
1258
1420
|
const mode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
1259
1421
|
const executionSandbox = this.buildExecutionSandboxOptions(sessionKey, mode, projectPath);
|
|
@@ -1278,18 +1440,30 @@ export class CodexRunner {
|
|
|
1278
1440
|
return forkedThreadId;
|
|
1279
1441
|
}
|
|
1280
1442
|
async forkSessionAt(agentSessionId, projectPath, assistantMessageId, title) {
|
|
1281
|
-
const
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
const
|
|
1289
|
-
if (
|
|
1290
|
-
|
|
1443
|
+
const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId), 'forkAt');
|
|
1444
|
+
try {
|
|
1445
|
+
const turns = buildSessionTurnList(await this.getSessionMessages(agentSessionId, projectPath));
|
|
1446
|
+
const targetIndex = turns.findIndex(turn => turn.assistantUuid === assistantMessageId);
|
|
1447
|
+
if (targetIndex < 0)
|
|
1448
|
+
throw new Error('fork point not found');
|
|
1449
|
+
const forkedThreadId = await this.forkSessionLocked(agentSessionId, projectPath, title);
|
|
1450
|
+
const turnsToDiscard = turns.length - targetIndex - 1;
|
|
1451
|
+
if (turnsToDiscard > 0) {
|
|
1452
|
+
const rollbackRelease = await this.acquireThreadOperation(this.threadOperationKey(forkedThreadId), 'rollback');
|
|
1453
|
+
try {
|
|
1454
|
+
const rolledBack = await this.rollbackSessionTurnsLocked(forkedThreadId, turnsToDiscard);
|
|
1455
|
+
if (!rolledBack)
|
|
1456
|
+
throw new Error('forked thread rollback failed');
|
|
1457
|
+
}
|
|
1458
|
+
finally {
|
|
1459
|
+
rollbackRelease();
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
return forkedThreadId;
|
|
1463
|
+
}
|
|
1464
|
+
finally {
|
|
1465
|
+
release();
|
|
1291
1466
|
}
|
|
1292
|
-
return forkedThreadId;
|
|
1293
1467
|
}
|
|
1294
1468
|
async setSessionName(agentSessionId, name) {
|
|
1295
1469
|
return this.getAppServerClient().threadSetName(agentSessionId, name);
|
|
@@ -1421,6 +1595,25 @@ export class CodexRunner {
|
|
|
1421
1595
|
// proactive 模式行为策略(首次工具调用必须是 ec msg send)
|
|
1422
1596
|
const policyResult = this.permissionContexts.get(sessionKey)?.policyHook?.(toolName, toolInput);
|
|
1423
1597
|
if (policyResult?.block) {
|
|
1598
|
+
logger.info(`[CodexRunner] app-server approval declined by session policy: session=${sessionKey} ` +
|
|
1599
|
+
`requestId=${request.id ?? '<missing>'} code=${policyResult.policyCode ?? 'session_policy_hook'} ` +
|
|
1600
|
+
`reason=${policyResult.reason ?? 'policy denied'} executed=false`);
|
|
1601
|
+
try {
|
|
1602
|
+
await permissionContext?.recordExecutionAnomaly?.({
|
|
1603
|
+
code: 'operation_blocked',
|
|
1604
|
+
severity: 'warning',
|
|
1605
|
+
phase: 'execution',
|
|
1606
|
+
occurredAt: Date.now(),
|
|
1607
|
+
toolName,
|
|
1608
|
+
...(request.id !== undefined ? { requestId: String(request.id) } : {}),
|
|
1609
|
+
policyCode: policyResult.policyCode ?? 'session_policy_hook',
|
|
1610
|
+
summary: summarizeToolInputForAudit(toolName, toolInput).slice(0, 512),
|
|
1611
|
+
effect: 'operation_skipped',
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
catch (error) {
|
|
1615
|
+
logger.warn(`[CodexRunner] failed to record policy-hook denial: session=${sessionKey} tool=${toolName} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1616
|
+
}
|
|
1424
1617
|
return this.toAppServerApprovalResponse(request.method, 'deny', toolInput, unattended);
|
|
1425
1618
|
}
|
|
1426
1619
|
const summary = this.summarizeAppServerRequest(request.method, params);
|
|
@@ -1434,7 +1627,7 @@ export class CodexRunner {
|
|
|
1434
1627
|
const sessionMode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
1435
1628
|
logger.info(`[CodexRunner] app-server approval request id=${request.id} method=${request.method} session=${sessionKey} mode=${sessionMode} tool=${toolName} summary=${summary}`);
|
|
1436
1629
|
try {
|
|
1437
|
-
const decision = await this.resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath, operationCwd);
|
|
1630
|
+
const decision = await this.resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath, operationCwd, request.id);
|
|
1438
1631
|
if (decision !== 'deny'
|
|
1439
1632
|
&& (request.method === 'item/commandExecution/requestApproval' || request.method === 'execCommandApproval')
|
|
1440
1633
|
&& this.approvedEvolcoreCommandArgv(toolInput)) {
|
|
@@ -1965,7 +2158,7 @@ export class CodexRunner {
|
|
|
1965
2158
|
return true;
|
|
1966
2159
|
});
|
|
1967
2160
|
}
|
|
1968
|
-
async resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath = process.cwd(), operationCwd = workspacePath) {
|
|
2161
|
+
async resolvePermissionDecision(sessionKey, toolName, toolInput, summary, reason, workspacePath = process.cwd(), operationCwd = workspacePath, requestId) {
|
|
1969
2162
|
const permissionContext = this.permissionContexts.get(sessionKey);
|
|
1970
2163
|
const permissionPrompt = permissionContext?.sendPrompt ?? this.sendPromptFn;
|
|
1971
2164
|
const hasFilesystemPermissions = (toolName === 'Bash' || toolName === 'PermissionGrant')
|
|
@@ -1980,6 +2173,24 @@ export class CodexRunner {
|
|
|
1980
2173
|
// per-session 权限模式(runQuery 写入);缺省回落实例级 currentMode(兼容无 runQuery 上下文的调用)
|
|
1981
2174
|
const rawMode = this.chatModes.get(sessionKey) ?? this.currentMode;
|
|
1982
2175
|
const mode = normalizePermissionMode(rawMode).mode;
|
|
2176
|
+
const recordBlockedOperation = async (policyCode, operationInput = toolInput) => {
|
|
2177
|
+
try {
|
|
2178
|
+
await permissionContext?.recordExecutionAnomaly?.({
|
|
2179
|
+
code: 'operation_blocked',
|
|
2180
|
+
severity: 'warning',
|
|
2181
|
+
phase: 'execution',
|
|
2182
|
+
occurredAt: Date.now(),
|
|
2183
|
+
toolName,
|
|
2184
|
+
...(typeof requestId === 'string' || typeof requestId === 'number' ? { requestId: String(requestId) } : {}),
|
|
2185
|
+
policyCode,
|
|
2186
|
+
summary: summarizeToolInputForAudit(toolName, operationInput).slice(0, 512),
|
|
2187
|
+
effect: 'operation_skipped',
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
catch (error) {
|
|
2191
|
+
logger.warn(`[CodexRunner] failed to record blocked operation: session=${sessionKey} tool=${toolName} code=${policyCode} error=${error instanceof Error ? error.message : String(error)}`);
|
|
2192
|
+
}
|
|
2193
|
+
};
|
|
1983
2194
|
const preflight = evaluateToolPreflight(toolName, toolInput, {
|
|
1984
2195
|
sessionId: sessionKey,
|
|
1985
2196
|
selfAid: permissionContext?.selfAid,
|
|
@@ -1990,8 +2201,12 @@ export class CodexRunner {
|
|
|
1990
2201
|
projectPath: operationCwd,
|
|
1991
2202
|
workspacePath,
|
|
1992
2203
|
});
|
|
1993
|
-
if (preflight.behavior === 'deny')
|
|
2204
|
+
if (preflight.behavior === 'deny') {
|
|
2205
|
+
if (preflight.policyCode) {
|
|
2206
|
+
await recordBlockedOperation(preflight.policyCode);
|
|
2207
|
+
}
|
|
1994
2208
|
return 'deny';
|
|
2209
|
+
}
|
|
1995
2210
|
if (preflight.behavior === 'allow')
|
|
1996
2211
|
return 'allow';
|
|
1997
2212
|
const checkedInput = preflight.input;
|
|
@@ -2015,8 +2230,10 @@ export class CodexRunner {
|
|
|
2015
2230
|
const grantScope = `codex:${this.permissionProfileName(sessionKey)}:${mode}`;
|
|
2016
2231
|
if (mode === 'readonly') {
|
|
2017
2232
|
const readonly = this.checkCodexReadonly(toolName, checkedInput, operationCwd, sessionKey);
|
|
2018
|
-
if (readonly.behavior === 'deny')
|
|
2233
|
+
if (readonly.behavior === 'deny') {
|
|
2234
|
+
await recordBlockedOperation('readonly_mode', checkedInput);
|
|
2019
2235
|
return 'deny';
|
|
2236
|
+
}
|
|
2020
2237
|
return 'allow';
|
|
2021
2238
|
}
|
|
2022
2239
|
if (toolName.startsWith('MCP:')) {
|
|
@@ -2024,13 +2241,19 @@ export class CodexRunner {
|
|
|
2024
2241
|
}
|
|
2025
2242
|
// auto 模式下检查危险命令,危险命令自动拒绝
|
|
2026
2243
|
if (mode === 'auto') {
|
|
2027
|
-
if (hasFileChangeExpansion || hasFilesystemPermissions || hasUnknownPermissions)
|
|
2244
|
+
if (hasFileChangeExpansion || hasFilesystemPermissions || hasUnknownPermissions) {
|
|
2245
|
+
await recordBlockedOperation('auto_permission_expansion', checkedInput);
|
|
2028
2246
|
return 'deny';
|
|
2029
|
-
|
|
2247
|
+
}
|
|
2248
|
+
if (toolName === 'PermissionGrant' && !hasNetworkPermissions) {
|
|
2249
|
+
await recordBlockedOperation('auto_permission_expansion', checkedInput);
|
|
2030
2250
|
return 'deny';
|
|
2251
|
+
}
|
|
2031
2252
|
const dangerous = checkDangerousCommand(toolName, checkedInput);
|
|
2032
|
-
if (dangerous.isDangerous)
|
|
2253
|
+
if (dangerous.isDangerous) {
|
|
2254
|
+
await recordBlockedOperation('auto_dangerous_command', checkedInput);
|
|
2033
2255
|
return 'deny';
|
|
2256
|
+
}
|
|
2034
2257
|
return 'allow';
|
|
2035
2258
|
}
|
|
2036
2259
|
const requiresSandboxExpansionApproval = hasFileChangeExpansion
|
|
@@ -2083,6 +2306,15 @@ export class CodexRunner {
|
|
|
2083
2306
|
throw new Error('Unsupported Codex app-server request: ' + method);
|
|
2084
2307
|
}
|
|
2085
2308
|
async rollbackSessionTurns(agentSessionId, _projectPath, numTurns) {
|
|
2309
|
+
const release = await this.acquireThreadOperation(this.threadOperationKey(agentSessionId), 'rollback');
|
|
2310
|
+
try {
|
|
2311
|
+
return await this.rollbackSessionTurnsLocked(agentSessionId, numTurns);
|
|
2312
|
+
}
|
|
2313
|
+
finally {
|
|
2314
|
+
release();
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
async rollbackSessionTurnsLocked(agentSessionId, numTurns) {
|
|
2086
2318
|
if (numTurns < 1)
|
|
2087
2319
|
return true;
|
|
2088
2320
|
const response = await this.getAppServerClient().threadRollback(agentSessionId, numTurns);
|
|
@@ -2121,7 +2353,11 @@ export class CodexRunner {
|
|
|
2121
2353
|
}
|
|
2122
2354
|
readGitHeadFile(projectPath, filePath) {
|
|
2123
2355
|
try {
|
|
2124
|
-
return execFileSync('git', ['show', `HEAD:${filePath.replace(/\\/g, '/')}`], {
|
|
2356
|
+
return execFileSync('git', ['show', `HEAD:${filePath.replace(/\\/g, '/')}`], {
|
|
2357
|
+
cwd: projectPath,
|
|
2358
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
2359
|
+
windowsHide: process.platform === 'win32',
|
|
2360
|
+
});
|
|
2125
2361
|
}
|
|
2126
2362
|
catch {
|
|
2127
2363
|
return null;
|
|
@@ -2877,6 +3113,8 @@ export class CodexRunner {
|
|
|
2877
3113
|
this.pendingInterrupts.clear();
|
|
2878
3114
|
this.permissionContexts.clear();
|
|
2879
3115
|
this.chatModes.clear();
|
|
3116
|
+
this.pruneThreadOperationLocks();
|
|
3117
|
+
this.threadOperationLocks.clear();
|
|
2880
3118
|
for (const sessionId of [...this.sessionTempDirs.keys()])
|
|
2881
3119
|
this.cleanupSessionTempDir(sessionId);
|
|
2882
3120
|
await this.appServerClient?.close();
|
|
@@ -9,6 +9,7 @@ import { requestDangerousCommandPermission } from '../core/permission/approval-g
|
|
|
9
9
|
import { normalizePermissionMode } from '../core/permission/mode.js';
|
|
10
10
|
import { checkDangerousCommand, checkReadonly, evaluateToolPreflight } from '../core/permission/tool-policy.js';
|
|
11
11
|
import { logger } from '../utils/logger.js';
|
|
12
|
+
import { summarizeToolInputForAudit } from '../utils/tool-summary.js';
|
|
12
13
|
import { resolveEcagentConfig } from './baseagent.js';
|
|
13
14
|
import { buildModelRequestHeaders } from './request-identity.js';
|
|
14
15
|
const PROVIDER_ID = 'evolcore-gateway';
|
|
@@ -957,9 +958,28 @@ export class EcagentRunner {
|
|
|
957
958
|
}
|
|
958
959
|
async authorizeTool(sessionId, projectPath, mode, toolName, input) {
|
|
959
960
|
const permissionContext = this.permissionContexts.get(sessionId);
|
|
961
|
+
const recordBlockedOperation = async (policyCode, operationInput = input) => {
|
|
962
|
+
try {
|
|
963
|
+
await permissionContext?.recordExecutionAnomaly?.({
|
|
964
|
+
code: 'operation_blocked',
|
|
965
|
+
severity: 'warning',
|
|
966
|
+
phase: 'execution',
|
|
967
|
+
occurredAt: Date.now(),
|
|
968
|
+
toolName,
|
|
969
|
+
policyCode,
|
|
970
|
+
summary: summarizeToolInputForAudit(toolName, operationInput).slice(0, 512),
|
|
971
|
+
effect: 'operation_skipped',
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
catch (error) {
|
|
975
|
+
logger.warn(`[EcagentRunner] failed to record blocked operation: session=${sessionId} tool=${toolName} code=${policyCode} error=${error instanceof Error ? error.message : String(error)}`);
|
|
976
|
+
}
|
|
977
|
+
};
|
|
960
978
|
const policy = permissionContext?.policyHook?.(toolName, input);
|
|
961
|
-
if (policy?.block)
|
|
979
|
+
if (policy?.block) {
|
|
980
|
+
await recordBlockedOperation(policy.policyCode ?? 'session_policy_hook');
|
|
962
981
|
return { block: true, reason: policy.reason || '当前会话策略拒绝此工具调用' };
|
|
982
|
+
}
|
|
963
983
|
const preflight = evaluateToolPreflight(toolName, input, {
|
|
964
984
|
sessionId,
|
|
965
985
|
selfAid: permissionContext?.selfAid,
|
|
@@ -969,8 +989,12 @@ export class EcagentRunner {
|
|
|
969
989
|
permissionMode: mode,
|
|
970
990
|
projectPath,
|
|
971
991
|
});
|
|
972
|
-
if (preflight.behavior === 'deny')
|
|
992
|
+
if (preflight.behavior === 'deny') {
|
|
993
|
+
if (preflight.policyCode) {
|
|
994
|
+
await recordBlockedOperation(preflight.policyCode, input);
|
|
995
|
+
}
|
|
973
996
|
return { block: true, reason: preflight.message };
|
|
997
|
+
}
|
|
974
998
|
if (preflight.behavior === 'allow')
|
|
975
999
|
return {};
|
|
976
1000
|
const checkedInput = preflight.input;
|
|
@@ -978,11 +1002,19 @@ export class EcagentRunner {
|
|
|
978
1002
|
const decision = checkReadonly(toolName, checkedInput, projectPath, {
|
|
979
1003
|
sessionId, channel: permissionContext?.channel, peerId: permissionContext?.userId, role: permissionContext?.role,
|
|
980
1004
|
});
|
|
981
|
-
|
|
1005
|
+
if (decision.behavior === 'deny') {
|
|
1006
|
+
await recordBlockedOperation('readonly_mode', checkedInput);
|
|
1007
|
+
return { block: true, reason: decision.message };
|
|
1008
|
+
}
|
|
1009
|
+
return {};
|
|
982
1010
|
}
|
|
983
1011
|
const dangerous = checkDangerousCommand(toolName, checkedInput);
|
|
984
1012
|
if (mode === 'auto') {
|
|
985
|
-
|
|
1013
|
+
if (dangerous.isDangerous) {
|
|
1014
|
+
await recordBlockedOperation('auto_dangerous_command', checkedInput);
|
|
1015
|
+
return { block: true, reason: `危险操作已由 auto 模式拒绝:${dangerous.reason}` };
|
|
1016
|
+
}
|
|
1017
|
+
return {};
|
|
986
1018
|
}
|
|
987
1019
|
const prompt = permissionContext?.sendPrompt ?? this.sendPromptFn;
|
|
988
1020
|
const decision = await requestDangerousCommandPermission(this.permissionGateway, sessionId, toolName, checkedInput, prompt, permissionContext, `ecagent:${mode}`, mode);
|
package/dist/aun/aid/client.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { execFileSync } from 'child_process';
|
|
5
|
-
import { isWindows } from '../../utils/cross-platform.js';
|
|
6
4
|
/**
|
|
7
5
|
* Suppress SDK console logs (DEBUG/INFO/WARN) in CLI context.
|
|
8
6
|
* Call once at CLI entry point — NOT at module load time, to avoid
|
|
@@ -40,10 +38,9 @@ export function suppressSdkLogs() {
|
|
|
40
38
|
return _origStderrWrite(chunk, encoding, callback);
|
|
41
39
|
};
|
|
42
40
|
}
|
|
43
|
-
// ====================
|
|
44
|
-
export const MIN_AUN_CORE_SDK = [0,
|
|
41
|
+
// ==================== SDK package ====================
|
|
42
|
+
export const MIN_AUN_CORE_SDK = [0, 5, 6];
|
|
45
43
|
export const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
|
|
46
|
-
// ==================== SDK & Environment ====================
|
|
47
44
|
function compareVersion(a, min) {
|
|
48
45
|
const parts = a.split('.').map(n => parseInt(n, 10));
|
|
49
46
|
if (parts.length < 3 || parts.some(isNaN))
|
|
@@ -57,6 +54,7 @@ function compareVersion(a, min) {
|
|
|
57
54
|
export function isAunSdkVersionOk(version) {
|
|
58
55
|
return compareVersion(version, MIN_AUN_CORE_SDK);
|
|
59
56
|
}
|
|
57
|
+
/** Resolve only the SDK bundled in EvolCore's own dependency tree. */
|
|
60
58
|
export function resolveAunCoreSdkPkg() {
|
|
61
59
|
try {
|
|
62
60
|
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -73,28 +71,20 @@ export function resolveAunCoreSdkPkg() {
|
|
|
73
71
|
dir = parent;
|
|
74
72
|
}
|
|
75
73
|
}
|
|
76
|
-
catch { /*
|
|
77
|
-
try {
|
|
78
|
-
const npmCmd = isWindows ? 'npm.cmd' : 'npm';
|
|
79
|
-
const globalRoot = execFileSync(npmCmd, ['root', '-g'], {
|
|
80
|
-
encoding: 'utf-8', timeout: 10000, shell: isWindows,
|
|
81
|
-
}).trim();
|
|
82
|
-
const pkgPath = path.join(globalRoot, AUN_CORE_SDK_PKG, 'package.json');
|
|
83
|
-
if (fs.existsSync(pkgPath)) {
|
|
84
|
-
const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
85
|
-
return { version: data.version, path: pkgPath };
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
catch { /* not found */ }
|
|
74
|
+
catch { /* invalid or missing package metadata */ }
|
|
89
75
|
return null;
|
|
90
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Compatibility guard retained for callers that explicitly check SDK readiness.
|
|
79
|
+
* The SDK is bundled with EvolCore and must never be installed independently.
|
|
80
|
+
*/
|
|
91
81
|
export async function ensureAunSdk() {
|
|
92
82
|
const installed = resolveAunCoreSdkPkg();
|
|
93
83
|
if (installed && isAunSdkVersionOk(installed.version))
|
|
94
84
|
return;
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
85
|
+
const expected = MIN_AUN_CORE_SDK.join('.');
|
|
86
|
+
const detected = installed?.version ?? 'missing';
|
|
87
|
+
throw new Error(`${AUN_CORE_SDK_PKG} ${detected} is not the bundled SDK expected by EvolCore (>= ${expected}); reinstall or upgrade EvolCore`);
|
|
98
88
|
}
|
|
99
89
|
export function isAunSdkReady() {
|
|
100
90
|
const installed = resolveAunCoreSdkPkg();
|