@ai-workbench/remote-agent 0.8.17 → 0.8.18
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/README.md +10 -4
- package/dist/acp.js +15 -4
- package/dist/employee-provider.js +18 -10
- package/dist/employee-ssh.d.ts +1 -1
- package/dist/employee-ssh.js +1 -1
- package/dist/failure-evidence.d.ts +2 -0
- package/dist/failure-evidence.js +9 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +62 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
**`@ai-workbench/remote-agent@0.8.
|
|
1
|
+
**`@ai-workbench/remote-agent@0.8.18` 发布形态包。继承0.8.17主机来源执行与L具体Need透传;本版修复Codex员工执行通道时序(需受保护aw.3)、去除逐RPC重复SSH,失败先交还官方界面并显示“正在确认主机清理”。通过官方插件 → 添加插件安装;固定包由编排发布,X不publish。工程/包证明与leo产品验收分别记录,未运行的门保持NOT_RUN。**
|
|
2
2
|
|
|
3
3
|
# 远程ACP插件
|
|
4
4
|
|
|
5
5
|
一个插件提供 **Claude(远程主机)** 和 **Codex(远程主机)**,通过员工主机上的官方CLI运行。Codex保留单次模式;Claude固定会话模式。只用官方模型列表、插件管理和配置入口,不提供自制面板、问答或登录按钮。登录和原生工作区绑定唯一归连接插件L。
|
|
6
6
|
|
|
7
|
-
当前版本0.8.
|
|
7
|
+
当前版本0.8.18,由X按编排裁定在X2的0.8.17上交付aw.3/去重复SSH/失败先交还;0.8.17的主机来源执行和L具体Need透传完整保留;包名采用`@ai-workbench/remote-agent`以涵盖两个品牌;本轮按编排放行准备发布形态包,供 leo 真机自验;X 不执行实际发布,由编排统一发布。开发fixture逐字节保留L0.7.20固定包,公开类型与SSH导出从该包消费。运行时仍要求L的`getContractInfo()`同时返回API0.7.0、implemented、native-local-v1;旧L包、draft.3或只有版本号的不完整实现会明确报缺项。依赖范围为`~0.7.20`,L由官方插件管理单独安装,不打进X。组件验证不代表真实开通、模型或leo E通过;HanaMesh另验。
|
|
8
8
|
|
|
9
9
|
已包含L0.7.18消费增量:来源确认、输入接纳、登录、本机接纳、输入准备、模型目录及运行时解析的早期失败,用正式`preflightDiagnostics:1` / `preflight-check`进入L「连接前技术详情(含登录与工作区确认)」。阶段、真实来源/状态和错误代码可展开;未知来源为null,旧login-check的完整安全原因保留。记录不选择来源、不接纳输入、不自动重发;无原生会话的菜单查询仍在模型设置报告。当前工程和设置组件验证不等于leo GUI验收。
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
## 员工路径
|
|
14
14
|
|
|
15
|
-
从官方插件管理安装,照常使用默认工作区/会话/模型入口。L沿唯一“远程主机连接”设置节记录文件来源;L0.7.20新选主机位置由两品牌按已核验的规范目录执行,旧legacy主机绑定仍沿原规则;本地来源接入N0.4.4/S0.4.11员工端点。旧映射可在本轮官方一次确认中选择改用本机文件夹;拒绝、取消或输入/来源变化时不切换、不执行。也可在L设置逐工作区确认沿用原主机关联。缺正式host解析能力时明确说明所缺组件、保留原话,本轮不派发。本地六类工具经Claude公开toolAliases路由到员工电脑:cwd是员工选定目录,Git使用本机仓库,不回退主机同名目录。本地真实员工输入先取得官方当前轮次CAS预期,再调用L0.7.17 acceptLocalWorkspaceUserTurn;核回包后才打开D19和本轮主机目录/执行。每轮localAcceptanceId冻结进原详情sink,迟到结果不借新轮ID;LOCAL_USER_TURN_CHANGED立即停止本轮,不重接纳或退legacy。已用固定真包完成本地D19组件联测;编排已回证0.8.14 Claude/Opus5/Default在leo本地目录首轮写文件成功;该回证不覆盖本0.8.
|
|
15
|
+
从官方插件管理安装,照常使用默认工作区/会话/模型入口。L沿唯一“远程主机连接”设置节记录文件来源;L0.7.20新选主机位置由两品牌按已核验的规范目录执行,旧legacy主机绑定仍沿原规则;本地来源接入N0.4.4/S0.4.11员工端点。旧映射可在本轮官方一次确认中选择改用本机文件夹;拒绝、取消或输入/来源变化时不切换、不执行。也可在L设置逐工作区确认沿用原主机关联。缺正式host解析能力时明确说明所缺组件、保留原话,本轮不派发。本地六类工具经Claude公开toolAliases路由到员工电脑:cwd是员工选定目录,Git使用本机仓库,不回退主机同名目录。本地真实员工输入先取得官方当前轮次CAS预期,再调用L0.7.17 acceptLocalWorkspaceUserTurn;核回包后才打开D19和本轮主机目录/执行。每轮localAcceptanceId冻结进原详情sink,迟到结果不借新轮ID;LOCAL_USER_TURN_CHANGED立即停止本轮,不重接纳或退legacy。已用固定真包完成本地D19组件联测;编排已回证0.8.14 Claude/Opus5/Default在leo本地目录首轮写文件成功;该回证不覆盖本0.8.18或Codex,产品验收以各版本leo实际结果为准。本地文件不自动上传,X不猜目录、不选首项。首次说明使用官方普通回复;先经官方会话提交、持久化并读回核对,才向L确认。持久记录不等于员工已看见,GUI另验;日志丢失后的未知claim由L保留,不宣称端到端恰好一次。
|
|
16
16
|
|
|
17
17
|
新主机位置允许当前SSH员工账号可访问的已选绝对目录、文件系统根目录、非Git或只读目录;不改写原projectRoot、不创建Git、不转本机同名文件夹。read/execute必须可核实,write=false不额外一刀切拒绝,实际写入服从主机系统权限。运行前必核来源UUID、连接代次、目录及链接入口dev/ino和权限;revision与内容增删导致的mtime/ctime变化不单独失效。此核对属于正确性条件,不可关闭。最后inspect到主机spawn之间仍有OS时序边界,不表示原子目录句柄租约。
|
|
18
18
|
|
|
@@ -80,7 +80,7 @@ D21(0.8.5):两个品牌的官方模型菜单从 L 的 `modelCatalogTarget:
|
|
|
80
80
|
|
|
81
81
|
每轮先核对目录代次和所选模型,再经 ACP 逐项配置模型及推理等级,收到完整配置确认后才发送输入。Claude 别名每轮须有精确的模型配置成功回执,并由同一员工主机、同一连接代际的目录唯一映射到具体型号。具体型号注明来自主机目录,会话未单独回报;旧恢复记录不作本轮证据。目录缺项、映射不唯一、回执不符或身份/代际变化时不发送输入。所选值、远端确认值及型号来源仅在恢复标识与技术详情保留,插件不向真实回复追加技术参数,确认配置不等于验证推理效果。连接/身份/目录变化清除旧确认;远端清理待定或结果未知时阻止派发,不自动清理重试或另开替代会话。Codex 单次模式当前不能确认此选择,会在发送前明确拒绝。新增设置 0;本轮工程证据、真实只读链路与产品未验项见 `docs/evidence/2026-10-22-X2-D21/`。
|
|
82
82
|
|
|
83
|
-
0.8.5统一包原依赖L `~0.7.13`;0.8.16原对齐L `~0.7.19`;当前0.8.
|
|
83
|
+
0.8.5统一包原依赖L `~0.7.13`;0.8.16原对齐L `~0.7.19`;当前0.8.18对齐L `~0.7.20`。D8模型目录双事件长期监听保留;同epoch的recovery提示/失败消息变化不触发执行状态失效,真实实例/epoch/phase/可信目标/登录变化仍失效。完整D9两品牌正文/状态/就绪门呈现按编排00:50允许的范围记下一版,本版不宣称已消费完成;L的connectionMessages.administratorAfterMs仍只由L管理,X新增设置0。
|
|
84
84
|
|
|
85
85
|
D18(0.8.6):Codex 需要主机自有 adapter `2.2.2-aw.1` 的明确新建会话上下文政策,官方 CLI 仍为未改的0.160.1。系统/开发者要求仅在新建时生效;恢复时与初建摘要相同则正常恢复且省略新建专用字段,不声称恢复接口会更新指令。要求不同或清空时,在同一真实回复说明“会话要求已变化,已新建 Codex 会话继续处理,原会话仍保留。”,并新建远端承接公开对话,保留旧标识和查询记录;旧/非法初建摘要明确拒绝,不能猜测。
|
|
86
86
|
|
|
@@ -130,3 +130,9 @@ D20(0.8.8开发记录):本地来源的Codex原生文件与命令通过固
|
|
|
130
130
|
0.8.14 不把空菜单、非穷举 picker、可选 UI category/label 或同值配置通知当作额外发送门槛;每轮仍要求可靠模型入口与 fresh exact setter ACK。本轮完成后的配置变化只记录 warning,保留真实回复与 finish,并明确未获单独的实际执行型号回报。同一身份、代际与可信目标的 needCode/诊断变化只记 info;真实身份、代际、目标或能力失效仍撤回。每次关闭/取消保留 caller、字段前后值和首次观测原因(不等于根因)。新增配置、阈值、超时、重试为 0。
|
|
131
131
|
|
|
132
132
|
0.8.16 不新增 Config、默认值、阈值、超时或重试。首因可见、旧代不重投、实际选择与绑定一致属于不可关闭的正确性不变量。目录完成结果不用于后续请求缓存;活跃租约仅保留更新观测的选择有效性证据,释放即撤登记。断连/错代仍保留原详情信封及未确认状态,仅按旧合同读 receipt 核验。`unconfirmed-<eventId>.json` 私有记录追加是否尝试及失败阶段/固定码,不能当作 L 的 ACK;员工可见原因沿官方错误消息,无新增面板。
|
|
133
|
+
|
|
134
|
+
### 本机执行的核验位置(不可关闭)
|
|
135
|
+
|
|
136
|
+
L接纳每轮真实输入时核对实际目录和N端点/代次。X只在该代执行通道open时向S核对一次target,并继续验证员工执行器固定SHA和官方版本;原生注册发生在open完成之后。之后broker输入及每个native RPC只核对既有内存连接、工作区绑定和接纳事实,撤销来自已订阅的L连接/位置、N网络事件和执行SSH关闭。N实例/代次改变会重新接纳并建立新通道,旧流不会跨代迁移。
|
|
137
|
+
|
|
138
|
+
这属于执行位置与已接纳输入的一致性不变量,没有关闭设置。失败先交还官方会话并显示“正在确认主机清理”,详情落盘及条件kill/list随后在原清理promise进行;同一会话仍由原cleanupFailure/未知送达门阻止不安全重发。没有新增超时、重试、模型限制或中止检查。技术详情事件EMPLOYEE_EXECUTION_VALIDATION_POLICY列出同一核验位置。
|
package/dist/acp.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CloseDiagnostics } from "./close-diagnostics.js";
|
|
1
|
+
import { CloseDiagnostics, withFailureEvidence } from "./close-diagnostics.js";
|
|
2
2
|
import * as acp from '@agentclientprotocol/sdk/unstable';
|
|
3
3
|
import { NeedError } from "./remote.js";
|
|
4
4
|
import { employeeFailure } from "./presentation.js";
|
|
@@ -390,7 +390,7 @@ export class AcpSession {
|
|
|
390
390
|
signal?.addEventListener('abort', abort, { once: true });
|
|
391
391
|
try {
|
|
392
392
|
const hello = await this.stage('acp-initialize', () => this.connection.agent.request(acp.methods.agent.initialize, {
|
|
393
|
-
protocolVersion: acp.PROTOCOL_VERSION, clientInfo: { name: '@ai-workbench/remote-agent', version: '0.8.
|
|
393
|
+
protocolVersion: acp.PROTOCOL_VERSION, clientInfo: { name: '@ai-workbench/remote-agent', version: '0.8.18' },
|
|
394
394
|
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false, session: { notices: {} }, ...(this.employeeMeta ? { _meta: { awEmployeeExecution: { version: 1 } } } : {}) },
|
|
395
395
|
}, { cancellationSignal: signal }));
|
|
396
396
|
this.hooks.broker?.acknowledge(acp.methods.agent.initialize);
|
|
@@ -609,8 +609,18 @@ export function acpNeed(error) {
|
|
|
609
609
|
if (typeof value !== 'string')
|
|
610
610
|
continue;
|
|
611
611
|
const code = value.split(':', 1)[0];
|
|
612
|
-
if (employeeAdapterCodes.has(code))
|
|
613
|
-
|
|
612
|
+
if (employeeAdapterCodes.has(code)) {
|
|
613
|
+
const failure = new NeedError(code, employeeFailure(code), `员工电脑执行适配器明确拒绝:${code};RPC 错误码 ${error.code}`);
|
|
614
|
+
if (code === 'AW_EMPLOYEE_ENVIRONMENT_REGISTRATION_FAILED' && value.startsWith(code + ': ')) {
|
|
615
|
+
const reason = value.slice(code.length + 2);
|
|
616
|
+
if (employeeAdapterCodes.has(reason) || employeeOpenCauseCodes.has(reason)) {
|
|
617
|
+
const retained = withFailureEvidence(Object.assign(failure, { cause: new NeedError(reason, '员工执行通道开通失败', '核对固定开通原因') }), [], 'diagnostic');
|
|
618
|
+
retained.requires += `;RPC 错误码 ${error.code}`;
|
|
619
|
+
return retained;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return failure;
|
|
623
|
+
}
|
|
614
624
|
}
|
|
615
625
|
}
|
|
616
626
|
if (error instanceof acp.RequestError && error.code === -32000)
|
|
@@ -619,6 +629,7 @@ export function acpNeed(error) {
|
|
|
619
629
|
return new NeedError('ACP_FAILED', '远端 ACP 请求失败,根因未查明;未切换到新会话', `有效的远端 ACP 连接;RPC 错误码 ${error.code}`);
|
|
620
630
|
return new NeedError('ACP_FAILED', '远端 ACP 请求失败或连接中断,根因未查明;未切换到新会话', '有效的远端 ACP 连接');
|
|
621
631
|
}
|
|
632
|
+
const employeeOpenCauseCodes = new Set(['AW_EMPLOYEE_BINARY_UNAVAILABLE', 'AW_EMPLOYEE_BINARY_HASH_FAILED', 'AW_EMPLOYEE_BINARY_HASH_MISMATCH', 'AW_EMPLOYEE_BINARY_VERSION_FAILED', 'AW_EMPLOYEE_BINARY_VERSION_MISMATCH', 'EMPLOYEE_CONTEXT_CHANGED', 'EMPLOYEE_ENDPOINT_INVALID', 'EMPLOYEE_BINARY_REQUIRED', 'EMPLOYEE_EXECUTION_UNSUPPORTED', 'EMPLOYEE_EXEC_PROTOCOL_INVALID', 'EMPLOYEE_EXEC_IDENTITY_MISMATCH', 'EMPLOYEE_EXEC_CLOSED', 'EMPLOYEE_EXEC_UNKNOWN', 'EMPLOYEE_EXEC_ALREADY_OPEN', 'EMPLOYEE_EXEC_READ_BUSY', 'ACP_MESSAGE_TOO_LARGE', 'ABORTED', 'SETTINGS_REQUIRED']);
|
|
622
633
|
function record(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined; }
|
|
623
634
|
const knownUpdates = new Set(['user_message_chunk', 'agent_message_chunk', 'agent_thought_chunk', 'tool_call', 'tool_call_update', 'plan', 'plan_update', 'plan_removed', 'available_commands_update', 'current_mode_update', 'config_option_update', 'session_info_update', 'usage_update', 'notice', 'compaction_update', 'compaction_summary_chunk', 'subagent_update', 'session_message', 'session_message_chunk']);
|
|
624
635
|
function safeLabel(value) { return value.replaceAll(/[\r\n\x00-\x1f\x7f]/g, ' '); }
|
|
@@ -8,11 +8,11 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
8
8
|
};
|
|
9
9
|
import { connectNeedError } from "./connect-need.js";
|
|
10
10
|
import { CODEX_EMPLOYEE_SCOPE_NOTICE } from "./employee-presentation.js";
|
|
11
|
-
import { CloseDiagnostics, withFailureEvidence, closeCode, closeErrorCodes } from "./close-diagnostics.js";
|
|
11
|
+
import { CloseDiagnostics, withFailureEvidence, withFirstFailureEvidence, closeCode, closeErrorCodes } from "./close-diagnostics.js";
|
|
12
12
|
import { createHash } from 'node:crypto';
|
|
13
13
|
import { admitEmployeeLocation } from "./employee-location.js";
|
|
14
14
|
import { EmployeeExecution } from "./employee-exec.js";
|
|
15
|
-
import { createEmployeeSshTransport
|
|
15
|
+
import { createEmployeeSshTransport } from "./employee-ssh.js";
|
|
16
16
|
import { sshIo } from "./broker.js";
|
|
17
17
|
import { NeedError } from "./remote.js";
|
|
18
18
|
const fail = (code) => new NeedError(code, code === 'EMPLOYEE_OPERATION_UNSUPPORTED' ? '当前本机工作区只支持正在连接的会话;单次运行和断线后的冷恢复暂不支持,请新建会话。' : '本机工作区的执行连接无法继续(' + code + '),本轮没有改用主机执行。', '当前 L 工作区绑定、N/S 员工执行端点与支持本机执行的会话适配器');
|
|
@@ -177,17 +177,25 @@ export function createEmployeeProvider(get, options, agent) {
|
|
|
177
177
|
await prior.dispose(fail('EMPLOYEE_CONTEXT_CHANGED'), { layer: 'endpoint', initiator: 'provider.scope-change', reason: 'EMPLOYEE_CONTEXT_CHANGED', action: 'dispose' });
|
|
178
178
|
let entry = prior && prior.scope === scope && !prior.ended ? prior : undefined;
|
|
179
179
|
let candidate = entry;
|
|
180
|
+
// Admission still verifies the real directory and N generation. Per-RPC gates below
|
|
181
|
+
// reuse the subscribed connection/location/network facts, without another SSH.
|
|
182
|
+
await admitted.validate(request.signal ?? new AbortController().signal);
|
|
183
|
+
details?.record({ kind: 'session', severity: 'info', code: 'EMPLOYEE_EXECUTION_VALIDATION_POLICY', summary: '本机执行核验不可关闭:L接纳核对实际目录与N端点;同代执行通道仅在open时核对一次主机target与固定执行器。之后每条消息只核对内存中的连接、工作区绑定和已接纳输入,撤销沿现有连接/位置/N网络事件及执行SSH关闭处理;不重复SSH,不改用主机执行。' });
|
|
184
|
+
if (entry)
|
|
185
|
+
entry.current = input;
|
|
180
186
|
const base = async (signal) => {
|
|
181
187
|
signal.throwIfAborted();
|
|
182
188
|
if (get() !== api || employee.getNetwork() !== network)
|
|
183
189
|
throw fail('EMPLOYEE_CONTEXT_CHANGED');
|
|
184
190
|
if (candidate?.ended)
|
|
185
191
|
throw candidate.reason ?? fail('EMPLOYEE_EXEC_CLOSED');
|
|
186
|
-
|
|
187
|
-
|
|
192
|
+
const current = candidate?.current ?? input;
|
|
193
|
+
current.request.localAdmissionCheck?.();
|
|
194
|
+
await current.stable(signal);
|
|
188
195
|
if (get() !== api || employee.getNetwork() !== network)
|
|
189
196
|
throw fail('EMPLOYEE_CONTEXT_CHANGED');
|
|
190
|
-
|
|
197
|
+
current.request.localAdmissionCheck?.();
|
|
198
|
+
await current.stable(signal);
|
|
191
199
|
signal.throwIfAborted();
|
|
192
200
|
};
|
|
193
201
|
let argv = employee.argv;
|
|
@@ -208,11 +216,11 @@ export function createEmployeeProvider(get, options, agent) {
|
|
|
208
216
|
} };
|
|
209
217
|
const ssh = { closeDiagnostics, target, endpoint: admitted.endpoint, employeeBinary: native.path, binarySha256: native.sha256, acpMaxMessageBytes: request.acpMaxMessageBytes, argv: argv ?? (() => []), validate: base, io: scopedIo };
|
|
210
218
|
const validate = async (signal) => { try {
|
|
211
|
-
await
|
|
219
|
+
await base(signal);
|
|
212
220
|
}
|
|
213
221
|
catch (error) {
|
|
214
222
|
if (candidate && errorCode(error) !== 'ABORTED')
|
|
215
|
-
|
|
223
|
+
void candidate.dispose(asNeed(error), { layer: 'endpoint', initiator: 'provider.validation-failure', reason: closeCode(error), action: 'invalidate' }).catch(cleanup => closeDiagnostics.record({ layer: 'runtime', initiator: 'provider.validation-cleanup', reason: closeCode(cleanup, 'REMOTE_CLEANUP_UNCONFIRMED'), action: 'failure' }));
|
|
216
224
|
throw error;
|
|
217
225
|
} };
|
|
218
226
|
await validate(request.signal ?? new AbortController().signal);
|
|
@@ -303,14 +311,14 @@ export function createEmployeeProvider(get, options, agent) {
|
|
|
303
311
|
try {
|
|
304
312
|
const result = await execution.handle(params, signal);
|
|
305
313
|
if (result.closed)
|
|
306
|
-
|
|
314
|
+
void entry.dispose(undefined, { layer: 'runtime', initiator: 'provider.rpc-closed-result', reason: 'EMPLOYEE_EXEC_CLOSED', operation: typeof params === 'object' && params && 'operation' in params && typeof params.operation === 'string' ? params.operation : 'unknown', action: 'dispose' }).catch(cleanup => closeDiagnostics.record({ layer: 'runtime', initiator: 'provider.rpc-cleanup', reason: closeCode(cleanup, 'REMOTE_CLEANUP_UNCONFIRMED'), action: 'failure' }));
|
|
307
315
|
return { ...result };
|
|
308
316
|
}
|
|
309
317
|
catch (error) {
|
|
310
318
|
// EmployeeExecution reserves ABORTED for an unconsumed read wait. Lost responses are UNKNOWN.
|
|
311
319
|
if (!(params && typeof params === 'object' && 'operation' in params && params.operation === 'read' && errorCode(error) === 'ABORTED'))
|
|
312
|
-
|
|
313
|
-
throw error;
|
|
320
|
+
void entry.dispose(asNeed(error), { layer: 'runtime', initiator: 'provider.rpc-failure', reason: closeCode(error), action: 'dispose' }).catch(cleanup => closeDiagnostics.record({ layer: 'runtime', initiator: 'provider.rpc-cleanup', reason: closeCode(cleanup, 'REMOTE_CLEANUP_UNCONFIRMED'), action: 'failure' }));
|
|
321
|
+
throw withFirstFailureEvidence(error, closeDiagnostics.first?.reason, errorCode(error));
|
|
314
322
|
}
|
|
315
323
|
};
|
|
316
324
|
runtime.dispose = cause => entry.dispose(undefined, cause ?? { layer: 'runtime', initiator: 'runtime.dispose', reason: 'RUNTIME_DISPOSE', action: 'dispose' });
|
package/dist/employee-ssh.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface EmployeeSshOptions {
|
|
|
18
18
|
}
|
|
19
19
|
/** Fixed bootstrap only; cwd for every native operation remains in its original RPC. */
|
|
20
20
|
export declare function employeeExecServerCommand(binary: string, binarySha256: string): string;
|
|
21
|
-
/**
|
|
21
|
+
/** Explicit read-only probe; production dispatch verifies S once in transport open. */
|
|
22
22
|
export declare function validateEmployeeSshTarget(options: EmployeeSshOptions, signal?: AbortSignal): Promise<void>;
|
|
23
23
|
/** Outer L-authenticated host SSH, then S-authenticated employee SSH; no host execution fallback. */
|
|
24
24
|
export declare function createEmployeeSshTransport(options: EmployeeSshOptions, signal?: AbortSignal): Promise<EmployeeExecutionTransport>;
|
package/dist/employee-ssh.js
CHANGED
|
@@ -99,7 +99,7 @@ async function currentTarget(options, signal) {
|
|
|
99
99
|
await gate();
|
|
100
100
|
return { io, args, gate };
|
|
101
101
|
}
|
|
102
|
-
/**
|
|
102
|
+
/** Explicit read-only probe; production dispatch verifies S once in transport open. */
|
|
103
103
|
export async function validateEmployeeSshTarget(options, signal = new AbortController().signal) { await currentTarget(options, signal); }
|
|
104
104
|
/** Outer L-authenticated host SSH, then S-authenticated employee SSH; no host execution fallback. */
|
|
105
105
|
export async function createEmployeeSshTransport(options, signal = new AbortController().signal) {
|
|
@@ -6,6 +6,8 @@ export declare function closeErrorCodes(error: unknown, fallback?: string): stri
|
|
|
6
6
|
export declare function closeFailure(code: string, message: string, requires: string, codes: readonly string[]): NeedError;
|
|
7
7
|
/** Saving succeeded: retain the original trusted error type, code and presentation. */
|
|
8
8
|
export declare function withFirstFailureEvidence<T>(primary: T, firstReason: unknown, primaryCode: string): T;
|
|
9
|
+
/** A known turn failure is final even while its original resource is still closing. */
|
|
10
|
+
export declare function withPendingCleanupEvidence<T>(primary: T, firstReason: unknown, primaryCode: string): T;
|
|
9
11
|
/** Preserve causal roles without copying arbitrary exception text into employee output. */
|
|
10
12
|
export declare function withFailureEvidence(primary: unknown, secondary: readonly unknown[], kind: 'diagnostic' | 'cleanup', firstReason?: unknown, primaryCode?: string): NeedError;
|
|
11
13
|
export declare function primaryFailureCode(error: unknown, fallback: string): string;
|
package/dist/failure-evidence.js
CHANGED
|
@@ -62,6 +62,14 @@ export function withFirstFailureEvidence(primary, firstReason, primaryCode) {
|
|
|
62
62
|
}
|
|
63
63
|
return primary;
|
|
64
64
|
}
|
|
65
|
+
/** A known turn failure is final even while its original resource is still closing. */
|
|
66
|
+
export function withPendingCleanupEvidence(primary, firstReason, primaryCode) {
|
|
67
|
+
if (primary && typeof primary === 'object') {
|
|
68
|
+
const previous = retainedEvidence(primary), first = previous?.first ?? (safeCode(firstReason) ? firstReason : undefined);
|
|
69
|
+
evidence.set(primary, Object.freeze({ ...previous, ...(first ? { first } : {}), original: previous?.original ?? Object.freeze([safeCode(primaryCode) ? primaryCode : 'TURN_FAILED']), diagnostic: previous?.diagnostic ?? Object.freeze([]), cleanup: previous?.cleanup ?? Object.freeze([]), cleanupPending: true }));
|
|
70
|
+
}
|
|
71
|
+
return primary;
|
|
72
|
+
}
|
|
65
73
|
/** Preserve causal roles without copying arbitrary exception text into employee output. */
|
|
66
74
|
export function withFailureEvidence(primary, secondary, kind, firstReason, primaryCode) {
|
|
67
75
|
const previous = retainedEvidence(primary), first = previous?.first ?? (safeCode(firstReason) ? firstReason : undefined);
|
|
@@ -79,5 +87,5 @@ export function failureEvidenceSummary(error) {
|
|
|
79
87
|
const facts = retainedEvidence(error);
|
|
80
88
|
if (!facts)
|
|
81
89
|
return '';
|
|
82
|
-
return '技术详情:' + [...(facts.first ? ['首次观察关闭 ' + facts.first] : []), '运行原因 ' + facts.original.join('、'), ...(facts.diagnostic.length ? ['详情保存未确认 ' + facts.diagnostic.join('、')] : []), ...(facts.cleanup.length ? ['清理未确认 ' + facts.cleanup.join('、')] : [])].join(';');
|
|
90
|
+
return '技术详情:' + [...(facts.first ? ['首次观察关闭 ' + facts.first] : []), '运行原因 ' + facts.original.join('、'), ...(facts.diagnostic.length ? ['详情保存未确认 ' + facts.diagnostic.join('、')] : []), ...(facts.cleanup.length ? ['清理未确认 ' + facts.cleanup.join('、')] : facts.cleanupPending ? ['正在确认主机清理,原资源清理结果尚待确认'] : [])].join(';');
|
|
83
91
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -106,6 +106,8 @@ export declare class CodexRemoteAdapter extends LlmAdapter {
|
|
|
106
106
|
jitterRatio: number;
|
|
107
107
|
};
|
|
108
108
|
private cleanupRuntime;
|
|
109
|
+
/** Publish an already known failure without coupling the official stream to cleanup. */
|
|
110
|
+
private finishFailedTurn;
|
|
109
111
|
disposeSession(sessionId: string): Promise<void>;
|
|
110
112
|
detachSession(sessionId: string): Promise<void>;
|
|
111
113
|
dispose(): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { connectNeedMessage } from "./connect-need.js";
|
|
2
2
|
import { CloseDiagnostics, withFailureEvidence, withFirstFailureEvidence, failureEvidenceSummary, closeCode, closeErrorCodes, closeFailure } from "./close-diagnostics.js";
|
|
3
|
+
import { withPendingCleanupEvidence } from "./failure-evidence.js";
|
|
3
4
|
import { runCatalog } from "./catalog-ssh.js";
|
|
4
5
|
import { createCatalogDiagnostics, CatalogDiagnosticsRemote } from "./catalog-diagnostics.js";
|
|
5
6
|
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
@@ -205,6 +206,35 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
205
206
|
this.cleanups.set(runtime, cleanup);
|
|
206
207
|
return cleanup;
|
|
207
208
|
}
|
|
209
|
+
/** Publish an already known failure without coupling the official stream to cleanup. */
|
|
210
|
+
finishFailedTurn(key, live, primary, code, firstReason) {
|
|
211
|
+
withPendingCleanupEvidence(primary, firstReason, code);
|
|
212
|
+
const runtime = live.runtime;
|
|
213
|
+
const disposal = this.cleanupRuntime(runtime).then(() => {
|
|
214
|
+
if (this.sessions.get(key) !== live || live.disposal !== disposal)
|
|
215
|
+
return;
|
|
216
|
+
live.disposal = undefined;
|
|
217
|
+
if (!live.failure) {
|
|
218
|
+
if (typeof runtime.dispose === 'function') {
|
|
219
|
+
this.sessions.delete(key);
|
|
220
|
+
this.preparationFailures.set(key, code);
|
|
221
|
+
}
|
|
222
|
+
else
|
|
223
|
+
live.cleanupFailure = new NeedError('REMOTE_CLEANUP_UNCONFIRMED', '缺少原实例结束证明,不能重开或重发', '原实例的条件结束与list消失证明');
|
|
224
|
+
}
|
|
225
|
+
}, error => {
|
|
226
|
+
if (this.sessions.get(key) === live && live.disposal === disposal)
|
|
227
|
+
live.cleanupFailure = withFailureEvidence(primary, [error], 'cleanup', firstReason, code);
|
|
228
|
+
throw error;
|
|
229
|
+
});
|
|
230
|
+
live.disposal = disposal;
|
|
231
|
+
const report = (reason) => {
|
|
232
|
+
runtime.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.failed-turn-cleanup', reason, action: 'dispose' });
|
|
233
|
+
// State settles before reporting. A delayed D19 write is not another release gate.
|
|
234
|
+
void Promise.resolve().then(() => runtime.details?.flush()).catch(error => runtime.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.failed-turn-cleanup-report', reason: closeCode(error, 'CLOSE_FLUSH_FAILED'), action: 'failure' }));
|
|
235
|
+
};
|
|
236
|
+
void disposal.then(() => report(typeof runtime.dispose === 'function' ? 'RUNTIME_CLEANUP_CONFIRMED' : 'REMOTE_CLEANUP_UNCONFIRMED'), () => report('REMOTE_CLEANUP_UNCONFIRMED'));
|
|
237
|
+
}
|
|
208
238
|
async disposeSession(sessionId) {
|
|
209
239
|
this.sessions.get(sessionId)?.runtime.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.dispose-session', reason: 'SESSION_DISPOSED', action: 'abort' });
|
|
210
240
|
this.turns.get(sessionId)?.abort();
|
|
@@ -677,22 +707,32 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
677
707
|
if (live && session && session.promptDispatchCount > promptBaseline)
|
|
678
708
|
live.failure = { cause: error };
|
|
679
709
|
const reporting = [];
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
710
|
+
const reportFailure = async () => {
|
|
711
|
+
if (request?.recordEarlyFailure) {
|
|
712
|
+
const record = request.recordEarlyFailure;
|
|
713
|
+
request.recordEarlyFailure = undefined;
|
|
714
|
+
try {
|
|
715
|
+
await record(Object.assign(new NeedError(failureCode ?? 'TURN_PREPARATION_FAILED', '本轮在执行前失败。', '白名单阶段与错误代码'), { cause: error }), earlyStage);
|
|
716
|
+
}
|
|
717
|
+
catch (failure) {
|
|
718
|
+
reporting.push(failure);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
683
721
|
try {
|
|
684
|
-
await
|
|
722
|
+
await runtime?.details?.flush();
|
|
685
723
|
}
|
|
686
724
|
catch (failure) {
|
|
687
725
|
reporting.push(failure);
|
|
688
726
|
}
|
|
727
|
+
};
|
|
728
|
+
if (runtime && key && live && live.runtime === runtime && this.sessions.get(key) === live) {
|
|
729
|
+
const captured = runtime;
|
|
730
|
+
void reportFailure().then(() => { for (const failure of reporting)
|
|
731
|
+
for (const code of closeErrorCodes(failure, 'DIAGNOSTIC_WRITE_FAILED'))
|
|
732
|
+
captured.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.failed-turn-details', reason: code, action: 'failure' }); }, failure => captured.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.failed-turn-details', reason: closeCode(failure, 'DIAGNOSTIC_WRITE_FAILED'), action: 'failure' }));
|
|
689
733
|
}
|
|
690
|
-
|
|
691
|
-
await
|
|
692
|
-
}
|
|
693
|
-
catch (failure) {
|
|
694
|
-
reporting.push(failure);
|
|
695
|
-
}
|
|
734
|
+
else
|
|
735
|
+
await reportFailure();
|
|
696
736
|
if (activeMapper)
|
|
697
737
|
yield* activeMapper.flush();
|
|
698
738
|
const firstReason = request?.closeDiagnostics?.first?.reason ?? runtime?.closeDiagnostics?.first?.reason;
|
|
@@ -716,15 +756,19 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
716
756
|
else if (!succeeded || !key || mode !== 'session') {
|
|
717
757
|
session?.close({ layer: 'adapter', initiator: 'adapter.turn-finally', reason: succeeded ? 'TURN_FINISHED' : 'TURN_FAILED', action: 'close' });
|
|
718
758
|
if (runtime) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
759
|
+
if (!succeeded && turnFailure && key && live && live.runtime === runtime && this.sessions.get(key) === live)
|
|
760
|
+
this.finishFailedTurn(key, live, turnFailure, failureCode ?? 'TURN_PREPARATION_FAILED', request?.closeDiagnostics?.first?.reason ?? runtime.closeDiagnostics?.first?.reason);
|
|
761
|
+
else {
|
|
762
|
+
await this.cleanupRuntime(runtime);
|
|
763
|
+
if (key && live && !live.failure && this.sessions.get(key) === live) {
|
|
764
|
+
if (typeof runtime.dispose === 'function') {
|
|
765
|
+
this.sessions.delete(key);
|
|
766
|
+
if (!succeeded)
|
|
767
|
+
this.preparationFailures.set(key, failureCode ?? 'TURN_PREPARATION_FAILED');
|
|
768
|
+
}
|
|
769
|
+
else
|
|
770
|
+
live.cleanupFailure = new NeedError('REMOTE_CLEANUP_UNCONFIRMED', '缺少原实例结束证明,不能重开或重发', '原实例的条件结束与list消失证明');
|
|
725
771
|
}
|
|
726
|
-
else
|
|
727
|
-
live.cleanupFailure = new NeedError('REMOTE_CLEANUP_UNCONFIRMED', '缺少原实例结束证明,不能重开或重发', '原实例的条件结束与list消失证明');
|
|
728
772
|
}
|
|
729
773
|
}
|
|
730
774
|
}
|