@ai-workbench/remote-agent 0.8.14 → 0.8.15

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 CHANGED
@@ -1,10 +1,10 @@
1
- **`@ai-workbench/remote-agent@0.8.14` 发布形态包;消费审定的 B1–B9 校验分类及 X2 首次关闭原因观测,供 leo 真机复验。Claude 型号按本轮 setter 精确回执与同域目录映射确认,并如实注明来源。Codex 上次 `EMPLOYEE_EXEC_CLOSED` 的真机根因仍未查明,本版补充首因留痕,不宣称其已在真机修复。由编排发布后,通过官方插件 → 添加插件安装;X 不执行 publish。**
1
+ **`@ai-workbench/remote-agent@0.8.15` 发布形态包。模型菜单逐项解析与同次展示目录保持一致;失效选择明确提示重新选择,发送准备和每轮发送仍独立查可信目录。模型型号、推理等级和来源只进技术详情;本机执行说明每个会话首次一句,完整限制可在技术详情查阅。编排已回证0.8.14 Claude在leo本地文件夹首轮成功;0.8.15仍待真机复验,Codex历史EMPLOYEE_EXEC_CLOSED根因仍未查明。X2出固定包及SHA,不publish。通过官方插件 → 添加插件安装,由编排发布;X2不publish。**
2
2
 
3
3
  # 远程ACP插件
4
4
 
5
5
  一个插件提供 **Claude(远程主机)** 和 **Codex(远程主机)**,通过员工主机上的官方CLI运行。Codex保留单次模式;Claude固定会话模式。只用官方模型列表、插件管理和配置入口,不提供自制面板、问答或登录按钮。登录和原生工作区绑定唯一归连接插件L。
6
6
 
7
- 当前版本号为0.8.14;包名采用`@ai-workbench/remote-agent`以涵盖两个品牌;本轮按编排放行准备发布形态包,供 leo 真机自验;X 不执行实际发布,由编排统一发布。开发fixture逐字节保留L0.7.19固定包,公开类型与SSH导出从该包消费。运行时仍要求L的`getContractInfo()`同时返回API0.7.0、implemented、native-local-v1;旧L包、draft.3或只有版本号的不完整实现会明确报缺项。依赖范围为`~0.7.19`,L由官方插件管理单独安装,不打进X。组件验证不代表真实开通、模型或leo E通过;HanaMesh另验。
7
+ 当前版本号为0.8.15;包名采用`@ai-workbench/remote-agent`以涵盖两个品牌;本轮按编排放行准备发布形态包,供 leo 真机自验;X 不执行实际发布,由编排统一发布。开发fixture逐字节保留L0.7.19固定包,公开类型与SSH导出从该包消费。运行时仍要求L的`getContractInfo()`同时返回API0.7.0、implemented、native-local-v1;旧L包、draft.3或只有版本号的不完整实现会明确报缺项。依赖范围为`~0.7.19`,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
- 从官方插件管理安装,照常使用默认工作区/会话/模型入口。L0.7.17沿唯一“远程主机连接”设置节记录文件来源;主机来源沿用L既有解析;本地来源接入N0.4.4/S0.4.11员工端点。旧映射可在本轮官方一次确认中选择改用本机文件夹;拒绝、取消或输入/来源变化时不切换、不执行。也可在L设置逐工作区确认沿用原主机关联,新主机位置仍按L现有合同停止为WORKSPACE_LOCATION_PROVIDER_REQUIRED。本地六类工具经Claude公开toolAliases路由到员工电脑:cwd是员工选定目录,Git使用本机仓库,不回退主机同名目录。本地真实员工输入先取得官方当前轮次CAS预期,再调用L0.7.17 acceptLocalWorkspaceUserTurn;核回包后才打开D19和本轮主机目录/执行。每轮localAcceptanceId冻结进原详情sink,迟到结果不借新轮ID;LOCAL_USER_TURN_CHANGED立即停止本轮,不重接纳或退legacy。已用固定真包完成本地D19组件联测;真实N/S员工端仍NOT_RUN,产品验收以 leo 实际执行结果为准。本地文件不自动上传,X不猜目录、不选首项。首次说明使用官方普通回复;先经官方会话提交、持久化并读回核对,才向L确认。持久记录不等于员工已看见,GUI另验;日志丢失后的未知claim由L保留,不宣称端到端恰好一次。
15
+ 从官方插件管理安装,照常使用默认工作区/会话/模型入口。L0.7.17沿唯一“远程主机连接”设置节记录文件来源;主机来源沿用L既有解析;本地来源接入N0.4.4/S0.4.11员工端点。旧映射可在本轮官方一次确认中选择改用本机文件夹;拒绝、取消或输入/来源变化时不切换、不执行。也可在L设置逐工作区确认沿用原主机关联,新主机位置仍按L现有合同停止为WORKSPACE_LOCATION_PROVIDER_REQUIRED。本地六类工具经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或Codex,产品验收以各版本leo实际结果为准。本地文件不自动上传,X不猜目录、不选首项。首次说明使用官方普通回复;先经官方会话提交、持久化并读回核对,才向L确认。持久记录不等于员工已看见,GUI另验;日志丢失后的未知claim由L保留,不宣称端到端恰好一次。
16
16
 
