@ai-workbench/remote-agent 0.8.10 → 0.8.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -6
- package/dist/acp.js +1 -1
- package/dist/catalog-diagnostics.d.ts +16 -0
- package/dist/catalog-diagnostics.js +108 -0
- package/dist/catalog-ssh.d.ts +51 -0
- package/dist/catalog-ssh.js +165 -0
- package/dist/client.js +41 -1
- package/dist/employee-mcp.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +133 -27
- package/dist/model-catalog.d.ts +24 -5
- package/dist/model-catalog.js +174 -132
- package/dist/model-defaults.d.ts +29 -0
- package/dist/model-defaults.js +62 -0
- package/dist/model-input-retention.d.ts +22 -0
- package/dist/model-input-retention.js +137 -0
- package/dist/preflight-diagnostic.d.ts +22 -0
- package/dist/preflight-diagnostic.js +112 -0
- package/dist/preflight-payload.d.ts +11 -0
- package/dist/preflight-payload.js +11 -0
- package/dist/presentation.js +6 -0
- package/dist/provider.d.ts +5 -1
- package/dist/provider.js +48 -21
- package/dist/remote-details.d.ts +18 -0
- package/dist/remote-details.js +140 -125
- package/dist/workspace-source-confirmation.d.ts +4 -0
- package/dist/workspace-source-confirmation.js +12 -0
- package/dist/workspace-source.d.ts +25 -0
- package/dist/workspace-source.js +136 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
**`@ai-workbench/remote-agent@0.8.
|
|
1
|
+
**`@ai-workbench/remote-agent@0.8.12` 发布形态包;由编排发布后通过官方插件 → 添加插件安装。修复 Claude 模型目录及旧默认模型,供 leo 真机复验。Codex 线因主机适配器 aw.2 实装失败(UNSAFE_MODE_MISMATCH),待 S 修复后另验,当前未验证可用。由编排统一发布,X 未执行 publish。**
|
|
2
2
|
|
|
3
3
|
# 远程ACP插件
|
|
4
4
|
|
|
5
5
|
一个插件提供 **Claude(远程主机)** 和 **Codex(远程主机)**,通过员工主机上的官方CLI运行。Codex保留单次模式;Claude固定会话模式。只用官方模型列表、插件管理和配置入口,不提供自制面板、问答或登录按钮。登录和原生工作区绑定唯一归连接插件L。
|
|
6
6
|
|
|
7
|
-
当前为0.8.
|
|
7
|
+
当前为0.8.11,包名采用`@ai-workbench/remote-agent`以涵盖两个品牌;本轮按编排放行准备发布形态包,供 leo 真机自验;X 不执行实际发布,由编排统一发布。开发fixture逐字节保留L0.7.18固定包,公开类型与SSH导出从该包消费。运行时仍要求L的`getContractInfo()`同时返回API0.7.0、implemented、native-local-v1;旧L包、draft.3或只有版本号的不完整实现会明确报缺项。依赖范围为`~0.7.18`,L由官方插件管理单独安装,不打进X。组件验证不代表真实开通、模型或leo E通过;HanaMesh另验。
|
|
8
|
+
|
|
9
|
+
L0.7.18消费增量待X统一0.8.12:来源确认、输入接纳、登录、本机接纳、输入准备、模型目录及运行时解析的早期失败,用正式`preflightDiagnostics:1` / `preflight-check`进入L「连接前技术详情(含登录与工作区确认)」。阶段、真实来源/状态和错误代码可展开;未知来源为null,旧login-check的完整安全原因保留。记录不选择来源、不接纳输入、不自动重发;无原生会话的菜单查询仍在模型设置报告。当前工程和设置组件验证不等于leo GUI验收。
|
|
8
10
|
|
|
9
11
|
## 员工路径
|
|
10
12
|
|
|
11
|
-
从官方插件管理安装,照常使用默认工作区/会话/模型入口。L0.7.17沿唯一“远程主机连接”设置节记录文件来源;主机来源沿用L既有解析;本地来源接入N0.4.4/S0.4.11
|
|
13
|
+
从官方插件管理安装,照常使用默认工作区/会话/模型入口。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保留,不宣称端到端恰好一次。
|
|
12
14
|
|
|
13
15
|
未登录时两个模型均回复:**需要登录,请到设置 → 远程主机连接 点登录**。无L或缺正式能力时直接说明缺项,不让员工填写主机、用户、路径或密钥。
|
|
14
16
|
|
|
@@ -60,7 +62,7 @@ D19(0.8.1):相邻正文增量合成连续文本块,工作区首次说明
|
|
|
60
62
|
技术详情由 L 0.7.11 的唯一“远程主机连接”设置节展示,正文只放模型文本。命令目录保留完整描述;未知事件和工具输入输出保留字段、JSON类型及处理结果,未获准原文明确脱敏,数字用量可核对。没有 `remoteConversationDetails:1` 时明确提示“技术详情暂不可用”,本轮不发送模型请求。X 在官方当前 profile 的 `ai-workbench-remote-agent/diagnostics/` 保存私有诊断日志(目录700、文件600、落盘同步);写入/归属/顺序/容量失败中止本轮,不自动重发、换代重投、过期或淘汰。结果未知时只读核对原事件完整回执,未找到仍保持未知。X本轮无新增设置/阈值;L的单事件容量和分页设置在L设置节可见。
|
|
61
63
|
|
|
62
64
|
|
|
63
|
-
0.8.2 登录检查原交付需要 L `~0.7.11`(本统一版peer为`~0.7.
|
|
65
|
+
0.8.2 登录检查原交付需要 L `~0.7.11`(本统一版peer为`~0.7.18`),失败来源及固定分支保留到 L“登录前技术详情”(`loginDiagnostics:1`),员工正文保持原文。按 L 明确的本品牌 queued/checking/authorizing 状态订阅等待;不以全局checking猜测在途,不新增刷新、轮询、超时或重试。每轮ensureReady仍是合同规定的显式检查,L的新可信结果可以替代旧检查,较早返回不得覆盖它。
|
|
64
66
|
|
|
65
67
|
L安全规范的officialReason原串完整保留;旧/不支持格式明确记OFFICIAL_REASON_UNSUPPORTED与字节数/SHA,秘密原文不保存。L0.7.11的LOGIN_INSTANCE_MISMATCH记录通过observedLoginInstanceId与原始login.instanceId保存实际观测ID,事件及connection仍绑定当前可信ID/代次;L技术详情和X私有日志均保留真实双ID,不归一、不清空快照。复合未知官方原因以固定summary完整保存OFFICIAL_REASON_UNSUPPORTED及bytes/SHA,不交原文。X私有原快照先同步落盘,再追加L;ACK未知只读核实原事件,跨代不重写/重发。此版本的工程验证不替代leo员工路径复验,真实故障根因尚未证实。
|
|
66
68
|
|
|
@@ -72,7 +74,7 @@ D21(0.8.5):两个品牌的官方模型菜单从 L 的 `modelCatalogTarget:
|
|
|
72
74
|
|
|
73
75
|
每轮先核对目录代次和所选模型,再经 ACP 逐项配置模型及推理等级,收到完整配置确认后才发送输入。Claude 别名必须有当前会话的具体型号证据;没有就明确拒绝。真实回复追加所选值和远端确认值,并在恢复标识与技术详情保留,确认配置不等于验证推理效果。连接/身份/目录变化清除旧确认;远端清理待定或结果未知时阻止派发,不自动清理重试或另开替代会话。Codex 单次模式当前不能确认此选择,会在发送前明确拒绝。新增设置 0;本轮工程证据、真实只读链路与产品未验项见 `docs/evidence/2026-10-22-X2-D21/`。
|
|
74
76
|
|
|
75
|
-
0.8.5统一包原依赖L `~0.7.13`;当前0.8.
|
|
77
|
+
0.8.5统一包原依赖L `~0.7.13`;当前0.8.12对齐L `~0.7.18`。D8模型目录双事件长期监听保留;同epoch的recovery提示/失败消息变化不触发执行状态失效,真实实例/epoch/phase/可信目标/登录变化仍失效。完整D9两品牌正文/状态/就绪门呈现按编排00:50允许的范围记下一版,本版不宣称已消费完成;L的connectionMessages.administratorAfterMs仍只由L管理,X新增设置0。
|
|
76
78
|
|
|
77
79
|
D18(0.8.6):Codex 需要主机自有 adapter `2.2.2-aw.1` 的明确新建会话上下文政策,官方 CLI 仍为未改的0.160.1。系统/开发者要求仅在新建时生效;恢复时与初建摘要相同则正常恢复且省略新建专用字段,不声称恢复接口会更新指令。要求不同或清空时,在同一真实回复说明“会话要求已变化,已新建 Codex 会话继续处理,原会话仍保留。”,并新建远端承接公开对话,保留旧标识和查询记录;旧/非法初建摘要明确拒绝,不能猜测。
|
|
78
80
|
|
|
@@ -86,7 +88,7 @@ Read/Write/Edit/Glob/Grep由员工DSH同UID后端执行;Bash经L严格SSH到S
|
|
|
86
88
|
|
|
87
89
|
每轮同一官方回复明确:子代理和harness内部直接工具调用尚未证明可在员工端执行,可能仍在主机;已识别的原生未改道工具/Agent审批拒绝。PDF等二进制读取及后台命令明确不支持,不静默主机代跑。六类组件/MCP/配置转交证明不等于真实Claude原生alias执行或真实员工链路通过。实际Git依赖员工环境中已有Git,缺失会返回实际命令失败,不在主机替代,也不自动安装。
|
|
88
90
|
|
|
89
|
-
本地目前仅支持macOS(arm64/x64);Grep固定携带微软@vscode/ripgrep-universal1.18.0的两架构原字节并核SHA,无员工PATH搜索或安装下载。主机需S0.4.11,N可选peer~0.4.4(缺失明确报能力缺项),L当前~0.7.
|
|
91
|
+
本地目前仅支持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发布。
|
|
90
92
|
|
|
91
93
|
## 合并保留的 Codex D20 路线
|
|
92
94
|
|
|
@@ -97,3 +99,24 @@ D20(0.8.8开发记录):本地来源的Codex原生文件与命令通过固
|
|
|
97
99
|
当前明确不支持本地单次模式、断开后的冷恢复/load/fork、快捷命令、主机文件报告及附加MCP/hooks/plugins。主机同路径的启用项目配置、会话层项目发现/信任覆盖也会明确拒绝;配置检查不静默清空或改写原配置。普通原生shell/patch/fs使用所选员工环境,未覆盖的辅助入口保留具体原因到员工回复与技术详情。位置、身份或代次改变只撤回对应执行器,在途结果保持UNKNOWN,不自动重放。新增产品设置0,执行通道和桥队列沿已有`acpMaxMessageBytes`控制容量。当前工程进度、首红和开放门见`docs/evidence/2026-10-23-X2-D20-088/STATUS.md`。
|
|
98
100
|
|
|
99
101
|
0.8.10仅增加公开插件参数页,不改变上述七项执行语义。mode/ACP/frame/control/grace的修改不改变运行中当轮;保留会话下一轮配置签名不一致会停止,须新建会话。ACP容量还在下一次独立目录查询读取新值。sandbox与approval仅单次通道适用,当前单次仍明确拒绝执行。GUI与真实链路门仍NOT_RUN;本次由编排在 leo 验证 Claude 线、设置界面和 D24 重开,Codex 线另验。完整逐项盘点见 docs/evidence/2026-10-23-X-settings-0810/INVENTORY.md。
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## 0.8.11 模型目录与选择
|
|
105
|
+
|
|
106
|
+
不提供推理档位的模型省略官方 reasoning 元数据;单项资料不合法时只剔除该项,在官方“设置 → 模型”页按模型名称显示中文原因和诊断代码。整组查询失败在菜单显示中文,可到同一设置页核对技术原因。没有会话的目录查询不写入其他会话的技术详情。
|
|
107
|
+
|
|
108
|
+
只修正属于本插件的旧版或失效全局默认:Claude 只取目录 value 为 default 的唯一别名,resolvedModel 原值包括 null 都保留;Codex 只取 source.isDefault 为 true 的唯一项。有效的员工明确选择保持不变。没有唯一默认时不选首项,发送前提示“请先选择模型”。官方默认设置只有字符串 model、没有 nullable 清除接口;此时写入空字符串,原生界面可能显示提供方加斜线,而无实际模型选中。通过官方 configEditor.entries/edit 找到唯一默认模型设置,在配置锁内重新核对员工选择后才更新;不使用缺少条件保护的 saveSelection。保存失败或选择已变化时在官方模型设置页明示。全局默认也会影响没有独立选择的旧空白会话,已有持久模型选择的老会话不改。
|
|
109
|
+
|
|
110
|
+
老会话存着 remote 时,请在该会话的模型菜单重新选择当前目录中的型号。插件不会替员工改写会话选择。
|
|
111
|
+
|
|
112
|
+
官方 loop 在 prepareCall 成功后才把原话追加到对话正文。插件不能更改此核心顺序;由实际 X 准备入口标记、同一错误对象及同一信号可核对的零发送准备失败,会在官方 agent 空闲后,把原话(原 ID、附件及顺序)恢复到官方持久待发送队列,核验落盘后在模型设置诊断中说明;员工可在原生输入区待发送队列查看。恢复本身不会唤醒、重试或再发模型请求;更换模型后由员工手动发送,新的外部输入仍可能触发官方队列消费。取消、卸载、未知错误、已进入模型派发、无法证明来自 X 的官方外围元数据/effort 校验错误,以及早于公开 pre-step 的附件准入/assembly 失败不盲目重排。没有承诺把这些失败直接补成正文气泡;这一能力位于官方 loop 的核心追加顺序,插件的最佳可用结果是持久待发送队列。恢复/持久化不能确认时显示明确诊断。
|
|
113
|
+
|
|
114
|
+
工程验证使用 emp-a 实际完整 Claude 13 项及 Codex 7 项只读目录,交官方 rc.2 校验;不代表本版 leo 菜单、输入显示或真实模型执行已经验收。新增设置、上限、阈值为 0;七项参数沿用 0.8.10。
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
0.8.11 同时包含 main f8cddc5 的旧来源一次确认:先确认唯一原生工作区、原始输入与连接代次,再按 L0.7.17 公开 CAS 选择本机来源,接纳后继续原本这一轮。不会自动保留主机作为替代,也不会重发旧任务。确认前后失败的私有耐久记录随本版提供;L 可见的前置诊断入口尚待 L0.7.18 后续交付,本版不宣称已显示该详情。
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
0.8.12目录诊断修复:入口为“设置 → 模型 → 模型目录诊断”,官方既有 settings.models.footer 公开扩展位直接显示;“内置插件 → 远程助手参数”也可只读查看,即使参数命名空间暂未提供也保留入口;参数编辑使用当前插件公开Loader entry id对应的官方ConfigForms。该页只读本机记录,刷新不查询主机、不登录、不发送待发送原话。模型页原错误行仍通过公开提供方目录发布,诊断数据不再调用directory.replace或发布适配器拓扑变化;changed只负责真实连接/目标代次变化,菜单提示指向上述实际入口。
|
|
121
|
+
|
|
122
|
+
每次目录SSH保独立事件ID、品牌、UTC起止、耗时、PID、L实例/epoch/phase、严格L argv/目标/known_hosts/ProxyCommand、退出码/信号、完整脱敏stderr与原字节数/SHA256、stdout字节数/SHA及主机typed错误;不记录凭据或整份模型目录。日志在官方profile的 `ai-workbench-remote-agent/catalog-diagnostics/<eventId>-started.json` 与 `-closed.json`,700目录/600文件、fsync;无自动过期,最新两品牌在界面可展开,旧调用保留不可变历史。容量沿现有`acpMaxMessageBytes`,超限停止并标记stderrComplete=false,UTF-8替换另显式标记,未返回部分模型;保存失败明确显示仅内存,不冒称耐久。旧0811丢失的stderr无法还原。同品牌、instanceId、epoch只合并仍在进行的查询;完成后不缓存,身份/代际/可信目标改变即作废。调用方各持独立lease/取消与错误身份,一个取消不会取消其他等待者;全员取消才撤销该次查询,取消中的旧进程结束前同作用域新调用等待清理。登录检查的显示变化不会取消无需登录的host-user目录。此次没有增加超时、重试、SSH连接复用或产品设置;不等于255、Claude主机错误及N层根因已修复,也未进行leo GUI验收。
|
package/dist/acp.js
CHANGED
|
@@ -369,7 +369,7 @@ export class AcpSession {
|
|
|
369
369
|
signal?.addEventListener('abort', abort, { once: true });
|
|
370
370
|
try {
|
|
371
371
|
const hello = await this.stage('acp-initialize', () => this.connection.agent.request(acp.methods.agent.initialize, {
|
|
372
|
-
protocolVersion: acp.PROTOCOL_VERSION, clientInfo: { name: '@ai-workbench/remote-agent', version: '0.8.
|
|
372
|
+
protocolVersion: acp.PROTOCOL_VERSION, clientInfo: { name: '@ai-workbench/remote-agent', version: '0.8.12' },
|
|
373
373
|
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false, session: { notices: {} }, ...(this.employeeMeta ? { _meta: { awEmployeeExecution: { version: 1 } } } : {}) },
|
|
374
374
|
}, { cancellationSignal: signal }));
|
|
375
375
|
this.hooks.broker?.acknowledge(acp.methods.agent.initialize);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
3
|
+
import type { CatalogSshEvent } from './catalog-ssh.ts';
|
|
4
|
+
export declare function createCatalogDiagnostics(profile: () => string | undefined): {
|
|
5
|
+
record(event: Readonly<CatalogSshEvent>): Promise<void>;
|
|
6
|
+
snapshot(): Promise<{
|
|
7
|
+
storageError: string | null;
|
|
8
|
+
records: CatalogSshEvent[];
|
|
9
|
+
}>;
|
|
10
|
+
};
|
|
11
|
+
export declare class CatalogDiagnosticsRemote extends TypertRemoteService {
|
|
12
|
+
private readonly snapshot;
|
|
13
|
+
private disposed;
|
|
14
|
+
constructor(ctx: Context, snapshot: () => Promise<unknown>);
|
|
15
|
+
read(request: unknown, signal: AbortSignal): Promise<unknown>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
2
|
+
var useValue = arguments.length > 2;
|
|
3
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
4
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
5
|
+
}
|
|
6
|
+
return useValue ? value : void 0;
|
|
7
|
+
};
|
|
8
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
9
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
10
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
11
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
12
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
13
|
+
var _, done = false;
|
|
14
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
15
|
+
var context = {};
|
|
16
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
17
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
18
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
19
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
20
|
+
if (kind === "accessor") {
|
|
21
|
+
if (result === void 0) continue;
|
|
22
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
23
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
24
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
25
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
26
|
+
}
|
|
27
|
+
else if (_ = accept(result)) {
|
|
28
|
+
if (kind === "field") initializers.unshift(_);
|
|
29
|
+
else descriptor[key] = _;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
33
|
+
done = true;
|
|
34
|
+
};
|
|
35
|
+
import { join } from 'node:path';
|
|
36
|
+
import { open, rename, lstat, realpath } from 'node:fs/promises';
|
|
37
|
+
import { randomUUID } from 'node:crypto';
|
|
38
|
+
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
39
|
+
import { privateDirectory, durableWrite, readJournal } from "./remote-details.js";
|
|
40
|
+
export function createCatalogDiagnostics(profile) {
|
|
41
|
+
let tail = Promise.resolve();
|
|
42
|
+
const errors = new Map();
|
|
43
|
+
const latest = new Map();
|
|
44
|
+
const dir = () => { const p = profile(); if (!p)
|
|
45
|
+
throw Error('PROFILE_UNAVAILABLE'); return join(p, 'ai-workbench-remote-agent', 'catalog-diagnostics'); };
|
|
46
|
+
const prepare = async () => { const root = dir(); await privateDirectory(join(root, '..')); await privateDirectory(root); return root; };
|
|
47
|
+
return {
|
|
48
|
+
record(event) {
|
|
49
|
+
const saved = structuredClone(event);
|
|
50
|
+
if (event.stage === 'started' || !latest.has(event.provider) || latest.get(event.provider)?.eventId === event.eventId)
|
|
51
|
+
latest.set(event.provider, saved);
|
|
52
|
+
const work = tail.then(async () => { const root = await prepare(); await durableWrite(join(root, event.eventId + '-' + event.stage + '.json'), saved); if (latest.get(event.provider)?.eventId !== event.eventId)
|
|
53
|
+
return; const temp = join(root, randomUUID() + '.current'); await durableWrite(temp, saved); await rename(temp, join(root, event.provider + '-latest.json')); const d = await open(root, 'r'); try {
|
|
54
|
+
await d.sync();
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await d.close();
|
|
58
|
+
} errors.delete(event.provider); });
|
|
59
|
+
tail = work.catch(() => { errors.set(event.provider, '本机目录诊断未能写入磁盘,本次记录仅在当前客户端内可见;请联系管理员检查本机诊断目录。'); });
|
|
60
|
+
return tail;
|
|
61
|
+
},
|
|
62
|
+
async snapshot() {
|
|
63
|
+
await tail;
|
|
64
|
+
for (const brand of ['claude', 'codex'])
|
|
65
|
+
if (!latest.has(brand)) {
|
|
66
|
+
try {
|
|
67
|
+
const root = dir(), info = await lstat(root);
|
|
68
|
+
if (!info.isDirectory() || info.isSymbolicLink() || await realpath(root) !== root || (info.mode & 0o077) !== 0 || process.getuid && info.uid !== process.getuid())
|
|
69
|
+
throw Error('CATALOG_DIAGNOSTIC_INVALID');
|
|
70
|
+
const value = await readJournal(join(root, brand + '-latest.json'));
|
|
71
|
+
if (value?.version !== 1 || value.provider !== brand || !['started', 'closed'].includes(value.stage) || !/^[-a-f0-9]{36}$/.test(value.eventId))
|
|
72
|
+
throw Error('CATALOG_DIAGNOSTIC_INVALID');
|
|
73
|
+
if (!latest.has(brand))
|
|
74
|
+
latest.set(brand, value);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error.code !== 'ENOENT')
|
|
78
|
+
errors.set(brand, '本机已保存的目录诊断暂不可读;未将它当作当前状态,请联系管理员检查本机诊断目录。');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { storageError: errors.size ? [...new Set(errors.values())].join('\n') : null, records: [...latest.values()].map(v => structuredClone(v)) };
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
let CatalogDiagnosticsRemote = (() => {
|
|
86
|
+
let _classSuper = TypertRemoteService;
|
|
87
|
+
let _instanceExtraInitializers = [];
|
|
88
|
+
let _read_decorators;
|
|
89
|
+
return class CatalogDiagnosticsRemote extends _classSuper {
|
|
90
|
+
static {
|
|
91
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
92
|
+
_read_decorators = [Remote];
|
|
93
|
+
__esDecorate(this, null, _read_decorators, { kind: "method", name: "read", static: false, private: false, access: { has: obj => "read" in obj, get: obj => obj.read }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
94
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
95
|
+
}
|
|
96
|
+
snapshot = __runInitializers(this, _instanceExtraInitializers);
|
|
97
|
+
disposed = false;
|
|
98
|
+
constructor(ctx, snapshot) {
|
|
99
|
+
super(ctx, 'awRemoteModelDiagnostics');
|
|
100
|
+
this.snapshot = snapshot;
|
|
101
|
+
ctx.effect(() => () => { this.disposed = true; });
|
|
102
|
+
}
|
|
103
|
+
async read(request, signal) { signal.throwIfAborted(); if (this.disposed)
|
|
104
|
+
throw Error('DISPOSED'); const value = await this.snapshot(); signal.throwIfAborted(); if (this.disposed)
|
|
105
|
+
throw Error('DISPOSED'); return value; }
|
|
106
|
+
};
|
|
107
|
+
})();
|
|
108
|
+
export { CatalogDiagnosticsRemote };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ModelConfigurationError } from './model-configuration.ts';
|
|
2
|
+
import type { ConnectTarget } from './provider.ts';
|
|
3
|
+
export interface CatalogSshEvent {
|
|
4
|
+
version: 1;
|
|
5
|
+
eventId: string;
|
|
6
|
+
stage: 'started' | 'closed';
|
|
7
|
+
provider: 'claude' | 'codex';
|
|
8
|
+
startedAt: string;
|
|
9
|
+
endedAt: string | null;
|
|
10
|
+
pid: number | null;
|
|
11
|
+
command: string;
|
|
12
|
+
argv: string[];
|
|
13
|
+
target: {
|
|
14
|
+
host: string;
|
|
15
|
+
username: string;
|
|
16
|
+
port: number;
|
|
17
|
+
knownHostsFile: string;
|
|
18
|
+
proxyCommand: string;
|
|
19
|
+
};
|
|
20
|
+
exitCode: number | null;
|
|
21
|
+
signal: string | null;
|
|
22
|
+
stdoutBytes: number;
|
|
23
|
+
stderrBytes: number;
|
|
24
|
+
stderr: string;
|
|
25
|
+
stderrSha256: string;
|
|
26
|
+
stdoutSha256: string;
|
|
27
|
+
hostErrorCode: string | null;
|
|
28
|
+
hostErrorMessage: string | null;
|
|
29
|
+
elapsedMs: number | null;
|
|
30
|
+
stderrUtf8Valid: boolean;
|
|
31
|
+
failureCode: string | null;
|
|
32
|
+
stderrComplete: boolean;
|
|
33
|
+
processErrorCode?: string | null;
|
|
34
|
+
diagnosticWriteError?: string;
|
|
35
|
+
connection?: {
|
|
36
|
+
instanceId: string;
|
|
37
|
+
epoch: number;
|
|
38
|
+
phase: string;
|
|
39
|
+
} | null;
|
|
40
|
+
}
|
|
41
|
+
export declare const diagnosticLocation = "\u8BBE\u7F6E \u2192 \u6A21\u578B";
|
|
42
|
+
/** Preserve ordinary line boundaries; never persist raw structured secrets. */
|
|
43
|
+
export declare function catalogSafeText(value: string): string;
|
|
44
|
+
export declare class CatalogSshError extends ModelConfigurationError {
|
|
45
|
+
readonly event: CatalogSshEvent;
|
|
46
|
+
constructor(reason: string, event: CatalogSshEvent);
|
|
47
|
+
get diagnostic(): string;
|
|
48
|
+
}
|
|
49
|
+
export type CatalogSshObserver = (event: Readonly<CatalogSshEvent>) => void | Promise<void>;
|
|
50
|
+
/** Each query starts a fresh strict L SSH invocation. No connection pool or retry. */
|
|
51
|
+
export declare function runCatalog(target: ConnectTarget, command: string, signal: AbortSignal, maxBytes: number, observe?: CatalogSshObserver): Promise<string>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
11
|
+
import { ModelConfigurationError } from "./model-configuration.js";
|
|
12
|
+
import { sanitizeDisplayLabel, sanitizeObservation } from "./conversation-details.js";
|
|
13
|
+
export const diagnosticLocation = '设置 → 模型';
|
|
14
|
+
/** Strings inside serialized JSON use the same rules as ordinary stderr. */
|
|
15
|
+
function renderDiagnosticJson(wire) {
|
|
16
|
+
try {
|
|
17
|
+
const scrub = (value) => typeof value === 'string' ? catalogSafeText(value) : Array.isArray(value) ? value.map(scrub) : value && typeof value === 'object' ? Object.fromEntries(Object.entries(value).map(([key, item]) => [key, scrub(item)])) : value;
|
|
18
|
+
const clean = sanitizeObservation({ kind: 'session', severity: 'warning', code: 'MODEL_CATALOG_STDERR', summary: '目录命令诊断', payload: wire }).payload;
|
|
19
|
+
return JSON.stringify(scrub(clean), null, 2);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return '[已脱敏:结构诊断未能安全处理,原文未保存]';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Preserve ordinary line boundaries; never persist raw structured secrets. */
|
|
26
|
+
export function catalogSafeText(value) {
|
|
27
|
+
const whole = sanitizeDisplayLabel(value);
|
|
28
|
+
if (whole === '[已脱敏:凭据文本]')
|
|
29
|
+
return whole;
|
|
30
|
+
let wire;
|
|
31
|
+
try {
|
|
32
|
+
wire = JSON.parse(value);
|
|
33
|
+
}
|
|
34
|
+
catch { }
|
|
35
|
+
if (wire && typeof wire === 'object')
|
|
36
|
+
return renderDiagnosticJson(wire);
|
|
37
|
+
if (typeof wire === 'string' && wire !== value)
|
|
38
|
+
return catalogSafeText(wire);
|
|
39
|
+
return value.split('\n').map(line => {
|
|
40
|
+
if (/\b(?:[\w-]*authorization|(?:set[-_])?cookie|password|passwd|[\w-]*secret|[\w-]*token[\w-]*|[\w-]*api[_-]?key|credentials?)(?:(?:\\*["'])|\s)*[:=]/i.test(line.replace(/\\+u([\da-f]{4})/gi, (_all, hex) => String.fromCharCode(parseInt(hex, 16)))))
|
|
41
|
+
return '[已脱敏:敏感赋值]';
|
|
42
|
+
let item;
|
|
43
|
+
try {
|
|
44
|
+
item = JSON.parse(line);
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
if (item && typeof item === 'object')
|
|
48
|
+
return renderDiagnosticJson(item);
|
|
49
|
+
return sanitizeDisplayLabel(line);
|
|
50
|
+
}).join('\n');
|
|
51
|
+
}
|
|
52
|
+
export class CatalogSshError extends ModelConfigurationError {
|
|
53
|
+
event;
|
|
54
|
+
constructor(reason, event) {
|
|
55
|
+
super(reason);
|
|
56
|
+
this.event = event;
|
|
57
|
+
}
|
|
58
|
+
get diagnostic() { return '目录连接技术详情:' + JSON.stringify(this.event); }
|
|
59
|
+
}
|
|
60
|
+
const hash = (value) => createHash('sha256').update(value).digest('hex');
|
|
61
|
+
/** Each query starts a fresh strict L SSH invocation. No connection pool or retry. */
|
|
62
|
+
export async function runCatalog(target, command, signal, maxBytes, observe) {
|
|
63
|
+
const provider = command === '/usr/local/aw-host/bin/aw-model-catalog --provider claude' ? 'claude' : command === '/usr/local/aw-host/bin/aw-model-catalog --provider codex' ? 'codex' : undefined;
|
|
64
|
+
if (!provider)
|
|
65
|
+
throw new ModelConfigurationError('目录查询命令未确认');
|
|
66
|
+
const moduleName = '@ai-workbench/connect';
|
|
67
|
+
let argv;
|
|
68
|
+
try {
|
|
69
|
+
const l = await import(__rewriteRelativeImportExtension(moduleName));
|
|
70
|
+
if (typeof l.sshArgs !== 'function')
|
|
71
|
+
throw Error();
|
|
72
|
+
argv = ['-n', ...l.sshArgs(target), command];
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw new ModelConfigurationError('远程主机连接组件缺少可信 SSH 能力');
|
|
76
|
+
}
|
|
77
|
+
signal.throwIfAborted();
|
|
78
|
+
const safeTarget = { host: catalogSafeText(target.host), username: catalogSafeText(target.username), port: target.port, knownHostsFile: catalogSafeText(target.knownHostsFile), proxyCommand: catalogSafeText(target.proxyCommand) };
|
|
79
|
+
const tick = performance.now();
|
|
80
|
+
const base = { version: 1, eventId: randomUUID(), provider, startedAt: new Date().toISOString(), command, argv: argv.map(catalogSafeText), target: safeTarget };
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
const child = spawn('ssh', argv, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
83
|
+
const output = [], diagnostics = [];
|
|
84
|
+
let total = 0, stdoutBytes = 0, stderrBytes = 0, processErrorCode = null, failure;
|
|
85
|
+
const stdoutHash = createHash('sha256'), stderrHash = createHash('sha256');
|
|
86
|
+
const event = (stage, extra = {}) => ({ ...base, stage, endedAt: null, pid: child.pid ?? null, exitCode: null, signal: null, stdoutBytes, stderrBytes, stderr: '', stderrSha256: hash(Buffer.alloc(0)), stdoutSha256: hash(Buffer.alloc(0)), hostErrorCode: null, hostErrorMessage: null, elapsedMs: null, stderrUtf8Valid: true, failureCode: null, stderrComplete: true, ...extra });
|
|
87
|
+
const first = event('started');
|
|
88
|
+
const started = Promise.resolve().then(() => observe?.(first));
|
|
89
|
+
started.catch(() => { });
|
|
90
|
+
const abort = () => { failure ??= { code: 'MODEL_CATALOG_CANCELLED', reason: '连接已改变或目录查询已取消' }; child.kill('SIGTERM'); };
|
|
91
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
92
|
+
const receive = (buffer, stdout) => {
|
|
93
|
+
if (stdout) {
|
|
94
|
+
stdoutBytes += buffer.length;
|
|
95
|
+
stdoutHash.update(buffer);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
stderrBytes += buffer.length;
|
|
99
|
+
stderrHash.update(buffer);
|
|
100
|
+
}
|
|
101
|
+
total += buffer.length;
|
|
102
|
+
if (failure)
|
|
103
|
+
return;
|
|
104
|
+
if (total > maxBytes) {
|
|
105
|
+
failure = { code: 'MODEL_CATALOG_OUTPUT_LIMIT', reason: '目录响应超过 acpMaxMessageBytes,未截断返回' };
|
|
106
|
+
child.kill('SIGTERM');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
(stdout ? output : diagnostics).push(buffer);
|
|
110
|
+
};
|
|
111
|
+
child.stdout.on('data', (b) => receive(b, true));
|
|
112
|
+
child.stderr.on('data', (b) => receive(b, false));
|
|
113
|
+
child.on('error', (error) => { processErrorCode = typeof error.code === 'string' && /^[A-Z0-9_]+$/.test(error.code) ? error.code : 'SSH_SPAWN_FAILED'; failure ??= { code: processErrorCode, reason: '可信 SSH 进程启动失败' }; });
|
|
114
|
+
child.once('close', (code, closeSignal) => {
|
|
115
|
+
void (async () => {
|
|
116
|
+
signal.removeEventListener('abort', abort);
|
|
117
|
+
let value = '', hostErrorCode = null, hostErrorMessage = null, typed = false, stderrUtf8Valid = true;
|
|
118
|
+
let stderr;
|
|
119
|
+
try {
|
|
120
|
+
stderr = new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(diagnostics));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
stderrUtf8Valid = false;
|
|
124
|
+
stderr = Buffer.concat(diagnostics).toString('utf8');
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
value = new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(output));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
failure ??= { code: 'MODEL_CATALOG_UTF8_INVALID', reason: '主机目录响应不是有效 UTF-8,未替换模型值' };
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const wire = JSON.parse(value);
|
|
134
|
+
typed = wire?.ok === false;
|
|
135
|
+
if (typed && typeof wire.error?.code === 'string' && /^[A-Z][A-Z0-9_]*$/.test(wire.error.code))
|
|
136
|
+
hostErrorCode = wire.error.code;
|
|
137
|
+
if (typed && typeof wire.error?.message === 'string')
|
|
138
|
+
hostErrorMessage = catalogSafeText(wire.error.message);
|
|
139
|
+
}
|
|
140
|
+
catch { }
|
|
141
|
+
if (!failure && code !== 0 && !typed)
|
|
142
|
+
failure = { code: 'SSH_EXIT_NONZERO', reason: '与主机的目录查询连接未完成,请到' + diagnosticLocation + '查看诊断。' };
|
|
143
|
+
if (!failure && code === 0 && stderrBytes && !typed)
|
|
144
|
+
failure = { code: 'MODEL_CATALOG_STDERR', reason: '目录命令返回了非预期诊断输出,请到' + diagnosticLocation + '查看诊断。' };
|
|
145
|
+
const final = event('closed', { endedAt: new Date().toISOString(), exitCode: code ?? null, signal: closeSignal ?? null, stderr: catalogSafeText(stderr), stderrUtf8Valid, elapsedMs: performance.now() - tick, stderrSha256: stderrHash.digest('hex'), stdoutSha256: stdoutHash.digest('hex'), hostErrorCode, hostErrorMessage, failureCode: failure?.code ?? null, processErrorCode, stderrComplete: Buffer.concat(diagnostics).length === stderrBytes });
|
|
146
|
+
try {
|
|
147
|
+
await started;
|
|
148
|
+
await observe?.(final);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
reject(new CatalogSshError((failure?.reason ?? '目录查询未能确认') + ';目录诊断也未能保存,请到' + diagnosticLocation + '查看诊断。', { ...final, diagnosticWriteError: 'CATALOG_DIAGNOSTIC_WRITE_FAILED' }));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (failure) {
|
|
155
|
+
reject(new CatalogSshError(failure.reason, final));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
resolve(value);
|
|
159
|
+
})().catch(reject);
|
|
160
|
+
});
|
|
161
|
+
if (signal.aborted)
|
|
162
|
+
abort();
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
;
|
package/dist/client.js
CHANGED
|
@@ -13,6 +13,45 @@ function mountRuntimeSettings(ctx,React,fields){
|
|
|
13
13
|
['未知结果与旧审批保护','始终开启,不可关闭','断线或取消不等于员工命令已结束;不自动重放未知操作,不代答旧审批,清理未确认时不擅自重开。'],
|
|
14
14
|
['本机执行位置','始终开启,不可关闭','已提供的本机工具通道不悄悄回退主机;文件夹不是权限隔离根,也不新增目录白名单。Claude子代理及内部直接调用尚未完整覆盖,可能仍在主机,每轮另有范围提示。']
|
|
15
15
|
];
|
|
16
|
+
const unavailable={status:'unavailable',mode:'memory',writable:false};
|
|
17
|
+
const unavailableForm={subscribe:()=>()=>{},getSnapshot:()=>unavailable};
|
|
18
|
+
const formIdentities=new WeakMap();let nextFormIdentity=0;
|
|
19
|
+
function RuntimePanel({showParameters=true}){
|
|
20
|
+
const [snapshot,setSnapshot]=React.useState(null),[failure,setFailure]=React.useState(''),[busy,setBusy]=React.useState(false);
|
|
21
|
+
const active=React.useRef(null),serial=React.useRef(0),alive=React.useRef(false);
|
|
22
|
+
const load=async()=>{
|
|
23
|
+
active.current?.abort();const controller=new AbortController(),mine=++serial.current;active.current=controller;
|
|
24
|
+
if(ctx.connection.state.getSnapshot()!=='connected'){setSnapshot(null);setFailure('尚未连上本机服务,暂时无法读取目录诊断。');setBusy(false);return;}
|
|
25
|
+
setBusy(true);setFailure('');
|
|
26
|
+
try{
|
|
27
|
+
const result=await ctx.connection.rpc.call('/api','awRemoteModelDiagnostics/read',{args:{request:null}},controller.signal);
|
|
28
|
+
if(!alive.current||controller.signal.aborted||mine!==serial.current)return;
|
|
29
|
+
const value=result?.value;
|
|
30
|
+
if(result?.ok!==true||!value||!Array.isArray(value.providers)||!Array.isArray(value.records)||!value.providers.every(provider=>provider&&['claude-code-workbench','codex-remote'].includes(provider.provider)&&typeof provider.name==='string'&&Array.isArray(provider.messages)&&provider.messages.every(message=>typeof message==='string'))||!value.records.every(record=>record&&['claude','codex'].includes(record.provider)))throw Error('INVALID_DIAGNOSTICS');
|
|
31
|
+
setSnapshot(value);
|
|
32
|
+
}catch{if(alive.current&&!controller.signal.aborted&&mine===serial.current){setFailure('目录诊断暂时无法读取。请检查本机服务连接,再点“刷新本机诊断”;这不会重新查询主机或发送原话。');}}
|
|
33
|
+
finally{if(alive.current&&mine===serial.current)setBusy(false);}
|
|
34
|
+
};
|
|
35
|
+
React.useEffect(()=>{alive.current=true;void load();const off=ctx.connection.state.subscribe(()=>{void load();});return()=>{alive.current=false;serial.current++;active.current?.abort();off();};},[]);
|
|
36
|
+
const ns=snapshot?.settingsNamespace,form=typeof ns==='string'&&ns?ctx.configForms.get(ns):unavailableForm;
|
|
37
|
+
if(!formIdentities.has(form))formIdentities.set(form,++nextFormIdentity);
|
|
38
|
+
return h('div',null,
|
|
39
|
+
h('section',{'aria-label':'模型目录诊断'},h('h2',null,'模型目录诊断'),
|
|
40
|
+
h('p',null,'这里显示本客户端最近的目录查询记录。读取本页只查看本机记录,不会重试主机查询、登录或发送待发送的原话。每次查询都保留本机诊断历史,不自动过期;记录容量沿用下方会话消息容量,超限会明确停止并标明记录不完整。'),
|
|
41
|
+
h('button',{type:'button',disabled:busy,onClick:()=>{void load();}},busy?'正在读取本机诊断…':'刷新本机诊断'),
|
|
42
|
+
failure&&h('p',{role:'alert'},failure),snapshot?.storageError&&h('p',{role:'alert'},snapshot.storageError),
|
|
43
|
+
...(snapshot?.providers??[]).map(provider=>{
|
|
44
|
+
const record=snapshot.records.find(item=>item.provider===(provider.provider==='claude-code-workbench'?'claude':'codex'));
|
|
45
|
+
return h('article',{key:provider.provider},h('h3',null,provider.name),
|
|
46
|
+
h('p',null,provider.messages.length?'本提供方有目录或模型设置诊断,请展开技术详情。':record?'当前没有新增的模型设置诊断;以下为最近一次查询记录,不代表此刻连接可用。':'尚无本客户端的目录查询记录;旧版本未保存的错误无法还原。'),
|
|
47
|
+
record&&h('p',null,'最近查询:'+record.startedAt+';'+(record.stage==='started'?'尚未确认结束':record.failureCode||record.hostErrorCode?'未完成':'已返回主机响应')+'。'),
|
|
48
|
+
(record||provider.messages.length>0)&&h('details',null,h('summary',null,'技术详情('+provider.name+')'),
|
|
49
|
+
...provider.messages.map((message,index)=>h('pre',{key:index,style:{whiteSpace:'pre-wrap',overflowWrap:'anywhere'}},message)),
|
|
50
|
+
record&&h('pre',{style:{whiteSpace:'pre-wrap',overflowWrap:'anywhere'}},JSON.stringify(record,null,2))));
|
|
51
|
+
})),
|
|
52
|
+
showParameters&&!ns&&h('p',{role:'status'},'尚未确认本插件的官方配置入口,参数暂不可编辑;上方目录诊断仍可读取。'),
|
|
53
|
+
showParameters&&h(Panel,{key:(ns??'unavailable')+':'+formIdentities.get(form),form}));
|
|
54
|
+
}
|
|
16
55
|
function Panel({form}){
|
|
17
56
|
const snapshot=React.useSyncExternalStore(fn=>form.subscribe(fn),()=>form.getSnapshot(),()=>form.getSnapshot());
|
|
18
57
|
const [draft,setDraft]=React.useState({}),[busy,setBusy]=React.useState(false),[notice,setNotice]=React.useState('');
|
|
@@ -66,7 +105,8 @@ function mountRuntimeSettings(ctx,React,fields){
|
|
|
66
105
|
h('p',null,'以下保护默认开启且不能关闭。关闭会使输入、审批、执行位置或记录失去可信归属,并非可以放宽的偏好。'),
|
|
67
106
|
h('dl',null,...invariants.flatMap(([name,state,why])=>[h('dt',{key:name},name+':'+state),h('dd',{key:name+'-why'},why)])));
|
|
68
107
|
}
|
|
69
|
-
ctx.effect(()=>ctx.
|
|
108
|
+
ctx.effect(()=>ctx.slots.inject('settings.models.footer',()=>ctx.slots.register({name:'settings.models.footer',id:'ai-workbench-remote-model-diagnostics',order:90,inject:()=>({showParameters:false})},RuntimePanel)));
|
|
109
|
+
ctx.effect(()=>ctx.slots.inject('settings.plugins.tab',()=>ctx.slots.register({name:'settings.plugins.tab',id:'ai-workbench-remote-parameters',order:90,label:()=> '远程助手参数',inject:()=>({})},RuntimePanel)));
|
|
70
110
|
}
|
|
71
111
|
|
|
72
112
|
return{inject:['locale','connection'],apply(ctx){
|
package/dist/employee-mcp.js
CHANGED
|
@@ -121,7 +121,7 @@ export async function openEmployeeMcp(options) {
|
|
|
121
121
|
socket.destroy();
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
|
-
const mcp = new Server({ name: 'ai-workbench-employee', version: '0.8.
|
|
124
|
+
const mcp = new Server({ name: 'ai-workbench-employee', version: '0.8.12' }, { capabilities: { tools: {} } }), transport = new EmployeeSocketTransport(socket, options.maxBytes);
|
|
125
125
|
clients.add(mcp);
|
|
126
126
|
transports.add(transport);
|
|
127
127
|
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: EMPLOYEE_MCP_TOOLS }));
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { type AgentDefinition } from './agents.ts';
|
|
|
7
7
|
import { type ImageReader } from './input.ts';
|
|
8
8
|
import type { LoginDiagnostic } from './login-diagnostic.ts';
|
|
9
9
|
import { type ModelDirectory, type HostModel } from './model-catalog.ts';
|
|
10
|
+
import { type PreflightDiagnostics } from './preflight-diagnostic.ts';
|
|
10
11
|
export declare const name = "ai-workbench-remote-agent";
|
|
11
12
|
export declare const settingsNamespace = "ai-workbench-codex";
|
|
12
13
|
export declare const inject: string[];
|
|
@@ -65,6 +66,7 @@ export declare class CodexRemoteAdapter extends LlmAdapter {
|
|
|
65
66
|
private readonly readImage?;
|
|
66
67
|
private readonly reportLoginFailure?;
|
|
67
68
|
private readonly directory?;
|
|
69
|
+
private readonly preflightDiagnostics?;
|
|
68
70
|
private config;
|
|
69
71
|
private sessions;
|
|
70
72
|
private turns;
|
|
@@ -72,7 +74,7 @@ export declare class CodexRemoteAdapter extends LlmAdapter {
|
|
|
72
74
|
private shuttingDown;
|
|
73
75
|
private detached;
|
|
74
76
|
private cleanups;
|
|
75
|
-
constructor(config: PluginConfig | (() => PluginConfig), resolveRuntime: RuntimeProvider, approve?: InvocationApproval | undefined, providerName?: () => string, agent?: AgentDefinition, readImage?: ImageReader | undefined, reportLoginFailure?: LoginFailureReporter | undefined, directory?: ModelDirectory | undefined);
|
|
77
|
+
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);
|
|
76
78
|
providerInfo(provider: string): {
|
|
77
79
|
id: string;
|
|
78
80
|
name: string;
|
|
@@ -104,7 +106,7 @@ export declare class CodexRemoteAdapter extends LlmAdapter {
|
|
|
104
106
|
/** One official registration owns both routes, preserving opaque replay ownership. */
|
|
105
107
|
export declare class RemoteAgentsAdapter extends LlmAdapter {
|
|
106
108
|
private adapters;
|
|
107
|
-
constructor(config: () => PluginConfig, makeRuntime: (agent: AgentDefinition) => RuntimeProvider, approve: InvocationApproval, nameFor: (agent: AgentDefinition) => string, readImage?: ImageReader, reportLoginFailure?: LoginFailureReporter, directory?: ModelDirectory);
|
|
109
|
+
constructor(config: () => PluginConfig, makeRuntime: (agent: AgentDefinition) => RuntimeProvider, approve: InvocationApproval, nameFor: (agent: AgentDefinition) => string, readImage?: ImageReader, reportLoginFailure?: LoginFailureReporter, directory?: ModelDirectory, preflightDiagnostics?: PreflightDiagnostics);
|
|
108
110
|
private adapter;
|
|
109
111
|
providerInfo(provider: string): {
|
|
110
112
|
id: string;
|