dsh-agy-cli 0.12.2 → 0.12.4
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 +1 -1
- package/bin/win32-x64/agy-launcher.exe +0 -0
- package/bin/win32-x64/agy-launcher.old.161676.exe +0 -0
- package/dist/client.js +4 -4
- package/lib/agy/experimental-transport.js +12 -4
- package/lib/agy/keepalive.js +41 -40
- package/lib/agy/persistent-transport.js +12 -8
- package/lib/index.js +1 -0
- package/lib/mcp/bridge.d.ts +4 -0
- package/lib/mcp/bridge.js +23 -8
- package/lib/mcp/tool-projection.js +1 -1
- package/lib/provider/agy.js +29 -13
- package/lib/provider/serialize.js +70 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
- **双工具路线**:MCP 桥把 DSH 工具交给 AGY(真工具事件、过程流可视化,推荐),失败自动降级为 prompt 契约
|
|
32
32
|
- **DSH 主导 harness**:工作区、审批、沙箱、上下文预算全在 DSH;AGY 只出模型
|
|
33
|
-
- **流式 + 状态机**:`stdinMode: hold`
|
|
33
|
+
- **流式 + 状态机**:`stdinMode: hold` 防回滚、工具步骤折叠显示(只列执行了哪些工具,不显示模型的内部思考内容)、尾流交付、断连保文本
|
|
34
34
|
- **会话管理 + 历史指纹链**:resume / full 双模式,每轮成功后记录 DSH 历史指纹;resume 前本地比对前缀,检出历史被编辑/压缩/截断即废弃旧会话并从完整历史重建,delta prompt 不会落在过期上下文上;死亡会话自动重放,会话映射落盘可迁移
|
|
35
35
|
- **图片输入**(experimental):附件经图片桥落盘,由最小权限的 view_file Agent 查看
|
|
36
36
|
- **配额面板与凭据保活**:loopback-only;面板显示真实双窗口配额,令牌临近过期时自动拉起官方 CLI 续期
|
|
Binary file
|
|
Binary file
|
package/dist/client.js
CHANGED
|
@@ -3846,7 +3846,7 @@ function apply(ctx) {
|
|
|
3846
3846
|
observer.observe(row ?? (parent ?? root), { childList: true });
|
|
3847
3847
|
return () => observer.disconnect();
|
|
3848
3848
|
}, []);
|
|
3849
|
-
const expanded = manualOpen ?? hostOpen ??
|
|
3849
|
+
const expanded = manualOpen ?? hostOpen ?? false;
|
|
3850
3850
|
const showToast = (text, type = 'info') => {
|
|
3851
3851
|
setToast({ text, type, id: Date.now() });
|
|
3852
3852
|
if (toastTimerRef.current !== null)
|
|
@@ -4193,20 +4193,20 @@ function apply(ctx) {
|
|
|
4193
4193
|
style: { display: 'block', color: 'var(--agy-text-primary)', fontWeight: 600, fontSize: '12.5px', marginBottom: '6px', cursor: 'help' },
|
|
4194
4194
|
title: t('models.hint'),
|
|
4195
4195
|
}, t('models.label')), visibilityList));
|
|
4196
|
-
return h('div', { ref: cardRef, style: S.container }, h('style', null, GLOBAL_CSS), h('div', { style: S.headerCard }, h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap', minWidth: 0 } }, h('span', { style: { fontWeight: 700, fontSize: '13.5px', color: 'var(--agy-text-primary)' }, title: accountAlias }, accountAlias), h('span', { style: isAuthed ? S.badgeReady : S.badgeUnready }, isAuthed ? t('status.ready') : t('status.needsAuth')), tokenLine), h('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', flexShrink: 0 } }, h('button', {
|
|
4196
|
+
return h('div', { ref: cardRef, style: S.container }, h('style', null, GLOBAL_CSS), h('div', { style: S.headerCard }, h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap', minWidth: 0 } }, expanded ? h('span', { style: { fontWeight: 700, fontSize: '13.5px', color: 'var(--agy-text-primary)' }, title: accountAlias }, accountAlias) : null, h('span', { style: isAuthed ? S.badgeReady : S.badgeUnready }, isAuthed ? t('status.ready') : t('status.needsAuth')), expanded ? tokenLine : null), h('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', flexShrink: 0 } }, h('button', {
|
|
4197
4197
|
type: 'button',
|
|
4198
4198
|
className: 'agy-btn',
|
|
4199
4199
|
style: { ...S.btn, padding: '4px 7px', gap: '0', minWidth: '24px', justifyContent: 'center' },
|
|
4200
4200
|
title: expanded ? t('panel.collapse') : t('panel.expand'),
|
|
4201
4201
|
'aria-expanded': expanded ? 'true' : 'false',
|
|
4202
4202
|
onClick: () => setManualOpen(!expanded),
|
|
4203
|
-
}, expanded ? '▾' : '▸'), h('button', {
|
|
4203
|
+
}, expanded ? '▾' : '▸'), expanded ? h('button', {
|
|
4204
4204
|
type: 'button',
|
|
4205
4205
|
className: 'agy-btn',
|
|
4206
4206
|
style: { ...S.btn, gap: '4px', flexShrink: 0 },
|
|
4207
4207
|
disabled: isBusy,
|
|
4208
4208
|
onClick: () => void refreshQuota(),
|
|
4209
|
-
}, loadingAction === 'refresh:all' ? [renderSpinner(), t('header.refreshing')] : [uiIcon('refresh', 12, 'var(--agy-text-btn)'), ` ${t('header.refreshQuota')}`]))), renderToastBanner(), expanded
|
|
4209
|
+
}, loadingAction === 'refresh:all' ? [renderSpinner(), t('header.refreshing')] : [uiIcon('refresh', 12, 'var(--agy-text-btn)'), ` ${t('header.refreshQuota')}`]) : null)), renderToastBanner(), expanded
|
|
4210
4210
|
? h('div', { style: { display: 'contents' } }, expiredBanner, quotaCard, settingsBlock)
|
|
4211
4211
|
: null);
|
|
4212
4212
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { buildWindowsNoConsoleLaunch } from './windows-launcher.js';
|
|
2
3
|
import { randomUUID } from 'node:crypto';
|
|
3
4
|
export class PersistentTransportError extends Error {
|
|
4
5
|
code;
|
|
@@ -200,8 +201,9 @@ class PersistentAgyWorker {
|
|
|
200
201
|
await this.stop('reset');
|
|
201
202
|
throw new PersistentTransportError('Persistent AGY request aborted before write', 'ABORTED');
|
|
202
203
|
}
|
|
203
|
-
const timeoutMs = input.timeoutMs
|
|
204
|
-
|
|
204
|
+
const timeoutMs = input.timeoutMs !== undefined
|
|
205
|
+
? validateInteger('timeoutMs', input.timeoutMs, 1, 3_600_000)
|
|
206
|
+
: undefined;
|
|
205
207
|
const maxOutputBytes = Math.min(input.maxOutputBytes ?? this.options.maxOutputBytes, this.options.maxOutputBytes);
|
|
206
208
|
validateInteger('maxOutputBytes', maxOutputBytes, 1, this.options.maxOutputBytes);
|
|
207
209
|
this.clearIdleTimer();
|
|
@@ -225,7 +227,9 @@ class PersistentAgyWorker {
|
|
|
225
227
|
this.active = active;
|
|
226
228
|
active.abortListener = () => this.failActiveAndReset(new PersistentTransportError('Persistent AGY request aborted', 'ABORTED'));
|
|
227
229
|
input.signal?.addEventListener('abort', active.abortListener, { once: true });
|
|
228
|
-
|
|
230
|
+
if (timeoutMs !== undefined) {
|
|
231
|
+
active.timer = setTimeout(() => this.failActiveAndReset(new PersistentTransportError('Persistent AGY request exceeded its timeout', 'TIMEOUT')), timeoutMs);
|
|
232
|
+
}
|
|
229
233
|
void this.sendRequest(active, input.payload);
|
|
230
234
|
});
|
|
231
235
|
}
|
|
@@ -279,7 +283,11 @@ class PersistentAgyWorker {
|
|
|
279
283
|
this.readyReject = reject;
|
|
280
284
|
});
|
|
281
285
|
try {
|
|
282
|
-
const
|
|
286
|
+
const args = [...this.options.args];
|
|
287
|
+
const launch = process.platform === 'win32'
|
|
288
|
+
? buildWindowsNoConsoleLaunch(this.options.executable, args)
|
|
289
|
+
: { executable: this.options.executable, args };
|
|
290
|
+
const child = spawn(launch.executable, [...launch.args], {
|
|
283
291
|
cwd: this.options.cwd,
|
|
284
292
|
env: {
|
|
285
293
|
...process.env,
|
package/lib/agy/keepalive.js
CHANGED
|
@@ -56,46 +56,46 @@ export const AGY_KEYRING_TARGET = 'gemini:antigravity';
|
|
|
56
56
|
* 2026-10-22. Duplicated rather than imported so the keep-alive path has no
|
|
57
57
|
* dependency on the HTTP layer.
|
|
58
58
|
*/
|
|
59
|
-
const WIN_CRED_SCRIPT = `
|
|
60
|
-
param($target)
|
|
61
|
-
$sig = @'
|
|
62
|
-
using System;
|
|
63
|
-
using System.Runtime.InteropServices;
|
|
64
|
-
public class WinCredKeepalive {
|
|
65
|
-
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
66
|
-
public static extern bool CredRead(string target, int type, int flags, out IntPtr credential);
|
|
67
|
-
[DllImport("advapi32.dll", SetLastError=true)]
|
|
68
|
-
public static extern void CredFree(IntPtr cred);
|
|
69
|
-
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
|
|
70
|
-
public struct CREDENTIAL {
|
|
71
|
-
public int Flags;
|
|
72
|
-
public int Type;
|
|
73
|
-
public string TargetName;
|
|
74
|
-
public string Comment;
|
|
75
|
-
public long LastWritten;
|
|
76
|
-
public int CredentialBlobSize;
|
|
77
|
-
public IntPtr CredentialBlob;
|
|
78
|
-
public int Persist;
|
|
79
|
-
public int AttributeCount;
|
|
80
|
-
public IntPtr Attributes;
|
|
81
|
-
public string TargetAlias;
|
|
82
|
-
public string UserName;
|
|
83
|
-
}
|
|
84
|
-
public static string Read(string target) {
|
|
85
|
-
IntPtr ptr;
|
|
86
|
-
if (CredRead(target, 1, 0, out ptr)) {
|
|
87
|
-
CREDENTIAL c = (CREDENTIAL)Marshal.PtrToStructure(ptr, typeof(CREDENTIAL));
|
|
88
|
-
byte[] b = new byte[c.CredentialBlobSize];
|
|
89
|
-
Marshal.Copy(c.CredentialBlob, b, 0, c.CredentialBlobSize);
|
|
90
|
-
CredFree(ptr);
|
|
91
|
-
return System.Convert.ToBase64String(b);
|
|
92
|
-
}
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
'@
|
|
97
|
-
Add-Type -TypeDefinition $sig -ErrorAction SilentlyContinue
|
|
98
|
-
[Console]::Out.Write([WinCredKeepalive]::Read($target))
|
|
59
|
+
const WIN_CRED_SCRIPT = `
|
|
60
|
+
param($target)
|
|
61
|
+
$sig = @'
|
|
62
|
+
using System;
|
|
63
|
+
using System.Runtime.InteropServices;
|
|
64
|
+
public class WinCredKeepalive {
|
|
65
|
+
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
66
|
+
public static extern bool CredRead(string target, int type, int flags, out IntPtr credential);
|
|
67
|
+
[DllImport("advapi32.dll", SetLastError=true)]
|
|
68
|
+
public static extern void CredFree(IntPtr cred);
|
|
69
|
+
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
|
|
70
|
+
public struct CREDENTIAL {
|
|
71
|
+
public int Flags;
|
|
72
|
+
public int Type;
|
|
73
|
+
public string TargetName;
|
|
74
|
+
public string Comment;
|
|
75
|
+
public long LastWritten;
|
|
76
|
+
public int CredentialBlobSize;
|
|
77
|
+
public IntPtr CredentialBlob;
|
|
78
|
+
public int Persist;
|
|
79
|
+
public int AttributeCount;
|
|
80
|
+
public IntPtr Attributes;
|
|
81
|
+
public string TargetAlias;
|
|
82
|
+
public string UserName;
|
|
83
|
+
}
|
|
84
|
+
public static string Read(string target) {
|
|
85
|
+
IntPtr ptr;
|
|
86
|
+
if (CredRead(target, 1, 0, out ptr)) {
|
|
87
|
+
CREDENTIAL c = (CREDENTIAL)Marshal.PtrToStructure(ptr, typeof(CREDENTIAL));
|
|
88
|
+
byte[] b = new byte[c.CredentialBlobSize];
|
|
89
|
+
Marshal.Copy(c.CredentialBlob, b, 0, c.CredentialBlobSize);
|
|
90
|
+
CredFree(ptr);
|
|
91
|
+
return System.Convert.ToBase64String(b);
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
'@
|
|
97
|
+
Add-Type -TypeDefinition $sig -ErrorAction SilentlyContinue
|
|
98
|
+
[Console]::Out.Write([WinCredKeepalive]::Read($target))
|
|
99
99
|
`;
|
|
100
100
|
/** macOS/Linux read path: untouched, never part of the Windows hot path. */
|
|
101
101
|
function readKeyringTokenStateSyncImpl() {
|
|
@@ -401,6 +401,7 @@ export async function startAgyKeepaliveRun(input) {
|
|
|
401
401
|
executable: input.executable,
|
|
402
402
|
args: buildKeepaliveArgs(input.model),
|
|
403
403
|
stdinMode: 'close',
|
|
404
|
+
windowsNoConsole: true,
|
|
404
405
|
...(home === undefined ? {} : { cwd: home }),
|
|
405
406
|
timeoutMs: input.timeoutMs ?? 45_000,
|
|
406
407
|
});
|
|
@@ -276,10 +276,12 @@ class PersistentAgyWorker {
|
|
|
276
276
|
await this.stop('reset');
|
|
277
277
|
throw new PersistentTransportError('Persistent AGY request aborted before write', 'ABORTED');
|
|
278
278
|
}
|
|
279
|
-
const timeoutMs = input.timeoutMs
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
279
|
+
const timeoutMs = input.timeoutMs !== undefined
|
|
280
|
+
? validateInteger('timeoutMs', input.timeoutMs, 1, 3_600_000)
|
|
281
|
+
: undefined;
|
|
282
|
+
const idleTimeoutMs = input.idleTimeoutMs !== undefined
|
|
283
|
+
? validateInteger('idleTimeoutMs', input.idleTimeoutMs, 1, 3_600_000)
|
|
284
|
+
: this.options.defaultRequestTimeoutMs;
|
|
283
285
|
const maxOutputBytes = Math.min(input.maxOutputBytes ?? this.options.maxOutputBytes, this.options.maxOutputBytes);
|
|
284
286
|
validateInteger('maxOutputBytes', maxOutputBytes, 1, this.options.maxOutputBytes);
|
|
285
287
|
this.clearIdleTimer();
|
|
@@ -305,10 +307,12 @@ class PersistentAgyWorker {
|
|
|
305
307
|
this.active = active;
|
|
306
308
|
active.abortListener = () => this.failActiveAndReset(new PersistentTransportError('Persistent AGY request aborted', 'ABORTED'));
|
|
307
309
|
input.signal?.addEventListener('abort', active.abortListener, { once: true });
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
|
|
310
|
+
// Idle watchdog is re-armed by every frame so a long thinking or tool pause is not a kill.
|
|
311
|
+
// Wall clock is an optional ceiling only armed when an explicit timeoutMs is requested.
|
|
312
|
+
if (timeoutMs !== undefined) {
|
|
313
|
+
const wallClock = setTimeout(() => this.failActiveAndReset(new PersistentTransportError('Persistent AGY request exceeded its wall-clock timeout', 'TIMEOUT')), timeoutMs);
|
|
314
|
+
active.timer = wallClock;
|
|
315
|
+
}
|
|
312
316
|
this.touchActive(active);
|
|
313
317
|
void this.sendRequest(active, input.text);
|
|
314
318
|
});
|
package/lib/index.js
CHANGED
|
@@ -229,6 +229,7 @@ export function apply(ctx, config) {
|
|
|
229
229
|
// controller bundle finishes mounting, and a value captured here
|
|
230
230
|
// stays undefined for the life of the host.
|
|
231
231
|
sessionControllerLookup: () => ctx.get('sessionController'),
|
|
232
|
+
agentsLookup: () => ctx.get('agents'),
|
|
232
233
|
...((activeConfig.mcpToolAllowlist ?? []).length > 0 ? { toolAllowlist: activeConfig.mcpToolAllowlist } : {}),
|
|
233
234
|
serverVersion: '0.0.0',
|
|
234
235
|
logger: message => {
|
package/lib/mcp/bridge.d.ts
CHANGED
|
@@ -38,6 +38,10 @@ export interface DshMcpBridgeOptions {
|
|
|
38
38
|
* agent plane). Resolving per turn removes the load-order dependency.
|
|
39
39
|
*/
|
|
40
40
|
readonly sessionControllerLookup?: (() => DshSessionControllerLike | undefined) | undefined;
|
|
41
|
+
/** Direct Cordis agent registry lookup, resolving child/subagent sessions. */
|
|
42
|
+
readonly agentsLookup?: (() => {
|
|
43
|
+
get(sessionId: unknown): unknown;
|
|
44
|
+
} | undefined) | undefined;
|
|
41
45
|
/** Optional allowlist of DSH tool names exposed to AGY. */
|
|
42
46
|
readonly toolAllowlist?: readonly string[] | undefined;
|
|
43
47
|
readonly serverVersion: string;
|
package/lib/mcp/bridge.js
CHANGED
|
@@ -174,14 +174,29 @@ async function agentOf(options, sessionHandle) {
|
|
|
174
174
|
}
|
|
175
175
|
if (sessionHandle.length === 0)
|
|
176
176
|
return undefined;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
177
|
+
let resolved;
|
|
178
|
+
if (controller !== undefined) {
|
|
179
|
+
resolved = await controller.resolveAgent(sessionHandle).catch((error) => {
|
|
180
|
+
const rejection = subagentRejectionIn(error);
|
|
181
|
+
options.logger?.(rejection === undefined
|
|
182
|
+
? `dsh mcp bridge: resolveAgent("${sessionHandle}") threw ${String(error)}`
|
|
183
|
+
: `dsh mcp bridge: session "${sessionHandle}" is owned by subagent routing (${rejection}); attempting direct agents lookup`);
|
|
184
|
+
return undefined;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
let agent = agentIn(resolved);
|
|
188
|
+
if (agent === undefined && options.agentsLookup !== undefined) {
|
|
189
|
+
try {
|
|
190
|
+
const direct = options.agentsLookup()?.get(sessionHandle);
|
|
191
|
+
if (direct !== undefined) {
|
|
192
|
+
agent = direct;
|
|
193
|
+
options.logger?.(`dsh mcp bridge: resolved agent for session "${sessionHandle}" via direct agents registry`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
options.logger?.(`dsh mcp bridge: direct agents lookup threw ${String(err)}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
185
200
|
if (agent === undefined) {
|
|
186
201
|
options.logger?.(`dsh mcp bridge: no agent resolved for session "${sessionHandle}"`);
|
|
187
202
|
}
|
|
@@ -107,7 +107,7 @@ function expectedDescriptionOf(expected) {
|
|
|
107
107
|
}
|
|
108
108
|
export function projectDshTools(registry, options = {}) {
|
|
109
109
|
const excluded = [];
|
|
110
|
-
const maxTools = options.maxTools ??
|
|
110
|
+
const maxTools = options.maxTools ?? Number.POSITIVE_INFINITY;
|
|
111
111
|
if (registry === undefined || typeof registry.schemas !== 'function') {
|
|
112
112
|
return { tools: [], excluded };
|
|
113
113
|
}
|
package/lib/provider/agy.js
CHANGED
|
@@ -911,8 +911,14 @@ export class AgyAdapter extends LlmAdapter {
|
|
|
911
911
|
this.resolveToolBridge = dependencies.resolveToolBridge;
|
|
912
912
|
this.mcpBridgeEnabled = config.mcpBridge !== false;
|
|
913
913
|
this.agyPath = config.agyPath?.trim() === '' ? undefined : config.agyPath?.trim();
|
|
914
|
-
|
|
915
|
-
this.
|
|
914
|
+
const envTimeout = process.env.DSH_AGY_TIMEOUT_MS ? Number(process.env.DSH_AGY_TIMEOUT_MS) : undefined;
|
|
915
|
+
this.timeoutMs = envTimeout !== undefined && Number.isFinite(envTimeout) && envTimeout > 0
|
|
916
|
+
? envTimeout
|
|
917
|
+
: undefined;
|
|
918
|
+
const envIdleTimeout = process.env.DSH_AGY_IDLE_TIMEOUT_MS ? Number(process.env.DSH_AGY_IDLE_TIMEOUT_MS) : undefined;
|
|
919
|
+
this.idleTimeoutMs = envIdleTimeout !== undefined && Number.isFinite(envIdleTimeout) && envIdleTimeout > 0
|
|
920
|
+
? envIdleTimeout
|
|
921
|
+
: (config.idleTimeoutMs ?? 60_000);
|
|
916
922
|
this.stdinMode = config.stdinMode ?? 'hold';
|
|
917
923
|
this.run = dependencies.runAgyProcess ?? runAgyProcess;
|
|
918
924
|
this.sessionMode = config.sessionMode === 'resume' ? 'resume' : 'full';
|
|
@@ -1042,7 +1048,7 @@ export class AgyAdapter extends LlmAdapter {
|
|
|
1042
1048
|
...(this.agyPath === undefined ? {} : { executable: this.agyPath }),
|
|
1043
1049
|
expectedAgent: this.agent,
|
|
1044
1050
|
minimumVersion: this.minimumAgyVersion,
|
|
1045
|
-
timeoutMs: Math.min(this.timeoutMs, 10_000),
|
|
1051
|
+
timeoutMs: Math.min(this.timeoutMs ?? 10_000, 10_000),
|
|
1046
1052
|
});
|
|
1047
1053
|
}
|
|
1048
1054
|
providerInfo(provider) {
|
|
@@ -1162,7 +1168,7 @@ export class AgyAdapter extends LlmAdapter {
|
|
|
1162
1168
|
...(persistentExtraArgsOf(this.mode).length === 0
|
|
1163
1169
|
? {}
|
|
1164
1170
|
: { extraArgs: persistentExtraArgsOf(this.mode) }),
|
|
1165
|
-
timeoutMs: this.timeoutMs,
|
|
1171
|
+
...(this.timeoutMs === undefined ? {} : { timeoutMs: this.timeoutMs }),
|
|
1166
1172
|
idleTimeoutMs: this.idleTimeoutMs,
|
|
1167
1173
|
maxOutputBytes: this.maxOutputBytes,
|
|
1168
1174
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
@@ -1288,6 +1294,16 @@ export class AgyAdapter extends LlmAdapter {
|
|
|
1288
1294
|
}
|
|
1289
1295
|
await persistentRequest;
|
|
1290
1296
|
}
|
|
1297
|
+
catch (error) {
|
|
1298
|
+
if (modelText.trim().length > 0) {
|
|
1299
|
+
telemetry.partialAnswerDelivered = true;
|
|
1300
|
+
lastResultError = error instanceof Error ? error.message : String(error);
|
|
1301
|
+
finalStatus = "ERROR";
|
|
1302
|
+
}
|
|
1303
|
+
else {
|
|
1304
|
+
throw error;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1291
1307
|
finally {
|
|
1292
1308
|
queue.end();
|
|
1293
1309
|
persistentRequest?.catch(() => undefined);
|
|
@@ -1355,9 +1371,8 @@ export class AgyAdapter extends LlmAdapter {
|
|
|
1355
1371
|
// text already streamed, keep that text and park AGY's reason as a
|
|
1356
1372
|
// reasoning annotation; otherwise fail the turn exactly like one-shot —
|
|
1357
1373
|
// delivering the placeholder as the answer is worse than either.
|
|
1358
|
-
const streamedPartialAnswer = finalStatus !== undefined
|
|
1359
|
-
&&
|
|
1360
|
-
&& modelText.trim().length > 0;
|
|
1374
|
+
const streamedPartialAnswer = ((finalStatus !== undefined && !isSuccessStatus(finalStatus))
|
|
1375
|
+
|| lastResultError !== undefined) && modelText.trim().length > 0;
|
|
1361
1376
|
if (finalStatus !== undefined && !isSuccessStatus(finalStatus) && !streamedPartialAnswer) {
|
|
1362
1377
|
const detail = [finalStatus, finalErrorDetail].filter(value => value !== undefined).join(' ');
|
|
1363
1378
|
const code = classifyAgyFailure(detail, 'AGY_STATUS');
|
|
@@ -1784,7 +1799,7 @@ export class AgyAdapter extends LlmAdapter {
|
|
|
1784
1799
|
prompt,
|
|
1785
1800
|
agent: imageRuntime?.agent ?? route.agent,
|
|
1786
1801
|
model: route.model,
|
|
1787
|
-
timeoutMs: this.timeoutMs,
|
|
1802
|
+
...(this.timeoutMs === undefined ? {} : { timeoutMs: this.timeoutMs }),
|
|
1788
1803
|
idleTimeoutMs: this.idleTimeoutMs,
|
|
1789
1804
|
maxStdoutBytes: this.maxOutputBytes,
|
|
1790
1805
|
maxStderrBytes: this.maxOutputBytes,
|
|
@@ -2013,7 +2028,11 @@ export class AgyAdapter extends LlmAdapter {
|
|
|
2013
2028
|
: result === undefined
|
|
2014
2029
|
? new LlmError('AGY process did not return a result', 'AGY_PROCESS')
|
|
2015
2030
|
: processFailure(result);
|
|
2016
|
-
|
|
2031
|
+
const isFailureOrCutShort = failure !== undefined || (resultSeen && !isSuccessStatus(finalStatus));
|
|
2032
|
+
const streamedPartialAnswer = isFailureOrCutShort && modelText.trim().length > 0;
|
|
2033
|
+
if (streamedPartialAnswer)
|
|
2034
|
+
telemetry.partialAnswerDelivered = true;
|
|
2035
|
+
if (failure !== undefined && !streamedPartialAnswer)
|
|
2017
2036
|
throw failure;
|
|
2018
2037
|
// AGY reports `status: ERROR` on a turn it cut short, and it can carry a
|
|
2019
2038
|
// placeholder under `response` ("(no content)") instead of the answer. When
|
|
@@ -2025,9 +2044,6 @@ export class AgyAdapter extends LlmAdapter {
|
|
|
2025
2044
|
// 1508 and the result branch at src/host/mapper.ts:400-428); the difference
|
|
2026
2045
|
// is that a placeholder response is not deliverable content, so the streamed
|
|
2027
2046
|
// text decides whether anything survives.
|
|
2028
|
-
const streamedPartialAnswer = resultSeen && !isSuccessStatus(finalStatus) && modelText.trim().length > 0;
|
|
2029
|
-
if (streamedPartialAnswer)
|
|
2030
|
-
telemetry.partialAnswerDelivered = true;
|
|
2031
2047
|
if (resultSeen && !isSuccessStatus(finalStatus) && !streamedPartialAnswer) {
|
|
2032
2048
|
const detail = [finalStatus, finalErrorDetail].filter(value => value !== undefined).join(' ');
|
|
2033
2049
|
const code = classifyAgyFailure(detail, 'AGY_STATUS');
|
|
@@ -2119,7 +2135,7 @@ export class AgyAdapter extends LlmAdapter {
|
|
|
2119
2135
|
// AGY's own reason for a cut-short turn belongs in the same reasoning block
|
|
2120
2136
|
// as the bridged tool steps, so the transcript gets one annotation, not two.
|
|
2121
2137
|
if (streamedPartialAnswer) {
|
|
2122
|
-
const reason = (lastResultError ?? finalErrorDetail ?? 'AGY reported status ERROR').trim();
|
|
2138
|
+
const reason = (lastResultError ?? finalErrorDetail ?? failure?.message ?? 'AGY reported status ERROR').trim();
|
|
2123
2139
|
const line = `[agy finished with error] ${reason}\n\n`;
|
|
2124
2140
|
if (!mcpStepsOpen) {
|
|
2125
2141
|
mcpStepsOpen = true;
|
|
@@ -6,6 +6,35 @@ export class AgyPromptError extends Error {
|
|
|
6
6
|
this.name = 'AgyPromptError';
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
+
function imageBlockText(block) {
|
|
10
|
+
const att = block.attachment;
|
|
11
|
+
const meta = [];
|
|
12
|
+
if (att?.name)
|
|
13
|
+
meta.push(`name=${att.name}`);
|
|
14
|
+
if (att?.mediaType)
|
|
15
|
+
meta.push(`media_type=${att.mediaType}`);
|
|
16
|
+
if (att?.width !== undefined && att?.height !== undefined) {
|
|
17
|
+
meta.push(`width=${att.width} height=${att.height}`);
|
|
18
|
+
}
|
|
19
|
+
if (att?.bytes !== undefined)
|
|
20
|
+
meta.push(`bytes=${att.bytes}`);
|
|
21
|
+
if (att?.attachmentId !== undefined)
|
|
22
|
+
meta.push(`id=${att.attachmentId}`);
|
|
23
|
+
const metaStr = meta.length > 0 ? ` ${meta.join(' ')}` : '';
|
|
24
|
+
return `[IMAGE_ATTACHMENT${metaStr}]`;
|
|
25
|
+
}
|
|
26
|
+
function fileBlockText(block) {
|
|
27
|
+
const att = block.attachment;
|
|
28
|
+
const meta = [];
|
|
29
|
+
if (att?.name)
|
|
30
|
+
meta.push(`name=${att.name}`);
|
|
31
|
+
if (att?.bytes !== undefined)
|
|
32
|
+
meta.push(`bytes=${att.bytes}`);
|
|
33
|
+
if (att?.attachmentId !== undefined)
|
|
34
|
+
meta.push(`id=${att.attachmentId}`);
|
|
35
|
+
const metaStr = meta.length > 0 ? ` ${meta.join(' ')}` : '';
|
|
36
|
+
return `[FILE_ATTACHMENT${metaStr}]`;
|
|
37
|
+
}
|
|
9
38
|
function blockText(block, messageIndex, blockIndex) {
|
|
10
39
|
switch (block.type) {
|
|
11
40
|
case 'text':
|
|
@@ -18,8 +47,18 @@ function blockText(block, messageIndex, blockIndex) {
|
|
|
18
47
|
name: block.name,
|
|
19
48
|
arguments: block.arguments,
|
|
20
49
|
})}`;
|
|
21
|
-
|
|
22
|
-
|
|
50
|
+
case 'image':
|
|
51
|
+
return imageBlockText(block);
|
|
52
|
+
case 'file':
|
|
53
|
+
return fileBlockText(block);
|
|
54
|
+
case 'tool-addition':
|
|
55
|
+
return `[DSH TOOL ADDITION: ${block.toolName}]`;
|
|
56
|
+
case 'tool-removal':
|
|
57
|
+
return `[DSH TOOL REMOVAL: ${block.toolName}]`;
|
|
58
|
+
default: {
|
|
59
|
+
const unknownBlock = block;
|
|
60
|
+
throw new AgyPromptError(`AGY text MVP does not support content block ${unknownBlock.type} at message ${messageIndex}, block ${blockIndex}`, 'UNSUPPORTED_CONTENT');
|
|
61
|
+
}
|
|
23
62
|
}
|
|
24
63
|
}
|
|
25
64
|
function blockValue(block, messageIndex, blockIndex) {
|
|
@@ -30,8 +69,35 @@ function blockValue(block, messageIndex, blockIndex) {
|
|
|
30
69
|
return { type: 'reasoning', text: block.text };
|
|
31
70
|
case 'tool-call':
|
|
32
71
|
return { type: 'tool-call', id: block.id, name: block.name, arguments: block.arguments };
|
|
33
|
-
|
|
34
|
-
|
|
72
|
+
case 'image': {
|
|
73
|
+
const att = block.attachment;
|
|
74
|
+
return {
|
|
75
|
+
type: 'image',
|
|
76
|
+
...(att?.attachmentId !== undefined ? { attachmentId: att.attachmentId } : {}),
|
|
77
|
+
...(att?.mediaType !== undefined ? { mediaType: att.mediaType } : {}),
|
|
78
|
+
...(att?.width !== undefined ? { width: att.width } : {}),
|
|
79
|
+
...(att?.height !== undefined ? { height: att.height } : {}),
|
|
80
|
+
...(att?.name !== undefined ? { name: att.name } : {}),
|
|
81
|
+
...(att?.bytes !== undefined ? { bytes: att.bytes } : {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
case 'file': {
|
|
85
|
+
const att = block.attachment;
|
|
86
|
+
return {
|
|
87
|
+
type: 'file',
|
|
88
|
+
...(att?.attachmentId !== undefined ? { attachmentId: att.attachmentId } : {}),
|
|
89
|
+
...(att?.name !== undefined ? { name: att.name } : {}),
|
|
90
|
+
...(att?.bytes !== undefined ? { bytes: att.bytes } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
case 'tool-addition':
|
|
94
|
+
return { type: 'tool-addition', toolName: block.toolName };
|
|
95
|
+
case 'tool-removal':
|
|
96
|
+
return { type: 'tool-removal', toolName: block.toolName };
|
|
97
|
+
default: {
|
|
98
|
+
const unknownBlock = block;
|
|
99
|
+
throw new AgyPromptError(`AGY text MVP does not support content block ${unknownBlock.type} at message ${messageIndex}, block ${blockIndex}`, 'UNSUPPORTED_CONTENT');
|
|
100
|
+
}
|
|
35
101
|
}
|
|
36
102
|
}
|
|
37
103
|
function messageText(message, messageIndex) {
|