17
17
  未登录时两个模型均回复:**需要登录,请到设置 → 远程主机连接 点登录**。无L或缺正式能力时直接说明缺项,不让员工填写主机、用户、路径或密钥。
18
18
 
@@ -74,9 +74,9 @@ D22阶段耗时进入技术详情;D23文件审批用中文动作,原工具
74
74
 
75
75
  D21(0.8.5):两个品牌的官方模型菜单从 L 的 `modelCatalogTarget:1` / `getModelCatalogTarget` 取得当前可信员工目标,再只读调用 S 的 `aw-model-catalog --provider codex|claude`。目录按 host-user 作用域展示完整模型与逐模型推理等级,保留远端标识原值;失败显示“无法获取模型列表:原因”,不登录、不创建项目、不返回替代模型。L 必须宣告该正式能力,S 必须安装保护入口;这两项未就绪时不能据组件测试声称菜单可用。
76
76
 
77
- 每轮先核对目录代次和所选模型,再经 ACP 逐项配置模型及推理等级,收到完整配置确认后才发送输入。Claude 别名每轮须有精确的模型配置成功回执,并由同一员工主机、同一连接代际的目录唯一映射到具体型号。具体型号注明来自主机目录,会话未单独回报;旧恢复记录不作本轮证据。目录缺项、映射不唯一、回执不符或身份/代际变化时不发送输入。真实回复追加所选值和远端确认值,并在恢复标识与技术详情保留,确认配置不等于验证推理效果。连接/身份/目录变化清除旧确认;远端清理待定或结果未知时阻止派发,不自动清理重试或另开替代会话。Codex 单次模式当前不能确认此选择,会在发送前明确拒绝。新增设置 0;本轮工程证据、真实只读链路与产品未验项见 `docs/evidence/2026-10-22-X2-D21/`。
77
+ 每轮先核对目录代次和所选模型,再经 ACP 逐项配置模型及推理等级,收到完整配置确认后才发送输入。Claude 别名每轮须有精确的模型配置成功回执,并由同一员工主机、同一连接代际的目录唯一映射到具体型号。具体型号注明来自主机目录,会话未单独回报;旧恢复记录不作本轮证据。目录缺项、映射不唯一、回执不符或身份/代际变化时不发送输入。所选值、远端确认值及型号来源仅在恢复标识与技术详情保留,插件不向真实回复追加技术参数,确认配置不等于验证推理效果。连接/身份/目录变化清除旧确认;远端清理待定或结果未知时阻止派发,不自动清理重试或另开替代会话。Codex 单次模式当前不能确认此选择,会在发送前明确拒绝。新增设置 0;本轮工程证据、真实只读链路与产品未验项见 `docs/evidence/2026-10-22-X2-D21/`。
78
78
 
79
- 0.8.5统一包原依赖L `~0.7.13`;当前0.8.14对齐L `~0.7.19`。D8模型目录双事件长期监听保留;同epoch的recovery提示/失败消息变化不触发执行状态失效,真实实例/epoch/phase/可信目标/登录变化仍失效。完整D9两品牌正文/状态/就绪门呈现按编排00:50允许的范围记下一版,本版不宣称已消费完成;L的connectionMessages.administratorAfterMs仍只由L管理,X新增设置0。
79
+ 0.8.5统一包原依赖L `~0.7.13`;当前0.8.15对齐L `~0.7.19`。D8模型目录双事件长期监听保留;同epoch的recovery提示/失败消息变化不触发执行状态失效,真实实例/epoch/phase/可信目标/登录变化仍失效。完整D9两品牌正文/状态/就绪门呈现按编排00:50允许的范围记下一版,本版不宣称已消费完成;L的connectionMessages.administratorAfterMs仍只由L管理,X新增设置0。
80
80
 
81
81
  D18(0.8.6):Codex 需要主机自有 adapter `2.2.2-aw.1` 的明确新建会话上下文政策,官方 CLI 仍为未改的0.160.1。系统/开发者要求仅在新建时生效;恢复时与初建摘要相同则正常恢复且省略新建专用字段,不声称恢复接口会更新指令。要求不同或清空时,在同一真实回复说明“会话要求已变化,已新建 Codex 会话继续处理,原会话仍保留。”,并新建远端承接公开对话,保留旧标识和查询记录;旧/非法初建摘要明确拒绝,不能猜测。
82
82
 
@@ -88,7 +88,7 @@ D18(0.8.6):Codex 需要主机自有 adapter `2.2.2-aw.1` 的明确新建
88
88
 
