@ai-workbench/remote-agent 0.8.5 → 0.8.11
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 +49 -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 +235 -41
- 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/model-catalog.d.ts +18 -2
- package/dist/model-catalog.js +101 -36
- package/dist/model-defaults.d.ts +29 -0
- package/dist/model-defaults.js +62 -0
- package/dist/model-input-retention.d.ts +16 -0
- package/dist/model-input-retention.js +129 -0
- package/dist/presentation.d.ts +1 -1
- package/dist/presentation.js +52 -2
- package/dist/provider.d.ts +39 -2
- package/dist/provider.js +69 -13
- 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/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/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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { LlmError } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { Session } from '@deepseek-ai/dsh-session';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
const preparationFailures = new WeakMap();
|
|
5
|
+
/** Mark only at the actual X adapter's preparation catch, then rethrow the same
|
|
6
|
+
* error. One error object can prove only its first preparation, for that signal.
|
|
7
|
+
* Public request config is an intermediate proposal and is not route evidence. */
|
|
8
|
+
export function markModelPreparationFailure(provider, error, signal) {
|
|
9
|
+
if (!signal || signal.aborted || error === null || typeof error !== 'object' || preparationFailures.has(error))
|
|
10
|
+
return;
|
|
11
|
+
preparationFailures.set(error, { provider, signal, consumed: false });
|
|
12
|
+
}
|
|
13
|
+
// Even a source-proven unknown early failure is deliberately not replayed.
|
|
14
|
+
const preparationCodes = new Set(['MODEL_REQUIRED', 'MODEL_CATALOG_UNAVAILABLE', 'INVALID_MODEL_INFO', 'INVALID_MODEL_CONTEXT', 'INVALID_MODEL_MAX_TOKENS', 'INVALID_MODEL_REASONING', 'UNSUPPORTED_REASONING_EFFORT']);
|
|
15
|
+
function codeOf(error) {
|
|
16
|
+
const code = error !== null && typeof error === 'object' && 'code' in error ? error.code : undefined;
|
|
17
|
+
return typeof code === 'string' && /^[A-Z][A-Z0-9_]*$/u.test(code) ? code : undefined;
|
|
18
|
+
}
|
|
19
|
+
function committed(events, messages) {
|
|
20
|
+
return events.some(event => event.type === 'user/message' && messages.some(message => message.id === event.data.id));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Retain a claimed human batch only after a known, undispatched metadata prepare
|
|
24
|
+
* failure. Restoration uses the public durable inbox without sending or waking
|
|
25
|
+
* the agent. This cannot cover errors before pre-step/request, or unknown core
|
|
26
|
+
* admission failures; it never manufactures transcript messages or model routes.
|
|
27
|
+
*/
|
|
28
|
+
export function installModelInputRetention(ctx, options) {
|
|
29
|
+
const attempts = new Map();
|
|
30
|
+
let disposed = false;
|
|
31
|
+
const live = (a) => !disposed && attempts.get(a.agent) === a && !a.signal.aborted
|
|
32
|
+
&& ctx.get('agents')?.get(a.agent.id) === a.agent && ctx.get('sessions')?.get(a.session.id) === a.session && a.agent.session === a.session;
|
|
33
|
+
const safe = (a) => {
|
|
34
|
+
const events = a.session.snapshotEvents();
|
|
35
|
+
return !a.dispatched && !committed(events, a.messages) && !events.slice(a.offset).some(event => event.type === 'request/header' || event.type === 'assistant/message' || event.type === 'assistant/attempt');
|
|
36
|
+
};
|
|
37
|
+
const report = (a, message) => { if (!disposed && a.provider)
|
|
38
|
+
options.diagnostic(a.provider, message); };
|
|
39
|
+
const restore = async (a, originalCode) => {
|
|
40
|
+
try {
|
|
41
|
+
await a.agent.whenIdle();
|
|
42
|
+
if (!live(a) || !safe(a))
|
|
43
|
+
return;
|
|
44
|
+
// Claim true idle synchronously. A competing maintenance claim is reported,
|
|
45
|
+
// not retried. Input waking during this task remains the driver's own work.
|
|
46
|
+
await a.agent.runMaintenance(async (signal) => {
|
|
47
|
+
if (!live(a) || signal.aborted || !safe(a))
|
|
48
|
+
return;
|
|
49
|
+
const queue = [...a.agent.inbox.nextTurn, ...a.agent.inbox.nextStep];
|
|
50
|
+
if (a.messages.some(message => queue.some(item => item.id === message.id)))
|
|
51
|
+
return;
|
|
52
|
+
a.agent.inbox.splice('next-turn', 0, 0, a.messages);
|
|
53
|
+
const store = ctx.get('sessions'), persistence = ctx.get('sessionPersistence');
|
|
54
|
+
if (!store || typeof persistence?.open !== 'function' || !await store.flush(a.session))
|
|
55
|
+
throw Object.assign(new Error('缺少官方持久化能力'), { code: 'INPUT_RETENTION_PERSISTENCE_REQUIRED' });
|
|
56
|
+
if (!live(a) || signal.aborted)
|
|
57
|
+
return;
|
|
58
|
+
const handle = await persistence.open(a.session.id, 'read');
|
|
59
|
+
try {
|
|
60
|
+
if (handle.id !== a.session.id || handle.header?.id !== a.session.id || handle.access !== 'read')
|
|
61
|
+
throw Object.assign(new Error('持久会话身份不符'), { code: 'INPUT_RETENTION_VERIFY_FAILED' });
|
|
62
|
+
const { events } = await handle.read();
|
|
63
|
+
// Replay through the official Session validator and registered inbox fold,
|
|
64
|
+
// including exact fork metadata. Do not normalize invalid durable splices.
|
|
65
|
+
const replay = Session.create(a.session.id, events, handle.header, handle.inheritedEventCount);
|
|
66
|
+
const inbox = ctx.get('sessionProjections')?.snapshot(replay, ['inbox']).values.inbox;
|
|
67
|
+
if (!inbox)
|
|
68
|
+
throw Object.assign(new Error('缺少官方待发队列投影'), { code: 'INPUT_RETENTION_PROJECTION_REQUIRED' });
|
|
69
|
+
const restored = [...inbox['next-turn'], ...inbox['next-step']];
|
|
70
|
+
if (committed(events, a.messages) || a.messages.some(message => restored.filter(item => item !== null && typeof item === 'object' && !Array.isArray(item) && item.id === message.id).length !== 1 || !restored.some(item => isDeepStrictEqual(item, message))))
|
|
71
|
+
throw Object.assign(new Error('持久待发队列核对失败'), { code: 'INPUT_RETENTION_VERIFY_FAILED' });
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
await handle.close();
|
|
75
|
+
}
|
|
76
|
+
if (live(a) && !signal.aborted)
|
|
77
|
+
report(a, `本次模型准备失败,员工原话已保留在待发送队列;请先选择模型或修正设置后手动发送。没有自动重试。(诊断代码:${originalCode})`);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
report(a, `本次模型准备失败;员工原话的恢复或持久化未能确认,请联系管理员核对,不要重复发送。没有自动重试。(原诊断代码:${originalCode};恢复诊断代码:${codeOf(error) ?? 'INPUT_RETENTION_FAILED'})`);
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
if (attempts.get(a.agent) === a)
|
|
85
|
+
attempts.delete(a.agent);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
ctx.on('agent/pre-step', async (payload, next) => {
|
|
89
|
+
const decision = await next();
|
|
90
|
+
if (disposed)
|
|
91
|
+
return decision;
|
|
92
|
+
const messages = payload.messages.filter(message => message.source.kind === 'user');
|
|
93
|
+
if (decision.kind !== 'enter' || messages.length === 0 || messages.some(message => !decision.messages.some(accepted => accepted === message || isDeepStrictEqual(accepted, message)))) {
|
|
94
|
+
attempts.delete(payload.agent);
|
|
95
|
+
return decision;
|
|
96
|
+
}
|
|
97
|
+
attempts.set(payload.agent, { agent: payload.agent, session: payload.agent.session, turn: payload.turn, step: payload.step, signal: payload.signal, messages, offset: payload.agent.session.snapshotEvents().length, scheduled: false, dispatched: false });
|
|
98
|
+
return decision;
|
|
99
|
+
});
|
|
100
|
+
ctx.on('agent/request', async (payload, next) => {
|
|
101
|
+
const config = await next(), a = attempts.get(payload.agent);
|
|
102
|
+
if (a && a.turn === payload.turn && a.step === payload.step && a.signal === payload.signal && options.isOurProvider(config.provider)) {
|
|
103
|
+
if (!config.model?.trim()) {
|
|
104
|
+
const error = new LlmError('请先选择模型', 'MODEL_REQUIRED');
|
|
105
|
+
markModelPreparationFailure(config.provider, error, payload.signal);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return config;
|
|
110
|
+
});
|
|
111
|
+
ctx.on('agent/assistant-stream', ({ agent, frame }) => { const a = attempts.get(agent); if (a && frame.type === 'start' && frame.turn === a.turn && frame.step === a.step)
|
|
112
|
+
a.dispatched = true; });
|
|
113
|
+
ctx.on('agent/error', ({ agent, turn, step, error }) => {
|
|
114
|
+
const a = attempts.get(agent), code = codeOf(error);
|
|
115
|
+
const proof = error !== null && typeof error === 'object' ? preparationFailures.get(error) : undefined;
|
|
116
|
+
if (!a || a.turn !== turn || a.step !== step || !proof || proof.consumed || proof.signal !== a.signal || !options.isOurProvider(proof.provider))
|
|
117
|
+
return;
|
|
118
|
+
// Consume before further checks: canceled/unsafe attempts must not leave a
|
|
119
|
+
// reusable proof that could authorize a later failure of the same object.
|
|
120
|
+
proof.consumed = true;
|
|
121
|
+
if (a.scheduled || !code || !preparationCodes.has(code) || !live(a) || !safe(a))
|
|
122
|
+
return;
|
|
123
|
+
a.provider = proof.provider;
|
|
124
|
+
a.scheduled = true;
|
|
125
|
+
void restore(a, code);
|
|
126
|
+
});
|
|
127
|
+
ctx.on('agent/disposed', ({ agent }) => { attempts.delete(agent); });
|
|
128
|
+
ctx.effect(() => () => { disposed = true; attempts.clear(); });
|
|
129
|
+
}
|
package/dist/presentation.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export declare const REMOTE_CODEX_NAME = "Codex\uFF08\u8FDC\u7A0B\u4E3B\u673A\uF
|
|
|
3
3
|
export declare const CONNECTION_REQUIRED = "\u8FDC\u7A0B\u4E3B\u673A\u8FDE\u63A5\u7EC4\u4EF6\u6CA1\u6709\u88C5\u597D\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002";
|
|
4
4
|
export declare const TEST_HOST_NOTICE = "\u8FDC\u7A0B\u4E3B\u673A\u6B63\u5728\u4F7F\u7528\u6D4B\u8BD5\u914D\u7F6E\uFF0C\u9047\u5230\u95EE\u9898\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002";
|
|
5
5
|
/** Employee failures use business language; diagnostic codes remain separately available. */
|
|
6
|
-
export declare function employeeFailure(code: string): string;
|
|
6
|
+
export declare function employeeFailure(code: string, brand?: 'codex' | 'claude'): string;
|
package/dist/presentation.js
CHANGED
|
@@ -3,11 +3,25 @@ export const REMOTE_CODEX_NAME = 'Codex(远程主机)';
|
|
|
3
3
|
export const CONNECTION_REQUIRED = '远程主机连接组件没有装好,请联系管理员。';
|
|
4
4
|
export const TEST_HOST_NOTICE = '远程主机正在使用测试配置,遇到问题请联系管理员。';
|
|
5
5
|
/** Employee failures use business language; diagnostic codes remain separately available. */
|
|
6
|
-
export function employeeFailure(code) {
|
|
6
|
+
export function employeeFailure(code, brand = 'codex') {
|
|
7
7
|
const messages = {
|
|
8
|
+
WORKSPACE_SOURCE_CONFIRMATION_DECLINED: '你没有确认改用本机文件夹,本轮未执行;你的原话仍保留。',
|
|
9
|
+
WORKSPACE_SOURCE_CONFIRMATION_UNAVAILABLE: '我需要打开工作区来源确认,目前无法显示确认动作;本轮未执行,你的原话仍保留。',
|
|
10
|
+
WORKSPACE_SOURCE_SELECTION_UNCONFIRMED: '无法核对工作区来源的确认结果,本轮已停止;你的原话仍保留。',
|
|
11
|
+
WORKSPACE_SOURCE_CONFIRMATION_FAILED: '工作区来源确认未完成,本轮已停止;你的原话仍保留。',
|
|
12
|
+
WORKSPACE_LOCATION_REQUIRED: '我需要你确认这个工作区使用哪一个文件夹,本轮未执行;你的原话仍保留。',
|
|
13
|
+
WORKSPACE_LOCATION_CONFLICT: '工作区来源选择已更新,本次确认不能覆盖新的选择;本轮未执行,你的原话仍保留。',
|
|
14
|
+
LOCAL_USER_TURN_CHANGED: '本次输入已变化,本轮已停止,请确认对话后重新发送。',
|
|
15
|
+
LOCAL_USER_TURN_UNCONFIRMED: '无法核对本次员工输入,本轮已停止;请在当前会话重新发送。',
|
|
16
|
+
LOCAL_USER_TURN_ACCEPTANCE_INVALID: '本次输入的核验回执不一致,本轮已停止,请联系管理员。',
|
|
8
17
|
TECHNICAL_DETAILS_UNAVAILABLE: '技术详情暂不可用,请更新远程主机连接组件。',
|
|
9
18
|
REMOTE_DETAIL_STORAGE_FAILED: '技术详情保存失败,本轮已停止,请联系管理员检查。',
|
|
10
19
|
REMOTE_DETAIL_DELIVERY_UNKNOWN: '技术详情的持久结果无法核对,本轮已停止。',
|
|
20
|
+
REMOTE_DETAIL_SCOPE_INVALID: '无法核对当前会话的技术详情,本轮已停止;我需要更新后的远程主机连接组件。',
|
|
21
|
+
EMPLOYEE_CONTEXT_UNAVAILABLE: '我需要远程主机连接组件提供当前本机工作区,本轮未执行。',
|
|
22
|
+
EMPLOYEE_ENDPOINT_UNAVAILABLE: '我需要员工电脑的执行连接就绪,本轮未执行;请联系管理员检查连接组件。',
|
|
23
|
+
EMPLOYEE_CONTEXT_CHANGED: '本机文件夹或连接已改变,原工具已停止;请在当前工作区重新开始。',
|
|
24
|
+
EMPLOYEE_WORKSPACE_INVALID: '无法核对这条对话所属的本机文件夹,本轮未执行;请在工作区中重新打开对话。',
|
|
11
25
|
REMOTE_DETAIL_TOO_LARGE: '技术详情超过已配置的单事件容量,本轮已停止;请联系管理员调整conversationDetails.maxEventBytes。',
|
|
12
26
|
DEPENDENCY_REQUIRED: CONNECTION_REQUIRED,
|
|
13
27
|
LOGIN_REQUIRED: '需要登录,请到设置 → 远程主机连接 点登录',
|
|
@@ -33,6 +47,12 @@ export function employeeFailure(code) {
|
|
|
33
47
|
CONTENT_UNSUPPORTED: '远程 Codex 暂不支持这类附件,请联系管理员。',
|
|
34
48
|
SYSTEM_PROMPT_UPDATE_PENDING: 'Claude原会话无法保证立即应用已变化的系统或开发者要求,本轮未发送;请新建会话应用新要求。',
|
|
35
49
|
SYSTEM_PROMPT_UNSUPPORTED: '远端没有声明系统与开发者上下文能力,本轮未发送;需要管理员提供支持该能力的远端适配器。',
|
|
50
|
+
SYSTEM_CONTEXT_TYPE_UNSUPPORTED: '系统或开发者要求包含暂不支持的非文本内容,本轮未新建或发送;请使用文本要求或联系管理员。',
|
|
51
|
+
RECOVERY_CONTEXT_POLICY_UNSUPPORTED: '旧版会话恢复记录仍绑定模型要求,无法安全接回;本轮未发送,请联系管理员核对原会话。',
|
|
52
|
+
SYSTEM_PROMPT_POLICY_UNSUPPORTED: '远端还不支持保留原会话并按新要求继续,请联系管理员更新远端适配器;本轮未发送。',
|
|
53
|
+
SYSTEM_PROMPT_NEW_SESSION_REQUIRED: '会话要求已变化,需要新建 Codex 会话并承接历史后继续;本轮未发送。',
|
|
54
|
+
SESSION_CONTEXT_DIGEST_INVALID: '原会话的要求记录无法核对,本轮未发送;请联系管理员核对原会话。',
|
|
55
|
+
HISTORY_CONTENT_UNSUPPORTED: '此前对话含有暂时无法完整承接的内容,本轮未新建或发送;请联系管理员检查历史内容类型。',
|
|
36
56
|
SYSTEM_PROMPT_UPDATE_UNSUPPORTED: '远端不能更新原会话的系统与开发者要求,本轮未发送;请新建会话。',
|
|
37
57
|
PROMPT_CONTENT_UNSUPPORTED: '远端没有声明这类输入能力,本轮未发送;请使用受支持的内容或联系管理员。',
|
|
38
58
|
ATTACHMENT_REQUIRED: '我需要官方附件组件读取这张图片,请联系管理员检查组件。',
|
|
@@ -45,5 +65,35 @@ export function employeeFailure(code) {
|
|
|
45
65
|
MODEL_RESOLUTION_UNCONFIRMED: '所选模型的具体型号尚未获得当前会话确认,本轮未发送;请重新选择模型或联系管理员。',
|
|
46
66
|
REMOTE_CLEANUP_UNCONFIRMED: '原主机会话的清理结果尚未确认,已阻断继续发送;请联系管理员核对,不能自动重开。',
|
|
47
67
|
};
|
|
48
|
-
|
|
68
|
+
if (messages[code])
|
|
69
|
+
return messages[code];
|
|
70
|
+
if (code === 'EMPLOYEE_OPERATION_UNSUPPORTED')
|
|
71
|
+
return '当前本机工作区暂不支持单次运行和断开后的冷恢复;请使用会话模式新建会话,原记录保留可查。';
|
|
72
|
+
if (code === 'AW_EMPLOYEE_COLD_RESUME_UNSUPPORTED' || code === 'EMPLOYEE_RECOVERY_UNSUPPORTED')
|
|
73
|
+
return '当前版本暂不支持恢复已关闭的本机执行会话;请新建会话,原记录保留可查。';
|
|
74
|
+
if (code === 'AW_EMPLOYEE_HOST_AUXILIARY_CONFIG_UNSUPPORTED')
|
|
75
|
+
return '当前主机启用的MCP、hooks或插件尚未支持改到员工电脑,本轮没有执行,请联系管理员。';
|
|
76
|
+
if (code === 'AW_EMPLOYEE_HOST_PROJECT_CONFIG_UNSUPPORTED')
|
|
77
|
+
return '主机上存在与本机工作区路径重合的项目配置,当前版本无法安全应用,本轮没有执行,请联系管理员。';
|
|
78
|
+
if (code === 'AW_EMPLOYEE_EXECUTOR_MCP_UNSUPPORTED')
|
|
79
|
+
return '本机工作区配置了附加MCP工具,当前版本尚不支持这类工具,本轮没有继续执行。';
|
|
80
|
+
if (['AW_EMPLOYEE_CONFIG_ADMISSION_INVALID', 'AW_EMPLOYEE_CONFIG_ADMISSION_FAILED', 'AW_EMPLOYEE_EXECUTOR_CONFIG_PROTOCOL_INVALID', 'AW_EMPLOYEE_EXECUTOR_CONFIG_READ_FAILED'].includes(code))
|
|
81
|
+
return '无法核验这次本机工作区的助手配置,本轮没有继续执行,请联系管理员查看技术详情。';
|
|
82
|
+
if (code === 'AW_EMPLOYEE_MCP_UNSUPPORTED')
|
|
83
|
+
return '当前版本尚不支持把附加MCP工具改到员工电脑,本轮没有执行。';
|
|
84
|
+
if (code === 'AW_EMPLOYEE_HOST_AUXILIARY_UNSUPPORTED')
|
|
85
|
+
return '这项辅助操作尚未支持在员工电脑执行,本轮没有执行。';
|
|
86
|
+
if (code === 'AW_EMPLOYEE_SLASH_COMMAND_UNSUPPORTED')
|
|
87
|
+
return '这项快捷命令暂不支持本机执行,请直接描述任务。';
|
|
88
|
+
if (code === 'AW_EMPLOYEE_HOST_FILE_REPORT_UNSUPPORTED')
|
|
89
|
+
return '这项文件报告暂不支持本机工作区;请查看对话和技术详情中的已有执行记录。';
|
|
90
|
+
if (code === 'AW_EMPLOYEE_CONFIG_DISCOVERY_OVERRIDE_UNSUPPORTED')
|
|
91
|
+
return '当前助手的项目配置覆盖与本机执行不兼容,本轮没有执行,请联系管理员。';
|
|
92
|
+
if (code === 'EMPLOYEE_PLATFORM_UNSUPPORTED')
|
|
93
|
+
return brand === 'claude' ? '本机执行目前只支持Mac;当前设备尚不支持,本轮未执行。' : '当前版本的Codex本机执行仅验证了Apple芯片Mac,本轮没有执行。';
|
|
94
|
+
if (code.startsWith('AW_EMPLOYEE_BINARY_'))
|
|
95
|
+
return 'Codex本机执行组件缺失或校验失败,本轮没有执行,请联系管理员。';
|
|
96
|
+
if (code.startsWith('AW_EMPLOYEE_') || code.startsWith('EMPLOYEE_'))
|
|
97
|
+
return '员工电脑的执行连接不可用或工作区已变化,本轮已停止;在途操作结果尚待确认,请查看技术详情。';
|
|
98
|
+
return `远程主机暂时无法完成这次操作,请联系管理员。技术详情:诊断代码 ${code}`;
|
|
49
99
|
}
|
package/dist/provider.d.ts
CHANGED
|
@@ -11,9 +11,24 @@ import type { DetailChannel } from './remote-details.ts';
|
|
|
11
11
|
import type { WorkspaceNoticePresentation } from './workspace-notice.ts';
|
|
12
12
|
import { StageTiming } from './stage-timing.ts';
|
|
13
13
|
import type { RecoveryJournal, AcpRecovery } from './recovery.ts';
|
|
14
|
+
import type { EmployeeProviderOptions } from './employee-provider.ts';
|
|
15
|
+
import { type WorkspaceSourceOptions, type SourceAdmissionStage } from './workspace-source.ts';
|
|
16
|
+
import type { LocalUserTurnAcceptance } from '@ai-workbench/connect/api';
|
|
17
|
+
import type { EmployeeRouting } from './employee-relay.ts';
|
|
18
|
+
export interface EmployeeExecution {
|
|
19
|
+
hostCwd: string;
|
|
20
|
+
routing: EmployeeRouting;
|
|
21
|
+
notice: string;
|
|
22
|
+
scope: Record<string, unknown>;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
beginTurn(signal: AbortSignal): Promise<void>;
|
|
25
|
+
endTurn(): void;
|
|
26
|
+
validate(signal?: AbortSignal): Promise<void>;
|
|
27
|
+
}
|
|
14
28
|
export interface Runtime {
|
|
15
29
|
identity: string;
|
|
16
30
|
cwd: string;
|
|
31
|
+
execution?: EmployeeExecution;
|
|
17
32
|
acpTransport?: {
|
|
18
33
|
writable: WritableStream<Uint8Array>;
|
|
19
34
|
readable: ReadableStream<Uint8Array>;
|
|
@@ -39,13 +54,29 @@ export interface Runtime {
|
|
|
39
54
|
turn: string | number | null;
|
|
40
55
|
step: string | number | null;
|
|
41
56
|
};
|
|
57
|
+
employeeExecutionMeta?: {
|
|
58
|
+
version: 1;
|
|
59
|
+
bindingId: string;
|
|
60
|
+
workspaceId: string;
|
|
61
|
+
cwd: string;
|
|
62
|
+
acpMaxMessageBytes: number;
|
|
63
|
+
};
|
|
64
|
+
employeeExecution?: (params: unknown, signal?: AbortSignal) => Promise<Record<string, unknown>>;
|
|
65
|
+
employeePresentation?: string;
|
|
42
66
|
}
|
|
43
67
|
export interface RuntimeRequest extends Invocation {
|
|
68
|
+
recordEarlyFailure?: (error: unknown, stage: SourceAdmissionStage) => Promise<void>;
|
|
69
|
+
userInputIds?: readonly string[];
|
|
70
|
+
localAcceptance?: Readonly<LocalUserTurnAcceptance>;
|
|
71
|
+
localAdmissionCheck?: () => void;
|
|
72
|
+
readonly localAcceptanceId?: string;
|
|
73
|
+
userMessageIds?: readonly string[];
|
|
44
74
|
recoveryJournal?: RecoveryJournal;
|
|
45
75
|
detailChannel?: DetailChannel;
|
|
46
76
|
validateContext?: (ignoreCancellation?: boolean) => Promise<void>;
|
|
47
77
|
verifyRecovery?: () => Promise<boolean>;
|
|
48
78
|
acpMaxMessageBytes?: number;
|
|
79
|
+
employeeScope?: string;
|
|
49
80
|
contextSha256?: string;
|
|
50
81
|
resumeSessionId?: string;
|
|
51
82
|
recoveryCheckpointId?: string;
|
|
@@ -106,7 +137,7 @@ type Result<T> = {
|
|
|
106
137
|
} & Pick<ConnectNeed, 'officialReason'>;
|
|
107
138
|
};
|
|
108
139
|
/** 消费正式connect-api 0.7公开子集;X不响应L登录票据、不直接调用登录。 */
|
|
109
|
-
export interface ConnectApi extends Pick<LConnectApi, 'getConnectionState' | 'getLoginPromptState' | 'requestLoginPrompt' | 'refreshLoginPrompts'>, Partial<NativeWorkspaceApi>, Partial<Pick<LConnectApi, 'getWorkspaceBindingState' | 'getModelCatalogTarget' | 'onConnectionState' | 'onLoginPromptState' | 'appendRemoteConversationDetail' | 'getRemoteConversationDetailReceipt' | 'listRemoteConversationDetails'>> {
|
|
140
|
+
export interface ConnectApi extends Pick<LConnectApi, 'getConnectionState' | 'getLoginPromptState' | 'requestLoginPrompt' | 'refreshLoginPrompts'>, Partial<NativeWorkspaceApi>, Partial<Pick<LConnectApi, 'getWorkspaceBindingState' | 'getModelCatalogTarget' | 'onConnectionState' | 'onLoginPromptState' | 'appendRemoteConversationDetail' | 'getRemoteConversationDetailReceipt' | 'listRemoteConversationDetails' | 'getWorkspaceLocationState' | 'getLocalWorkspaceContext' | 'onWorkspaceLocationState' | 'acceptLocalWorkspaceUserTurn' | 'selectWorkspaceLocation'>> {
|
|
110
141
|
getState?(): ConnectState;
|
|
111
142
|
getSshTarget(): Promise<Result<ConnectTarget>>;
|
|
112
143
|
checkLogin(provider: LoginProvider): Promise<Result<{
|
|
@@ -130,7 +161,7 @@ export declare function requireLoginCoordinator(api: ConnectApi): void;
|
|
|
130
161
|
export declare function requireLoginState(state: LoginPromptState): void;
|
|
131
162
|
declare module '@deepseek-ai/cordis' {
|
|
132
163
|
interface Context {
|
|
133
|
-
aiWorkbenchConnect:
|
|
164
|
+
aiWorkbenchConnect: LConnectApi;
|
|
134
165
|
}
|
|
135
166
|
interface Events {
|
|
136
167
|
'ai-workbench/connection-state': (state: ConnectionState) => void;
|
|
@@ -152,11 +183,17 @@ export interface NoticeConsumer {
|
|
|
152
183
|
reconcile(request: RuntimeRequest, api: ConnectApi): Promise<void>;
|
|
153
184
|
deliver(notice: WorkspaceNotice, resolved: SessionProjectTarget, request: RuntimeRequest, api: ConnectApi): Promise<WorkspaceNoticePresentation | void>;
|
|
154
185
|
}
|
|
186
|
+
export type LocalRuntimeProvider = ((request: RuntimeRequest, api: ConnectApi, connection: ConnectionState, target: ConnectTarget) => Promise<Runtime | undefined>) & {
|
|
187
|
+
admit?: (request: RuntimeRequest, api: ConnectApi, connection: ConnectionState, target: ConnectTarget) => Promise<void>;
|
|
188
|
+
};
|
|
155
189
|
export declare function sameTarget(a: ConnectTarget, b: ConnectTarget): boolean;
|
|
156
190
|
export declare function connectProvider(get: () => ConnectApi | undefined, options?: {
|
|
157
191
|
agent?: AgentDefinition;
|
|
158
192
|
ask?: Interaction;
|
|
159
193
|
notice?: NoticeConsumer;
|
|
194
|
+
workspaceSource?: WorkspaceSourceOptions;
|
|
195
|
+
employee?: EmployeeProviderOptions;
|
|
196
|
+
local?: LocalRuntimeProvider;
|
|
160
197
|
details?: (request: RuntimeRequest, api: ConnectApi, connection: ConnectionState, target: ConnectTarget) => Promise<DetailChannel>;
|
|
161
198
|
open?: (target: ConnectTarget, project: Project, request: RuntimeRequest) => Promise<Runtime>;
|
|
162
199
|
}): RuntimeProvider;
|
package/dist/provider.js
CHANGED
|
@@ -3,6 +3,7 @@ import { NeedError } from "./remote.js";
|
|
|
3
3
|
import { CONNECTION_REQUIRED, TEST_HOST_NOTICE } from "./presentation.js";
|
|
4
4
|
import { CODEX_AGENT } from "./agents.js";
|
|
5
5
|
import { StageTiming } from "./stage-timing.js";
|
|
6
|
+
import { confirmLegacyWorkspaceSource } from "./workspace-source.js";
|
|
6
7
|
export function requireLoginCoordinator(api) {
|
|
7
8
|
if (!['getConnectionState', 'getLoginPromptState', 'requestLoginPrompt', 'refreshLoginPrompts'].every(key => typeof api[key] === 'function'))
|
|
8
9
|
throw new NeedError('CONNECT_API_MISMATCH', '我需要 connect-api 0.6.1 的统一登录询问与连接状态能力', 'L >=0.6.1');
|
|
@@ -154,6 +155,9 @@ export function sameTarget(a, b) {
|
|
|
154
155
|
}
|
|
155
156
|
export function connectProvider(get, options = {}) {
|
|
156
157
|
const agent = options.agent ?? CODEX_AGENT;
|
|
158
|
+
// Keep the legacy source SDK wrapper's strip-only import graph free of broker runtime syntax.
|
|
159
|
+
// Cache the initialization promise so concurrent sessions share this provider's binding lifetimes.
|
|
160
|
+
let employee;
|
|
157
161
|
const admissions = new WeakMap();
|
|
158
162
|
const preflight = async (request) => {
|
|
159
163
|
request.timing ??= new StageTiming();
|
|
@@ -165,19 +169,49 @@ export function connectProvider(get, options = {}) {
|
|
|
165
169
|
requireLoginCoordinator(api);
|
|
166
170
|
if (request.mode === 'one-shot' && !agent.oneShot)
|
|
167
171
|
throw new NeedError('MODE_UNSUPPORTED', '此远程模型仅支持会话模式', '使用会话模式');
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
172
|
+
const confirmedSource = options.workspaceSource ? await confirmLegacyWorkspaceSource(get, api, request, options.workspaceSource) : undefined;
|
|
173
|
+
let sourceStage = 'input-acceptance';
|
|
174
|
+
if (confirmedSource)
|
|
175
|
+
request.recordEarlyFailure = (error, stage) => confirmedSource.recordFailure(error, stage);
|
|
176
|
+
try {
|
|
177
|
+
confirmedSource?.assertCurrent();
|
|
178
|
+
if (options.employee) {
|
|
179
|
+
const module = await import("./employee-provider.js");
|
|
180
|
+
request.localAcceptance = await module.acceptEmployeeUserTurn(get, options.employee, agent, api, request);
|
|
181
|
+
}
|
|
182
|
+
confirmedSource?.assertCurrent();
|
|
183
|
+
if (confirmedSource) {
|
|
184
|
+
const previous = request.localAdmissionCheck;
|
|
185
|
+
request.localAdmissionCheck = () => { confirmedSource.assertCurrent(); previous?.(); };
|
|
186
|
+
}
|
|
187
|
+
sourceStage = 'login-readiness';
|
|
188
|
+
const { target, connection } = await request.timing.run('login-ready', async () => {
|
|
189
|
+
request.localAdmissionCheck?.();
|
|
190
|
+
const before = api.getConnectionState();
|
|
191
|
+
const target = await ensureReady(api, agent.loginProvider, request.signal);
|
|
192
|
+
validateTarget(target);
|
|
193
|
+
request.signal?.throwIfAborted();
|
|
194
|
+
if (get() !== api)
|
|
195
|
+
throw new NeedError('CONNECTION_LOST', '远程主机连接组件已更换', '当前员工连接');
|
|
196
|
+
const connection = await waitForLogin(api, agent.loginProvider, before, get, request.signal);
|
|
197
|
+
return { target, connection };
|
|
198
|
+
});
|
|
199
|
+
if (request.identity && request.identity !== identity(target))
|
|
200
|
+
throw new NeedError('SESSION_CONTEXT_MISMATCH', '员工主机与原会话不一致,请新建会话', '原员工主机');
|
|
201
|
+
if (options.employee && request.localAcceptance)
|
|
202
|
+
await (await import("./employee-provider.js")).validateEmployeeUserTurn(get, options.employee, api, request, connection);
|
|
203
|
+
request.localAdmissionCheck?.();
|
|
204
|
+
admissions.set(request, { api, target, connection });
|
|
205
|
+
sourceStage = 'local-admission';
|
|
206
|
+
await options.local?.admit?.(request, api, connection, target);
|
|
207
|
+
confirmedSource?.assertCurrent();
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
admissions.delete(request);
|
|
211
|
+
request.recordEarlyFailure = undefined;
|
|
212
|
+
await confirmedSource?.recordFailure(error, sourceStage);
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
181
215
|
};
|
|
182
216
|
const resolve = async (request) => {
|
|
183
217
|
if (!admissions.has(request))
|
|
@@ -187,8 +221,30 @@ export function connectProvider(get, options = {}) {
|
|
|
187
221
|
const { api, target, connection } = admitted;
|
|
188
222
|
const stable = () => request.timing.run('connection-confirm', () => waitForLogin(api, agent.loginProvider, connection, get, request.signal));
|
|
189
223
|
await stable();
|
|
224
|
+
request.localAdmissionCheck?.();
|
|
190
225
|
if (!request.sessionId)
|
|
191
226
|
throw new NeedError('PROJECT_BINDING_REQUIRED', '我需要当前会话和已选择的工作区,请使用原生工作区入口', 'DSH会话与原生工作区');
|
|
227
|
+
if (options.employee) {
|
|
228
|
+
employee ??= import("./employee-provider.js").then(module => module.createEmployeeProvider(get, options, agent));
|
|
229
|
+
const local = await (await employee)({ api, target, connection, request, stable: signal => request.timing.run('connection-confirm', () => waitForLogin(api, agent.loginProvider, connection, get, signal)) });
|
|
230
|
+
if (local) {
|
|
231
|
+
local.loginRequired = () => offerLogin(api, request, get, agent);
|
|
232
|
+
return local;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (agent.loginProvider === 'claude' && options.local) {
|
|
236
|
+
const runtime = await options.local(request, api, connection, target);
|
|
237
|
+
if (runtime) {
|
|
238
|
+
try {
|
|
239
|
+
await stable();
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
await runtime.dispose?.();
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
return runtime;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
192
248
|
await options.notice?.reconcile(request, api);
|
|
193
249
|
await stable();
|
|
194
250
|
const resolved = await request.timing.run('workspace-resolve', async () => unwrap(await awaitL(() => api.resolveSessionProject(request.sessionId, { provider: agent.loginProvider, purpose: 'user-turn' }), request.signal)));
|
package/dist/readiness.js
CHANGED
|
@@ -46,7 +46,7 @@ function details(target) {
|
|
|
46
46
|
return [{ label: '主机配置', value: h.enforcement }, { label: '检查退出码', value: String(h.checkExitCode) }, ...h.failedChecks.map(item => ({ label: '未通过检查', value: `${item.id}(${item.status})` })), ...h.unverifiedChecks.map(item => ({ label: '未核实检查', value: `${item.id}(${item.status})` }))];
|
|
47
47
|
}
|
|
48
48
|
function blocked(code, technical = [], agent = CODEX_AGENT) {
|
|
49
|
-
return { phase: 'blocked', loginStatus: 'unknown', title: `无法确认 ${agent.name.zh} 登录状态`, message: employeeFailure(code), technical: [...technical, { label: '诊断代码', value: code }] };
|
|
49
|
+
return { phase: 'blocked', loginStatus: 'unknown', title: `无法确认 ${agent.name.zh} 登录状态`, message: employeeFailure(code, agent.loginProvider), technical: [...technical, { label: '诊断代码', value: code }] };
|
|
50
50
|
}
|
|
51
51
|
/** L owns both evidence and inquiry. Missing per-brand evidence is never false. */
|
|
52
52
|
function snapshot(api, agent = CODEX_AGENT) {
|
package/dist/recovery.d.ts
CHANGED
|
@@ -27,8 +27,21 @@ export interface RecoveryJournal {
|
|
|
27
27
|
write(state: RecoveryState): Promise<void>;
|
|
28
28
|
}
|
|
29
29
|
export declare function validateRecovery(value: unknown): RecoveryState;
|
|
30
|
-
|
|
30
|
+
export declare const codexContextPolicy: Readonly<{
|
|
31
|
+
version: 1;
|
|
32
|
+
scope: "new-session";
|
|
33
|
+
mode: "append";
|
|
34
|
+
content: "text";
|
|
35
|
+
resume: "omit-only";
|
|
36
|
+
changed: "new-session-required";
|
|
37
|
+
mapping: "developerInstructions";
|
|
38
|
+
nativeCliUnmodified: true;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function supportsCodexContextPolicy(capabilities: AgentCapabilities): boolean;
|
|
41
|
+
/** Known capabilities plus the exact Codex policy whitelist; no arbitrary metadata. */
|
|
31
42
|
export declare function recoveryCapabilities(c: AgentCapabilities): AgentCapabilities;
|
|
32
43
|
export declare function recoveryKey(value: unknown): string;
|
|
44
|
+
/** Preserve legacy lookup bytes; each employee endpoint scope owns a separate journal. */
|
|
45
|
+
export declare function runtimeRecoveryKey(identity: string, provider: string, sessionId: string | undefined, employeeScope?: string): string;
|
|
33
46
|
/** History is immutable; current is atomically replaced only after fsync. No expiry. */
|
|
34
47
|
export declare function recoveryJournal(profileDir: string, key: string): RecoveryJournal;
|
package/dist/recovery.js
CHANGED
|
@@ -15,11 +15,20 @@ export function validateRecovery(value) {
|
|
|
15
15
|
throw invalid();
|
|
16
16
|
return structuredClone(s);
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
export const codexContextPolicy = Object.freeze({ version: 1, scope: 'new-session', mode: 'append', content: 'text', resume: 'omit-only', changed: 'new-session-required', mapping: 'developerInstructions', nativeCliUnmodified: true });
|
|
19
|
+
export function supportsCodexContextPolicy(capabilities) {
|
|
20
|
+
const policy = capabilities._meta?.aiWorkbenchSystemPrompt;
|
|
21
|
+
return !!policy && typeof policy === 'object' && !Array.isArray(policy) && Object.keys(policy).length === Object.keys(codexContextPolicy).length && Object.entries(codexContextPolicy).every(([key, value]) => policy[key] === value);
|
|
22
|
+
}
|
|
23
|
+
/** Known capabilities plus the exact Codex policy whitelist; no arbitrary metadata. */
|
|
19
24
|
export function recoveryCapabilities(c) {
|
|
20
|
-
return { loadSession: c.loadSession === true, promptCapabilities: { image: c.promptCapabilities?.image === true, audio: c.promptCapabilities?.audio === true, embeddedContext: c.promptCapabilities?.embeddedContext === true }, sessionCapabilities: { ...(c.sessionCapabilities?.resume ? { resume: {} } : {}), ...(c.sessionCapabilities?.systemPrompt ? { systemPrompt: {} } : {}) } };
|
|
25
|
+
return { loadSession: c.loadSession === true, promptCapabilities: { image: c.promptCapabilities?.image === true, audio: c.promptCapabilities?.audio === true, embeddedContext: c.promptCapabilities?.embeddedContext === true }, sessionCapabilities: { ...(c.sessionCapabilities?.resume ? { resume: {} } : {}), ...(c.sessionCapabilities?.systemPrompt ? { systemPrompt: {} } : {}) }, ...(supportsCodexContextPolicy(c) ? { _meta: { aiWorkbenchSystemPrompt: { ...codexContextPolicy } } } : {}) };
|
|
21
26
|
}
|
|
22
27
|
export function recoveryKey(value) { return createHash('sha256').update(JSON.stringify(value)).digest('hex'); }
|
|
28
|
+
/** Preserve legacy lookup bytes; each employee endpoint scope owns a separate journal. */
|
|
29
|
+
export function runtimeRecoveryKey(identity, provider, sessionId, employeeScope) {
|
|
30
|
+
return recoveryKey([identity, provider, sessionId, ...(employeeScope ? ['employee', employeeScope] : [])]);
|
|
31
|
+
}
|
|
23
32
|
/** History is immutable; current is atomically replaced only after fsync. No expiry. */
|
|
24
33
|
export function recoveryJournal(profileDir, key) {
|
|
25
34
|
if (!/^[a-f0-9]{64}$/.test(key))
|
package/dist/remote-details.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface DetailRequest {
|
|
|
18
18
|
provider: 'claude' | 'codex';
|
|
19
19
|
identity: string;
|
|
20
20
|
connection: Connection;
|
|
21
|
+
readonly localAcceptanceId?: string;
|
|
21
22
|
}
|
|
22
23
|
export declare function privateDirectory(path: string): Promise<void>;
|
|
23
24
|
export declare function durableWrite(path: string, value: unknown): Promise<void>;
|
|
@@ -26,6 +27,12 @@ export declare function createRemoteDetails(options: {
|
|
|
26
27
|
profileDir: string;
|
|
27
28
|
getApi: () => RemoteDetailsApi | undefined;
|
|
28
29
|
}): {
|
|
30
|
+
recordLocalFailure(request: {
|
|
31
|
+
sessionId?: string;
|
|
32
|
+
provider: "claude" | "codex";
|
|
33
|
+
code: string;
|
|
34
|
+
observation?: DetailObservation;
|
|
35
|
+
}): Promise<void>;
|
|
29
36
|
recordLoginFailure(request: {
|
|
30
37
|
sessionId?: string;
|
|
31
38
|
code: string;
|
package/dist/remote-details.js
CHANGED
|
@@ -91,6 +91,12 @@ export function createRemoteDetails(options) {
|
|
|
91
91
|
const scopes = new Map(), loginScopes = new Map();
|
|
92
92
|
const initialize = async () => { await privateDirectory(join(options.profileDir, 'ai-workbench-remote-agent')); await privateDirectory(root); };
|
|
93
93
|
return {
|
|
94
|
+
async recordLocalFailure(request) {
|
|
95
|
+
await initialize();
|
|
96
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(request.code))
|
|
97
|
+
throw failure('REMOTE_DETAIL_STORAGE_FAILED');
|
|
98
|
+
await durableWrite(join(root, randomUUID() + '.json'), { version: 1, kind: 'local-admission', code: request.code, provider: request.provider, sessionHash: request.sessionId ? hash(request.sessionId) : null, ...request.observation ? { observation: sanitizeObservation(request.observation, true) } : {} });
|
|
99
|
+
},
|
|
94
100
|
async recordLoginFailure(request) {
|
|
95
101
|
await initialize();
|
|
96
102
|
const eventId = randomUUID(), diagnostic = request.diagnostic;
|
|
@@ -221,6 +227,10 @@ export function createRemoteDetails(options) {
|
|
|
221
227
|
return { eventId, recorded: true };
|
|
222
228
|
},
|
|
223
229
|
async open(request) {
|
|
230
|
+
request = structuredClone(request);
|
|
231
|
+
if (request.localAcceptanceId !== undefined && (typeof request.localAcceptanceId !== 'string' || !request.localAcceptanceId.trim()))
|
|
232
|
+
throw failure('REMOTE_DETAIL_SCOPE_INVALID');
|
|
233
|
+
const localScope = request.localAcceptanceId === undefined ? undefined : Object.freeze({ localAcceptanceId: request.localAcceptanceId });
|
|
224
234
|
if (!request.sessionId || !request.identity || !request.connection.instanceId || !Number.isSafeInteger(request.connection.epoch) || request.connection.epoch < 0 || !['claude', 'codex'].includes(request.provider))
|
|
225
235
|
throw failure('REMOTE_DETAIL_SCOPE_INVALID');
|
|
226
236
|
try {
|
|
@@ -348,7 +358,7 @@ export function createRemoteDetails(options) {
|
|
|
348
358
|
stable();
|
|
349
359
|
let response;
|
|
350
360
|
try {
|
|
351
|
-
response = await api.appendRemoteConversationDetail(structuredClone(detail));
|
|
361
|
+
response = await (localScope === undefined ? api.appendRemoteConversationDetail(structuredClone(detail)) : api.appendRemoteConversationDetail(structuredClone(detail), localScope));
|
|
352
362
|
}
|
|
353
363
|
catch {
|
|
354
364
|
stable();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { WorkspaceSourceOptions } from './workspace-source.ts';
|
|
3
|
+
/** Official same-turn one-shot question; never changes policy or fabricates a tool call. */
|
|
4
|
+
export declare function nativeWorkspaceSourceConfirmation(ctx: Context): WorkspaceSourceOptions['confirm'];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
2
|
+
/** Official same-turn one-shot question; never changes policy or fabricates a tool call. */
|
|
3
|
+
export function nativeWorkspaceSourceConfirmation(ctx) {
|
|
4
|
+
return async (question, request) => {
|
|
5
|
+
const agent = request.sessionId ? ctx.get('agents')?.get(SessionId(request.sessionId)) : undefined;
|
|
6
|
+
const approval = ctx.get('approval');
|
|
7
|
+
const session = request.sessionId ? ctx.get('sessions')?.get(SessionId(request.sessionId)) : undefined;
|
|
8
|
+
if (!agent || !approval || !session || agent.session !== session)
|
|
9
|
+
return 'unavailable';
|
|
10
|
+
return approval.request({ agent, toolName: question.action, reason: question.question, signal: request.signal });
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ConnectApi, RuntimeRequest } from './provider.ts';
|
|
2
|
+
import type { DetailObservation } from './conversation-details.ts';
|
|
3
|
+
export type SourceDecision = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable';
|
|
4
|
+
export type SourceAdmissionStage = 'source-context' | 'source-confirmation' | 'input-acceptance' | 'login-readiness' | 'local-admission' | 'input-prepare' | 'model-catalog' | 'runtime-resolve';
|
|
5
|
+
export interface WorkspaceSourceAdmission {
|
|
6
|
+
assertCurrent(): void;
|
|
7
|
+
recordFailure(error: unknown, stage: SourceAdmissionStage): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export interface WorkspaceSourceOptions {
|
|
10
|
+
workspace(request: RuntimeRequest): Promise<{
|
|
11
|
+
id: string;
|
|
12
|
+
path: string;
|
|
13
|
+
}>;
|
|
14
|
+
currentUserTurn(request: RuntimeRequest): {
|
|
15
|
+
turn: number;
|
|
16
|
+
userMessageId: string;
|
|
17
|
+
};
|
|
18
|
+
confirm(question: {
|
|
19
|
+
question: string;
|
|
20
|
+
action: string;
|
|
21
|
+
}, request: RuntimeRequest): Promise<SourceDecision>;
|
|
22
|
+
record(request: RuntimeRequest, observation: DetailObservation): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** Explicit human source transition only. No source selection is inferred from a folder path. */
|
|
25
|
+
export declare function confirmLegacyWorkspaceSource(get: () => ConnectApi | undefined, api: ConnectApi, request: RuntimeRequest, hooks: WorkspaceSourceOptions): Promise<WorkspaceSourceAdmission | undefined>;
|