@ai-workbench/remote-agent 0.8.5 → 0.8.10
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 +33 -8
- package/dist/acp.d.ts +22 -2
- package/dist/acp.js +143 -25
- package/dist/approval-label.d.ts +1 -1
- package/dist/approval-label.js +15 -3
- package/dist/broker-runtime.js +40 -8
- package/dist/broker.js +5 -1
- package/dist/claude-local.d.ts +37 -0
- package/dist/claude-local.js +207 -0
- package/dist/client.js +71 -2
- package/dist/conversation-details.d.ts +0 -1
- package/dist/conversation-details.js +37 -7
- package/dist/employee-endpoint.d.ts +91 -0
- package/dist/employee-endpoint.js +141 -0
- package/dist/employee-exec.d.ts +53 -0
- package/dist/employee-exec.js +423 -0
- package/dist/employee-location.d.ts +66 -0
- package/dist/employee-location.js +51 -0
- package/dist/employee-mcp.d.ts +42 -0
- package/dist/employee-mcp.js +181 -0
- package/dist/employee-native.d.ts +18 -0
- package/dist/employee-native.js +38 -0
- package/dist/employee-provider.d.ts +42 -0
- package/dist/employee-provider.js +326 -0
- package/dist/employee-relay.d.ts +18 -0
- package/dist/employee-relay.js +49 -0
- package/dist/employee-shell.d.ts +26 -0
- package/dist/employee-shell.js +89 -0
- package/dist/employee-ssh.d.ts +22 -0
- package/dist/employee-ssh.js +123 -0
- package/dist/employee-tools.d.ts +117 -0
- package/dist/employee-tools.js +292 -0
- package/dist/employee-workspace.d.ts +26 -0
- package/dist/employee-workspace.js +61 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +148 -28
- package/dist/input.d.ts +11 -0
- package/dist/input.js +61 -23
- package/dist/local-user-turn.d.ts +24 -0
- package/dist/local-user-turn.js +103 -0
- package/dist/presentation.d.ts +1 -1
- package/dist/presentation.js +46 -2
- package/dist/provider.d.ts +36 -2
- package/dist/provider.js +33 -0
- package/dist/readiness.js +1 -1
- package/dist/recovery.d.ts +14 -1
- package/dist/recovery.js +11 -2
- package/dist/remote-details.d.ts +7 -0
- package/dist/remote-details.js +11 -1
- package/helpers/ripgrep/LICENSE-vscode +13 -0
- package/helpers/ripgrep/PROVENANCE.md +9 -0
- package/helpers/ripgrep/darwin-arm64/rg +0 -0
- package/helpers/ripgrep/darwin-x64/rg +0 -0
- package/helpers/ripgrep/manifest.json +56 -0
- package/helpers/ripgrep/notices/COPYING-ripgrep +3 -0
- package/helpers/ripgrep/notices/DEPENDENCIES.json +946 -0
- package/helpers/ripgrep/notices/LICENCE-PCRE2.md +103 -0
- package/helpers/ripgrep/notices/LICENSE-MIT-ripgrep +21 -0
- package/helpers/ripgrep/notices/UNLICENSE-ripgrep +24 -0
- package/helpers/ripgrep/notices/aho-corasick-1.1.3/COPYING +3 -0
- package/helpers/ripgrep/notices/aho-corasick-1.1.3/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/aho-corasick-1.1.3/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/anyhow-1.0.100/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/anyhow-1.0.100/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/arbitrary-1.4.2/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/arbitrary-1.4.2/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/bstr-1.12.0/COPYING +8 -0
- package/helpers/ripgrep/notices/bstr-1.12.0/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/bstr-1.12.0/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/cc-1.2.41/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/cc-1.2.41/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/cfg-if-1.0.4/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/cfg-if-1.0.4/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/crossbeam-channel-0.5.15/LICENSE-THIRD-PARTY +593 -0
- package/helpers/ripgrep/notices/crossbeam-deque-0.8.6/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/crossbeam-deque-0.8.6/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/crossbeam-epoch-0.9.18/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/crossbeam-epoch-0.9.18/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/crossbeam-utils-0.8.21/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/crossbeam-utils-0.8.21/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/derive_arbitrary-1.4.2/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/derive_arbitrary-1.4.2/LICENSE-MIT +27 -0
- package/helpers/ripgrep/notices/encoding_rs-0.8.35/COPYRIGHT +17 -0
- package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-APACHE +202 -0
- package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/encoding_rs-0.8.35/LICENSE-WHATWG +26 -0
- package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/COPYING +8 -0
- package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/encoding_rs_io-0.1.7/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/find-msvc-tools-0.1.4/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/find-msvc-tools-0.1.4/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/getrandom-0.3.4/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/getrandom-0.3.4/LICENSE-MIT +26 -0
- package/helpers/ripgrep/notices/glob-0.3.3/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/glob-0.3.3/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/itoa-1.0.15/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/itoa-1.0.15/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/jobserver-0.1.34/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/jobserver-0.1.34/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/lexopt-0.3.1/LICENSE +19 -0
- package/helpers/ripgrep/notices/libc-0.2.177/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/libc-0.2.177/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/log-0.4.28/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/log-0.4.28/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/memchr-2.7.6/COPYING +3 -0
- package/helpers/ripgrep/notices/memchr-2.7.6/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/memchr-2.7.6/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/memmap2-0.9.8/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/memmap2-0.9.8/LICENSE-MIT +26 -0
- package/helpers/ripgrep/notices/msvc_spectre_libs-0.1.3/LICENSE +21 -0
- package/helpers/ripgrep/notices/pcre2-0.2.11/COPYING +3 -0
- package/helpers/ripgrep/notices/pcre2-0.2.11/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/pcre2-0.2.11/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/pcre2-sys-0.2.10/COPYING +3 -0
- package/helpers/ripgrep/notices/pcre2-sys-0.2.10/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/pcre2-sys-0.2.10/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/pkg-config-0.3.32/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/pkg-config-0.3.32/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/proc-macro2-1.0.101/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/proc-macro2-1.0.101/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/quote-1.0.41/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/quote-1.0.41/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/r-efi-5.3.0/AUTHORS +74 -0
- package/helpers/ripgrep/notices/regex-1.12.2/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/regex-1.12.2/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/regex-automata-0.4.13/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/regex-automata-0.4.13/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/regex-syntax-0.8.8/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/regex-syntax-0.8.8/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/regex-syntax-0.8.8/src/unicode_tables/LICENSE-UNICODE +57 -0
- package/helpers/ripgrep/notices/ryu-1.0.20/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/ryu-1.0.20/LICENSE-BOOST +23 -0
- package/helpers/ripgrep/notices/same-file-1.0.6/COPYING +3 -0
- package/helpers/ripgrep/notices/same-file-1.0.6/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/same-file-1.0.6/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/serde-1.0.228/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/serde-1.0.228/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/serde_core-1.0.228/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/serde_core-1.0.228/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/serde_derive-1.0.228/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/serde_derive-1.0.228/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/serde_json-1.0.145/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/serde_json-1.0.145/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/shlex-1.3.0/LICENSE-APACHE +13 -0
- package/helpers/ripgrep/notices/shlex-1.3.0/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/syn-2.0.106/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/syn-2.0.106/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/termcolor-1.4.1/COPYING +3 -0
- package/helpers/ripgrep/notices/termcolor-1.4.1/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/termcolor-1.4.1/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/textwrap-0.16.2/LICENSE +21 -0
- package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/tikv-jemalloc-sys-0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7/jemalloc/COPYING +27 -0
- package/helpers/ripgrep/notices/tikv-jemallocator-0.6.0/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/tikv-jemallocator-0.6.0/LICENSE-MIT +25 -0
- package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-APACHE +176 -0
- package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/unicode-ident-1.0.19/LICENSE-UNICODE +39 -0
- package/helpers/ripgrep/notices/walkdir-2.5.0/COPYING +3 -0
- package/helpers/ripgrep/notices/walkdir-2.5.0/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/walkdir-2.5.0/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
- package/helpers/ripgrep/notices/wasip2-1.0.1+wasi-0.2.4/LICENSE-MIT +23 -0
- package/helpers/ripgrep/notices/winapi-util-0.1.11/COPYING +3 -0
- package/helpers/ripgrep/notices/winapi-util-0.1.11/LICENSE-MIT +21 -0
- package/helpers/ripgrep/notices/winapi-util-0.1.11/UNLICENSE +24 -0
- package/helpers/ripgrep/notices/windows-link-0.2.1/license-apache-2.0 +201 -0
- package/helpers/ripgrep/notices/windows-link-0.2.1/license-mit +21 -0
- package/helpers/ripgrep/notices/windows-sys-0.61.2/license-apache-2.0 +201 -0
- package/helpers/ripgrep/notices/windows-sys-0.61.2/license-mit +21 -0
- package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-APACHE +201 -0
- package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
- package/helpers/ripgrep/notices/wit-bindgen-0.46.0/LICENSE-MIT +23 -0
- package/locale/en.json +1 -1
- package/locale/zh.json +2 -2
- package/package.json +29 -11
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import Schema from '@deepseek-ai/schemastery';
|
|
|
3
3
|
import { LlmAdapter, LlmError } from '@deepseek-ai/dsh-llm';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
|
-
import { recoveryJournal,
|
|
6
|
+
import { recoveryJournal, runtimeRecoveryKey, supportsCodexContextPolicy } from "./recovery.js";
|
|
7
7
|
import * as unstable from '@agentclientprotocol/sdk/unstable';
|
|
8
8
|
import * as acp from '@agentclientprotocol/sdk';
|
|
9
9
|
import { AcpSession } from "./acp.js";
|
|
@@ -12,7 +12,7 @@ import { StreamMapper } from "./mapping.js";
|
|
|
12
12
|
import { connectProvider } from "./provider.js";
|
|
13
13
|
import { brokerFactory } from "./broker.js";
|
|
14
14
|
import { AGENTS, CODEX_AGENT } from "./agents.js";
|
|
15
|
-
import { prepareInput } from "./input.js";
|
|
15
|
+
import { prepareInput, prepareContinuation } from "./input.js";
|
|
16
16
|
import { createWorkspaceNoticeConsumer, isWorkspaceNoticeMessage } from "./workspace-notice.js";
|
|
17
17
|
import { NeedError } from "./remote.js";
|
|
18
18
|
import { employeeFailure } from "./presentation.js";
|
|
@@ -23,12 +23,18 @@ import { StageTiming } from "./stage-timing.js";
|
|
|
23
23
|
import { verifyRecoveryCheckpoint, currentInputIds } from "./recovery-checkpoint.js";
|
|
24
24
|
import { ModelCatalog, modelInfo } from "./model-catalog.js";
|
|
25
25
|
import { ModelConfigurationError } from "./model-configuration.js";
|
|
26
|
+
import { officialEmployeeWorkspace } from "./employee-workspace.js";
|
|
27
|
+
import { resolveEmployeeNative } from "./employee-native.js";
|
|
28
|
+
import { createLocalUserTurnObserver } from "./local-user-turn.js";
|
|
29
|
+
import { createClaudeLocalProvider } from "./claude-local.js";
|
|
30
|
+
import { readLocalUserTurn } from "./local-user-turn.js";
|
|
31
|
+
import { readEmployeeWorkspace } from "./employee-workspace.js";
|
|
26
32
|
export const name = 'ai-workbench-remote-agent';
|
|
27
33
|
export const settingsNamespace = 'ai-workbench-codex';
|
|
28
34
|
// L can be installed/enabled after X; keeping X mounted lets it explain the prerequisite.
|
|
29
35
|
export const inject = ['llm'];
|
|
30
36
|
/** Owner-approved defaults; administrators may override through the existing settings. */
|
|
31
|
-
export const DEFAULT_CONFIG = Object.freeze({ mode: 'session', acpMaxMessageBytes: 4194304, brokerMaxFrameBytes: 2097152, brokerControlMaxBytes: 1048576, oneShotSandbox: 'workspace-write', oneShotApproval: 'never' });
|
|
37
|
+
export const DEFAULT_CONFIG = Object.freeze({ mode: 'session', acpMaxMessageBytes: 4194304, brokerMaxFrameBytes: 2097152, brokerControlMaxBytes: 1048576, oneShotSandbox: 'workspace-write', oneShotApproval: 'never', employeeShellKillGraceMs: 1000 });
|
|
32
38
|
export const Config = Schema.object({
|
|
33
39
|
mode: Schema.union([Schema.const('session').i18n({ en: 'Session', zh: '会话模式' }), Schema.const('one-shot').i18n({ en: 'One-shot', zh: '单次模式' })]).i18n({ en: 'Run mode; start a new session after switching modes', zh: 'Codex运行模式;Claude固定会话模式;切换后请新建会话' }).default(DEFAULT_CONFIG.mode).volatile(),
|
|
34
40
|
acpMaxMessageBytes: Schema.number().min(1).step(1).i18n({ en: 'Session message limit; default 4 MiB, adjustable by administrators', zh: '会话消息容量;默认4 MiB,管理员可调整' }).default(DEFAULT_CONFIG.acpMaxMessageBytes).volatile(),
|
|
@@ -36,11 +42,13 @@ export const Config = Schema.object({
|
|
|
36
42
|
brokerControlMaxBytes: Schema.number().min(1).step(1).i18n({ en: 'Host session control reply limit; default 1 MiB, adjustable by administrators', zh: '主机会话管理回复容量;默认1 MiB,管理员可调整' }).default(DEFAULT_CONFIG.brokerControlMaxBytes).volatile(),
|
|
37
43
|
oneShotSandbox: Schema.union(['read-only', 'workspace-write', 'danger-full-access']).i18n({ en: 'One-shot sandbox; workspace changes are allowed by default', zh: '单次任务沙箱;默认允许工作区内修改' }).default(DEFAULT_CONFIG.oneShotSandbox).volatile(),
|
|
38
44
|
oneShotApproval: Schema.const('never').i18n({ en: 'Non-interactive approval; permissions follow the launch policy, never automatic approval', zh: '单次模式无交互审批;权限由启动策略决定,不表示自动批准' }).default(DEFAULT_CONFIG.oneShotApproval).volatile(),
|
|
45
|
+
employeeShellKillGraceMs: Schema.number().min(0).max(2147483647).step(1).i18n({ en: 'Local command SSH cleanup grace in milliseconds; cancellation keeps the remote result unknown', zh: '本机命令SSH取消后的清理宽限毫秒;默认1000,取消仍保留员工端结果未知' }).default(DEFAULT_CONFIG.employeeShellKillGraceMs).volatile(),
|
|
39
46
|
});
|
|
40
47
|
/** DSH volatile settings are handles; adapters freeze their values once per invocation. */
|
|
41
48
|
export function readConfig(config) {
|
|
42
49
|
return Object.fromEntries(Object.entries(config).map(([key, value]) => [key, typeof value === 'object' && value !== null && 'get' in value ? value.get() : value]));
|
|
43
50
|
}
|
|
51
|
+
function validContextDigest(value) { return typeof value === 'string' && /^[a-fA-F0-9]{64}$/u.test(value); }
|
|
44
52
|
function readPointer(options, agent) {
|
|
45
53
|
const last = options.messages.findLast(message => message.role === 'assistant' && !isWorkspaceNoticeMessage(message));
|
|
46
54
|
if (!last || last.role !== 'assistant')
|
|
@@ -53,6 +61,8 @@ function readPointer(options, agent) {
|
|
|
53
61
|
throw new NeedError('SESSION_STATE_INVALID', '原对话缺少新版工作区/模型绑定,不能静默迁移,请新建会话', '有效的新一代远端会话恢复记录');
|
|
54
62
|
if (options.sessionId && pointer.dshSessionId !== options.sessionId)
|
|
55
63
|
throw new NeedError('SESSION_CONTEXT_MISMATCH', '此恢复记录属于另一会话,复制或分叉后不能沿用原远端进程,请新建会话', '当前DSH会话自己的远端恢复记录');
|
|
64
|
+
if (agent.loginProvider === 'codex' && (!validContextDigest(pointer.contextSha256) || (pointer.previousRemoteSessions !== undefined && (!Array.isArray(pointer.previousRemoteSessions) || !pointer.previousRemoteSessions.every(ref => ref && typeof ref.sessionId === 'string' && ref.sessionId.length > 0 && validContextDigest(ref.contextSha256))))))
|
|
65
|
+
throw new NeedError('SESSION_CONTEXT_DIGEST_INVALID', '原Codex会话的初建上下文摘要未知或无效,不能猜测是否相同,本轮未发送', '原会话合法的64位十六进制初建上下文摘要');
|
|
56
66
|
return pointer;
|
|
57
67
|
}
|
|
58
68
|
export class CodexRemoteAdapter extends LlmAdapter {
|
|
@@ -101,6 +111,7 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
101
111
|
} }
|
|
102
112
|
async invalidateModelState() { for (const turn of this.turns.values())
|
|
103
113
|
turn.abort(); await Promise.all([...this.sessions.keys()].map(id => this.disposeSession(id))); }
|
|
114
|
+
async invalidateEmployeeState() { await Promise.all([...this.sessions].filter(([, live]) => live.runtime.employeeExecutionMeta || live.runtime.execution).map(([id]) => this.disposeSession(id))); }
|
|
104
115
|
providerRetryPolicy() { return { mode: 'normal', maxRetries: 0, retryableCodes: [], initialDelayMs: 0, maxDelayMs: 0, jitterRatio: 0 }; }
|
|
105
116
|
cleanupRuntime(runtime) {
|
|
106
117
|
const existing = this.cleanups.get(runtime);
|
|
@@ -165,7 +176,7 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
165
176
|
throw new LlmError(error.message, error.code, { cause: error });
|
|
166
177
|
if (error instanceof NeedError) {
|
|
167
178
|
const rpc = /RPC 错误码 (-?\d+)/u.exec(error.requires)?.[1];
|
|
168
|
-
const message = employeeFailure(error.code) + (rpc ? `(技术详情:RPC 错误码 ${rpc})` : '');
|
|
179
|
+
const message = employeeFailure(error.code, this.agent.loginProvider) + (rpc ? `(技术详情:RPC 错误码 ${rpc})` : '');
|
|
169
180
|
let diagnostic = error.technical ? JSON.stringify(error.technical) : '';
|
|
170
181
|
if (error.technical && this.reportLoginFailure) {
|
|
171
182
|
try {
|
|
@@ -200,6 +211,7 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
200
211
|
let live;
|
|
201
212
|
let succeeded = false;
|
|
202
213
|
let remoteAttempted = false;
|
|
214
|
+
let preserveRecoveredRuntime = false;
|
|
203
215
|
let activeMapper;
|
|
204
216
|
let catalog;
|
|
205
217
|
let selected;
|
|
@@ -221,18 +233,34 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
221
233
|
throw new NeedError('SESSION_STATE_UNCONFIRMED', '上轮远端状态未确认,不能自动重发或重开,请新建会话并核对项目', '管理员核对原会话状态');
|
|
222
234
|
if (live && !previous)
|
|
223
235
|
throw new NeedError('SESSION_STATE_MISSING', '上一轮回复尚无持久恢复记录,不能继续当前远端会话', '原会话的持久回复记录');
|
|
224
|
-
const configuration = JSON.stringify([mode, config.acpMaxMessageBytes, config.brokerMaxFrameBytes, config.brokerControlMaxBytes]);
|
|
236
|
+
const configuration = JSON.stringify([mode, config.acpMaxMessageBytes, config.brokerMaxFrameBytes, config.brokerControlMaxBytes, config.employeeShellKillGraceMs]);
|
|
225
237
|
if (live && live.configuration !== configuration)
|
|
226
238
|
throw new NeedError('SESSION_CONTEXT_MISMATCH', '会话运行设置已改变,请新建会话', '原会话运行设置');
|
|
227
239
|
const request = { timing, acpMaxMessageBytes: config.acpMaxMessageBytes, resumeSessionId: previous?.sessionId, recoveryCheckpointId: previous?.recoveryCheckpointId, sessionId: options.sessionId, mode, cwd: previous?.cwd, identity: previous?.identity, bindingId: previous?.bindingId, workspaceId: previous?.workspaceId, agent: this.agent, retainProcess: !!key && mode === 'session', signal, brokerSettings: Object.freeze({ brokerMaxFrameBytes: config.brokerMaxFrameBytes, brokerControlMaxBytes: config.brokerControlMaxBytes }) };
|
|
240
|
+
const after = options.messages.findLastIndex(message => message.role === 'assistant' && !isWorkspaceNoticeMessage(message));
|
|
241
|
+
request.userInputIds = Object.freeze(options.messages.slice(after + 1).flatMap(message => message.role === 'user' && message.source?.kind === 'user' && typeof message.id === 'string' ? [message.id] : []));
|
|
242
|
+
request.userMessageIds = request.userInputIds;
|
|
228
243
|
await this.resolveRuntime.preflight?.(request);
|
|
229
|
-
|
|
244
|
+
let input = await timing.run('input-prepare', () => prepareInput(options, this.readImage));
|
|
230
245
|
const contextSha256 = createHash('sha256').update(input.systemPrompt ?? '').digest('hex');
|
|
231
246
|
request.contextSha256 = contextSha256;
|
|
247
|
+
const codex = this.agent.loginProvider === 'codex';
|
|
248
|
+
if (codex && live && previous && live.contextSha256 !== previous.contextSha256.toLowerCase())
|
|
249
|
+
throw new NeedError('SESSION_CONTEXT_DIGEST_INVALID', '持久上下文摘要与当前远端会话的初建摘要不一致,本轮未发送', '原会话一致的初建上下文摘要');
|
|
250
|
+
const contextChanged = !!previous && codex && previous.contextSha256.toLowerCase() !== contextSha256;
|
|
251
|
+
// Only identical durable context can prove a legacy context-bound journal.
|
|
252
|
+
// New Codex transport scopes are independent of the native context digest.
|
|
253
|
+
if (contextChanged)
|
|
254
|
+
request.contextSha256 = undefined;
|
|
232
255
|
if (previous && this.agent.loginProvider === 'claude' && previous.contextSha256 !== contextSha256)
|
|
233
256
|
throw new NeedError('SYSTEM_PROMPT_UPDATE_PENDING', 'Claude原会话的系统或开发者要求已变化;主机仅承诺下次上下文整理后生效,本轮未发送,请新建会话应用新要求', '新建Claude会话以立即应用当前系统与开发者要求');
|
|
257
|
+
if (contextChanged)
|
|
258
|
+
input = await prepareContinuation(options, input, this.readImage);
|
|
234
259
|
if (live) {
|
|
235
|
-
|
|
260
|
+
if (contextChanged)
|
|
261
|
+
live.session.validateNewSessionContext({ systemPrompt: input.systemPrompt });
|
|
262
|
+
else
|
|
263
|
+
live.session.validateContext({ systemPrompt: input.systemPrompt });
|
|
236
264
|
live.session.validatePrompt(input.prompt);
|
|
237
265
|
}
|
|
238
266
|
if (mode === 'one-shot' && (input.systemPrompt || input.prompt.some(block => block.type !== 'text')))
|
|
@@ -241,36 +269,60 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
241
269
|
throw new NeedError('ACP_MESSAGE_TOO_LARGE', '本轮输入超过已设置的会话消息容量,未截断或发送', '管理员调整acpMaxMessageBytes');
|
|
242
270
|
if (mode === 'one-shot')
|
|
243
271
|
throw new NeedError('MODEL_CONFIGURATION_UNSUPPORTED', '单次模式不支持确认当前模型与推理等级,请改用会话模式', '支持模型配置确认的会话模式');
|
|
272
|
+
request.localAdmissionCheck?.();
|
|
244
273
|
catalog = await this.catalog(signal);
|
|
245
274
|
selected = catalog.models.find(model => model.id === options.model);
|
|
246
275
|
if (!selected)
|
|
247
276
|
throw new ModelConfigurationError('所选模型不在本次可信主机目录中,请重新选择模型');
|
|
248
277
|
if (options.reasoningEffort !== undefined && !selected.reasoning.options.some(option => option.value === options.reasoningEffort))
|
|
249
278
|
throw new ModelConfigurationError('所选推理等级不在本次模型目录中');
|
|
250
|
-
|
|
279
|
+
let modelSignal = AbortSignal.any([signal, catalog.signal]);
|
|
251
280
|
options.signal = modelSignal;
|
|
252
281
|
request.signal = modelSignal;
|
|
253
282
|
runtime = await this.resolveRuntime(request);
|
|
254
283
|
signal.throwIfAborted();
|
|
284
|
+
if (runtime.execution?.signal) {
|
|
285
|
+
modelSignal = AbortSignal.any([modelSignal, runtime.execution.signal]);
|
|
286
|
+
options.signal = modelSignal;
|
|
287
|
+
request.signal = modelSignal;
|
|
288
|
+
modelSignal.throwIfAborted();
|
|
289
|
+
}
|
|
255
290
|
catalog.assertCurrent();
|
|
256
291
|
if (runtime.connection && (runtime.connection.instanceId !== catalog.target.instanceId || runtime.connection.epoch !== catalog.target.epoch) || runtime.identity !== `${catalog.target.target.username}@${catalog.target.target.host}:${catalog.target.target.port}`)
|
|
257
292
|
throw new ModelConfigurationError('模型目录与当前会话可信员工主机不一致');
|
|
258
293
|
if (previous && (previous.identity !== runtime.identity || previous.cwd !== runtime.cwd || previous.bindingId && previous.bindingId !== runtime.binding?.bindingId || previous.workspaceId && previous.workspaceId !== runtime.binding?.workspaceId))
|
|
259
294
|
throw new NeedError('SESSION_CONTEXT_MISMATCH', '原会话的员工主机或工作区已改变,请新建会话', '原会话的可信目标和工作区');
|
|
295
|
+
if (contextChanged && runtime.recoveredSession) {
|
|
296
|
+
const capabilities = runtime.recoveredSession.capabilities;
|
|
297
|
+
// A clean reattach has sent no ACP input. A known policy refusal must
|
|
298
|
+
// detach it, preserving the original native session and checkpoint.
|
|
299
|
+
if (!capabilities.sessionCapabilities?.systemPrompt || !supportsCodexContextPolicy(capabilities)) {
|
|
300
|
+
preserveRecoveredRuntime = true;
|
|
301
|
+
if (!capabilities.sessionCapabilities?.systemPrompt)
|
|
302
|
+
throw new NeedError('SYSTEM_PROMPT_UNSUPPORTED', '远端未声明系统与开发者上下文能力,不能丢弃这些要求继续', '支持 systemPrompt 的公开 ACP 能力');
|
|
303
|
+
throw new NeedError('SYSTEM_PROMPT_POLICY_UNSUPPORTED', '远端没有明确声明Codex上下文仅新建生效的兼容政策,本轮未发送', 'systemPrompt与精确的aiWorkbenchSystemPrompt新建政策');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
260
306
|
if (runtime.restartNotice) {
|
|
261
307
|
const ids = runtime.restartInputIds;
|
|
262
308
|
if (!ids?.length || new Set(ids).size !== ids.length || ids.some(id => input.users.filter(u => u.id === id).length !== 1))
|
|
263
309
|
throw new NeedError('CURRENT_INPUT_UNCONFIRMED', '无法核对本次输入,尚未发送给模型', '官方当前步骤的已接纳用户输入');
|
|
264
|
-
input
|
|
310
|
+
input = { ...input, prompt: ids.flatMap(id => input.users.find(u => u.id === id).prompt) };
|
|
311
|
+
if (contextChanged)
|
|
312
|
+
input = await timing.run('input-prepare', () => prepareContinuation(options, input, this.readImage));
|
|
265
313
|
}
|
|
266
314
|
const details = runtime.details;
|
|
267
315
|
if (!details)
|
|
268
316
|
throw new NeedError('TECHNICAL_DETAILS_UNAVAILABLE', '技术详情暂不可用,请更新远程主机连接组件。', 'L技术详情持久接收能力');
|
|
269
317
|
timing.bind(detail => details.record(detail));
|
|
270
|
-
const mapper = new StreamMapper(mode === 'session' ? (runtime.restartNotice ? undefined : live ? live.usageSnapshot : previous ? (previous.usageSnapshot ?? null) : undefined) : undefined, observation => details.record(observation));
|
|
318
|
+
const mapper = new StreamMapper(mode === 'session' && !contextChanged ? (runtime.restartNotice ? undefined : live ? live.usageSnapshot : previous ? (previous.usageSnapshot ?? null) : undefined) : undefined, observation => details.record(observation));
|
|
271
319
|
activeMapper = mapper;
|
|
272
320
|
let id;
|
|
273
321
|
let stop;
|
|
322
|
+
if (runtime.employeePresentation) {
|
|
323
|
+
yield* mapper.text(runtime.employeePresentation + '\n\n');
|
|
324
|
+
yield* mapper.flush();
|
|
325
|
+
}
|
|
274
326
|
if (runtime.workspaceNotice) {
|
|
275
327
|
yield* mapper.text(runtime.workspaceNotice.text);
|
|
276
328
|
yield* mapper.flush();
|
|
@@ -279,6 +331,11 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
279
331
|
yield* mapper.text(runtime.restartNotice + '\n\n');
|
|
280
332
|
yield* mapper.flush();
|
|
281
333
|
}
|
|
334
|
+
if (runtime.execution) {
|
|
335
|
+
details.record({ kind: 'session', severity: 'warning', code: 'CLAUDE_EMPLOYEE_SCOPE', summary: '本机工具路由及未覆盖操作范围', payload: runtime.execution.scope });
|
|
336
|
+
yield* mapper.text(runtime.execution.notice + '\n\n');
|
|
337
|
+
yield* mapper.flush();
|
|
338
|
+
}
|
|
282
339
|
if (mode === 'session') {
|
|
283
340
|
if (live && live.runtime !== runtime)
|
|
284
341
|
throw new NeedError('SESSION_TRANSPORT_MISMATCH', '原会话的主机通道已更换,不能自动切换进程', '原主机会话通道');
|
|
@@ -289,7 +346,22 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
289
346
|
let done = false;
|
|
290
347
|
let failure;
|
|
291
348
|
const push = (chunks) => { queue.push(...chunks); wake?.(); wake = undefined; };
|
|
292
|
-
const hooks = { broker: runtime.broker, timing, approve:
|
|
349
|
+
const hooks = { broker: runtime.broker, employeeExecution: runtime.employeeExecution, timing, approve: async (permission, approvalSignal) => {
|
|
350
|
+
const execution = runtime.execution;
|
|
351
|
+
const toolName = permission.toolCall.name ?? permission.toolCall._meta?.claudeCode?.toolName;
|
|
352
|
+
if (execution) {
|
|
353
|
+
if (toolName && ['Agent', 'Task', 'TaskOutput', 'TaskStop', 'Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash'].includes(toolName)) {
|
|
354
|
+
details.record({ kind: 'need', severity: 'warning', code: 'CLAUDE_HOST_TOOL_UNSUPPORTED', summary: '这类原生调用没有经过员工端别名,本机来源不支持;本次审批已取消', payload: { operation: toolName } });
|
|
355
|
+
push(mapper.text('\n这类操作尚不支持在本机执行,已取消本次审批,没有在主机代跑。\n'));
|
|
356
|
+
return 'cancelled';
|
|
357
|
+
}
|
|
358
|
+
await execution.validate(approvalSignal);
|
|
359
|
+
}
|
|
360
|
+
const answer = await this.approve?.(permission, approvalSignal, options, execution && toolName?.startsWith('mcp__employee__') ? 'employee-local' : undefined) ?? 'unavailable';
|
|
361
|
+
if (execution && answer === 'allowed-once')
|
|
362
|
+
await execution.validate(approvalSignal);
|
|
363
|
+
return answer;
|
|
364
|
+
}, onUpdate: notification => { try {
|
|
293
365
|
push(mapper.acp(notification.update));
|
|
294
366
|
}
|
|
295
367
|
catch (error) {
|
|
@@ -300,7 +372,7 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
300
372
|
if (fresh) {
|
|
301
373
|
const transport = runtime.acpTransport instanceof acp.AgentApp || runtime.acpTransport instanceof unstable.AgentApp ? runtime.acpTransport : acp.ndJsonStream(runtime.acpTransport.writable, runtime.acpTransport.readable, { maxMessageBytes: config.acpMaxMessageBytes });
|
|
302
374
|
session = new AcpSession(transport, hooks);
|
|
303
|
-
live = { session, runtime, configuration, usageSnapshot: mapper.usageSnapshot };
|
|
375
|
+
live = { session, runtime, configuration, contextSha256, usageSnapshot: mapper.usageSnapshot };
|
|
304
376
|
if (key)
|
|
305
377
|
this.sessions.set(key, live);
|
|
306
378
|
}
|
|
@@ -313,17 +385,30 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
313
385
|
const task = (async () => {
|
|
314
386
|
remoteAttempted = true;
|
|
315
387
|
try {
|
|
316
|
-
if (fresh) {
|
|
317
|
-
if (runtime.recoveredSession)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
388
|
+
if (fresh && runtime.recoveredSession) {
|
|
389
|
+
if (previous && runtime.recoveredSession.sessionId !== previous.sessionId)
|
|
390
|
+
throw new NeedError('SESSION_STATE_INVALID', '已接回的会话与持久标识不一致,本轮未发送', '原会话的恢复标识');
|
|
391
|
+
session.restore(runtime.execution?.hostCwd ?? runtime.cwd, runtime.recoveredSession, { systemPrompt: input.systemPrompt, ...(runtime.execution ? { employeeRouting: runtime.execution.routing } : {}), ...(codex ? { codexNewSessionOnly: true } : {}) }, codex && !!previous && !contextChanged);
|
|
392
|
+
session.validatePrompt(input.prompt);
|
|
393
|
+
if (contextChanged)
|
|
394
|
+
await session.newContextSession({ systemPrompt: input.systemPrompt }, modelSignal);
|
|
321
395
|
}
|
|
396
|
+
else if (fresh)
|
|
397
|
+
await session.start(runtime.execution?.hostCwd ?? runtime.cwd, contextChanged || runtime.restartNotice ? undefined : previous?.sessionId, modelSignal, { systemPrompt: contextChanged ? (input.systemPrompt ?? '') : input.systemPrompt, ...(runtime.execution ? { employeeRouting: runtime.execution.routing } : {}), ...(codex ? { codexNewSessionOnly: true } : {}), ...(runtime.employeeExecutionMeta ? { employeeExecution: runtime.employeeExecutionMeta } : {}) }, input.prompt);
|
|
398
|
+
else if (contextChanged)
|
|
399
|
+
await session.newContextSession({ systemPrompt: input.systemPrompt }, modelSignal);
|
|
322
400
|
else {
|
|
323
401
|
const updated = await session.updateContext({ systemPrompt: input.systemPrompt }, modelSignal);
|
|
324
402
|
if (updated && this.agent.loginProvider === 'claude')
|
|
325
403
|
throw new NeedError('SYSTEM_PROMPT_UPDATE_PENDING', 'Claude原会话的系统或开发者要求已变化;主机仅承诺下次上下文整理后生效,本轮未发送,请新建会话应用新要求', '新建Claude会话以立即应用当前系统与开发者要求');
|
|
326
404
|
}
|
|
405
|
+
if (contextChanged) {
|
|
406
|
+
if (session.id === previous.sessionId)
|
|
407
|
+
throw new NeedError('SESSION_STATE_INVALID', '新会话标识与原会话相同,无法确认独立承接,本轮未发送', '独立的新远端会话标识');
|
|
408
|
+
live.contextSha256 = contextSha256;
|
|
409
|
+
live.usageSnapshot = mapper.usageSnapshot;
|
|
410
|
+
details.record({ kind: 'session', severity: 'info', code: 'CODEX_CONTEXT_SESSION_REPLACED', summary: 'Codex要求仅在新建生效,已保留原会话;新会话承接可见用户及助手正文,内部推理不转发', payload: { reason: 'system-context-changed', oldSessionId: previous.sessionId, newSessionId: session.id, oldContextSha256: previous.contextSha256, newContextSha256: contextSha256, history: input.historyTransfer } });
|
|
411
|
+
}
|
|
327
412
|
catalog.assertCurrent();
|
|
328
413
|
const actual = await session.selectModel(selected.value, options.reasoningEffort, modelSignal);
|
|
329
414
|
catalog.assertCurrent();
|
|
@@ -350,11 +435,16 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
350
435
|
details.beginTurn(runtime.detailContext?.() ?? { turn: null, step: null });
|
|
351
436
|
catalog.assertCurrent();
|
|
352
437
|
session.assertModelSelection(selected.value, options.reasoningEffort);
|
|
438
|
+
await runtime.execution?.beginTurn(modelSignal);
|
|
353
439
|
id = session.id;
|
|
354
440
|
try {
|
|
355
|
-
|
|
441
|
+
const result = await session.prompt(input.prompt, modelSignal);
|
|
442
|
+
if (contextChanged)
|
|
443
|
+
push(mapper.text(runtime.restartNotice ? '\n\n会话要求已变化,已把此前可见对话带入新 Codex 会话继续处理。' : '\n\n会话要求已变化,已新建 Codex 会话继续处理,原会话仍保留。'));
|
|
444
|
+
return result;
|
|
356
445
|
}
|
|
357
446
|
finally {
|
|
447
|
+
runtime.execution?.endTurn();
|
|
358
448
|
details.endTurn();
|
|
359
449
|
}
|
|
360
450
|
})();
|
|
@@ -406,7 +496,8 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
406
496
|
if (await runtime.saveCheckpoint(id, session.checkpoint))
|
|
407
497
|
recoveryCheckpointId = id;
|
|
408
498
|
}
|
|
409
|
-
const
|
|
499
|
+
const previousRemoteSessions = [...(previous?.previousRemoteSessions ?? []), ...(contextChanged ? [{ sessionId: previous.sessionId, contextSha256: previous.contextSha256 }] : [])];
|
|
500
|
+
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 } : {}) };
|
|
410
501
|
succeeded = true;
|
|
411
502
|
yield { type: 'finish', reason: { kind: stop === 'max_tokens' || stop === 'max_turn_requests' ? 'max-tokens' : 'stop' }, replayState: { response: pointer } };
|
|
412
503
|
}
|
|
@@ -427,9 +518,10 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
427
518
|
throw error;
|
|
428
519
|
}
|
|
429
520
|
finally {
|
|
521
|
+
runtime?.execution?.endTurn();
|
|
430
522
|
catalog?.release();
|
|
431
523
|
try {
|
|
432
|
-
if (this.shuttingDown || key && this.detached.has(key)) {
|
|
524
|
+
if (preserveRecoveredRuntime || this.shuttingDown || key && this.detached.has(key)) {
|
|
433
525
|
session?.close();
|
|
434
526
|
await runtime?.detach?.();
|
|
435
527
|
}
|
|
@@ -459,6 +551,7 @@ export class RemoteAgentsAdapter extends LlmAdapter {
|
|
|
459
551
|
listModels(provider) { return this.adapter(provider).listModels(provider); }
|
|
460
552
|
resolveModel(provider, model, signal) { return this.adapter(provider).resolveModel(provider, model, signal); }
|
|
461
553
|
async invalidateModelState() { await Promise.all([...this.adapters.values()].map(adapter => adapter.invalidateModelState())); }
|
|
554
|
+
async invalidateEmployeeState() { await Promise.all([...this.adapters.values()].map(adapter => adapter.invalidateEmployeeState())); }
|
|
462
555
|
providerRetryPolicy() { return { mode: 'normal', maxRetries: 0, retryableCodes: [], initialDelayMs: 0, maxDelayMs: 0, jitterRatio: 0 }; }
|
|
463
556
|
stream(options) { return this.adapter(options.provider).stream(options); }
|
|
464
557
|
async disposeSession(sessionId) { await Promise.all([...this.adapters.values()].map(adapter => adapter.disposeSession(sessionId))); }
|
|
@@ -476,6 +569,7 @@ export function apply(ctx, live) {
|
|
|
476
569
|
return attachments.readImage(ref, signal);
|
|
477
570
|
};
|
|
478
571
|
const openByAgent = new Map(AGENTS.map(agent => [agent.id, brokerFactory(get, undefined, config)]));
|
|
572
|
+
const localTurns = createLocalUserTurnObserver(ctx);
|
|
479
573
|
const detailManagers = new Map();
|
|
480
574
|
const managerForProfile = () => {
|
|
481
575
|
const profile = ctx.get('profileContext');
|
|
@@ -490,13 +584,15 @@ export function apply(ctx, live) {
|
|
|
490
584
|
};
|
|
491
585
|
let changed = () => { };
|
|
492
586
|
const modelDirectory = new ModelCatalog(get, () => config().acpMaxMessageBytes, undefined, () => changed());
|
|
493
|
-
const adapter = new RemoteAgentsAdapter(config, agent =>
|
|
494
|
-
|
|
495
|
-
|
|
587
|
+
const adapter = new RemoteAgentsAdapter(config, agent => {
|
|
588
|
+
const details = async (request, api, connection, target) => {
|
|
589
|
+
return managerForProfile().open({ sessionId: request.sessionId, provider: agent.loginProvider, identity: `${target.username}@${target.host}:${target.port}`, connection, localAcceptanceId: request.localAcceptanceId ?? request.localAcceptance?.acceptanceId });
|
|
590
|
+
};
|
|
591
|
+
const open = async (...args) => {
|
|
496
592
|
const profile = ctx.get('profileContext');
|
|
497
593
|
if (!profile?.dir)
|
|
498
594
|
throw new NeedError('RECOVERY_STORAGE_REQUIRED', '会话恢复需要官方profile目录', '官方profileContext.dir');
|
|
499
|
-
args[2].recoveryJournal = recoveryJournal(profile.dir,
|
|
595
|
+
args[2].recoveryJournal = recoveryJournal(profile.dir, runtimeRecoveryKey(`${args[0].username}@${args[0].host}:${args[0].port}`, agent.id, args[2].sessionId, args[2].employeeScope));
|
|
500
596
|
args[2].verifyRecovery = () => verifyRecoveryCheckpoint(ctx, args[2], agent.id);
|
|
501
597
|
args[2].currentInputIds = await currentInputIds(ctx, args[2]);
|
|
502
598
|
const runtime = await openByAgent.get(agent.id)(...args);
|
|
@@ -520,7 +616,19 @@ export function apply(ctx, live) {
|
|
|
520
616
|
return { turn, step };
|
|
521
617
|
};
|
|
522
618
|
return runtime;
|
|
523
|
-
}
|
|
619
|
+
};
|
|
620
|
+
const local = agent.loginProvider === 'claude' ? createClaudeLocalProvider({ getConnect: get, getNetwork: () => ctx.get('aiWorkbenchNetwork'), readWorkspace: id => readEmployeeWorkspace(ctx, id), readUserTurn: request => readLocalUserTurn(ctx, { sessionId: request.sessionId, route: agent.id, userMessageIds: request.userMessageIds ?? [] }), reportAdmissionFailure: (request, code) => managerForProfile().recordLocalFailure({ sessionId: request.sessionId, provider: 'claude', code }), recordUnscoped: (request, observation) => managerForProfile().recordLocalFailure({ sessionId: request.sessionId, provider: 'claude', code: observation.code, observation }), configuration: config, details, open }) : undefined;
|
|
621
|
+
return connectProvider(get, { agent, ...(agent.loginProvider === 'codex' ? { employee: {
|
|
622
|
+
currentUserTurn: request => localTurns.current(request),
|
|
623
|
+
getNetwork: () => ctx.get('aiWorkbenchNetwork'),
|
|
624
|
+
workspace: async (request) => {
|
|
625
|
+
const services = ctx;
|
|
626
|
+
const registry = services.get('workspaceRegistry');
|
|
627
|
+
const sessions = services.get('sessions');
|
|
628
|
+
return officialEmployeeWorkspace(request.sessionId, registry, sessions?.get(request.sessionId)?.header);
|
|
629
|
+
}, native: () => resolveEmployeeNative(),
|
|
630
|
+
} } : {}), notice: createWorkspaceNoticeConsumer(ctx, agent), details, open, local });
|
|
631
|
+
}, dshApproval(ctx), agent => agent.name[language], readImage, (sessionId, code, diagnostic) => {
|
|
524
632
|
const events = sessionId ? ctx.get('agents')?.get(SessionId(sessionId))?.session.snapshotEvents() : undefined;
|
|
525
633
|
let turn = null, step = null;
|
|
526
634
|
for (const event of events ?? []) {
|
|
@@ -544,8 +652,20 @@ export function apply(ctx, live) {
|
|
|
544
652
|
const directory = ctx.llm.registerConfigurableProviders(entries());
|
|
545
653
|
const registration = ctx.llm.registerAdapter(ids, adapter);
|
|
546
654
|
changed = () => { registration.replace(ids); void adapter.invalidateModelState().catch(() => { ctx.logger('remote-agent').warn('原会话清理结果未确认,已阻断继续发送;请联系管理员核对。'); }); };
|
|
547
|
-
|
|
548
|
-
|
|
655
|
+
const serviceValue = (name) => ctx.get(name);
|
|
656
|
+
let employeeConnect = serviceValue('aiWorkbenchConnect'), employeeNetwork = serviceValue('aiWorkbenchNetwork');
|
|
657
|
+
ctx.on('internal/service', service => {
|
|
658
|
+
if (service === 'aiWorkbenchConnect')
|
|
659
|
+
modelDirectory.refresh();
|
|
660
|
+
if (service !== 'aiWorkbenchConnect' && service !== 'aiWorkbenchNetwork')
|
|
661
|
+
return;
|
|
662
|
+
const nextConnect = serviceValue('aiWorkbenchConnect'), nextNetwork = serviceValue('aiWorkbenchNetwork');
|
|
663
|
+
if (nextConnect === employeeConnect && nextNetwork === employeeNetwork)
|
|
664
|
+
return;
|
|
665
|
+
employeeConnect = nextConnect;
|
|
666
|
+
employeeNetwork = nextNetwork;
|
|
667
|
+
void adapter.invalidateEmployeeState().catch(() => { ctx.logger('remote-agent').warn('本机执行连接的清理结果未确认,已阻断继续发送;请联系管理员核对。'); });
|
|
668
|
+
});
|
|
549
669
|
new ProviderLocaleRemote(ctx, next => { language = next; directory.replace(entries()); registration.replace(ids); });
|
|
550
670
|
const cleanup = (id) => adapter.detachSession(id);
|
|
551
671
|
ctx.on('agent/disposed', ({ agent }) => cleanup(agent.session.id));
|
|
@@ -558,7 +678,7 @@ export function dshApproval(ctx) {
|
|
|
558
678
|
const approval = ctx.get('approval');
|
|
559
679
|
if (!agent || !approval)
|
|
560
680
|
return 'unavailable';
|
|
561
|
-
const label = approvalLabel(request);
|
|
681
|
+
const label = approvalLabel(request, context === 'employee-local' ? 'local' : 'host');
|
|
562
682
|
if (!label)
|
|
563
683
|
return 'unavailable';
|
|
564
684
|
return approval.request({ agent, ...label, signal });
|
package/dist/input.d.ts
CHANGED
|
@@ -4,6 +4,14 @@ export type ImageReader = (ref: ImageBlock['attachment'], signal?: AbortSignal)
|
|
|
4
4
|
ref: ImageBlock['attachment'];
|
|
5
5
|
data: Uint8Array;
|
|
6
6
|
}>;
|
|
7
|
+
export interface HistoryTransfer {
|
|
8
|
+
scope: 'visible-user-assistant-text-and-images';
|
|
9
|
+
textBlocks: number;
|
|
10
|
+
imageBlocks: number;
|
|
11
|
+
offloadedImagePlaceholders: number;
|
|
12
|
+
omittedReasoningBlocks: number;
|
|
13
|
+
reasoningOmissionReason: 'dsh-internal-reasoning';
|
|
14
|
+
}
|
|
7
15
|
export interface PreparedInput {
|
|
8
16
|
prompt: AcpContentBlock[];
|
|
9
17
|
users: {
|
|
@@ -11,6 +19,9 @@ export interface PreparedInput {
|
|
|
11
19
|
prompt: AcpContentBlock[];
|
|
12
20
|
}[];
|
|
13
21
|
systemPrompt?: string;
|
|
22
|
+
historyTransfer?: HistoryTransfer;
|
|
14
23
|
}
|
|
24
|
+
/** Role-labelled public history only; never serialize source/replay/tool internals. */
|
|
25
|
+
export declare function prepareContinuation(options: GenerateOptions, input: PreparedInput, readImage?: ImageReader): Promise<PreparedInput>;
|
|
15
26
|
/** The attachment service supplies verified bytes; client paths never cross SSH. */
|
|
16
27
|
export declare function prepareInput(options: GenerateOptions, readImage?: ImageReader): Promise<PreparedInput>;
|
package/dist/input.js
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
import { NeedError } from "./remote.js";
|
|
2
2
|
import { isWorkspaceNoticeMessage, withoutWorkspaceNotice } from "./workspace-notice.js";
|
|
3
|
+
async function publicBlock(block, options, readImage, history = false) {
|
|
4
|
+
options.signal?.throwIfAborted();
|
|
5
|
+
if (block.type === 'text')
|
|
6
|
+
return { type: 'text', text: block.text };
|
|
7
|
+
if (block.type === 'image') {
|
|
8
|
+
if (block.offloaded) {
|
|
9
|
+
return { type: 'text', text: `图片 ${block.attachment.name ?? block.attachment.attachmentId} 已按官方会话策略移出当前上下文。` };
|
|
10
|
+
}
|
|
11
|
+
if (!readImage)
|
|
12
|
+
throw new NeedError('ATTACHMENT_REQUIRED', '我需要官方附件组件读取这张图片', '官方attachments.readImage');
|
|
13
|
+
const image = await readImage(block.attachment, options.signal);
|
|
14
|
+
options.signal?.throwIfAborted();
|
|
15
|
+
if (!image || !(image.data instanceof Uint8Array) || image.ref?.attachmentId !== block.attachment.attachmentId || !['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(image.ref.mediaType) || image.ref.bytes !== image.data.byteLength)
|
|
16
|
+
throw new NeedError('ATTACHMENT_INVALID', '图片附件没有返回匹配的已核验内容', '官方核验的图片字节与媒体类型');
|
|
17
|
+
return { type: 'image', mimeType: image.ref.mediaType, data: Buffer.from(image.data).toString('base64') };
|
|
18
|
+
}
|
|
19
|
+
throw new NeedError(history ? 'HISTORY_CONTENT_UNSUPPORTED' : 'INPUT_TYPE_REQUIRED', `我需要此${history ? '历史' : '输入'}类型的远程转接能力:${block.type};本轮未新建或发送`, '对应类型的公开ACP输入映射;内部推理及工具记录不可降为用户要求');
|
|
20
|
+
}
|
|
21
|
+
/** Role-labelled public history only; never serialize source/replay/tool internals. */
|
|
22
|
+
export async function prepareContinuation(options, input, readImage) {
|
|
23
|
+
const messages = options.messages.filter(message => !isWorkspaceNoticeMessage(message)).map(withoutWorkspaceNotice);
|
|
24
|
+
for (const message of messages)
|
|
25
|
+
if (!['user', 'assistant', 'system', 'developer'].includes(message.role))
|
|
26
|
+
throw new NeedError('HISTORY_CONTENT_UNSUPPORTED', `此前含有无法可靠承接的历史角色:${message.role};本轮未新建或发送`, '完整公开的用户及助手历史;工具记录的公开内容映射');
|
|
27
|
+
const after = messages.findLastIndex(message => message.role === 'assistant');
|
|
28
|
+
const prompt = [];
|
|
29
|
+
const historyTransfer = { scope: 'visible-user-assistant-text-and-images', textBlocks: 0, imageBlocks: 0, offloadedImagePlaceholders: 0, omittedReasoningBlocks: 0, reasoningOmissionReason: 'dsh-internal-reasoning' };
|
|
30
|
+
for (const message of messages.slice(0, after + 1)) {
|
|
31
|
+
if (message.role === 'system' || message.role === 'developer')
|
|
32
|
+
continue;
|
|
33
|
+
if (message.role !== 'user' && message.role !== 'assistant')
|
|
34
|
+
throw new NeedError('HISTORY_CONTENT_UNSUPPORTED', `此前含有无法可靠承接的历史角色:${message.role};本轮未新建或发送`, '完整公开的用户及助手历史;工具记录的公开内容映射');
|
|
35
|
+
prompt.push({ type: 'text', text: `此前可见对话(历史角色:${message.role};仅为历史记录):\n` });
|
|
36
|
+
for (const block of message.content) {
|
|
37
|
+
// DSH explicitly distinguishes internal thinking from visible user text.
|
|
38
|
+
if (block.type === 'reasoning') {
|
|
39
|
+
historyTransfer.omittedReasoningBlocks++;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
prompt.push(await publicBlock(block, options, readImage, true));
|
|
43
|
+
if (block.type === 'text')
|
|
44
|
+
historyTransfer.textBlocks++;
|
|
45
|
+
else if (block.type === 'image') {
|
|
46
|
+
if (block.offloaded)
|
|
47
|
+
historyTransfer.offloadedImagePlaceholders++;
|
|
48
|
+
else
|
|
49
|
+
historyTransfer.imageBlocks++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
prompt.push({ type: 'text', text: `\n(历史角色:${message.role} 的记录结束)\n` });
|
|
53
|
+
}
|
|
54
|
+
prompt.push({ type: 'text', text: '此前可见对话结束。以下为当前用户输入:\n' }, ...input.prompt);
|
|
55
|
+
return { ...input, prompt, historyTransfer };
|
|
56
|
+
}
|
|
3
57
|
/** The attachment service supplies verified bytes; client paths never cross SSH. */
|
|
4
58
|
export async function prepareInput(options, readImage) {
|
|
5
59
|
options.signal?.throwIfAborted();
|
|
@@ -10,7 +64,11 @@ export async function prepareInput(options, readImage) {
|
|
|
10
64
|
for (const message of messages) {
|
|
11
65
|
if (message.role !== 'system' && message.role !== 'developer')
|
|
12
66
|
continue;
|
|
13
|
-
const parts = message.content.map(block =>
|
|
67
|
+
const parts = message.content.map(block => {
|
|
68
|
+
if (block.type !== 'text')
|
|
69
|
+
throw new NeedError('SYSTEM_CONTEXT_TYPE_UNSUPPORTED', '系统或开发者要求包含暂不支持的非文本内容,本轮未新建或发送', '系统及开发者仅支持公开text上下文;非文本内容需要已定义的公开ACP投影');
|
|
70
|
+
return block.text;
|
|
71
|
+
});
|
|
14
72
|
if (parts.length)
|
|
15
73
|
context.push((message.role === 'system' ? '系统上下文:' : '开发者上下文:') + '\n' + parts.join('\n'));
|
|
16
74
|
}
|
|
@@ -22,28 +80,8 @@ export async function prepareInput(options, readImage) {
|
|
|
22
80
|
const preparedUsers = [];
|
|
23
81
|
for (const message of users) {
|
|
24
82
|
const before = prompt.length;
|
|
25
|
-
for (const block of message.content)
|
|
26
|
-
|
|
27
|
-
if (block.type === 'text') {
|
|
28
|
-
prompt.push({ type: 'text', text: block.text });
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
if (block.type === 'image') {
|
|
32
|
-
if (block.offloaded) {
|
|
33
|
-
prompt.push({ type: 'text', text: `图片 ${block.attachment.name ?? block.attachment.attachmentId} 已按官方会话策略移出当前上下文。` });
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (!readImage)
|
|
37
|
-
throw new NeedError('ATTACHMENT_REQUIRED', '我需要官方附件组件读取这张图片', '官方attachments.readImage');
|
|
38
|
-
const image = await readImage(block.attachment, options.signal);
|
|
39
|
-
options.signal?.throwIfAborted();
|
|
40
|
-
if (!image || !(image.data instanceof Uint8Array) || image.ref?.attachmentId !== block.attachment.attachmentId || !['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(image.ref.mediaType) || image.ref.bytes !== image.data.byteLength)
|
|
41
|
-
throw new NeedError('ATTACHMENT_INVALID', '图片附件没有返回匹配的已核验内容', '官方核验的图片字节与媒体类型');
|
|
42
|
-
prompt.push({ type: 'image', mimeType: image.ref.mediaType, data: Buffer.from(image.data).toString('base64') });
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
throw new NeedError('INPUT_TYPE_REQUIRED', `我需要此输入类型的远程转接能力:${block.type}`, '对应类型的公开ACP输入映射');
|
|
46
|
-
}
|
|
83
|
+
for (const block of message.content)
|
|
84
|
+
prompt.push(await publicBlock(block, options, readImage));
|
|
47
85
|
preparedUsers.push({ id: message.id, prompt: prompt.slice(before) });
|
|
48
86
|
}
|
|
49
87
|
if (!prompt.length)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RuntimeRequest } from './provider.ts';
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
export interface LocalTurnProof {
|
|
4
|
+
expectedUserTurn: {
|
|
5
|
+
turn: number;
|
|
6
|
+
userMessageId: string;
|
|
7
|
+
};
|
|
8
|
+
startSequence: number;
|
|
9
|
+
userMessageSequence: number;
|
|
10
|
+
assertCurrent(): void;
|
|
11
|
+
}
|
|
12
|
+
/** CAS expectation only. L independently establishes live human-turn/brand evidence. */
|
|
13
|
+
export declare function readLocalUserTurn(ctx: Context, request: {
|
|
14
|
+
sessionId: string;
|
|
15
|
+
route: string;
|
|
16
|
+
userMessageIds: readonly string[];
|
|
17
|
+
}): Promise<LocalTurnProof>;
|
|
18
|
+
/** Only metadata observed live since this plugin mounted; never seed or persisted tails. */
|
|
19
|
+
export declare function createLocalUserTurnObserver(ctx: Pick<Context, 'get' | 'on'>): {
|
|
20
|
+
current(request: Pick<RuntimeRequest, "sessionId" | "userInputIds">): {
|
|
21
|
+
turn: number;
|
|
22
|
+
userMessageId: string;
|
|
23
|
+
};
|
|
24
|
+
};
|