89
89
  Read/Write/Edit/Glob/Grep由员工DSH同UID后端执行;Bash经L严格SSH到S同代桥再经N员工SSH端点执行,命令正文只走stdin。每次调用核官方会话唯一工作区、L实例/epoch/绑定/目录身份、N真实prepare及S实时target。取消、SSH255或退出证明缺失记录UNKNOWN,不重放。只关闭自己的绑定执行器,不撤其他工作区或设备服务。cwd不是权限根,绝对路径和上级访问遵循真实OS权限;不增加目录白名单。
90
90
 
91
- 每轮同一官方回复明确:子代理和harness内部直接工具调用尚未证明可在员工端执行,可能仍在主机;已识别的原生未改道工具/Agent审批拒绝。PDF等二进制读取及后台命令明确不支持,不静默主机代跑。六类组件/MCP/配置转交证明不等于真实Claude原生alias执行或真实员工链路通过。实际Git依赖员工环境中已有Git,缺失会返回实际命令失败,不在主机替代,也不自动安装。
91
+ 本机执行在每个DSH会话首次用一句“文件和命令在你的电脑上执行”说明;既有0.8.14说明也计为已显示。完整限制每轮记录在技术详情,遇到不支持的操作再明确提示:子代理和harness内部直接工具调用尚未证明可在员工端执行,可能仍在主机;已识别的原生未改道工具/Agent审批拒绝。PDF等二进制读取及后台命令明确不支持,不静默主机代跑。六类组件/MCP/配置转交证明不等于真实Claude原生alias执行或真实员工链路通过。实际Git依赖员工环境中已有Git,缺失会返回实际命令失败,不在主机替代,也不自动安装。
92
92
 
93
93
  本地目前仅支持macOS(arm64/x64);Grep固定携带微软@vscode/ripgrep-universal1.18.0的两架构原字节并核SHA,无员工PATH搜索或安装下载。主机需S0.4.11,N可选peer~0.4.4(缺失明确报能力缺项),L当前~0.7.18,本地接纳与D19工程路径已接,真实员工端门仍开放。无DSH/CLI修改、额外设置节或npm发布。
94
94
 
package/dist/acp.js CHANGED
@@ -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.14' },
393
+ protocolVersion: acp.PROTOCOL_VERSION, clientInfo: { name: '@ai-workbench/remote-agent', version: '0.8.15' },
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);
@@ -6,6 +6,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
6
6
  }
7
7
  return path;
8
8
  };
9
+ import { CLAUDE_EMPLOYEE_SCOPE_NOTICE as NOTICE } from "./employee-presentation.js";
9
10
  import { CloseDiagnostics, closeCode } from "./close-diagnostics.js";
10
11
  import { isDeepStrictEqual } from 'node:util';
11
12
  import { openEmployeeLease } from "./employee-endpoint.js";
@@ -16,7 +17,6 @@ import { sshIo } from "./broker.js";
16
17
  import { NeedError, shellQuote } from "./remote.js";
17
18
  import { awaitL } from "./provider.js";
18
19
  const failure = (code) => new NeedError(code, '当前本机执行上下文无法确认,本轮已停止;没有改在主机执行。', 'L当前本地绑定、N员工端点与S同代桥');
19
- const NOTICE = '本轮六类文件与命令工具在你的电脑执行,使用你选择的文件夹;Git也使用本机仓库。子代理和内部直接调用尚不支持本机,可能仍在主机;已识别的此类审批不会执行。PDF等二进制读取、后台命令暂不支持。';
20
20
  /** Local source only. Host and legacy-host remain with the original provider. */
