evolcore 0.0.26 → 0.0.28
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 +36 -0
- package/bin/codex-managed-acl.ps1 +43 -0
- package/dist/agents/baseagent-availability.js +68 -0
- package/dist/agents/baseagent.js +10 -5
- package/dist/agents/claude-runner.js +123 -22
- package/dist/agents/codex/app-server-client.js +70 -26
- package/dist/agents/codex/catalog.js +7 -1
- package/dist/agents/codex-runner.js +363 -421
- package/dist/agents/ecagent-runner.js +170 -25
- package/dist/agents/gemini-runner.js +6 -0
- package/dist/agents/runner-types.js +50 -2
- package/dist/aun/aid/agentmd.js +73 -31
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +180 -1
- package/dist/aun/aid/identity.js +3 -3
- package/dist/aun/group/history.js +303 -0
- package/dist/aun/group/identity.js +43 -0
- package/dist/aun/group/index.js +5 -0
- package/dist/aun/{msg/group.js → group/operations.js} +161 -38
- package/dist/aun/{menu-client.js → menu/client.js} +13 -7
- package/dist/aun/menu/index.js +2 -0
- package/dist/aun/menu/transport.js +49 -0
- package/dist/aun/msg/index.js +2 -1
- package/dist/aun/msg/managed-operation.js +84 -18
- package/dist/aun/msg/mention-schema.js +51 -0
- package/dist/aun/msg/p2p.js +13 -8
- package/dist/aun/msg/upload.js +43 -88
- package/dist/aun/outbox.js +29 -7
- package/dist/aun/rpc/client.js +1 -0
- package/dist/aun/storage/client.js +39 -50
- package/dist/channels/aun.js +544 -239
- package/dist/channels/daemon.js +10 -6
- package/dist/cli/agent-command.js +108 -6
- package/dist/cli/agent-create.js +515 -2
- package/dist/cli/agent.js +109 -8
- package/dist/cli/aun-commands.js +191 -42
- package/dist/cli/config.js +8 -5
- package/dist/cli/contact.js +2 -2
- package/dist/cli/ctl-command.js +2 -2
- package/dist/cli/daemon-commands.js +111 -162
- package/dist/cli/diag.js +3 -3
- package/dist/cli/fs-command.js +3 -3
- package/dist/cli/handoff-command.js +6 -6
- package/dist/cli/index.js +8 -2
- package/dist/cli/init.js +34 -1
- package/dist/cli/managed-command-guard.js +2 -1
- package/dist/cli/menu-client.js +13 -5
- package/dist/cli/model.js +3 -3
- package/dist/cli/queue-command.js +2 -2
- package/dist/cli/response.js +2 -2
- package/dist/cli/restart-monitor.js +81 -19
- package/dist/cli/task-command.js +59 -0
- package/dist/cli/task-context.js +97 -49
- package/dist/cli/trigger-command.js +20 -10
- package/dist/config/builtin-roles.js +10 -0
- package/dist/config/config-field-policy.js +11 -0
- package/dist/config/config-manager.js +32 -4
- package/dist/config/config-operation-service.js +20 -5
- package/dist/config/contact-book-store.js +1 -1
- package/dist/config/contact-book.js +1 -1
- package/dist/config/contact-operation-service.js +1 -1
- package/dist/config/peer-role-resolver.js +1 -1
- package/dist/config/resolved-config-op.js +3 -0
- package/dist/config-store.js +1 -1
- package/dist/core/agent-application-service.js +5 -0
- package/dist/core/audit/event-key.js +17 -2
- package/dist/core/audit/log-integrity.js +34 -8
- package/dist/core/auth/agent-delegation.js +48 -14
- package/dist/core/auth/auth-gateway.js +2 -0
- package/dist/core/auth/authorization-audit.js +34 -38
- package/dist/core/auth/operation-authorizer.js +67 -5
- package/dist/core/auth/operation-catalog.js +110 -0
- package/dist/core/auth/remote-access-store.js +115 -9
- package/dist/core/bootstrap-messages.js +66 -7
- package/dist/core/bootstrap-service.js +110 -19
- package/dist/core/command/agent-control.js +88 -7
- package/dist/core/command/agent-storage-menu.js +443 -0
- package/dist/core/command/cli-intent-parser.js +19 -0
- package/dist/core/command/command-handler.js +105 -40
- package/dist/core/command/data-root-resolver.js +273 -0
- package/dist/core/command/diag-service.js +292 -27
- package/dist/core/command/group-menu.js +7 -2
- package/dist/core/command/menu-catalog.js +34 -4
- package/dist/core/command/menu-handler.js +364 -96
- package/dist/core/command/menu-protocol.js +43 -6
- package/dist/core/command/role-menu.js +2 -2
- package/dist/core/command/slash-handler.js +61 -37
- package/dist/core/event-catalog.js +4 -0
- package/dist/core/evolagent-registry.js +28 -2
- package/dist/core/group/invite-service.js +924 -0
- package/dist/core/interaction-router.js +22 -8
- package/dist/core/message/im-renderer.js +94 -16
- package/dist/core/message/message-bridge.js +140 -13
- package/dist/core/message/message-log.js +5 -3
- package/dist/core/message/message-queue.js +73 -41
- package/dist/core/message/response-engine.js +729 -118
- package/dist/core/permission/approval-gateway.js +36 -3
- package/dist/core/permission/ec-command-parser.js +76 -36
- package/dist/core/permission/protected-paths.js +134 -16
- package/dist/core/permission/sandbox-runtime.js +72 -3
- package/dist/core/permission/tool-error-code.js +15 -0
- package/dist/core/permission/tool-policy.js +8 -8
- package/dist/core/relation/peer-identity.js +57 -6
- package/dist/core/relation/shared-data.js +106 -0
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/runtime-lock.js +10 -26
- package/dist/core/session/runner-task-history.js +170 -0
- package/dist/core/session/session-manager.js +38 -6
- package/dist/core/session/session-mapper.js +41 -0
- package/dist/core/session/session-turn-coordinator.js +37 -7
- package/dist/core/session/session-turns.js +48 -7
- package/dist/core/task/task-recovery-service.js +176 -0
- package/dist/core/task/task-resume-operation.js +32 -0
- package/dist/eck/group-rules-sync.js +5 -3
- package/dist/eck/kit-renderer.js +1 -0
- package/dist/index.js +322 -207
- package/dist/ipc.js +141 -15
- package/dist/paths.js +17 -4
- package/dist/product.js +3 -0
- package/dist/trigger/event-source.js +19 -0
- package/dist/trigger/feedback.js +233 -40
- package/dist/trigger/parser.js +55 -5
- package/dist/trigger/patch.js +9 -0
- package/dist/trigger/scheduler.js +30 -4
- package/dist/trigger/validation.js +20 -3
- package/dist/utils/atomic-write.js +98 -0
- package/dist/utils/cross-platform.js +87 -16
- package/dist/utils/ecweb-utils.js +1 -0
- package/dist/utils/error-utils.js +40 -2
- package/dist/utils/logger.js +23 -13
- package/dist/utils/npm-ops.js +119 -4
- package/dist/utils/package-version-info.js +244 -0
- package/dist/utils/tool-summary.js +59 -3
- package/dist/utils/welcome.js +174 -1
- package/dist/utils/windows-shell-trust.js +37 -0
- package/ecagent/dist/harness/agent-harness.js +3 -0
- package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
- package/kits/docs/INDEX.md +0 -6
- package/kits/docs/channels/aun.md +23 -2
- package/kits/docs/channels/feishu.md +1 -1
- package/kits/docs/context-assembly.md +29 -17
- package/kits/docs/evolcore/INDEX.md +8 -3
- package/kits/docs/evolcore/agent-create.md +39 -7
- package/kits/docs/evolcore/agent.md +33 -1
- package/kits/docs/evolcore/aid.md +9 -1
- package/kits/docs/evolcore/config.md +40 -1
- package/kits/docs/evolcore/group-collaboration.md +1 -1
- package/kits/docs/evolcore/group.md +117 -2
- package/kits/docs/evolcore/menu-client.md +16 -0
- package/kits/docs/evolcore/msg.md +3 -0
- package/kits/docs/evolcore/storage.md +3 -0
- package/kits/docs/evolcore/task.md +14 -0
- package/kits/docs/evolcore/trigger.md +51 -3
- package/kits/docs/venues/group.md +5 -0
- package/kits/eck_manifest.json +27 -0
- package/kits/rules/01-overview.md +11 -7
- package/kits/rules/04-relation.md +2 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/rules/06-channel.md +6 -4
- package/kits/schemas/agent-config.schema.12.json +25 -1
- package/kits/schemas/daemon.schema.6.json +2 -2
- package/kits/schemas/defaults.schema.5.json +1 -1
- package/kits/schemas/relation-config.schema.1.json +1 -1
- package/kits/schemas/relation-config.schema.5.json +1 -1
- package/kits/schemas/role-config.schema.1.json +14 -0
- package/kits/templates/roles/member.json +5 -0
- package/kits/templates/roles/visitor.json +5 -0
- package/kits/templates/system-fragments/baseagent.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +26 -9
- package/kits/templates/system-fragments/channel.md +8 -44
- package/kits/templates/system-fragments/commands.md +10 -2
- package/kits/templates/system-fragments/group-unread-history.md +7 -0
- package/kits/templates/system-fragments/reply.md +56 -0
- package/package.json +7 -3
- package/dist/aun/group-identity.js +0 -9
- /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
- /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
本文件记录 EvolCore 的重要变更。EvolClaw 版本线的历史记录已归档至
|
|
4
4
|
[`docs/_archive/CHANGELOG-evolclaw.md`](docs/_archive/CHANGELOG-evolclaw.md)。
|
|
5
5
|
|
|
6
|
+
## Unreleased
|
|
7
|
+
|
|
8
|
+
## 0.0.28 (2026-09-24)
|
|
9
|
+
|
|
10
|
+
- 增加 npm 版本缓存,改进升级状态展示及启动、重启流程。
|
|
11
|
+
- 改进 AUN 回复投递与消息展示。
|
|
12
|
+
- 加固 Windows CLI 启动和 PowerShell 命令解析,完善 Codex CLI 诊断。
|
|
13
|
+
- 强化 Menu 请求校验与错误处理。
|
|
14
|
+
|
|
15
|
+
## 0.0.27 (2026-09-24)
|
|
16
|
+
|
|
17
|
+
### AUN、群聊与消息
|
|
18
|
+
|
|
19
|
+
- 持久化 AUN 群邀请的 Owner 审核流程,并增加 Base Agent 清单查询入口。
|
|
20
|
+
- 支持 EC Agent 的角色级模型与推理强度策略;完善 Claude、Codex、EC Agent 会话的任务历史和中断恢复。
|
|
21
|
+
- 模型类 Trigger 支持单次执行时限;目标会话失联时明确记录为失败。
|
|
22
|
+
- 修复 AUN 撤回消息造成的私聊游标跳跃;手动停止的 Agent 重新加载后可恢复渠道连接。
|
|
23
|
+
- 规范工具活动中的 Shell 命令展示,并在缺少本地证书时跳过控制 AID 名片发布。
|
|
24
|
+
- 优化消息大小处理,提升长消息和复杂内容的发送稳定性。
|
|
25
|
+
- 升级 AUN SDK 至 `0.5.17`,改进消息、群文件和存储上传的兼容性。
|
|
26
|
+
- 改进附件、头像和文件存储处理,提升文件类型识别、上传和公开访问的稳定性。
|
|
27
|
+
- 改进身份信息缓存刷新,确保对端资料更新后及时生效。
|
|
28
|
+
- 支持无连续编号消息的完整拉取信息,并修正消息数量统计。
|
|
29
|
+
- 群命令和托管群文件操作支持标准群 AID 格式。
|
|
30
|
+
- 优化群名称生成和冲突处理,自定义名称保持原样。
|
|
31
|
+
- 改进群组参数校验和成员身份识别,提升群聊操作准确性。
|
|
32
|
+
|
|
33
|
+
### Agent、权限与运行稳定性
|
|
34
|
+
|
|
35
|
+
- 收紧 Codex 分支会话的权限上下文,避免旧会话继承过期授权。
|
|
36
|
+
- 完善 Agent 初始化、会话恢复、权限边界和运行状态处理。
|
|
37
|
+
|
|
6
38
|
## 0.0.26 (2026-09-18)
|
|
7
39
|
|
|
8
40
|
### AUN、联系人与远程诊断
|
|
@@ -21,6 +53,10 @@
|
|
|
21
53
|
|
|
22
54
|
### 权限与兼容性
|
|
23
55
|
|
|
56
|
+
- 修复 App 管理页读取指定群关系配置时被当前关系限制拒绝:owner/admin 可读取当前 Agent 的目标关系字段,
|
|
57
|
+
保留 admin 字段权限、普通成员和跨关系写入限制。
|
|
58
|
+
- 批量配置读取为当前版本未支持的 runner 字段返回逐字段 `UNSUPPORTED_FIELD`,
|
|
59
|
+
保留有效字段并以成功状态返回兼容缺项;真实权限与执行错误仍按失败处理。
|
|
24
60
|
- npm 包整体排除本机 `skills/` 目录,避免本地 skill 文件随包发布。
|
|
25
61
|
- 新建 agent.md 默认使用 `agent` 类型,并按白名单归一化类型;
|
|
26
62
|
对齐 SDK 的可选 description、Unicode 字数、avatar 长度及 4 KB 文档上限校验。
|
|
@@ -28,6 +28,48 @@ function Add-Grant($Acl, [string]$Sid, [Security.AccessControl.FileSystemRights]
|
|
|
28
28
|
$Acl.AddAccessRule($rule)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function Grant-PipeAccess([string]$Target) {
|
|
32
|
+
$prefix = '\\.\pipe\'
|
|
33
|
+
if (-not $Target.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) {
|
|
34
|
+
throw 'Only a local named pipe can receive Codex sandbox access.'
|
|
35
|
+
}
|
|
36
|
+
$pipeName = $Target.Substring($prefix.Length)
|
|
37
|
+
if (-not $pipeName -or $pipeName.Contains('\') -or $pipeName.Contains('/')) {
|
|
38
|
+
throw 'Invalid local named pipe name.'
|
|
39
|
+
}
|
|
40
|
+
$accounts = @()
|
|
41
|
+
foreach ($name in @('CodexSandboxOffline', 'CodexSandboxOnline')) {
|
|
42
|
+
try {
|
|
43
|
+
$account = New-Object Security.Principal.NTAccount([Environment]::MachineName, $name)
|
|
44
|
+
$sid = $account.Translate([Security.Principal.SecurityIdentifier])
|
|
45
|
+
$accounts += @{ name = $name; sid = $sid }
|
|
46
|
+
} catch [Security.Principal.IdentityNotMappedException] {
|
|
47
|
+
# Codex creates these local users during sandbox provisioning. Do not
|
|
48
|
+
# replace a missing user with Everyone, Authenticated Users, or a group.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if ($accounts.Count -eq 0) { return @{ granted = @() } }
|
|
52
|
+
$pipe = New-Object System.IO.Pipes.NamedPipeClientStream('.', $pipeName,
|
|
53
|
+
[System.IO.Pipes.PipeAccessRights]'ReadWrite,ReadPermissions,ChangePermissions',
|
|
54
|
+
[System.IO.Pipes.PipeOptions]::Asynchronous,
|
|
55
|
+
[Security.Principal.TokenImpersonationLevel]::Identification,
|
|
56
|
+
[System.IO.HandleInheritability]::None)
|
|
57
|
+
try {
|
|
58
|
+
$pipe.Connect(5000)
|
|
59
|
+
$acl = $pipe.GetAccessControl()
|
|
60
|
+
$rights = [System.IO.Pipes.PipeAccessRights]'ReadWrite,Synchronize'
|
|
61
|
+
foreach ($account in $accounts) {
|
|
62
|
+
# GenericWrite/FullControl also grant CreateNewInstance on named pipes.
|
|
63
|
+
# Grant only data I/O to the actual restricted user; the sandbox group
|
|
64
|
+
# alone does not satisfy Windows restricted-token access checks.
|
|
65
|
+
$acl.AddAccessRule((New-Object System.IO.Pipes.PipeAccessRule(
|
|
66
|
+
$account.sid, $rights, [Security.AccessControl.AccessControlType]::Allow)))
|
|
67
|
+
}
|
|
68
|
+
$pipe.SetAccessControl($acl)
|
|
69
|
+
return @{ granted = @($accounts | ForEach-Object { $_.name }) }
|
|
70
|
+
} finally { $pipe.Dispose() }
|
|
71
|
+
}
|
|
72
|
+
|
|
31
73
|
function Initialize-AccessProbe {
|
|
32
74
|
# An elevated process reading the file is not evidence that a normal process
|
|
33
75
|
# can read it. Evaluate the DACL with its UAC linked token when available.
|
|
@@ -122,6 +164,7 @@ function Verify-Access($Entry) {
|
|
|
122
164
|
try {
|
|
123
165
|
$payload = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Request)) | ConvertFrom-Json
|
|
124
166
|
$result = switch ($payload.action) {
|
|
167
|
+
'pipe' { Grant-PipeAccess ([string]$payload.path) }
|
|
125
168
|
'preflight' {
|
|
126
169
|
$principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
|
|
127
170
|
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeBaseagent } from './baseagent.js';
|
|
1
2
|
import { commandExists } from '../utils/cross-platform.js';
|
|
2
3
|
import { getCodexAppServerAvailability } from './codex-runner.js';
|
|
3
4
|
export const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini', 'ecagent'];
|
|
@@ -46,3 +47,70 @@ export function detectBaseagentDetails(codexAvailability = getCodexAppServerAvai
|
|
|
46
47
|
},
|
|
47
48
|
];
|
|
48
49
|
}
|
|
50
|
+
/** Only loaded runners can be switched to; never infer this from PATH/config. */
|
|
51
|
+
export function loadedBaseagentIds(keys, owner) {
|
|
52
|
+
const result = new Set();
|
|
53
|
+
for (const key of keys) {
|
|
54
|
+
if (owner !== undefined && !key.startsWith(`${owner}::`))
|
|
55
|
+
continue;
|
|
56
|
+
const separator = key.indexOf('::');
|
|
57
|
+
result.add(separator < 0 ? key : key.slice(separator + 2));
|
|
58
|
+
}
|
|
59
|
+
return [...result];
|
|
60
|
+
}
|
|
61
|
+
function canonical(value) {
|
|
62
|
+
if (typeof value !== 'string')
|
|
63
|
+
return null;
|
|
64
|
+
const id = normalizeBaseagent(value).canonical;
|
|
65
|
+
return id === 'unknown' ? null : id;
|
|
66
|
+
}
|
|
67
|
+
function record(value) {
|
|
68
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
69
|
+
}
|
|
70
|
+
function text(value) {
|
|
71
|
+
return typeof value === 'string' && value.trim() ? value : null;
|
|
72
|
+
}
|
|
73
|
+
/** Safe public projection: credentials and arbitrary backend settings never leave this boundary. */
|
|
74
|
+
export function buildBaseagentSnapshot(config, detections, loaded) {
|
|
75
|
+
const blocks = new Map();
|
|
76
|
+
for (const [name, value] of Object.entries(record(config.baseagents))) {
|
|
77
|
+
const id = canonical(name);
|
|
78
|
+
if (id)
|
|
79
|
+
blocks.set(id, record(value));
|
|
80
|
+
}
|
|
81
|
+
const active = canonical(config.active_baseagent);
|
|
82
|
+
const probes = new Map((detections ?? []).map(item => [String(item.id), item]));
|
|
83
|
+
const loadedIds = loaded === undefined ? null : new Set(loaded);
|
|
84
|
+
const ids = new Set([
|
|
85
|
+
...blocks.keys(),
|
|
86
|
+
...(detections ?? []).filter(item => item.detected).map(item => item.id),
|
|
87
|
+
...(loaded ?? []),
|
|
88
|
+
...(active ? [active] : []),
|
|
89
|
+
]);
|
|
90
|
+
return {
|
|
91
|
+
active,
|
|
92
|
+
baseagents: [...ids].map(id => ({
|
|
93
|
+
id,
|
|
94
|
+
label: normalizeBaseagent(id).displayName,
|
|
95
|
+
configured: blocks.has(id),
|
|
96
|
+
detected: detections === null ? null : probes.get(id)?.detected === true,
|
|
97
|
+
loaded: loadedIds?.has(id) ?? null,
|
|
98
|
+
active: id === active,
|
|
99
|
+
model: text(blocks.get(id)?.model),
|
|
100
|
+
effort: text(blocks.get(id)?.effort ?? blocks.get(id)?.reasoning),
|
|
101
|
+
version: probes.get(id)?.version ?? null,
|
|
102
|
+
})),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Shared defaults/Agent inventory for menu, slash and local history readers. */
|
|
106
|
+
export async function getBaseagentSnapshot(options = {}) {
|
|
107
|
+
let config = options.config;
|
|
108
|
+
if (config === undefined) {
|
|
109
|
+
const { read, ConfigTarget } = await import('../config/config-manager.js');
|
|
110
|
+
config = read(ConfigTarget.Defaults, undefined, { cache: false, expand: false }) ?? {};
|
|
111
|
+
}
|
|
112
|
+
const detections = options.probe === false
|
|
113
|
+
? null
|
|
114
|
+
: (await import('./baseagent-availability.js')).detectBaseagentDetails();
|
|
115
|
+
return buildBaseagentSnapshot(config ?? {}, detections, options.loaded);
|
|
116
|
+
}
|
package/dist/agents/baseagent.js
CHANGED
|
@@ -12,22 +12,26 @@
|
|
|
12
12
|
import fs from 'fs';
|
|
13
13
|
import path from 'path';
|
|
14
14
|
import os from 'os';
|
|
15
|
-
import { commandExists, isWindows, resolveProbePath } from '../utils/cross-platform.js';
|
|
15
|
+
import { buildCliLaunchCommand, commandExists, isWindows, resolvePowerShellShimPath, resolveProbePath, } from '../utils/cross-platform.js';
|
|
16
16
|
import { execFileSync } from 'child_process';
|
|
17
17
|
import { parse as parseToml } from 'smol-toml';
|
|
18
18
|
/** Resolve the installed Codex CLI entrypoint, including npm shims on Windows. */
|
|
19
19
|
export function resolveCodexCliPath() {
|
|
20
20
|
return resolveProbePath('codex');
|
|
21
21
|
}
|
|
22
|
+
/** Return a PS1-only Codex shim for installation diagnostics, never execution. */
|
|
23
|
+
export function resolveCodexPowerShellShimPath() {
|
|
24
|
+
return resolvePowerShellShimPath('codex');
|
|
25
|
+
}
|
|
22
26
|
export function resolveCodexLaunchCommand(args, options = {}) {
|
|
23
27
|
const windows = options.isWindows ?? isWindows;
|
|
24
28
|
const commandPath = options.commandPath !== undefined ? options.commandPath : resolveCodexCliPath();
|
|
25
29
|
if (!commandPath)
|
|
26
30
|
return null;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
return buildCliLaunchCommand(commandPath, args, {
|
|
32
|
+
isWindows: windows,
|
|
33
|
+
comSpec: options.comSpec,
|
|
34
|
+
});
|
|
31
35
|
}
|
|
32
36
|
/** Execute a Codex CLI command using the resolved npm shim/path. */
|
|
33
37
|
export function execCodexCliSync(args, options) {
|
|
@@ -40,6 +44,7 @@ export function execCodexCliSync(args, options) {
|
|
|
40
44
|
return execFileSync(launchCommand.command, launchCommand.args, {
|
|
41
45
|
...(options ?? {}),
|
|
42
46
|
windowsHide: isWindows,
|
|
47
|
+
windowsVerbatimArguments: launchCommand.windowsVerbatimArguments,
|
|
43
48
|
});
|
|
44
49
|
}
|
|
45
50
|
const BASEAGENT_ALIASES = {
|
|
@@ -25,14 +25,15 @@ import { sanitizeSessionTitle } from '../core/session/session-title.js';
|
|
|
25
25
|
import { resolveClaudeCapabilityRunOptionsForProject } from '../core/capability/capability-manager.js';
|
|
26
26
|
import { normalizeExecutionPermissionMode, normalizePermissionMode, resolvePhaseOneExecutionSandbox } from '../core/permission/mode.js';
|
|
27
27
|
import { buildClaudeProtectedFilesystem, containsHClassReference, containsLClassReference, isHClassPath, isLClassPath, isSameOrDescendant, resolveProtectedCandidate, } from '../core/permission/protected-paths.js';
|
|
28
|
-
import { buildHClassGuardCommand, collectSandboxGuardDiagnostics, createSandboxInitializationError, getActiveSandboxInitializationFailure, isSandboxInitializationFailure, isSandboxInitializationToolResult, recordSandboxInitializationFailure, SANDBOX_INITIALIZATION_COOLDOWN_MS, SANDBOX_INITIALIZATION_FAILED, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, summarizeBubblewrapArgsForLog, summarizeSandboxGuardDiagnostics, shouldFailIfClaudeSandboxUnavailable, shouldLogSandboxCircuitNotice, } from '../core/permission/sandbox-runtime.js';
|
|
28
|
+
import { buildHClassGuardCommand, collectSandboxGuardDiagnostics, createSandboxInitializationError, getActiveSandboxInitializationFailure, isSandboxInitializationFailure, isSandboxInitializationToolResult, recordSandboxInitializationFailure, getSandboxInitializationFailureCounters, SANDBOX_INITIALIZATION_COOLDOWN_MS, SANDBOX_INITIALIZATION_FAILED, ensureClaudeGitWorktreeConfig, prependExecutableDirectory, resolveBubblewrapPath, summarizeBubblewrapArgsForLog, summarizeSandboxGuardDiagnostics, shouldEnableClaudeWeakerNestedSandbox, shouldFailIfClaudeSandboxUnavailable, shouldLogSandboxCircuitNotice, } from '../core/permission/sandbox-runtime.js';
|
|
29
29
|
import { buildClaudeUnixSocketAllowlist } from '../core/permission/unix-socket-policy.js';
|
|
30
30
|
import { auditToolInfrastructureFailure, auditToolPreflightDenial } from '../core/auth/authorization-audit.js';
|
|
31
31
|
import { getManagedTaskTempDir, isManagedSessionRuntimeDir } from '../cli/task-context.js';
|
|
32
|
-
import { hasTrustedFullAccessContext } from './runner-types.js';
|
|
32
|
+
import { unsettledInterruptError, withInterruptDeadline, hasTrustedFullAccessContext } from './runner-types.js';
|
|
33
33
|
import { contextTokensForUsage, usageForContext, isClaudeContextUsageModel, isOneMillionContextModel, realContextWindowForModel, autoCompactWindowForModel, sdkAutoCompactWindowForModel } from './runner-types.js';
|
|
34
34
|
import { hasContextWindowSuffix, stripContextWindowSuffix } from '../core/model/context-window-suffix.js';
|
|
35
35
|
const resolveModule = createRequire(import.meta.url);
|
|
36
|
+
const CLAUDE_INTERRUPT_TIMEOUT_MS = 5_000;
|
|
36
37
|
/**
|
|
37
38
|
* Resolve the platform package explicitly instead of relying on the SDK's
|
|
38
39
|
* internal optional-dependency lookup. The latter is sensitive to the module
|
|
@@ -282,16 +283,17 @@ function buildClaudeManagedLockdownSettings(capabilityOptions) {
|
|
|
282
283
|
}
|
|
283
284
|
const CLAUDE_SANDBOX_GLOB_MAGIC = /[*?[\]]/;
|
|
284
285
|
function trustedRuntimeWritePaths(runtimeEnv) {
|
|
285
|
-
const
|
|
286
|
-
if (!
|
|
286
|
+
const sessionDir = runtimeEnv?.EVOLCORE_SESSION_RUNTIME_DIR?.trim();
|
|
287
|
+
if (!sessionDir || !isManagedSessionRuntimeDir(sessionDir))
|
|
287
288
|
return [];
|
|
288
|
-
const root = resolveProtectedCandidate(
|
|
289
|
+
const root = resolveProtectedCandidate(sessionDir);
|
|
289
290
|
const candidates = [runtimeEnv?.EVOLCORE_SESSION_RUNTIME_DIR, runtimeEnv?.EVOLCORE_RUNTIME_LOCK_DIR]
|
|
290
291
|
.filter((value) => typeof value === 'string' && path.isAbsolute(value));
|
|
291
292
|
const paths = [];
|
|
292
293
|
for (const candidate of candidates) {
|
|
293
294
|
const resolved = resolveProtectedCandidate(candidate);
|
|
294
|
-
if (!isSameOrDescendant(resolved, root)
|
|
295
|
+
if (!isSameOrDescendant(resolved, root)
|
|
296
|
+
|| (resolved !== root && !(path.dirname(resolved) === root && path.basename(resolved) === 'locks')))
|
|
295
297
|
continue;
|
|
296
298
|
try {
|
|
297
299
|
const stat = fs.lstatSync(resolved);
|
|
@@ -480,7 +482,7 @@ class MessageStream {
|
|
|
480
482
|
}
|
|
481
483
|
export class AgentRunner {
|
|
482
484
|
name = 'claude';
|
|
483
|
-
capabilities = { fullaccess: true, clear: true, compact: true, fork: true, forkAtTurn: true, askUserQuestion: true, planApproval: true, fileRewind: 'checkpoint' };
|
|
485
|
+
capabilities = { taskRecovery: true, fullaccess: true, clear: true, compact: true, fork: true, forkAtTurn: true, askUserQuestion: true, planApproval: true, fileRewind: 'checkpoint' };
|
|
484
486
|
apiKey;
|
|
485
487
|
model;
|
|
486
488
|
effort;
|
|
@@ -499,6 +501,7 @@ export class AgentRunner {
|
|
|
499
501
|
sandboxSettingsFiles = new Map();
|
|
500
502
|
streamDone = new Map();
|
|
501
503
|
streamDoneResolvers = new Map();
|
|
504
|
+
unconfirmedLifecycleFences = new Map();
|
|
502
505
|
onSessionIdUpdate;
|
|
503
506
|
onCompactStart;
|
|
504
507
|
permissionGateway;
|
|
@@ -524,6 +527,11 @@ export class AgentRunner {
|
|
|
524
527
|
callId: inputId,
|
|
525
528
|
taskId: context?.taskId,
|
|
526
529
|
occurrences: circuit.occurrences,
|
|
530
|
+
sandboxFailure: {
|
|
531
|
+
kind: circuit.kind,
|
|
532
|
+
observation: 'initialization_failed',
|
|
533
|
+
counters: getSandboxInitializationFailureCounters(),
|
|
534
|
+
},
|
|
527
535
|
});
|
|
528
536
|
await context?.recordExecutionAnomaly?.({
|
|
529
537
|
code: 'capability_unavailable',
|
|
@@ -1337,7 +1345,7 @@ export class AgentRunner {
|
|
|
1337
1345
|
* SDK 原始事件 → 标准 AgentEvent 转换
|
|
1338
1346
|
* 所有 SDK 特有的事件类型引用封装在此方法内
|
|
1339
1347
|
*/
|
|
1340
|
-
async *transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, isResuming = false, sandboxSettingsFile, permissionMode, turn, projectPath = process.cwd(), topicBinding = false) {
|
|
1348
|
+
async *transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, isResuming = false, sandboxSettingsFile, permissionMode, turn, projectPath = process.cwd(), topicBinding = false, recoverySessionId) {
|
|
1341
1349
|
let lastSessionId;
|
|
1342
1350
|
let pendingSessionId;
|
|
1343
1351
|
let hasTurnActivity = false;
|
|
@@ -1364,6 +1372,9 @@ export class AgentRunner {
|
|
|
1364
1372
|
const candidate = pendingSessionId;
|
|
1365
1373
|
if (!candidate)
|
|
1366
1374
|
return undefined;
|
|
1375
|
+
if (recoverySessionId && candidate !== recoverySessionId) {
|
|
1376
|
+
throw new Error('Task recovery cannot replace the original Claude session');
|
|
1377
|
+
}
|
|
1367
1378
|
if (!isResuming && topicBinding) {
|
|
1368
1379
|
const ready = waitForFile
|
|
1369
1380
|
? await waitForClaudeSessionFile(projectPath, candidate)
|
|
@@ -1490,6 +1501,12 @@ export class AgentRunner {
|
|
|
1490
1501
|
if (event.type === 'stream_event') {
|
|
1491
1502
|
const streamEvent = event.event;
|
|
1492
1503
|
if (streamEvent?.type === 'content_block_start'
|
|
1504
|
+
&& streamEvent.content_block?.type === 'text') {
|
|
1505
|
+
hasTurnActivity = true;
|
|
1506
|
+
const contentIndex = typeof streamEvent.index === 'number' ? streamEvent.index : 0;
|
|
1507
|
+
yield { type: 'text_start', contentIndex, turn: turnCount || undefined };
|
|
1508
|
+
}
|
|
1509
|
+
else if (streamEvent?.type === 'content_block_start'
|
|
1493
1510
|
&& (streamEvent.content_block?.type === 'thinking' || streamEvent.content_block?.type === 'redacted_thinking')) {
|
|
1494
1511
|
hasTurnActivity = true;
|
|
1495
1512
|
yield { type: 'heartbeat', source: 'reasoning', turn: turnCount || undefined };
|
|
@@ -1936,6 +1953,17 @@ export class AgentRunner {
|
|
|
1936
1953
|
// Validate the daemon-injected session namespace before the SDK inherits
|
|
1937
1954
|
// the process environment for any temporary state.
|
|
1938
1955
|
getManagedTaskTempDir(runtimeEnv);
|
|
1956
|
+
this.assertLifecycleQuiescent(sessionId);
|
|
1957
|
+
const staleLifecycle = this.activeStreams.has(sessionId)
|
|
1958
|
+
|| this.activeQueries.has(sessionId)
|
|
1959
|
+
|| this.activeMessageStreams.has(sessionId)
|
|
1960
|
+
|| this.interruptFns.has(sessionId);
|
|
1961
|
+
if (staleLifecycle) {
|
|
1962
|
+
logger.warn(`[AgentRunner] Stale query lifecycle before runQuery; forcing cleanup: session=${sessionId}`);
|
|
1963
|
+
await this.interrupt(sessionId).catch(error => {
|
|
1964
|
+
logger.warn(`[AgentRunner] Stale query interrupt failed before runQuery: session=${sessionId} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1939
1967
|
// 记录当前 evolcore session ID,用于 Agent ctl 环境变量注入
|
|
1940
1968
|
// 同步用户级配置到内存
|
|
1941
1969
|
this.syncFromUserSettings();
|
|
@@ -1966,6 +1994,10 @@ export class AgentRunner {
|
|
|
1966
1994
|
if (topicBinding && topicAgentSessionId !== agentSessionId) {
|
|
1967
1995
|
agentSessionId = topicAgentSessionId || undefined;
|
|
1968
1996
|
}
|
|
1997
|
+
const recoverySessionId = modelOverride?.recovery?.agentSessionId;
|
|
1998
|
+
if (modelOverride?.recovery && (!recoverySessionId || agentSessionId !== recoverySessionId)) {
|
|
1999
|
+
throw new Error('Task recovery requires the original Claude session binding');
|
|
2000
|
+
}
|
|
1969
2001
|
// 验证会话文件是否存在且有效(仅在有 agentSessionId 时)
|
|
1970
2002
|
if (agentSessionId) {
|
|
1971
2003
|
const homeDir = os.homedir();
|
|
@@ -1980,7 +2012,7 @@ export class AgentRunner {
|
|
|
1980
2012
|
for (const line of lines) {
|
|
1981
2013
|
try {
|
|
1982
2014
|
const data = JSON.parse(line);
|
|
1983
|
-
if (data.sessionId && data.version) {
|
|
2015
|
+
if (data.sessionId && data.version && (!recoverySessionId || data.sessionId === recoverySessionId)) {
|
|
1984
2016
|
isValid = true;
|
|
1985
2017
|
break;
|
|
1986
2018
|
}
|
|
@@ -1996,6 +2028,9 @@ export class AgentRunner {
|
|
|
1996
2028
|
}
|
|
1997
2029
|
}
|
|
1998
2030
|
if (!isValid) {
|
|
2031
|
+
if (recoverySessionId) {
|
|
2032
|
+
throw new Error(`Task recovery backend is unavailable: ${recoverySessionId}`);
|
|
2033
|
+
}
|
|
1999
2034
|
if (topicBinding) {
|
|
2000
2035
|
const error = new Error(`bound topic backend is unavailable: ${agentSessionId}`);
|
|
2001
2036
|
error.code = 'TOPIC_BACKEND_UNAVAILABLE';
|
|
@@ -2047,6 +2082,11 @@ export class AgentRunner {
|
|
|
2047
2082
|
callId: inputId,
|
|
2048
2083
|
taskId: context?.taskId,
|
|
2049
2084
|
occurrences: activeSandboxFailure.occurrences,
|
|
2085
|
+
sandboxFailure: {
|
|
2086
|
+
kind: activeSandboxFailure.kind,
|
|
2087
|
+
observation: 'circuit_open',
|
|
2088
|
+
counters: getSandboxInitializationFailureCounters(),
|
|
2089
|
+
},
|
|
2050
2090
|
});
|
|
2051
2091
|
await context?.recordExecutionAnomaly?.({
|
|
2052
2092
|
code: 'capability_unavailable',
|
|
@@ -2517,6 +2557,7 @@ export class AgentRunner {
|
|
|
2517
2557
|
: await (async () => {
|
|
2518
2558
|
const sandboxProjectionStartedAt = Date.now();
|
|
2519
2559
|
ensureClaudeGitWorktreeConfig(projectPath);
|
|
2560
|
+
const enableWeakerNestedSandbox = shouldEnableClaudeWeakerNestedSandbox();
|
|
2520
2561
|
const sandboxFilesystem = buildClaudeProtectedFilesystem(resolvePaths().root, {
|
|
2521
2562
|
denyLClassRead: callPermissionMode === 'readonly',
|
|
2522
2563
|
});
|
|
@@ -2537,10 +2578,12 @@ export class AgentRunner {
|
|
|
2537
2578
|
throw new Error(`[ClaudeSandbox] protected filesystem projection exceeded 2000ms: ${sandboxProjectionDurationMs}ms`);
|
|
2538
2579
|
}
|
|
2539
2580
|
logger.info(`[ClaudeSandbox] projection ready denyRead=${sandboxFilesystem.denyRead.length} ` +
|
|
2540
|
-
`denyWrite=${sandboxFilesystem.denyWrite.length} durationMs=${sandboxProjectionDurationMs}`
|
|
2581
|
+
`denyWrite=${sandboxFilesystem.denyWrite.length} durationMs=${sandboxProjectionDurationMs} ` +
|
|
2582
|
+
`weakerNestedSandbox=${enableWeakerNestedSandbox}`);
|
|
2541
2583
|
return {
|
|
2542
2584
|
enabled: true,
|
|
2543
2585
|
failIfUnavailable: shouldFailIfClaudeSandboxUnavailable(),
|
|
2586
|
+
enableWeakerNestedSandbox,
|
|
2544
2587
|
allowUnsandboxedCommands: true,
|
|
2545
2588
|
network: {
|
|
2546
2589
|
allowUnixSockets: sandboxUnixSockets,
|
|
@@ -2740,7 +2783,7 @@ export class AgentRunner {
|
|
|
2740
2783
|
// Topic bindings are authoritative for every input shape. Starting
|
|
2741
2784
|
// an unresumed Claude query for a bound topic would discover a second
|
|
2742
2785
|
// backend and then fail activation with a binding conflict.
|
|
2743
|
-
sdkStream = topicBinding
|
|
2786
|
+
sdkStream = topicBinding || recoverySessionId
|
|
2744
2787
|
? createQuery(msgStream, agentSessionId, resumeAt)
|
|
2745
2788
|
: createQuery(msgStream);
|
|
2746
2789
|
}
|
|
@@ -2785,7 +2828,7 @@ export class AgentRunner {
|
|
|
2785
2828
|
this.interruptFns.set(sessionId, () => sdkStream.interrupt());
|
|
2786
2829
|
}
|
|
2787
2830
|
// 返回标准 AgentEvent 流(重试由 MessageProcessor 层负责)
|
|
2788
|
-
const transformed = this.transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, !!agentSessionId, sandboxSettingsFile, callPermissionMode, modelOverride?.turn, projectPath, topicBinding);
|
|
2831
|
+
const transformed = this.transformStream(sdkStream, sessionId, inputId, callModel, callEffort, sdkModel, !!agentSessionId, sandboxSettingsFile, callPermissionMode, modelOverride?.turn, projectPath, topicBinding, recoverySessionId);
|
|
2789
2832
|
const self = this;
|
|
2790
2833
|
return (async function* () {
|
|
2791
2834
|
try {
|
|
@@ -2808,43 +2851,92 @@ export class AgentRunner {
|
|
|
2808
2851
|
async interrupt(sessionId) {
|
|
2809
2852
|
this.permissionContexts.get(sessionId)?.pauseController?.cancel();
|
|
2810
2853
|
const streamRegistered = this.activeStreams.has(sessionId);
|
|
2854
|
+
const registeredStream = this.activeStreams.get(sessionId);
|
|
2811
2855
|
const fn = this.interruptFns.get(sessionId);
|
|
2812
2856
|
const query = this.activeQueries.get(sessionId);
|
|
2857
|
+
const messageStream = this.activeMessageStreams.get(sessionId);
|
|
2813
2858
|
const cancelledInputIds = [];
|
|
2814
2859
|
let stillQueued;
|
|
2860
|
+
let interruptConfirmed = false;
|
|
2861
|
+
let queuedCancellationComplete = true;
|
|
2815
2862
|
if (fn) {
|
|
2863
|
+
let interruptTimer;
|
|
2816
2864
|
try {
|
|
2817
|
-
const receipt = await
|
|
2865
|
+
const receipt = await Promise.race([
|
|
2866
|
+
fn(),
|
|
2867
|
+
new Promise((_, reject) => {
|
|
2868
|
+
interruptTimer = setTimeout(() => reject(new Error('CLAUDE_INTERRUPT_TIMEOUT')), CLAUDE_INTERRUPT_TIMEOUT_MS);
|
|
2869
|
+
interruptTimer.unref?.();
|
|
2870
|
+
}),
|
|
2871
|
+
]);
|
|
2872
|
+
interruptConfirmed = true;
|
|
2818
2873
|
stillQueued = Array.isArray(receipt?.still_queued) ? receipt.still_queued.filter((id) => typeof id === 'string') : undefined;
|
|
2819
2874
|
if (stillQueued && query && typeof query.cancelAsyncMessage === 'function') {
|
|
2820
|
-
|
|
2875
|
+
const cancellations = await Promise.all(stillQueued.map(async (inputId) => {
|
|
2821
2876
|
try {
|
|
2822
|
-
|
|
2877
|
+
const cancelled = await withInterruptDeadline(query.cancelAsyncMessage(inputId), 'Claude cancel queued input', CLAUDE_INTERRUPT_TIMEOUT_MS);
|
|
2878
|
+
if (cancelled)
|
|
2823
2879
|
cancelledInputIds.push(inputId);
|
|
2880
|
+
return cancelled;
|
|
2824
2881
|
}
|
|
2825
2882
|
catch (error) {
|
|
2826
2883
|
logger.debug(`[AgentRunner] Failed to cancel queued input ${inputId}: ${error}`);
|
|
2884
|
+
return false;
|
|
2827
2885
|
}
|
|
2828
|
-
}
|
|
2886
|
+
}));
|
|
2887
|
+
queuedCancellationComplete = cancellations.every(Boolean);
|
|
2829
2888
|
}
|
|
2830
2889
|
logger.info(`[AgentRunner] Interrupted session: ${sessionId}`);
|
|
2831
2890
|
}
|
|
2832
2891
|
catch (error) {
|
|
2833
|
-
logger.warn(`[AgentRunner] Interrupt failed
|
|
2892
|
+
logger.warn(`[AgentRunner] Interrupt failed: ${sessionId} error=${error instanceof Error ? error.message : String(error)}`);
|
|
2893
|
+
}
|
|
2894
|
+
finally {
|
|
2895
|
+
if (interruptTimer)
|
|
2896
|
+
clearTimeout(interruptTimer);
|
|
2834
2897
|
}
|
|
2835
2898
|
}
|
|
2836
2899
|
this.activeMessageStreams.get(sessionId)?.end();
|
|
2837
2900
|
if (query && typeof query.close === 'function')
|
|
2838
2901
|
query.close();
|
|
2839
2902
|
const done = this.streamDone.get(sessionId);
|
|
2903
|
+
let streamDoneSettled = false;
|
|
2840
2904
|
// A stream interrupted after runQuery resolves but before registerStream has
|
|
2841
2905
|
// no consumer that can enter the wrapper generator's finally block. Waiting
|
|
2842
2906
|
// for streamDone here would always burn the full timeout.
|
|
2843
2907
|
if (done && streamRegistered) {
|
|
2844
|
-
await Promise.race([
|
|
2908
|
+
await Promise.race([
|
|
2909
|
+
done.then(() => { streamDoneSettled = queuedCancellationComplete; }),
|
|
2910
|
+
new Promise(resolve => setTimeout(resolve, CLAUDE_INTERRUPT_TIMEOUT_MS)),
|
|
2911
|
+
]);
|
|
2912
|
+
}
|
|
2913
|
+
else if (done) {
|
|
2914
|
+
// A runQuery result can exist before ResponseEngine registers a consumer.
|
|
2915
|
+
// In that state there is no wrapper-finally callback to observe. Trust an
|
|
2916
|
+
// acknowledged SDK interrupt; otherwise keep the session fenced.
|
|
2917
|
+
streamDoneSettled = (!query || interruptConfirmed) && queuedCancellationComplete;
|
|
2918
|
+
}
|
|
2919
|
+
// Local queue cleanup cannot authorize a second SDK writer while the old
|
|
2920
|
+
// query remains unconfirmed.
|
|
2921
|
+
// Do not let a late interrupt from an older turn delete a lifecycle that
|
|
2922
|
+
// a newer turn installed while the old SDK call was unwinding.
|
|
2923
|
+
if (this.activeStreams.get(sessionId) === registeredStream
|
|
2924
|
+
&& this.interruptFns.get(sessionId) === fn
|
|
2925
|
+
&& this.activeQueries.get(sessionId) === query
|
|
2926
|
+
&& this.activeMessageStreams.get(sessionId) === messageStream) {
|
|
2927
|
+
if ((done && !streamDoneSettled) || !queuedCancellationComplete) {
|
|
2928
|
+
const fence = done ?? new Promise(() => { });
|
|
2929
|
+
this.unconfirmedLifecycleFences.set(sessionId, fence);
|
|
2930
|
+
if (queuedCancellationComplete) {
|
|
2931
|
+
void fence.then(() => {
|
|
2932
|
+
if (this.unconfirmedLifecycleFences.get(sessionId) === fence) {
|
|
2933
|
+
this.unconfirmedLifecycleFences.delete(sessionId);
|
|
2934
|
+
}
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
this.cleanupStreamLifecycle(sessionId, false, streamDoneSettled);
|
|
2845
2939
|
}
|
|
2846
|
-
this.interruptFns.delete(sessionId);
|
|
2847
|
-
this.activeStreams.delete(sessionId);
|
|
2848
2940
|
this.cleanupSandboxSettingsFiles(sessionId);
|
|
2849
2941
|
return { stillQueued, cancelledInputIds, closed: true };
|
|
2850
2942
|
}
|
|
@@ -2855,9 +2947,18 @@ export class AgentRunner {
|
|
|
2855
2947
|
this.activeStreams.set(key, stream);
|
|
2856
2948
|
}
|
|
2857
2949
|
cleanupStream(sessionId) {
|
|
2858
|
-
this.
|
|
2950
|
+
this.cleanupStreamLifecycle(sessionId, true, true);
|
|
2951
|
+
}
|
|
2952
|
+
assertLifecycleQuiescent(sessionId) {
|
|
2953
|
+
if (this.unconfirmedLifecycleFences.has(sessionId))
|
|
2954
|
+
throw unsettledInterruptError('Claude');
|
|
2955
|
+
}
|
|
2956
|
+
cleanupStreamLifecycle(sessionId, endMessageStream, resolveDone) {
|
|
2957
|
+
if (endMessageStream)
|
|
2958
|
+
this.activeMessageStreams.get(sessionId)?.end();
|
|
2859
2959
|
this.activeMessageStreams.delete(sessionId);
|
|
2860
|
-
|
|
2960
|
+
if (resolveDone)
|
|
2961
|
+
this.streamDoneResolvers.get(sessionId)?.();
|
|
2861
2962
|
this.streamDoneResolvers.delete(sessionId);
|
|
2862
2963
|
this.streamDone.delete(sessionId);
|
|
2863
2964
|
this.activeStreams.delete(sessionId);
|