@ai-workbench/remote-agent 0.8.17 → 0.8.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -9
- package/dist/acp.d.ts +3 -0
- package/dist/acp.js +36 -8
- package/dist/approval-label.d.ts +2 -1
- package/dist/approval-label.js +9 -1
- package/dist/client.js +2 -2
- package/dist/close-diagnostics.d.ts +1 -1
- package/dist/conversation-details.js +12 -7
- package/dist/employee-exec.d.ts +5 -0
- package/dist/employee-exec.js +12 -2
- package/dist/employee-info.d.ts +13 -0
- package/dist/employee-info.js +28 -0
- package/dist/employee-provider.js +23 -14
- package/dist/employee-reasons.d.ts +4 -0
- package/dist/employee-reasons.js +61 -0
- package/dist/employee-ssh.d.ts +1 -1
- package/dist/employee-ssh.js +1 -1
- package/dist/failure-evidence.d.ts +4 -0
- package/dist/failure-evidence.js +22 -3
- package/dist/index.d.ts +8 -2
- package/dist/index.js +77 -26
- package/dist/input.d.ts +11 -0
- package/dist/input.js +48 -5
- package/dist/presentation.js +1 -1
- package/dist/provider.d.ts +5 -0
- package/dist/provider.js +1 -0
- package/dist/stage-timing.d.ts +4 -0
- package/dist/stage-timing.js +27 -5
- package/locale/en.json +2 -2
- package/locale/zh.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Approved protocol codes only; remote exception text is never a reason. */
|
|
2
|
+
export const employeeAdapterCodes = new Set([
|
|
3
|
+
"AW_EMPLOYEE_ADDITIONAL_DIRECTORIES_UNSUPPORTED",
|
|
4
|
+
"AW_EMPLOYEE_SLASH_COMMAND_UNSUPPORTED",
|
|
5
|
+
"AW_EMPLOYEE_HOST_FILE_REPORT_UNSUPPORTED",
|
|
6
|
+
"AW_EMPLOYEE_CONFIG_DISCOVERY_OVERRIDE_UNSUPPORTED",
|
|
7
|
+
"AW_EMPLOYEE_CONFIG_ADMISSION_INVALID",
|
|
8
|
+
"AW_EMPLOYEE_CONFIG_ADMISSION_FAILED",
|
|
9
|
+
"AW_EMPLOYEE_HOST_PROJECT_CONFIG_UNSUPPORTED",
|
|
10
|
+
"AW_EMPLOYEE_EXECUTOR_MCP_UNSUPPORTED",
|
|
11
|
+
"AW_EMPLOYEE_EXECUTOR_CONFIG_PROTOCOL_INVALID",
|
|
12
|
+
"AW_EMPLOYEE_EXECUTOR_CONFIG_READ_FAILED",
|
|
13
|
+
"AW_EMPLOYEE_AGENT_CONFIG_UNAVAILABLE",
|
|
14
|
+
"AW_EMPLOYEE_BINDING_INVALID",
|
|
15
|
+
"AW_EMPLOYEE_BINDING_MISMATCH",
|
|
16
|
+
"AW_EMPLOYEE_CAPABILITY_REQUIRED",
|
|
17
|
+
"AW_EMPLOYEE_CLOSE_FAILED",
|
|
18
|
+
"AW_EMPLOYEE_CLOSE_RESPONSE_INVALID",
|
|
19
|
+
"AW_EMPLOYEE_COLD_RESUME_UNSUPPORTED",
|
|
20
|
+
"AW_EMPLOYEE_CWD_MISMATCH",
|
|
21
|
+
"AW_EMPLOYEE_ENVIRONMENT_REGISTRATION_FAILED",
|
|
22
|
+
"AW_EMPLOYEE_ENVIRONMENT_REQUIRED",
|
|
23
|
+
"AW_EMPLOYEE_EXECUTION_CLOSED",
|
|
24
|
+
"AW_EMPLOYEE_EXECUTION_ID_REQUIRED",
|
|
25
|
+
"AW_EMPLOYEE_HOST_AUXILIARY_CONFIG_UNSUPPORTED",
|
|
26
|
+
"AW_EMPLOYEE_HOST_AUXILIARY_UNSUPPORTED",
|
|
27
|
+
"AW_EMPLOYEE_HOST_FALLBACK_FORBIDDEN",
|
|
28
|
+
"AW_EMPLOYEE_MCP_UNSUPPORTED",
|
|
29
|
+
"AW_EMPLOYEE_MESSAGE_INVALID",
|
|
30
|
+
"AW_EMPLOYEE_MESSAGE_TOO_LARGE",
|
|
31
|
+
"AW_EMPLOYEE_OPEN_RESPONSE_INVALID",
|
|
32
|
+
"AW_EMPLOYEE_READ_MESSAGE_INVALID",
|
|
33
|
+
"AW_EMPLOYEE_READ_RESPONSE_INVALID",
|
|
34
|
+
"AW_EMPLOYEE_RELAY_ADDRESS_INVALID",
|
|
35
|
+
"AW_EMPLOYEE_RELAY_FAILED",
|
|
36
|
+
"AW_EMPLOYEE_REQUEST_INVALID",
|
|
37
|
+
"AW_EMPLOYEE_SESSION_UNKNOWN",
|
|
38
|
+
"AW_EMPLOYEE_WIRE_FAILED",
|
|
39
|
+
"AW_EMPLOYEE_WRITE_RESPONSE_INVALID"
|
|
40
|
+
]);
|
|
41
|
+
export const employeeOpenCauseCodes = new Set([
|
|
42
|
+
"AW_EMPLOYEE_BINARY_UNAVAILABLE",
|
|
43
|
+
"AW_EMPLOYEE_BINARY_HASH_FAILED",
|
|
44
|
+
"AW_EMPLOYEE_BINARY_HASH_MISMATCH",
|
|
45
|
+
"AW_EMPLOYEE_BINARY_VERSION_FAILED",
|
|
46
|
+
"AW_EMPLOYEE_BINARY_VERSION_MISMATCH",
|
|
47
|
+
"EMPLOYEE_CONTEXT_CHANGED",
|
|
48
|
+
"EMPLOYEE_ENDPOINT_INVALID",
|
|
49
|
+
"EMPLOYEE_BINARY_REQUIRED",
|
|
50
|
+
"EMPLOYEE_EXECUTION_UNSUPPORTED",
|
|
51
|
+
"EMPLOYEE_EXEC_PROTOCOL_INVALID",
|
|
52
|
+
"EMPLOYEE_EXEC_IDENTITY_MISMATCH",
|
|
53
|
+
"EMPLOYEE_EXEC_CLOSED",
|
|
54
|
+
"EMPLOYEE_EXEC_UNKNOWN",
|
|
55
|
+
"EMPLOYEE_EXEC_ALREADY_OPEN",
|
|
56
|
+
"EMPLOYEE_EXEC_READ_BUSY",
|
|
57
|
+
"ACP_MESSAGE_TOO_LARGE",
|
|
58
|
+
"ABORTED",
|
|
59
|
+
"SETTINGS_REQUIRED"
|
|
60
|
+
]);
|
|
61
|
+
export function trustedEmployeeReason(value) { return typeof value === 'string' && (employeeAdapterCodes.has(value) || employeeOpenCauseCodes.has(value)) ? value : undefined; }
|
package/dist/employee-ssh.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface EmployeeSshOptions {
|
|
|
18
18
|
}
|
|
19
19
|
/** Fixed bootstrap only; cwd for every native operation remains in its original RPC. */
|
|
20
20
|
export declare function employeeExecServerCommand(binary: string, binarySha256: string): string;
|
|
21
|
-
/**
|
|
21
|
+
/** Explicit read-only probe; production dispatch verifies S once in transport open. */
|
|
22
22
|
export declare function validateEmployeeSshTarget(options: EmployeeSshOptions, signal?: AbortSignal): Promise<void>;
|
|
23
23
|
/** Outer L-authenticated host SSH, then S-authenticated employee SSH; no host execution fallback. */
|
|
24
24
|
export declare function createEmployeeSshTransport(options: EmployeeSshOptions, signal?: AbortSignal): Promise<EmployeeExecutionTransport>;
|
package/dist/employee-ssh.js
CHANGED
|
@@ -99,7 +99,7 @@ async function currentTarget(options, signal) {
|
|
|
99
99
|
await gate();
|
|
100
100
|
return { io, args, gate };
|
|
101
101
|
}
|
|
102
|
-
/**
|
|
102
|
+
/** Explicit read-only probe; production dispatch verifies S once in transport open. */
|
|
103
103
|
export async function validateEmployeeSshTarget(options, signal = new AbortController().signal) { await currentTarget(options, signal); }
|
|
104
104
|
/** Outer L-authenticated host SSH, then S-authenticated employee SSH; no host execution fallback. */
|
|
105
105
|
export async function createEmployeeSshTransport(options, signal = new AbortController().signal) {
|
|
@@ -6,6 +6,10 @@ export declare function closeErrorCodes(error: unknown, fallback?: string): stri
|
|
|
6
6
|
export declare function closeFailure(code: string, message: string, requires: string, codes: readonly string[]): NeedError;
|
|
7
7
|
/** Saving succeeded: retain the original trusted error type, code and presentation. */
|
|
8
8
|
export declare function withFirstFailureEvidence<T>(primary: T, firstReason: unknown, primaryCode: string): T;
|
|
9
|
+
/** A negotiated close reason is independent evidence, never a replacement error or new gate. */
|
|
10
|
+
export declare function withRemoteFailureEvidence<T>(primary: T, remoteReason: unknown, primaryCode: string): T;
|
|
11
|
+
/** A known turn failure is final even while its original resource is still closing. */
|
|
12
|
+
export declare function withPendingCleanupEvidence<T>(primary: T, firstReason: unknown, primaryCode: string): T;
|
|
9
13
|
/** Preserve causal roles without copying arbitrary exception text into employee output. */
|
|
10
14
|
export declare function withFailureEvidence(primary: unknown, secondary: readonly unknown[], kind: 'diagnostic' | 'cleanup', firstReason?: unknown, primaryCode?: string): NeedError;
|
|
11
15
|
export declare function primaryFailureCode(error: unknown, fallback: string): string;
|
package/dist/failure-evidence.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NeedError } from "./remote.js";
|
|
2
|
+
import { trustedEmployeeReason } from "./employee-reasons.js";
|
|
2
3
|
/** Only typed codes, never exception messages, causes, stderr or RPC data. */
|
|
3
4
|
export function closeCode(error, fallback = 'CLOSE_REASON_UNKNOWN') {
|
|
4
5
|
return error instanceof NeedError && /^[A-Z][A-Z0-9_]*$/.test(error.code) ? error.code : fallback;
|
|
@@ -58,7 +59,24 @@ function retainedEvidence(error) {
|
|
|
58
59
|
export function withFirstFailureEvidence(primary, firstReason, primaryCode) {
|
|
59
60
|
if (primary && typeof primary === 'object' && safeCode(firstReason) && safeCode(primaryCode)) {
|
|
60
61
|
const previous = retainedEvidence(primary);
|
|
61
|
-
evidence.set(primary, Object.freeze({ first: previous?.first ?? firstReason, original: previous?.original ?? Object.freeze([primaryCode]), diagnostic: previous?.diagnostic ?? Object.freeze([]), cleanup: previous?.cleanup ?? Object.freeze([]) }));
|
|
62
|
+
evidence.set(primary, Object.freeze({ ...previous, first: previous?.first ?? firstReason, original: previous?.original ?? Object.freeze([primaryCode]), diagnostic: previous?.diagnostic ?? Object.freeze([]), cleanup: previous?.cleanup ?? Object.freeze([]) }));
|
|
63
|
+
}
|
|
64
|
+
return primary;
|
|
65
|
+
}
|
|
66
|
+
/** A negotiated close reason is independent evidence, never a replacement error or new gate. */
|
|
67
|
+
export function withRemoteFailureEvidence(primary, remoteReason, primaryCode) {
|
|
68
|
+
const remote = trustedEmployeeReason(remoteReason);
|
|
69
|
+
if (primary && typeof primary === 'object' && remote) {
|
|
70
|
+
const previous = retainedEvidence(primary);
|
|
71
|
+
evidence.set(primary, Object.freeze({ ...previous, remote: previous?.remote ?? remote, original: previous?.original ?? Object.freeze([safeCode(primaryCode) ? primaryCode : 'TURN_FAILED']), diagnostic: previous?.diagnostic ?? Object.freeze([]), cleanup: previous?.cleanup ?? Object.freeze([]) }));
|
|
72
|
+
}
|
|
73
|
+
return primary;
|
|
74
|
+
}
|
|
75
|
+
/** A known turn failure is final even while its original resource is still closing. */
|
|
76
|
+
export function withPendingCleanupEvidence(primary, firstReason, primaryCode) {
|
|
77
|
+
if (primary && typeof primary === 'object') {
|
|
78
|
+
const previous = retainedEvidence(primary), first = previous?.first ?? (safeCode(firstReason) ? firstReason : undefined);
|
|
79
|
+
evidence.set(primary, Object.freeze({ ...previous, ...(first ? { first } : {}), original: previous?.original ?? Object.freeze([safeCode(primaryCode) ? primaryCode : 'TURN_FAILED']), diagnostic: previous?.diagnostic ?? Object.freeze([]), cleanup: previous?.cleanup ?? Object.freeze([]), cleanupPending: true }));
|
|
62
80
|
}
|
|
63
81
|
return primary;
|
|
64
82
|
}
|
|
@@ -67,7 +85,8 @@ export function withFailureEvidence(primary, secondary, kind, firstReason, prima
|
|
|
67
85
|
const previous = retainedEvidence(primary), first = previous?.first ?? (safeCode(firstReason) ? firstReason : undefined);
|
|
68
86
|
const original = previous?.original ?? (safeCode(primaryCode) ? [primaryCode] : closeErrorCodes(primary, 'TURN_FAILED'));
|
|
69
87
|
const secondaryFacts = secondary.map(retainedEvidence), added = [...new Set(secondary.flatMap((error, index) => secondaryFacts[index] ? secondaryFacts[index].original.filter(code => !original.includes(code)) : closeErrorCodes(error, kind === 'diagnostic' ? 'DIAGNOSTIC_WRITE_FAILED' : 'REMOTE_CLEANUP_UNCONFIRMED')))];
|
|
70
|
-
const
|
|
88
|
+
const remote = previous?.remote ?? secondaryFacts.find(f => f?.remote)?.remote;
|
|
89
|
+
const facts = Object.freeze({ ...(first ? { first } : {}), ...(remote ? { remote } : {}), original: Object.freeze(original), diagnostic: Object.freeze([...new Set([...(previous?.diagnostic ?? []), ...secondaryFacts.flatMap(f => f?.diagnostic ?? []), ...(kind === 'diagnostic' ? added : [])])]), cleanup: Object.freeze([...new Set([...(previous?.cleanup ?? []), ...secondaryFacts.flatMap(f => f?.cleanup ?? []), ...(kind === 'cleanup' ? added : [])])]) });
|
|
71
90
|
const code = kind === 'cleanup' ? 'REMOTE_CLEANUP_UNCONFIRMED' : !secondary.length ? primary instanceof NeedError ? primary.code : safeCode(primaryCode) ? primaryCode : 'TURN_FAILED' : primary instanceof NeedError && facts.diagnostic.length > 0 && facts.diagnostic.every(code => code === primary.code) && !facts.cleanup.length && !previous ? primary.code : 'CONVERSATION_DETAILS_FAILED';
|
|
72
91
|
const failure = Object.assign(new NeedError(code, '本轮已停止;运行原因与诊断或清理结果分别保留。', '核对原始原因及未确认的保存/清理结果'), { cause: new AggregateError([primary, ...(safeCode(primaryCode) && !(primary instanceof NeedError) ? [new NeedError(primaryCode, '运行错误代码', '可信调用点提供的固定代码')] : []), ...secondary, ...(first ? [new NeedError(first, '首次观察关闭代码', '不是已证明根因')] : [])], 'TURN_FAILURE_EVIDENCE') });
|
|
73
92
|
evidence.set(failure, facts);
|
|
@@ -79,5 +98,5 @@ export function failureEvidenceSummary(error) {
|
|
|
79
98
|
const facts = retainedEvidence(error);
|
|
80
99
|
if (!facts)
|
|
81
100
|
return '';
|
|
82
|
-
return '技术详情:' + [...(facts.first ? ['首次观察关闭 ' + facts.first] : []), '运行原因 ' + facts.original.join('、'), ...(facts.diagnostic.length ? ['详情保存未确认 ' + facts.diagnostic.join('、')] : []), ...(facts.cleanup.length ? ['清理未确认 ' + facts.cleanup.join('、')] : [])].join(';');
|
|
101
|
+
return '技术详情:' + [...(facts.first ? ['首次观察关闭 ' + facts.first] : []), '运行原因 ' + facts.original.join('、'), ...(facts.remote ? ['远端关闭原因 ' + facts.remote] : []), ...(facts.diagnostic.length ? ['详情保存未确认 ' + facts.diagnostic.join('、')] : []), ...(facts.cleanup.length ? ['清理未确认 ' + facts.cleanup.join('、')] : facts.cleanupPending ? ['正在确认主机清理,原资源清理结果尚待确认'] : [])].join(';');
|
|
83
102
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { LlmAdapter, type GenerateOptions, type StreamChunk } from '@deepseek-ai
|
|
|
5
5
|
import { type AcpHooks } from './acp.ts';
|
|
6
6
|
import { type RuntimeProvider } from './provider.ts';
|
|
7
7
|
import { type AgentDefinition } from './agents.ts';
|
|
8
|
-
import { type ImageReader } from './input.ts';
|
|
8
|
+
import { type ImageReader, type HostWorkspacePresentation } from './input.ts';
|
|
9
9
|
import type { LoginDiagnostic } from './login-diagnostic.ts';
|
|
10
10
|
import { type ModelDirectory, type HostModel } from './model-catalog.ts';
|
|
11
11
|
import { type PreflightDiagnostics } from './preflight-diagnostic.ts';
|
|
@@ -21,8 +21,10 @@ export declare const DEFAULT_CONFIG: Readonly<{
|
|
|
21
21
|
readonly oneShotSandbox: "workspace-write";
|
|
22
22
|
readonly oneShotApproval: "never";
|
|
23
23
|
readonly employeeShellKillGraceMs: 1000;
|
|
24
|
+
readonly employeeHttpMcpPolicy: "reject";
|
|
24
25
|
}>;
|
|
25
26
|
export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
27
|
+
employeeHttpMcpPolicy: Schema<"reject" | "ignore", "reject" | "ignore", "volatile-defined">;
|
|
26
28
|
mode: Schema<"session" | "one-shot", "session" | "one-shot", "volatile-defined">;
|
|
27
29
|
acpMaxMessageBytes: Schema<number, number, "volatile-defined">;
|
|
28
30
|
brokerMaxFrameBytes: Schema<number, number, "volatile-defined">;
|
|
@@ -31,6 +33,7 @@ export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
|
31
33
|
oneShotApproval: Schema<"never", "never", "volatile-defined">;
|
|
32
34
|
employeeShellKillGraceMs: Schema<number, number, "volatile-defined">;
|
|
33
35
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
36
|
+
employeeHttpMcpPolicy: Schema<"reject" | "ignore", "reject" | "ignore", "volatile-defined">;
|
|
34
37
|
mode: Schema<"session" | "one-shot", "session" | "one-shot", "volatile-defined">;
|
|
35
38
|
acpMaxMessageBytes: Schema<number, number, "volatile-defined">;
|
|
36
39
|
brokerMaxFrameBytes: Schema<number, number, "volatile-defined">;
|
|
@@ -47,6 +50,7 @@ export interface PluginConfig {
|
|
|
47
50
|
oneShotSandbox?: 'read-only' | 'workspace-write' | 'danger-full-access';
|
|
48
51
|
oneShotApproval?: 'never';
|
|
49
52
|
employeeShellKillGraceMs?: number;
|
|
53
|
+
employeeHttpMcpPolicy?: 'reject' | 'ignore';
|
|
50
54
|
}
|
|
51
55
|
type LiveConfig = {
|
|
52
56
|
[K in keyof PluginConfig]: PluginConfig[K] | {
|
|
@@ -106,6 +110,8 @@ export declare class CodexRemoteAdapter extends LlmAdapter {
|
|
|
106
110
|
jitterRatio: number;
|
|
107
111
|
};
|
|
108
112
|
private cleanupRuntime;
|
|
113
|
+
/** Publish an already known failure without coupling the official stream to cleanup. */
|
|
114
|
+
private finishFailedTurn;
|
|
109
115
|
disposeSession(sessionId: string): Promise<void>;
|
|
110
116
|
detachSession(sessionId: string): Promise<void>;
|
|
111
117
|
dispose(): Promise<void>;
|
|
@@ -148,6 +154,6 @@ export declare class RemoteAgentsAdapter extends LlmAdapter {
|
|
|
148
154
|
dispose(): Promise<void>;
|
|
149
155
|
}
|
|
150
156
|
export declare function apply(ctx: Context, live: LiveConfig): void;
|
|
151
|
-
type InvocationApproval = (request: Parameters<NonNullable<AcpHooks['approve']>>[0], signal: AbortSignal | undefined, options: GenerateOptions, context?: string) => ReturnType<NonNullable<AcpHooks['approve']>>;
|
|
157
|
+
type InvocationApproval = (request: Parameters<NonNullable<AcpHooks['approve']>>[0], signal: AbortSignal | undefined, options: GenerateOptions, context?: string, hostWorkspace?: HostWorkspacePresentation) => ReturnType<NonNullable<AcpHooks['approve']>>;
|
|
152
158
|
export declare function dshApproval(ctx: Context): InvocationApproval;
|
|
153
159
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { connectNeedMessage } from "./connect-need.js";
|
|
2
2
|
import { CloseDiagnostics, withFailureEvidence, withFirstFailureEvidence, failureEvidenceSummary, closeCode, closeErrorCodes, closeFailure } from "./close-diagnostics.js";
|
|
3
|
+
import { withPendingCleanupEvidence, withRemoteFailureEvidence } from "./failure-evidence.js";
|
|
3
4
|
import { runCatalog } from "./catalog-ssh.js";
|
|
4
5
|
import { createCatalogDiagnostics, CatalogDiagnosticsRemote } from "./catalog-diagnostics.js";
|
|
5
6
|
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
@@ -16,7 +17,7 @@ import { StreamMapper } from "./mapping.js";
|
|
|
16
17
|
import { connectProvider } from "./provider.js";
|
|
17
18
|
import { brokerFactory } from "./broker.js";
|
|
18
19
|
import { AGENTS, CODEX_AGENT } from "./agents.js";
|
|
19
|
-
import { prepareInput, prepareContinuation } from "./input.js";
|
|
20
|
+
import { prepareInput, prepareContinuation, projectHostWorkspaceInput } from "./input.js";
|
|
20
21
|
import { createWorkspaceNoticeConsumer, isWorkspaceNoticeMessage } from "./workspace-notice.js";
|
|
21
22
|
import { NeedError } from "./remote.js";
|
|
22
23
|
import { employeeFailure } from "./presentation.js";
|
|
@@ -44,8 +45,9 @@ export const settingsNamespace = 'ai-workbench-codex';
|
|
|
44
45
|
// L can be installed/enabled after X; keeping X mounted lets it explain the prerequisite.
|
|
45
46
|
export const inject = ['llm'];
|
|
46
47
|
/** Owner-approved defaults; administrators may override through the existing settings. */
|
|
47
|
-
export const DEFAULT_CONFIG = Object.freeze({ mode: 'session', acpMaxMessageBytes: 4194304, brokerMaxFrameBytes: 2097152, brokerControlMaxBytes: 1048576, oneShotSandbox: 'workspace-write', oneShotApproval: 'never', employeeShellKillGraceMs: 1000 });
|
|
48
|
+
export const DEFAULT_CONFIG = Object.freeze({ mode: 'session', acpMaxMessageBytes: 4194304, brokerMaxFrameBytes: 2097152, brokerControlMaxBytes: 1048576, oneShotSandbox: 'workspace-write', oneShotApproval: 'never', employeeShellKillGraceMs: 1000, employeeHttpMcpPolicy: 'reject' });
|
|
48
49
|
export const Config = Schema.object({
|
|
50
|
+
employeeHttpMcpPolicy: Schema.union([Schema.const('reject').i18n({ zh: '拒绝本机 HTTP MCP(默认)', en: '拒绝本机 HTTP MCP(默认)' }), Schema.const('ignore').i18n({ zh: '忽略本机 HTTP MCP 后继续', en: '忽略本机 HTTP MCP 后继续' })]).i18n({ zh: '本机 HTTP MCP 策略;默认拒绝;忽略仅剔除会话投影,不改员工配置。新连接生效,stdio 始终按原生规则忽略并说明名称', en: '本机 HTTP MCP 策略;默认拒绝;忽略仅剔除会话投影,不改员工配置。新连接生效,stdio 始终按原生规则忽略并说明名称' }).default(DEFAULT_CONFIG.employeeHttpMcpPolicy).volatile(),
|
|
49
51
|
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(),
|
|
50
52
|
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(),
|
|
51
53
|
brokerMaxFrameBytes: Schema.number().min(1).step(1).i18n({ en: 'Host output chunk limit; default 2 MiB, adjustable by administrators', zh: '主机回传单段容量;默认2 MiB,管理员可调整' }).default(DEFAULT_CONFIG.brokerMaxFrameBytes).volatile(),
|
|
@@ -205,6 +207,35 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
205
207
|
this.cleanups.set(runtime, cleanup);
|
|
206
208
|
return cleanup;
|
|
207
209
|
}
|
|
210
|
+
/** Publish an already known failure without coupling the official stream to cleanup. */
|
|
211
|
+
finishFailedTurn(key, live, primary, code, firstReason) {
|
|
212
|
+
withPendingCleanupEvidence(primary, firstReason, code);
|
|
213
|
+
const runtime = live.runtime;
|
|
214
|
+
const disposal = this.cleanupRuntime(runtime).then(() => {
|
|
215
|
+
if (this.sessions.get(key) !== live || live.disposal !== disposal)
|
|
216
|
+
return;
|
|
217
|
+
live.disposal = undefined;
|
|
218
|
+
if (!live.failure) {
|
|
219
|
+
if (typeof runtime.dispose === 'function') {
|
|
220
|
+
this.sessions.delete(key);
|
|
221
|
+
this.preparationFailures.set(key, code);
|
|
222
|
+
}
|
|
223
|
+
else
|
|
224
|
+
live.cleanupFailure = new NeedError('REMOTE_CLEANUP_UNCONFIRMED', '缺少原实例结束证明,不能重开或重发', '原实例的条件结束与list消失证明');
|
|
225
|
+
}
|
|
226
|
+
}, error => {
|
|
227
|
+
if (this.sessions.get(key) === live && live.disposal === disposal)
|
|
228
|
+
live.cleanupFailure = withFailureEvidence(primary, [error], 'cleanup', firstReason, code);
|
|
229
|
+
throw error;
|
|
230
|
+
});
|
|
231
|
+
live.disposal = disposal;
|
|
232
|
+
const report = (reason) => {
|
|
233
|
+
runtime.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.failed-turn-cleanup', reason, action: 'dispose' });
|
|
234
|
+
// State settles before reporting. A delayed D19 write is not another release gate.
|
|
235
|
+
void Promise.resolve().then(() => runtime.details?.flush()).catch(error => runtime.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.failed-turn-cleanup-report', reason: closeCode(error, 'CLOSE_FLUSH_FAILED'), action: 'failure' }));
|
|
236
|
+
};
|
|
237
|
+
void disposal.then(() => report(typeof runtime.dispose === 'function' ? 'RUNTIME_CLEANUP_CONFIRMED' : 'REMOTE_CLEANUP_UNCONFIRMED'), () => report('REMOTE_CLEANUP_UNCONFIRMED'));
|
|
238
|
+
}
|
|
208
239
|
async disposeSession(sessionId) {
|
|
209
240
|
this.sessions.get(sessionId)?.runtime.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.dispose-session', reason: 'SESSION_DISPOSED', action: 'abort' });
|
|
210
241
|
this.turns.get(sessionId)?.abort();
|
|
@@ -310,7 +341,7 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
310
341
|
let turnFailure;
|
|
311
342
|
let employeeNoticeShown = false;
|
|
312
343
|
const information = [];
|
|
313
|
-
const recordInformation = () => { if (runtime?.details)
|
|
344
|
+
const recordInformation = () => { timing.summarize(); if (runtime?.details)
|
|
314
345
|
for (const entry of information.splice(0))
|
|
315
346
|
runtime.details.record(entry); };
|
|
316
347
|
const config = this.config();
|
|
@@ -356,7 +387,7 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
356
387
|
const configuration = JSON.stringify([mode, config.acpMaxMessageBytes, config.brokerMaxFrameBytes, config.brokerControlMaxBytes, config.employeeShellKillGraceMs]);
|
|
357
388
|
if (live && live.configuration !== configuration)
|
|
358
389
|
throw new NeedError('SESSION_CONTEXT_MISMATCH', '会话运行设置已改变,请新建会话', '原会话运行设置');
|
|
359
|
-
Object.assign(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 }) });
|
|
390
|
+
Object.assign(request, { timing, employeeHttpMcpPolicy: config.employeeHttpMcpPolicy ?? DEFAULT_CONFIG.employeeHttpMcpPolicy, 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 }) });
|
|
360
391
|
if (requireFreshSession) {
|
|
361
392
|
request.requireFreshSession = true;
|
|
362
393
|
request.resumeSessionId = undefined;
|
|
@@ -457,12 +488,17 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
457
488
|
if (contextChanged)
|
|
458
489
|
input = await timing.run('input-prepare', () => prepareContinuation(options, input, this.readImage));
|
|
459
490
|
}
|
|
491
|
+
input = projectHostWorkspaceInput(input, runtime.hostWorkspace);
|
|
492
|
+
if (runtime.hostWorkspace && mode === 'session' && Buffer.byteLength(JSON.stringify({ prompt: input.prompt, systemPrompt: input.systemPrompt }), 'utf8') > config.acpMaxMessageBytes)
|
|
493
|
+
throw new NeedError('ACP_MESSAGE_TOO_LARGE', '本轮输入超过已设置的会话消息容量,未截断或发送', '管理员调整acpMaxMessageBytes');
|
|
460
494
|
const details = runtime.details;
|
|
461
495
|
if (!details)
|
|
462
496
|
throw new NeedError('TECHNICAL_DETAILS_UNAVAILABLE', '技术详情暂不可用,请更新远程主机连接组件。', 'L技术详情持久接收能力');
|
|
463
497
|
recordInformation();
|
|
464
498
|
timing.bind(detail => details.record(detail));
|
|
465
499
|
request.recordEarlyFailure = undefined;
|
|
500
|
+
if (runtime.hostWorkspace)
|
|
501
|
+
details.record({ kind: 'session', severity: 'info', code: 'HOST_WORKSPACE_CONTEXT', summary: '本轮模型工作区上下文及审批会话目录使用已确认的主机目录。', payload: { source: 'host', cwd: runtime.hostWorkspace.cwd } });
|
|
466
502
|
const mapper = new StreamMapper(mode === 'session' && !contextChanged ? (runtime.restartNotice ? undefined : live ? live.usageSnapshot : previous ? (previous.usageSnapshot ?? null) : undefined) : undefined, observation => details.record(observation));
|
|
467
503
|
activeMapper = mapper;
|
|
468
504
|
let id;
|
|
@@ -500,7 +536,7 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
500
536
|
let done = false;
|
|
501
537
|
let failure;
|
|
502
538
|
const push = (chunks) => { queue.push(...chunks); wake?.(); wake = undefined; };
|
|
503
|
-
const hooks = { closeDiagnostics: runtime.closeDiagnostics, broker: runtime.broker, employeeExecution: runtime.employeeExecution, timing, approve: async (permission, approvalSignal) => {
|
|
539
|
+
const hooks = { onEmployeeInfo: summary => push(mapper.text(summary + '\n\n')), closeDiagnostics: runtime.closeDiagnostics, broker: runtime.broker, employeeExecution: runtime.employeeExecution, timing, approve: async (permission, approvalSignal) => {
|
|
504
540
|
const execution = runtime.execution;
|
|
505
541
|
const toolName = permission.toolCall.name ?? permission.toolCall._meta?.claudeCode?.toolName;
|
|
506
542
|
if (execution) {
|
|
@@ -511,7 +547,7 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
511
547
|
}
|
|
512
548
|
await execution.validate(approvalSignal);
|
|
513
549
|
}
|
|
514
|
-
const answer = await this.approve?.(permission, approvalSignal, options, execution && toolName?.startsWith('mcp__employee__') ? 'employee-local' : undefined) ?? 'unavailable';
|
|
550
|
+
const answer = await this.approve?.(permission, approvalSignal, options, execution && toolName?.startsWith('mcp__employee__') ? 'employee-local' : undefined, runtime.hostWorkspace) ?? 'unavailable';
|
|
515
551
|
if (execution && answer === 'allowed-once')
|
|
516
552
|
await execution.validate(approvalSignal);
|
|
517
553
|
return answer;
|
|
@@ -674,25 +710,36 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
674
710
|
runtime?.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.turn-failure', reason: closeCode(error, signal.aborted ? 'ABORTED' : 'TURN_FAILED'), action: 'failure' });
|
|
675
711
|
recordInformation();
|
|
676
712
|
failureCode = error instanceof NeedError || error instanceof ModelConfigurationError ? error.code : error instanceof LlmError ? error.code : 'TURN_PREPARATION_FAILED';
|
|
713
|
+
withRemoteFailureEvidence(error, runtime?.employeeRemoteCloseReason?.(), failureCode);
|
|
677
714
|
if (live && session && session.promptDispatchCount > promptBaseline)
|
|
678
715
|
live.failure = { cause: error };
|
|
679
716
|
const reporting = [];
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
717
|
+
const reportFailure = async () => {
|
|
718
|
+
if (request?.recordEarlyFailure) {
|
|
719
|
+
const record = request.recordEarlyFailure;
|
|
720
|
+
request.recordEarlyFailure = undefined;
|
|
721
|
+
try {
|
|
722
|
+
await record(Object.assign(new NeedError(failureCode ?? 'TURN_PREPARATION_FAILED', '本轮在执行前失败。', '白名单阶段与错误代码'), { cause: error }), earlyStage);
|
|
723
|
+
}
|
|
724
|
+
catch (failure) {
|
|
725
|
+
reporting.push(failure);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
683
728
|
try {
|
|
684
|
-
await
|
|
729
|
+
await runtime?.details?.flush();
|
|
685
730
|
}
|
|
686
731
|
catch (failure) {
|
|
687
732
|
reporting.push(failure);
|
|
688
733
|
}
|
|
734
|
+
};
|
|
735
|
+
if (runtime && key && live && live.runtime === runtime && this.sessions.get(key) === live) {
|
|
736
|
+
const captured = runtime;
|
|
737
|
+
void reportFailure().then(() => { for (const failure of reporting)
|
|
738
|
+
for (const code of closeErrorCodes(failure, 'DIAGNOSTIC_WRITE_FAILED'))
|
|
739
|
+
captured.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.failed-turn-details', reason: code, action: 'failure' }); }, failure => captured.closeDiagnostics?.record({ layer: 'adapter', initiator: 'adapter.failed-turn-details', reason: closeCode(failure, 'DIAGNOSTIC_WRITE_FAILED'), action: 'failure' }));
|
|
689
740
|
}
|
|
690
|
-
|
|
691
|
-
await
|
|
692
|
-
}
|
|
693
|
-
catch (failure) {
|
|
694
|
-
reporting.push(failure);
|
|
695
|
-
}
|
|
741
|
+
else
|
|
742
|
+
await reportFailure();
|
|
696
743
|
if (activeMapper)
|
|
697
744
|
yield* activeMapper.flush();
|
|
698
745
|
const firstReason = request?.closeDiagnostics?.first?.reason ?? runtime?.closeDiagnostics?.first?.reason;
|
|
@@ -716,15 +763,19 @@ export class CodexRemoteAdapter extends LlmAdapter {
|
|
|
716
763
|
else if (!succeeded || !key || mode !== 'session') {
|
|
717
764
|
session?.close({ layer: 'adapter', initiator: 'adapter.turn-finally', reason: succeeded ? 'TURN_FINISHED' : 'TURN_FAILED', action: 'close' });
|
|
718
765
|
if (runtime) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
766
|
+
if (!succeeded && turnFailure && key && live && live.runtime === runtime && this.sessions.get(key) === live)
|
|
767
|
+
this.finishFailedTurn(key, live, turnFailure, failureCode ?? 'TURN_PREPARATION_FAILED', request?.closeDiagnostics?.first?.reason ?? runtime.closeDiagnostics?.first?.reason);
|
|
768
|
+
else {
|
|
769
|
+
await this.cleanupRuntime(runtime);
|
|
770
|
+
if (key && live && !live.failure && this.sessions.get(key) === live) {
|
|
771
|
+
if (typeof runtime.dispose === 'function') {
|
|
772
|
+
this.sessions.delete(key);
|
|
773
|
+
if (!succeeded)
|
|
774
|
+
this.preparationFailures.set(key, failureCode ?? 'TURN_PREPARATION_FAILED');
|
|
775
|
+
}
|
|
776
|
+
else
|
|
777
|
+
live.cleanupFailure = new NeedError('REMOTE_CLEANUP_UNCONFIRMED', '缺少原实例结束证明,不能重开或重发', '原实例的条件结束与list消失证明');
|
|
725
778
|
}
|
|
726
|
-
else
|
|
727
|
-
live.cleanupFailure = new NeedError('REMOTE_CLEANUP_UNCONFIRMED', '缺少原实例结束证明,不能重开或重发', '原实例的条件结束与list消失证明');
|
|
728
779
|
}
|
|
729
780
|
}
|
|
730
781
|
}
|
|
@@ -925,12 +976,12 @@ export function apply(ctx, live) {
|
|
|
925
976
|
ctx.effect(() => () => { modelDirectory.dispose(); return adapter.dispose(); });
|
|
926
977
|
}
|
|
927
978
|
export function dshApproval(ctx) {
|
|
928
|
-
return async (request, signal, options, context) => {
|
|
979
|
+
return async (request, signal, options, context, hostWorkspace) => {
|
|
929
980
|
const agent = options.sessionId ? ctx.get('agents')?.get(options.sessionId) : undefined;
|
|
930
981
|
const approval = ctx.get('approval');
|
|
931
982
|
if (!agent || !approval)
|
|
932
983
|
return 'unavailable';
|
|
933
|
-
const label = approvalLabel(request, context === 'employee-local' ? 'local' : 'host');
|
|
984
|
+
const label = approvalLabel(request, context === 'employee-local' ? 'local' : 'host', hostWorkspace);
|
|
934
985
|
if (!label)
|
|
935
986
|
return 'unavailable';
|
|
936
987
|
return approval.request({ agent, ...label, signal });
|
package/dist/input.d.ts
CHANGED
|
@@ -12,6 +12,13 @@ export interface HistoryTransfer {
|
|
|
12
12
|
omittedReasoningBlocks: number;
|
|
13
13
|
reasoningOmissionReason: 'dsh-internal-reasoning';
|
|
14
14
|
}
|
|
15
|
+
export interface HostWorkspacePresentation {
|
|
16
|
+
readonly cwd: string;
|
|
17
|
+
}
|
|
18
|
+
type RuntimeSection = {
|
|
19
|
+
name: string;
|
|
20
|
+
text: string;
|
|
21
|
+
};
|
|
15
22
|
export interface PreparedInput {
|
|
16
23
|
prompt: AcpContentBlock[];
|
|
17
24
|
users: {
|
|
@@ -20,8 +27,12 @@ export interface PreparedInput {
|
|
|
20
27
|
}[];
|
|
21
28
|
systemPrompt?: string;
|
|
22
29
|
historyTransfer?: HistoryTransfer;
|
|
30
|
+
runtimeSnapshots?: ReadonlyMap<AcpContentBlock, readonly RuntimeSection[]>;
|
|
23
31
|
}
|
|
32
|
+
/** Invocation-only model context; never modifies the native session/history. */
|
|
33
|
+
export declare function projectHostWorkspaceInput(input: PreparedInput, host?: HostWorkspacePresentation): PreparedInput;
|
|
24
34
|
/** Role-labelled public history only; never serialize source/replay/tool internals. */
|
|
25
35
|
export declare function prepareContinuation(options: GenerateOptions, input: PreparedInput, readImage?: ImageReader): Promise<PreparedInput>;
|
|
26
36
|
/** The attachment service supplies verified bytes; client paths never cross SSH. */
|
|
27
37
|
export declare function prepareInput(options: GenerateOptions, readImage?: ImageReader): Promise<PreparedInput>;
|
|
38
|
+
export {};
|
package/dist/input.js
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
import { NeedError } from "./remote.js";
|
|
2
2
|
import { isWorkspaceNoticeMessage, withoutWorkspaceNotice } from "./workspace-notice.js";
|
|
3
|
+
function runtimeSections(message) {
|
|
4
|
+
const source = message.source;
|
|
5
|
+
if (message.role !== 'user' || source?.kind !== 'runtime-context' || source.form !== 'snapshot' || !Array.isArray(source.sections) || message.content.length !== 1 || message.content[0].type !== 'text')
|
|
6
|
+
return;
|
|
7
|
+
if (!source.sections.every(s => s && typeof s.name === 'string' && typeof s.text === 'string'))
|
|
8
|
+
return;
|
|
9
|
+
const sections = source.sections, body = sections.map(s => s.text).join('\n\n');
|
|
10
|
+
// Only official, structurally attributed snapshot text is projected. Employee
|
|
11
|
+
// text and opaque contributions are never searched for or rewritten as paths.
|
|
12
|
+
if (body && message.content[0].text.endsWith('\n\n' + body))
|
|
13
|
+
return sections;
|
|
14
|
+
}
|
|
15
|
+
/** Invocation-only model context; never modifies the native session/history. */
|
|
16
|
+
export function projectHostWorkspaceInput(input, host) {
|
|
17
|
+
if (!host)
|
|
18
|
+
return input;
|
|
19
|
+
const context = `当前会话使用远程主机上的工作区:${JSON.stringify(host.cwd)}。文件和命令在该主机目录执行,受主机账号的操作系统权限约束;本机 DSH 文件沙箱的工作区路径不适用于这些远程操作。`;
|
|
20
|
+
const mapped = new Map();
|
|
21
|
+
for (const [block, sections] of input.runtimeSnapshots ?? []) {
|
|
22
|
+
if (block.type !== 'text' || !sections.some(s => s.name === 'sandbox:policy'))
|
|
23
|
+
continue;
|
|
24
|
+
const body = sections.map(s => s.text).join('\n\n');
|
|
25
|
+
mapped.set(block, { ...block, text: block.text.slice(0, -body.length) + sections.map(s => s.name === 'sandbox:policy' ? context : s.text).join('\n\n') });
|
|
26
|
+
}
|
|
27
|
+
const project = (blocks) => blocks.map(block => mapped.get(block) ?? block);
|
|
28
|
+
const prompt = project(input.prompt);
|
|
29
|
+
// Confirm this invocation after all current and historical text. A projected
|
|
30
|
+
// historical snapshot must not suppress host facts for an opaque current one;
|
|
31
|
+
// warm steps also need no new native snapshot to retain their host location.
|
|
32
|
+
prompt.push({ type: 'text', text: context });
|
|
33
|
+
return { ...input, prompt, users: input.users.map(user => ({ ...user, prompt: project(user.prompt) })) };
|
|
34
|
+
}
|
|
3
35
|
async function publicBlock(block, options, readImage, history = false) {
|
|
4
36
|
options.signal?.throwIfAborted();
|
|
5
37
|
if (block.type === 'text')
|
|
@@ -26,6 +58,7 @@ export async function prepareContinuation(options, input, readImage) {
|
|
|
26
58
|
throw new NeedError('HISTORY_CONTENT_UNSUPPORTED', `此前含有无法可靠承接的历史角色:${message.role};本轮未新建或发送`, '完整公开的用户及助手历史;工具记录的公开内容映射');
|
|
27
59
|
const after = messages.findLastIndex(message => message.role === 'assistant');
|
|
28
60
|
const prompt = [];
|
|
61
|
+
const runtimeSnapshots = new Map(input.runtimeSnapshots);
|
|
29
62
|
const historyTransfer = { scope: 'visible-user-assistant-text-and-images', textBlocks: 0, imageBlocks: 0, offloadedImagePlaceholders: 0, omittedReasoningBlocks: 0, reasoningOmissionReason: 'dsh-internal-reasoning' };
|
|
30
63
|
for (const message of messages.slice(0, after + 1)) {
|
|
31
64
|
if (message.role === 'system' || message.role === 'developer')
|
|
@@ -33,13 +66,17 @@ export async function prepareContinuation(options, input, readImage) {
|
|
|
33
66
|
if (message.role !== 'user' && message.role !== 'assistant')
|
|
34
67
|
throw new NeedError('HISTORY_CONTENT_UNSUPPORTED', `此前含有无法可靠承接的历史角色:${message.role};本轮未新建或发送`, '完整公开的用户及助手历史;工具记录的公开内容映射');
|
|
35
68
|
prompt.push({ type: 'text', text: `此前可见对话(历史角色:${message.role};仅为历史记录):\n` });
|
|
69
|
+
const sections = runtimeSections(message);
|
|
36
70
|
for (const block of message.content) {
|
|
37
71
|
// DSH explicitly distinguishes internal thinking from visible user text.
|
|
38
72
|
if (block.type === 'reasoning') {
|
|
39
73
|
historyTransfer.omittedReasoningBlocks++;
|
|
40
74
|
continue;
|
|
41
75
|
}
|
|
42
|
-
|
|
76
|
+
const prepared = await publicBlock(block, options, readImage, true);
|
|
77
|
+
prompt.push(prepared);
|
|
78
|
+
if (sections)
|
|
79
|
+
runtimeSnapshots.set(prepared, sections);
|
|
43
80
|
if (block.type === 'text')
|
|
44
81
|
historyTransfer.textBlocks++;
|
|
45
82
|
else if (block.type === 'image') {
|
|
@@ -52,7 +89,7 @@ export async function prepareContinuation(options, input, readImage) {
|
|
|
52
89
|
prompt.push({ type: 'text', text: `\n(历史角色:${message.role} 的记录结束)\n` });
|
|
53
90
|
}
|
|
54
91
|
prompt.push({ type: 'text', text: '此前可见对话结束。以下为当前用户输入:\n' }, ...input.prompt);
|
|
55
|
-
return { ...input, prompt, historyTransfer };
|
|
92
|
+
return { ...input, prompt, historyTransfer, runtimeSnapshots };
|
|
56
93
|
}
|
|
57
94
|
/** The attachment service supplies verified bytes; client paths never cross SSH. */
|
|
58
95
|
export async function prepareInput(options, readImage) {
|
|
@@ -78,13 +115,19 @@ export async function prepareInput(options, readImage) {
|
|
|
78
115
|
throw new NeedError('INPUT_REQUIRED', '我需要本轮用户输入', '用户输入');
|
|
79
116
|
const prompt = [];
|
|
80
117
|
const preparedUsers = [];
|
|
118
|
+
const runtimeSnapshots = new Map();
|
|
81
119
|
for (const message of users) {
|
|
82
120
|
const before = prompt.length;
|
|
83
|
-
|
|
84
|
-
|
|
121
|
+
const sections = runtimeSections(message);
|
|
122
|
+
for (const block of message.content) {
|
|
123
|
+
const prepared = await publicBlock(block, options, readImage);
|
|
124
|
+
prompt.push(prepared);
|
|
125
|
+
if (sections)
|
|
126
|
+
runtimeSnapshots.set(prepared, sections);
|
|
127
|
+
}
|
|
85
128
|
preparedUsers.push({ id: message.id, prompt: prompt.slice(before) });
|
|
86
129
|
}
|
|
87
130
|
if (!prompt.length)
|
|
88
131
|
throw new NeedError('INPUT_REQUIRED', '我需要本轮用户输入', '用户输入');
|
|
89
|
-
return { prompt, users: preparedUsers, ...(context.length ? { systemPrompt: context.join('\n\n') } : {}) };
|
|
132
|
+
return { prompt, users: preparedUsers, runtimeSnapshots, ...(context.length ? { systemPrompt: context.join('\n\n') } : {}) };
|
|
90
133
|
}
|
package/dist/presentation.js
CHANGED
|
@@ -81,7 +81,7 @@ export function employeeFailure(code, brand = 'codex') {
|
|
|
81
81
|
if (code === 'AW_EMPLOYEE_HOST_PROJECT_CONFIG_UNSUPPORTED')
|
|
82
82
|
return '主机上存在与本机工作区路径重合的项目配置,当前版本无法安全应用,本轮没有执行,请联系管理员。';
|
|
83
83
|
if (code === 'AW_EMPLOYEE_EXECUTOR_MCP_UNSUPPORTED')
|
|
84
|
-
return '
|
|
84
|
+
return '本机 HTTP MCP 当前配置策略为拒绝,本轮没有继续执行;管理员可在远程助手参数中调整本机 HTTP MCP 策略,新建会话后生效。';
|
|
85
85
|
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))
|
|
86
86
|
return '无法核验这次本机工作区的助手配置,本轮没有继续执行,请联系管理员查看技术详情。';
|
|
87
87
|
if (code === 'AW_EMPLOYEE_MCP_UNSUPPORTED')
|
package/dist/provider.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { EmployeeProviderOptions } from './employee-provider.ts';
|
|
|
16
16
|
import { type WorkspaceSourceOptions, type SourceAdmissionStage } from './workspace-source.ts';
|
|
17
17
|
import type { LocalUserTurnAcceptance } from '@ai-workbench/connect/api';
|
|
18
18
|
import type { EmployeeRouting } from './employee-relay.ts';
|
|
19
|
+
import type { HostWorkspacePresentation } from './input.ts';
|
|
19
20
|
export interface EmployeeExecution {
|
|
20
21
|
hostCwd: string;
|
|
21
22
|
routing: EmployeeRouting;
|
|
@@ -29,6 +30,7 @@ export interface EmployeeExecution {
|
|
|
29
30
|
export interface Runtime {
|
|
30
31
|
identity: string;
|
|
31
32
|
cwd: string;
|
|
33
|
+
hostWorkspace?: Readonly<HostWorkspacePresentation>;
|
|
32
34
|
contextSignal?: AbortSignal;
|
|
33
35
|
execution?: EmployeeExecution;
|
|
34
36
|
acpTransport?: {
|
|
@@ -63,9 +65,11 @@ export interface Runtime {
|
|
|
63
65
|
workspaceId: string;
|
|
64
66
|
cwd: string;
|
|
65
67
|
acpMaxMessageBytes: number;
|
|
68
|
+
httpMcpPolicy?: 'reject' | 'ignore';
|
|
66
69
|
};
|
|
67
70
|
employeeExecution?: (params: unknown, signal?: AbortSignal) => Promise<Record<string, unknown>>;
|
|
68
71
|
employeePresentation?: string;
|
|
72
|
+
employeeRemoteCloseReason?: () => string | undefined;
|
|
69
73
|
closeDiagnostics?: CloseDiagnostics;
|
|
70
74
|
}
|
|
71
75
|
export interface RuntimeRequest extends Invocation {
|
|
@@ -83,6 +87,7 @@ export interface RuntimeRequest extends Invocation {
|
|
|
83
87
|
detailChannel?: DetailChannel;
|
|
84
88
|
validateContext?: (ignoreCancellation?: boolean) => Promise<void>;
|
|
85
89
|
verifyRecovery?: () => Promise<boolean>;
|
|
90
|
+
employeeHttpMcpPolicy?: 'reject' | 'ignore';
|
|
86
91
|
acpMaxMessageBytes?: number;
|
|
87
92
|
employeeScope?: string;
|
|
88
93
|
contextSha256?: string;
|
package/dist/provider.js
CHANGED
|
@@ -342,6 +342,7 @@ export function connectProvider(get, options = {}) {
|
|
|
342
342
|
}
|
|
343
343
|
if (host)
|
|
344
344
|
retainHostScope(runtime, host);
|
|
345
|
+
runtime.hostWorkspace = host && 'directory' in resolved ? Object.freeze({ cwd: resolved.directory.path }) : undefined;
|
|
345
346
|
// The transport can outlive a turn; presentation belongs only to this invocation.
|
|
346
347
|
runtime.workspaceNotice = workspaceNotice || undefined;
|
|
347
348
|
runtime.details = details ?? runtime.details;
|
package/dist/stage-timing.d.ts
CHANGED
|
@@ -22,6 +22,10 @@ export declare class StageTiming {
|
|
|
22
22
|
private readonly started;
|
|
23
23
|
private pending;
|
|
24
24
|
private sink?;
|
|
25
|
+
private confirmations;
|
|
26
|
+
private emit;
|
|
27
|
+
/** Presentation aggregation only: no duration threshold and no new execution gate. */
|
|
28
|
+
summarize(): void;
|
|
25
29
|
bind(sink: (detail: DetailObservation) => void): void;
|
|
26
30
|
run<T>(phase: Stage, operation: () => Promise<T>): Promise<T>;
|
|
27
31
|
}
|