21
21
  export function createClaudeLocalProvider(options) {
22
22
  const entries = new Map();
@@ -0,0 +1,6 @@
1
+ import type { GenerateOptions } from '@deepseek-ai/dsh-llm';
2
+ export declare const CLAUDE_EMPLOYEE_SCOPE_NOTICE = "\u672C\u8F6E\u516D\u7C7B\u6587\u4EF6\u4E0E\u547D\u4EE4\u5DE5\u5177\u5728\u4F60\u7684\u7535\u8111\u6267\u884C\uFF0C\u4F7F\u7528\u4F60\u9009\u62E9\u7684\u6587\u4EF6\u5939\uFF1BGit\u4E5F\u4F7F\u7528\u672C\u673A\u4ED3\u5E93\u3002\u5B50\u4EE3\u7406\u548C\u5185\u90E8\u76F4\u63A5\u8C03\u7528\u5C1A\u4E0D\u652F\u6301\u672C\u673A\uFF0C\u53EF\u80FD\u4ECD\u5728\u4E3B\u673A\uFF1B\u5DF2\u8BC6\u522B\u7684\u6B64\u7C7B\u5BA1\u6279\u4E0D\u4F1A\u6267\u884C\u3002PDF\u7B49\u4E8C\u8FDB\u5236\u8BFB\u53D6\u3001\u540E\u53F0\u547D\u4EE4\u6682\u4E0D\u652F\u6301\u3002";
3
+ export declare const CODEX_EMPLOYEE_SCOPE_NOTICE = "\u6587\u4EF6\u4E0E\u547D\u4EE4\u5728\u672C\u673A\u9009\u5B9A\u76EE\u5F55\u8FD0\u884C\uFF0CGit \u4F7F\u7528\u672C\u673A\u4ED3\u5E93\uFF1B\u6A21\u578B\u3001\u767B\u5F55\u548C\u5168\u5C40\u52A9\u624B\u8981\u6C42\u4ECD\u5728\u8FDC\u7A0B\u4E3B\u673A\u3002\u672C\u673A\u81EA\u5B9A\u4E49\u7684\u9644\u52A0\u5DE5\u5177\u3001\u81EA\u52A8\u52A8\u4F5C\u3001\u63D2\u4EF6\u53CA\u65AD\u5F00\u540E\u7684\u4F1A\u8BDD\u6062\u590D\u6682\u4E0D\u652F\u6301\u3002\u5B50\u4EFB\u52A1\u88AB\u7CFB\u7EDF\u56DE\u6536\u540E\u80FD\u5426\u7EE7\u7EED\uFF0C\u5C1A\u672A\u9A8C\u8BC1\u3002";
4
+ export declare const EMPLOYEE_EXECUTION_NOTICE = "\u6587\u4EF6\u548C\u547D\u4EE4\u5728\u4F60\u7684\u7535\u8111\u4E0A\u6267\u884C";
5
+ /** Presentation receipt only; it grants no process, workspace or replay authority. */
6
+ export declare function employeeNoticePreviouslyShown(options: Pick<GenerateOptions, 'messages' | 'sessionId'>): boolean;
@@ -0,0 +1,16 @@
1
+ import { AGENTS } from "./agents.js";
2
+ export const CLAUDE_EMPLOYEE_SCOPE_NOTICE = '本轮六类文件与命令工具在你的电脑执行,使用你选择的文件夹;Git也使用本机仓库。子代理和内部直接调用尚不支持本机,可能仍在主机;已识别的此类审批不会执行。PDF等二进制读取、后台命令暂不支持。';
3
+ export const CODEX_EMPLOYEE_SCOPE_NOTICE = '文件与命令在本机选定目录运行,Git 使用本机仓库;模型、登录和全局助手要求仍在远程主机。本机自定义的附加工具、自动动作、插件及断开后的会话恢复暂不支持。子任务被系统回收后能否继续,尚未验证。';
4
+ export const EMPLOYEE_EXECUTION_NOTICE = '文件和命令在你的电脑上执行';
5
+ /** Presentation receipt only; it grants no process, workspace or replay authority. */
6
+ export function employeeNoticePreviouslyShown(options) {
7
+ if (!options.sessionId)
8
+ return false;
9
+ return options.messages.some(message => {
10
+ if (message.role !== 'assistant' || !AGENTS.some(agent => agent.id === message.source.provider))
11
+ return false;
12
+ const pointer = message.source.replayState?.response;
13
+ const sameSession = !!pointer && pointer.version === 2 && pointer.agent === message.source.provider && pointer.dshSessionId === options.sessionId && [pointer.identity, pointer.cwd, pointer.sessionId].every(value => typeof value === 'string' && value.length > 0);
14
+ return sameSession && (pointer.presentation?.employeeExecutionNotice === 'local-v1' || message.content.some(block => block.type === 'text' && [CLAUDE_EMPLOYEE_SCOPE_NOTICE, CODEX_EMPLOYEE_SCOPE_NOTICE].some(notice => block.text.startsWith(notice + '\n\n'))));
15
+ });
16
+ }
@@ -6,6 +6,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
6
6
  }
7
7
  return path;
8
8
  };
9
+ import { CODEX_EMPLOYEE_SCOPE_NOTICE } from "./employee-presentation.js";
9
10
  import { CloseDiagnostics, closeCode, closeErrorCodes } from "./close-diagnostics.js";
10
11
  import { createHash } from 'node:crypto';
11
12
  import { admitEmployeeLocation } from "./employee-location.js";
@@ -245,7 +246,7 @@ export function createEmployeeProvider(get, options, agent) {
245
246
  }
246
247
  const disposers = [];
247
248
  const execution = new EmployeeExecution({ closeDiagnostics, bindingId: admitted.bindingId, acpMaxMessageBytes: request.acpMaxMessageBytes, validate, transport: signal => createEmployeeSshTransport(ssh, signal) });
248
- const runtime = { ...host, closeDiagnostics, cwd: admitted.cwd, details, connection: { ...connection }, binding: { bindingId: admitted.bindingId, workspaceId: admitted.workspaceId, revision: state.revision }, saveCheckpoint: undefined, recoveredSession: undefined, employeeExecutionMeta: { version: 1, bindingId: admitted.bindingId, workspaceId: admitted.workspaceId, cwd: admitted.cwd, acpMaxMessageBytes: request.acpMaxMessageBytes }, employeePresentation: '文件与命令在本机选定目录运行,Git 使用本机仓库;模型、登录和全局助手要求仍在远程主机。本机自定义的附加工具、自动动作、插件及断开后的会话恢复暂不支持。子任务被系统回收后能否继续,尚未验证。' };
249
+ const runtime = { ...host, closeDiagnostics, cwd: admitted.cwd, details, connection: { ...connection }, binding: { bindingId: admitted.bindingId, workspaceId: admitted.workspaceId, revision: state.revision }, saveCheckpoint: undefined, recoveredSession: undefined, employeeExecutionMeta: { version: 1, bindingId: admitted.bindingId, workspaceId: admitted.workspaceId, cwd: admitted.cwd, acpMaxMessageBytes: request.acpMaxMessageBytes }, employeePresentation: CODEX_EMPLOYEE_SCOPE_NOTICE };
249
250
  entry = { scope, runtime, current: input, details, execution, network, ended: false, dispose: (reason, cause = { layer: 'runtime', initiator: 'provider.dispose', reason: reason?.code ?? 'EMPLOYEE_EXEC_CLOSED', action: 'dispose' }) => {
250
251
  closeDiagnostics.record({ ...cause, changes: cause.changes ?? [{ field: 'provider.ended', before: entry.ended, after: true }] });
251
252
  if (entry.closing)
package/dist/index.d.ts CHANGED
@@ -76,6 +76,8 @@ export declare class CodexRemoteAdapter extends LlmAdapter {
76
76
  private detached;
77
77
  private cleanups;
78
78
  private preparationFailures;
79
+ private modelPresentation;
80
+ private employeeNoticeSessions;
79
81
  constructor(config: PluginConfig | (() => PluginConfig), resolveRuntime: RuntimeProvider, approve?: InvocationApproval | undefined, providerName?: () => string, agent?: AgentDefinition, readImage?: ImageReader | undefined, reportLoginFailure?: LoginFailureReporter | undefined, directory?: ModelDirectory | undefined, preflightDiagnostics?: PreflightDiagnostics | undefined);
80
82
  providerInfo(provider: string): {
81
83
  id: string;
@@ -85,8 +87,12 @@ export declare class CodexRemoteAdapter extends LlmAdapter {
85
87
  listModels(provider: string): Promise<(import("@deepseek-ai/dsh-llm").LlmResolvedModelInfo & {
86
88
  catalog: HostModel;
87
89
  })[]>;
88
- resolveModel(provider: string, id: string, signal?: AbortSignal): Promise<import("@deepseek-ai/dsh-llm").LlmResolvedModelInfo & {
89
- catalog: HostModel;
90
+ resolveModel(provider: string, id: string, signal?: AbortSignal): Promise<import("@deepseek-ai/dsh-llm").LlmResolvedModelInfo>;
91
+ prepareCall(provider: string, id: string, signal?: AbortSignal): Promise<{
92
+ model: import("@deepseek-ai/dsh-llm").LlmResolvedModelInfo & {
93
+ catalog: HostModel;
94
+ };
95
+ stream: (options: GenerateOptions) => AsyncGenerator<StreamChunk, any, any>;
90
96
  }>;
91
97
  observeModelInvalidation(fields: readonly string[], changes?: readonly CloseChange[]): void;
92
98
  invalidateModelState(): Promise<void>;
@@ -118,8 +124,12 @@ export declare class RemoteAgentsAdapter extends LlmAdapter {
118
124
  listModels(provider: string): Promise<(import("@deepseek-ai/dsh-llm").LlmResolvedModelInfo & {
119
125
  catalog: HostModel;
120
126
  })[]>;
121
- resolveModel(provider: string, model: string, signal?: AbortSignal): Promise<import("@deepseek-ai/dsh-llm").LlmResolvedModelInfo & {
122
- catalog: HostModel;
127
+ resolveModel(provider: string, model: string, signal?: AbortSignal): Promise<import("@deepseek-ai/dsh-llm").LlmResolvedModelInfo>;
128
+ prepareCall(provider: string, model: string, signal?: AbortSignal): Promise<{
129
+ model: import("@deepseek-ai/dsh-llm").LlmResolvedModelInfo & {
130
+ catalog: HostModel;
131
+ };
132
+ stream: (options: GenerateOptions) => AsyncGenerator<StreamChunk, any, any>;
123
133
  }>;
124
134
  observeModelInvalidation(fields: readonly string[], changes?: readonly CloseChange[]): void;
125
135
  invalidateModelState(): Promise<void>;
package/dist/index.js CHANGED
@@ -25,6 +25,8 @@ import { approvalLabel } from "./approval-label.js";
25
25
  import { StageTiming } from "./stage-timing.js";
26
26
  import { verifyRecoveryCheckpoint, currentInputIds } from "./recovery-checkpoint.js";
27
27
  import { ModelCatalog, modelInfo } from "./model-catalog.js";
28
+ import { ModelPresentation, unavailableModelInfo } from "./model-presentation.js";
29
+ import { EMPLOYEE_EXECUTION_NOTICE, employeeNoticePreviouslyShown } from "./employee-presentation.js";
28
30
  import { ModelConfigurationError } from "./model-configuration.js";
29
31
  import { officialEmployeeWorkspace } from "./employee-workspace.js";
30
32
  import { nativeWorkspaceSourceConfirmation } from "./workspace-source-confirmation.js";
@@ -113,6 +115,8 @@ export class CodexRemoteAdapter extends LlmAdapter {
113
115
  detached = new Set();
114
116
  cleanups = new WeakMap();
115
117
  preparationFailures = new Map();
118
+ modelPresentation = new ModelPresentation();
119
+ employeeNoticeSessions = new Set();
116
120
  constructor(config, resolveRuntime, approve, providerName = () => PROVIDER_NAMES.en, agent = CODEX_AGENT, readImage, reportLoginFailure, directory, preflightDiagnostics) {
117
121
  super();
118
122
  this.resolveRuntime = resolveRuntime;
@@ -129,18 +133,21 @@ export class CodexRemoteAdapter extends LlmAdapter {
129
133
  catalog(signal) { if (!this.directory)
130
134
  throw new LlmError('无法获取模型列表:缺少可信主机目录能力', 'MODEL_CATALOG_UNAVAILABLE'); return this.directory.read(this.agent, signal); }
131
135
  async listModels(provider) { const catalog = await this.catalog(); try {
136
+ this.modelPresentation.remember(catalog);
132
137
  return catalog.models.map(model => modelInfo(provider, model));
133
138
  }
134
139
  finally {
135
140
  catalog.release();
136
141
  } }
137
142
  async resolveModel(provider, id, signal) { try {
143
+ signal?.throwIfAborted();
144
+ const displayed = this.modelPresentation.resolve(provider, id);
145
+ if (displayed)
146
+ return displayed;
138
147
  const catalog = await this.catalog(signal);
139
148
  try {
140
149
  const model = catalog.models.find(model => model.id === id);
141
- if (!model)
142
- throw new LlmError(id === 'remote' ? '无法获取模型列表:这个会话保存的是旧版模型选择,请打开本会话的模型菜单,重新选择模型后再发送。' : '所选模型已不在当前主机目录中,请打开本会话的模型菜单重新选择模型。', 'MODEL_CATALOG_UNAVAILABLE');
143
- return modelInfo(provider, model);
150
+ return model ? modelInfo(provider, model) : unavailableModelInfo(provider, id);
144
151
  }
145
152
  finally {
146
153
  catalog.release();
@@ -150,6 +157,25 @@ export class CodexRemoteAdapter extends LlmAdapter {
150
157
  markModelPreparationFailure(provider, error, signal);
151
158
  throw error;
152
159
  } }
160
+ async prepareCall(provider, id, signal) {
161
+ try {
162
+ const catalog = await this.catalog(signal);
163
+ try {
164
+ catalog.assertCurrent();
165
+ const selected = catalog.models.find(model => model.id === id);
166
+ if (!selected)
167
+ throw new LlmError('所选模型已不在当前主机目录中,请在模型菜单重新选择后再发送;本次输入尚未发送。', 'MODEL_CATALOG_UNAVAILABLE');
168
+ return { model: modelInfo(provider, selected), stream: (options) => this.stream(options) };
169
+ }
170
+ finally {
171
+ catalog.release();
172
+ }
173
+ }
174
+ catch (error) {
175
+ markModelPreparationFailure(provider, error, signal);
176
+ throw error;
177
+ }
178
+ }
153
179
  observeModelInvalidation(fields, changes = []) { for (const live of this.sessions.values())
154
180
  live.runtime.closeDiagnostics?.record({ layer: 'adapter', initiator: 'catalog.signature-change', reason: 'MODEL_CATALOG_SIGNATURE_CHANGED', action: 'invalidate', changedFields: fields, changes }); }
155
181
  async invalidateModelState() { for (const [id, turn] of this.turns) {
@@ -214,6 +240,8 @@ export class CodexRemoteAdapter extends LlmAdapter {
214
240
  for (const live of this.sessions.values())
215
241
  live.runtime.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.dispose', reason: 'PLUGIN_DISPOSED', action: 'dispose', changes: [{ field: 'adapter.shutting-down', before: this.shuttingDown, after: true }] });
216
242
  this.shuttingDown = true;
243
+ this.modelPresentation.clear();
244
+ this.employeeNoticeSessions.clear();
217
245
  const results = await Promise.allSettled([...new Set([...this.sessions.keys(), ...this.turns.keys()])].map(id => this.detachSession(id)));
218
246
  this.lifetime.abort();
219
247
  const failure = results.find(result => result.status === 'rejected');
@@ -275,6 +303,7 @@ export class CodexRemoteAdapter extends LlmAdapter {
275
303
  let catalog;
276
304
  let selected;
277
305
  let modelConfiguration;
306
+ let employeeNoticeShown = false;
278
307
  const information = [];
279
308
  const recordInformation = () => { if (runtime?.details)
280
309
  for (const entry of information.splice(0))
@@ -294,6 +323,9 @@ export class CodexRemoteAdapter extends LlmAdapter {
294
323
  if (!mode || (mode === 'session' && (!Number.isSafeInteger(config.acpMaxMessageBytes) || config.acpMaxMessageBytes < 1)))
295
324
  throw new NeedError('SETTINGS_REQUIRED', '远程模型连接设置无效,请联系管理员检查', '有效的mode/acpMaxMessageBytes');
296
325
  const previous = readPointer(options, this.agent);
326
+ employeeNoticeShown = !!key && this.employeeNoticeSessions.has(key) || employeeNoticePreviouslyShown(options);
327
+ if (employeeNoticeShown && key)
328
+ this.employeeNoticeSessions.add(key);
297
329
  const last = options.messages.findLast(message => message.role === 'assistant' && !isWorkspaceNoticeMessage(message));
298
330
  const missingPointer = !!last && !previous;
299
331
  let requireFreshSession = missingPointer || !!key && this.preparationFailures.has(key);
@@ -423,8 +455,19 @@ export class CodexRemoteAdapter extends LlmAdapter {
423
455
  activeMapper = mapper;
424
456
  let id;
425
457
  let stop;
426
- if (runtime.employeePresentation) {
427
- yield* mapper.text(runtime.employeePresentation + '\n\n');
458
+ if (runtime.employeePresentation)
459
+ details.record({ kind: 'session', severity: 'warning', code: 'EMPLOYEE_EXECUTION_SCOPE', summary: runtime.employeePresentation, payload: { source: 'local' } });
460
+ if (runtime.execution)
461
+ details.record({ kind: 'session', severity: 'warning', code: 'CLAUDE_EMPLOYEE_SCOPE', summary: runtime.execution.notice, payload: runtime.execution.scope });
462
+ if ((runtime.employeePresentation || runtime.execution) && !employeeNoticeShown) {
463
+ for (const chunk of mapper.text(EMPLOYEE_EXECUTION_NOTICE + '\n\n')) {
464
+ if (chunk.type === 'text-delta') {
465
+ employeeNoticeShown = true;
466
+ if (key)
467
+ this.employeeNoticeSessions.add(key);
468
+ }
469
+ yield chunk;
470
+ }
428
471
  yield* mapper.flush();
429
472
  }
430
473
  if (runtime.workspaceNotice) {
@@ -435,11 +478,6 @@ export class CodexRemoteAdapter extends LlmAdapter {
435
478
  yield* mapper.text(runtime.restartNotice + '\n\n');
436
479
  yield* mapper.flush();
437
480
  }
438
- if (runtime.execution) {
439
- details.record({ kind: 'session', severity: 'warning', code: 'CLAUDE_EMPLOYEE_SCOPE', summary: '本机工具路由及未覆盖操作范围', payload: runtime.execution.scope });
440
- yield* mapper.text(runtime.execution.notice + '\n\n');
441
- yield* mapper.flush();
442
- }
443
481
  if (mode === 'session') {
444
482
  if (live && live.runtime !== runtime)
445
483
  throw new NeedError('SESSION_TRANSPORT_MISMATCH', '原会话的主机通道已更换,不能自动切换进程', '原主机会话通道');
@@ -599,7 +637,6 @@ export class CodexRemoteAdapter extends LlmAdapter {
599
637
  details.record({ kind: 'session', severity: 'warning', code: 'MODEL_CONFIGURATION_CHANGED_DURING_TURN', summary: '本轮回复已经完成,期间远端配置发生变化;保留真实回复,具体执行型号未单独回报。', payload: { reason: 'POST_PROMPT_CONFIGURATION_CHANGED', configured: modelConfiguration.configured, actual: actual ?? null } });
600
638
  await details.flush();
601
639
  }
602
- yield* mapper.text(`\n\n本轮所选模型:${modelConfiguration.selected.name}(${modelConfiguration.selected.value});推理等级:${modelConfiguration.selected.effort ?? '沿用远端配置'}。发送前远端配置回执:${modelConfiguration.configured.model};${modelResolutionLabel(modelConfiguration)};推理等级:${modelConfiguration.configured.effort ?? '远端未提供'}(${changed ? '本轮期间配置有变化,实际执行型号未单独回报' : '发送前配置已确认,推理效果未验证'})。`);
603
640
  }
604
641
  yield* mapper.flush();
605
642
  let recoveryCheckpointId;
@@ -609,7 +646,8 @@ export class CodexRemoteAdapter extends LlmAdapter {
609
646
  recoveryCheckpointId = id;
610
647
  }
611
648
  const previousRemoteSessions = [...(previous?.previousRemoteSessions ?? []), ...(contextChanged ? [{ sessionId: previous.sessionId, contextSha256: previous.contextSha256 }] : [])];
612
- const pointer = { ...(previousRemoteSessions.length ? { previousRemoteSessions } : {}), ...(modelConfiguration ? { modelConfiguration } : {}), ...(recoveryCheckpointId ? { recoveryCheckpointId } : {}), ...(runtime.workspaceNotice ? { presentation: { workspaceNotice: runtime.workspaceNotice } } : {}), version: 2, agent: this.agent.id, contextSha256, ...(mode === 'session' ? { usageSnapshot: mapper.usageSnapshot } : {}), ...(options.sessionId ? { dshSessionId: options.sessionId } : {}), mode, identity: runtime.identity, cwd: runtime.cwd, sessionId: id, ...(runtime.binding ? { bindingId: runtime.binding.bindingId, workspaceId: runtime.binding.workspaceId } : {}) };
649
+ const presentation = { ...(runtime.workspaceNotice ? { workspaceNotice: runtime.workspaceNotice } : {}), ...(employeeNoticeShown ? { employeeExecutionNotice: 'local-v1' } : {}) };
650
+ const pointer = { ...(previousRemoteSessions.length ? { previousRemoteSessions } : {}), ...(modelConfiguration ? { modelConfiguration } : {}), ...(recoveryCheckpointId ? { recoveryCheckpointId } : {}), ...(Object.keys(presentation).length ? { presentation } : {}), version: 2, agent: this.agent.id, contextSha256, ...(mode === 'session' ? { usageSnapshot: mapper.usageSnapshot } : {}), ...(options.sessionId ? { dshSessionId: options.sessionId } : {}), mode, identity: runtime.identity, cwd: runtime.cwd, sessionId: id, ...(runtime.binding ? { bindingId: runtime.binding.bindingId, workspaceId: runtime.binding.workspaceId } : {}) };
613
651
  succeeded = true;
614
652
  yield { type: 'finish', reason: { kind: stop === 'max_tokens' || stop === 'max_turn_requests' ? 'max-tokens' : 'stop' }, replayState: { response: pointer } };
615
653
  }
@@ -697,6 +735,7 @@ export class RemoteAgentsAdapter extends LlmAdapter {
697
735
  providerInfo(provider) { return this.adapter(provider).providerInfo(provider); }
698
736
  listModels(provider) { return this.adapter(provider).listModels(provider); }
699
737
  resolveModel(provider, model, signal) { return this.adapter(provider).resolveModel(provider, model, signal); }
738
+ prepareCall(provider, model, signal) { return this.adapter(provider).prepareCall(provider, model, signal); }
700
739
  observeModelInvalidation(fields, changes = []) { for (const adapter of this.adapters.values())
701
740
  adapter.observeModelInvalidation(fields, changes); }
702
741
  async invalidateModelState() { await Promise.all([...this.adapters.values()].map(adapter => adapter.invalidateModelState())); }
@@ -0,0 +1,13 @@
1
+ import type { LlmResolvedModelInfo } from '@deepseek-ai/dsh-llm';
2
+ import { type CatalogLease, type HostModel } from './model-catalog.ts';
3
+ /** One displayed directory, never an execution/admission cache. No clock or TTL. */
4
+ export declare class ModelPresentation {
5
+ private snapshot?;
6
+ remember(catalog: CatalogLease): void;
7
+ resolve(provider: string, id: string): (LlmResolvedModelInfo & {
8
+ catalog: HostModel;
9
+ }) | undefined;
10
+ clear(): void;
11
+ }
12
+ /** Exact saved identity for display only: no invented catalog item or capabilities. */
13
+ export declare function unavailableModelInfo(provider: string, id: string): LlmResolvedModelInfo;
@@ -0,0 +1,18 @@
1
+ import { modelInfo } from "./model-catalog.js";
2
+ /** One displayed directory, never an execution/admission cache. No clock or TTL. */
3
+ export class ModelPresentation {
4
+ snapshot;
5
+ remember(catalog) { catalog.assertCurrent(); this.snapshot = { models: structuredClone(catalog.models), assertCurrent: catalog.assertCurrent }; }
6
+ resolve(provider, id) {
7
+ const snapshot = this.snapshot, model = snapshot?.models.find(row => row.id === id);
8
+ if (!model)
9
+ return;
10
+ snapshot.assertCurrent();
11
+ return modelInfo(provider, structuredClone(model));
12
+ }
13
+ clear() { this.snapshot = undefined; }
14
+ }
15
+ /** Exact saved identity for display only: no invented catalog item or capabilities. */
16
+ export function unavailableModelInfo(provider, id) {
17
+ return { provider, id, name: id === 'remote' ? '旧版选择已不可用,请重新选择模型' : '所选模型已不可用,请重新选择模型', description: '当前主机目录中没有这个选择;请在模型菜单重新选择后再发送。' };
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-workbench/remote-agent",
3
- "version": "0.8.14",
3
+ "version": "0.8.15",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",