@winmatrix/agent-sdk 1.0.3 → 1.0.5
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/dist/adapters/claude/ClaudeAdapter.d.ts +60 -1
- package/dist/adapters/claude/ClaudeAdapter.d.ts.map +1 -1
- package/dist/adapters/claude/ClaudeAdapter.js +404 -84
- package/dist/adapters/claude/ClaudeAdapter.js.map +1 -1
- package/dist/adapters/claude/ClaudeInvocationUsageTracker.d.ts +17 -0
- package/dist/adapters/claude/ClaudeInvocationUsageTracker.d.ts.map +1 -0
- package/dist/adapters/claude/ClaudeInvocationUsageTracker.js +79 -0
- package/dist/adapters/claude/ClaudeInvocationUsageTracker.js.map +1 -0
- package/dist/adapters/claude/ClaudeStreamParser.d.ts +19 -0
- package/dist/adapters/claude/ClaudeStreamParser.d.ts.map +1 -1
- package/dist/adapters/claude/ClaudeStreamParser.js +92 -0
- package/dist/adapters/claude/ClaudeStreamParser.js.map +1 -1
- package/dist/adapters/claude/claudeCommand.js +1 -1
- package/dist/adapters/claude/claudeCommand.js.map +1 -1
- package/dist/adapters/codex/CodexAdapter.d.ts +20 -0
- package/dist/adapters/codex/CodexAdapter.d.ts.map +1 -0
- package/dist/adapters/codex/CodexAdapter.js +313 -0
- package/dist/adapters/codex/CodexAdapter.js.map +1 -0
- package/dist/adapters/hermes/HermesAdapter.d.ts +10 -1
- package/dist/adapters/hermes/HermesAdapter.d.ts.map +1 -1
- package/dist/adapters/hermes/HermesAdapter.js +225 -21
- package/dist/adapters/hermes/HermesAdapter.js.map +1 -1
- package/dist/adapters/hermes/HermesInvocationUsageReader.d.ts +26 -0
- package/dist/adapters/hermes/HermesInvocationUsageReader.d.ts.map +1 -0
- package/dist/adapters/hermes/HermesInvocationUsageReader.js +128 -0
- package/dist/adapters/hermes/HermesInvocationUsageReader.js.map +1 -0
- package/dist/adapters/openclaw/OpenClawAdapter.d.ts +12 -37
- package/dist/adapters/openclaw/OpenClawAdapter.d.ts.map +1 -1
- package/dist/adapters/openclaw/OpenClawAdapter.js +191 -635
- package/dist/adapters/openclaw/OpenClawAdapter.js.map +1 -1
- package/dist/core/AgentAdapter.d.ts +75 -1
- package/dist/core/AgentAdapter.d.ts.map +1 -1
- package/dist/core/AgentAdapter.js +25 -0
- package/dist/core/AgentAdapter.js.map +1 -1
- package/dist/core/RuntimeDetector.d.ts.map +1 -1
- package/dist/core/RuntimeDetector.js +52 -19
- package/dist/core/RuntimeDetector.js.map +1 -1
- package/dist/core/WinMatrixAgentClient.d.ts +30 -4
- package/dist/core/WinMatrixAgentClient.d.ts.map +1 -1
- package/dist/core/WinMatrixAgentClient.js +122 -12
- package/dist/core/WinMatrixAgentClient.js.map +1 -1
- package/dist/index.d.ts +15 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -19
- package/dist/index.js.map +1 -1
- package/dist/observability/WorkstationTokenUsageRecorder.d.ts +54 -0
- package/dist/observability/WorkstationTokenUsageRecorder.d.ts.map +1 -0
- package/dist/observability/WorkstationTokenUsageRecorder.js +264 -0
- package/dist/observability/WorkstationTokenUsageRecorder.js.map +1 -0
- package/dist/utils/envSanitizer.d.ts +38 -0
- package/dist/utils/envSanitizer.d.ts.map +1 -0
- package/dist/utils/envSanitizer.js +94 -0
- package/dist/utils/envSanitizer.js.map +1 -0
- package/dist/utils/errorClassification.d.ts +26 -0
- package/dist/utils/errorClassification.d.ts.map +1 -0
- package/dist/utils/errorClassification.js +52 -0
- package/dist/utils/errorClassification.js.map +1 -0
- package/dist/utils/processUtils.d.ts +10 -0
- package/dist/utils/processUtils.d.ts.map +1 -1
- package/dist/utils/processUtils.js +42 -9
- package/dist/utils/processUtils.js.map +1 -1
- package/dist/utils/resumeFallback.d.ts +4 -0
- package/dist/utils/resumeFallback.d.ts.map +1 -0
- package/dist/utils/resumeFallback.js +65 -0
- package/dist/utils/resumeFallback.js.map +1 -0
- package/package.json +8 -4
- package/dist/adapters/openclaw/unwrapOpenClawPayloads.d.ts +0 -6
- package/dist/adapters/openclaw/unwrapOpenClawPayloads.d.ts.map +0 -1
- package/dist/adapters/openclaw/unwrapOpenClawPayloads.js +0 -24
- package/dist/adapters/openclaw/unwrapOpenClawPayloads.js.map +0 -1
- package/dist/runtime/DualRuntimeBridge.d.ts +0 -38
- package/dist/runtime/DualRuntimeBridge.d.ts.map +0 -1
- package/dist/runtime/DualRuntimeBridge.js +0 -321
- package/dist/runtime/DualRuntimeBridge.js.map +0 -1
- package/dist/runtime/bridgeConfigLoader.d.ts +0 -14
- package/dist/runtime/bridgeConfigLoader.d.ts.map +0 -1
- package/dist/runtime/bridgeConfigLoader.js +0 -133
- package/dist/runtime/bridgeConfigLoader.js.map +0 -1
- package/dist/runtime/bridgeConfigTypes.d.ts +0 -26
- package/dist/runtime/bridgeConfigTypes.d.ts.map +0 -1
- package/dist/runtime/bridgeConfigTypes.js +0 -6
- package/dist/runtime/bridgeConfigTypes.js.map +0 -1
- package/dist/runtime/bridgeHome.d.ts +0 -19
- package/dist/runtime/bridgeHome.d.ts.map +0 -1
- package/dist/runtime/bridgeHome.js +0 -61
- package/dist/runtime/bridgeHome.js.map +0 -1
- package/dist/runtime/bridgeReconnectPolicy.d.ts +0 -8
- package/dist/runtime/bridgeReconnectPolicy.d.ts.map +0 -1
- package/dist/runtime/bridgeReconnectPolicy.js +0 -16
- package/dist/runtime/bridgeReconnectPolicy.js.map +0 -1
- package/dist/runtime/bridgeTaskJournal.d.ts +0 -7
- package/dist/runtime/bridgeTaskJournal.d.ts.map +0 -1
- package/dist/runtime/bridgeTaskJournal.js +0 -66
- package/dist/runtime/bridgeTaskJournal.js.map +0 -1
- package/dist/runtime/localRuntimeAvailabilityMonitor.d.ts +0 -22
- package/dist/runtime/localRuntimeAvailabilityMonitor.d.ts.map +0 -1
- package/dist/runtime/localRuntimeAvailabilityMonitor.js +0 -81
- package/dist/runtime/localRuntimeAvailabilityMonitor.js.map +0 -1
- package/dist/runtime/runDualRuntimeBridge.d.ts +0 -2
- package/dist/runtime/runDualRuntimeBridge.d.ts.map +0 -1
- package/dist/runtime/runDualRuntimeBridge.js +0 -32
- package/dist/runtime/runDualRuntimeBridge.js.map +0 -1
|
@@ -7,11 +7,16 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFile
|
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
10
11
|
import { parseSkillMd } from '../../utils/parseSkillMd.js';
|
|
11
12
|
import { waitForExit, killProcess } from '../../utils/processUtils.js';
|
|
13
|
+
import { sanitizeTaskEnv } from '../../utils/envSanitizer.js';
|
|
14
|
+
import { executeWithResumeFallback } from '../../utils/resumeFallback.js';
|
|
15
|
+
import { classifyAdapterError } from '../../utils/errorClassification.js';
|
|
12
16
|
export { parseSkillMd };
|
|
13
17
|
import { isLocalTool } from '../../core/AgentAdapter.js';
|
|
14
|
-
import { parseClaudeLine, splitLines, } from './ClaudeStreamParser.js';
|
|
18
|
+
import { parseClaudeLine, parseClaudeInvocationUsageFrame, splitLines, } from './ClaudeStreamParser.js';
|
|
19
|
+
import { ClaudeInvocationUsageTracker } from './ClaudeInvocationUsageTracker.js';
|
|
15
20
|
import { resolveClaudeCommand } from './claudeCommand.js';
|
|
16
21
|
const CLAUDE_BASE_ARGS = [
|
|
17
22
|
'--print',
|
|
@@ -20,6 +25,20 @@ const CLAUDE_BASE_ARGS = [
|
|
|
20
25
|
'--output-format', 'stream-json',
|
|
21
26
|
'--include-partial-messages',
|
|
22
27
|
];
|
|
28
|
+
/**
|
|
29
|
+
* 权限默认档(harden-external-computer-daemon P1/D4)。
|
|
30
|
+
*
|
|
31
|
+
* 优先级:task 显式声明 > daemon 环境变量 > 'acceptEdits'(安全默认)。
|
|
32
|
+
* 交互确认通道(TASK_INTERACTION_RESPOND)当前未接通,print 模式下权限
|
|
33
|
+
* 提示会被自动拒绝——acceptEdits 下 Bash 等未预批工具将被拒;需要旧行为
|
|
34
|
+
* 的部署显式设 WINMATRIX_DAEMON_CLAUDE_PERMISSION_MODE=bypassPermissions,
|
|
35
|
+
* 或在任务配置里声明 permissionMode/allowedTools。
|
|
36
|
+
*/
|
|
37
|
+
function resolvePermissionMode(task) {
|
|
38
|
+
return task.claude?.permissionMode
|
|
39
|
+
?? process.env.WINMATRIX_DAEMON_CLAUDE_PERMISSION_MODE
|
|
40
|
+
?? 'acceptEdits';
|
|
41
|
+
}
|
|
23
42
|
export class ClaudeAdapter {
|
|
24
43
|
meta = {
|
|
25
44
|
agentType: 'claude-code',
|
|
@@ -28,7 +47,9 @@ export class ClaudeAdapter {
|
|
|
28
47
|
streaming: true,
|
|
29
48
|
toolCall: true,
|
|
30
49
|
session: true,
|
|
31
|
-
cancellation:
|
|
50
|
+
cancellation: true,
|
|
51
|
+
supportsCancel: true,
|
|
52
|
+
supportsResume: true,
|
|
32
53
|
},
|
|
33
54
|
tools: ['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep', 'WebSearch', 'WebFetch', 'Task', 'Agent', 'NotebookEdit', 'EnterPlanMode'],
|
|
34
55
|
skills: [],
|
|
@@ -42,22 +63,48 @@ export class ClaudeAdapter {
|
|
|
42
63
|
claudePath;
|
|
43
64
|
mcpBridgeUrl;
|
|
44
65
|
mcpApiKey;
|
|
66
|
+
/** steer 递话队列与当前 run 的 stdin 句柄(P2.1/D5) */
|
|
67
|
+
steerQueue = [];
|
|
68
|
+
activeStdin = null;
|
|
69
|
+
workstationId;
|
|
70
|
+
tokenUsageRecorder;
|
|
45
71
|
mcpConfigWritten = new Map();
|
|
46
72
|
constructor(config) {
|
|
47
|
-
|
|
48
|
-
this.
|
|
49
|
-
this.
|
|
73
|
+
const options = config;
|
|
74
|
+
this.claudePath = options?.claudePath ?? 'claude';
|
|
75
|
+
this.mcpBridgeUrl = options?.mcpBridgeUrl;
|
|
76
|
+
this.mcpApiKey = options?.mcpApiKey;
|
|
77
|
+
this.workstationId = options?.workstationId;
|
|
78
|
+
this.tokenUsageRecorder = options?.tokenUsageRecorder;
|
|
50
79
|
}
|
|
51
80
|
// ── execute ──
|
|
52
81
|
async *execute(task, signal) {
|
|
82
|
+
yield* executeWithResumeFallback(task, signal, (nextTask, nextSignal) => this.executeOnce(nextTask, nextSignal));
|
|
83
|
+
}
|
|
84
|
+
async *executeOnce(task, signal) {
|
|
85
|
+
// Pod 场景:通过 @anthropic-ai/claude-agent-sdk.query() 调 claude
|
|
86
|
+
if (task.claude?.useAgentSdk) {
|
|
87
|
+
yield* this.executeViaAgentSdk(task, signal);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
53
90
|
const workDir = task.workDir;
|
|
54
91
|
if (workDir && !existsSync(workDir)) {
|
|
55
|
-
yield {
|
|
92
|
+
yield {
|
|
93
|
+
type: 'error',
|
|
94
|
+
message: `Work directory does not exist: ${workDir}`,
|
|
95
|
+
code: 'cli_crash',
|
|
96
|
+
retryable: false,
|
|
97
|
+
};
|
|
56
98
|
return;
|
|
57
99
|
}
|
|
58
100
|
const args = [...CLAUDE_BASE_ARGS];
|
|
59
|
-
// Permission mode
|
|
60
|
-
|
|
101
|
+
// Permission mode(harden-external-computer-daemon P1/D4:默认档降为 acceptEdits)
|
|
102
|
+
const permissionMode = resolvePermissionMode(task);
|
|
103
|
+
if (permissionMode === 'bypassPermissions') {
|
|
104
|
+
console.warn('[ClaudeAdapter] 本次任务以 bypassPermissions 运行(显式声明)。' +
|
|
105
|
+
'外部 CLI 将不做任何权限确认,请确认任务来源可信。');
|
|
106
|
+
}
|
|
107
|
+
args.push('--permission-mode', permissionMode);
|
|
61
108
|
// workDir
|
|
62
109
|
if (workDir) {
|
|
63
110
|
args.push('--add-dir', workDir);
|
|
@@ -84,7 +131,9 @@ export class ClaudeAdapter {
|
|
|
84
131
|
}
|
|
85
132
|
}
|
|
86
133
|
// Local tools (whitelist)
|
|
87
|
-
const tools = task.claude?.tools
|
|
134
|
+
const tools = task.claude?.tools?.length
|
|
135
|
+
? task.claude.tools
|
|
136
|
+
: ['Bash', 'Read', 'Write', 'Edit'];
|
|
88
137
|
args.push('--allowedTools', tools.join(','));
|
|
89
138
|
// Disallowed tools
|
|
90
139
|
if (task.claude?.disallowedTools?.length) {
|
|
@@ -115,8 +164,11 @@ export class ClaudeAdapter {
|
|
|
115
164
|
else if (this.mcpBridgeUrl) {
|
|
116
165
|
console.warn(`[ClaudeAdapter] Skipping MCP config: URL likely unreachable: ${this.mcpBridgeUrl}`);
|
|
117
166
|
}
|
|
118
|
-
// Build env
|
|
119
|
-
const
|
|
167
|
+
// Build env(先剥凭据后显式加,harden-external-computer-daemon P1/D3)
|
|
168
|
+
const { env, strippedKeys } = sanitizeTaskEnv(task.env);
|
|
169
|
+
if (strippedKeys.length > 0) {
|
|
170
|
+
console.warn(`[ClaudeAdapter] 已剥离 ${strippedKeys.length} 个凭据形态环境变量(仅列 key 名): ${strippedKeys.join(', ')}`);
|
|
171
|
+
}
|
|
120
172
|
// Spawn
|
|
121
173
|
let proc;
|
|
122
174
|
try {
|
|
@@ -126,10 +178,17 @@ export class ClaudeAdapter {
|
|
|
126
178
|
env,
|
|
127
179
|
cwd: workDir ?? process.cwd(),
|
|
128
180
|
shell: spawnSpec.shell,
|
|
181
|
+
// POSIX 下独立进程组,killProcess 才能 kill(-pid) 杀整棵树
|
|
182
|
+
detached: process.platform !== 'win32',
|
|
129
183
|
});
|
|
130
184
|
}
|
|
131
185
|
catch (err) {
|
|
132
|
-
yield {
|
|
186
|
+
yield {
|
|
187
|
+
type: 'error',
|
|
188
|
+
message: `Failed to spawn claude: ${err instanceof Error ? err.message : String(err)}`,
|
|
189
|
+
code: 'cli_crash',
|
|
190
|
+
retryable: false,
|
|
191
|
+
};
|
|
133
192
|
return;
|
|
134
193
|
}
|
|
135
194
|
// Send user message via stdin (stream-json protocol)
|
|
@@ -142,15 +201,23 @@ export class ClaudeAdapter {
|
|
|
142
201
|
message: { role: 'user', content: task.input },
|
|
143
202
|
}) + '\n';
|
|
144
203
|
proc.stdin?.write(userMessage);
|
|
145
|
-
|
|
146
|
-
//
|
|
147
|
-
|
|
204
|
+
// steer(P2.1/D5):stdin 保持打开,turn 结束(result 帧)时若有排队递话
|
|
205
|
+
// 则注入下一 turn;无排队才关闭 stdin 让 CLI 退出
|
|
206
|
+
this.activeStdin = proc.stdin ?? null;
|
|
207
|
+
// Drain stderr to prevent backpressure stall, while keeping a bounded error summary.
|
|
208
|
+
let stderrText = '';
|
|
209
|
+
proc.stderr?.on('data', (chunk) => {
|
|
210
|
+
if (stderrText.length < 4096) {
|
|
211
|
+
stderrText += chunk.toString('utf-8');
|
|
212
|
+
}
|
|
213
|
+
});
|
|
148
214
|
proc.stderr?.on('error', () => { });
|
|
149
215
|
// Track result fields
|
|
150
216
|
let extractedSessionId;
|
|
151
217
|
let extractedResultText;
|
|
152
218
|
let extractedUsage;
|
|
153
219
|
let completed = false;
|
|
220
|
+
const invocationUsageTracker = new ClaudeInvocationUsageTracker();
|
|
154
221
|
// Delta text accumulator: fallback for empty/missing result frame
|
|
155
222
|
let accumulatedDeltaText = '';
|
|
156
223
|
// Abort handler
|
|
@@ -169,18 +236,56 @@ export class ClaudeAdapter {
|
|
|
169
236
|
const { lines, remainder } = splitLines(stdoutBuffer, text);
|
|
170
237
|
stdoutBuffer = remainder;
|
|
171
238
|
for (const line of lines) {
|
|
239
|
+
const observedAt = new Date().toISOString();
|
|
240
|
+
const usageFrame = parseClaudeInvocationUsageFrame(line);
|
|
241
|
+
if (usageFrame && this.tokenUsageRecorder) {
|
|
242
|
+
const completedUsage = invocationUsageTracker.accept(usageFrame, observedAt);
|
|
243
|
+
if (completedUsage) {
|
|
244
|
+
const workstationId = this.workstationId ?? task.env?.WINMATRIX_WORKSTATION_ID ?? process.env.WINMATRIX_WORKSTATION_ID;
|
|
245
|
+
if (!workstationId?.trim()) {
|
|
246
|
+
throw new Error('WORKSTATION_TOKEN_USAGE_WORKSTATION_ID_MISSING');
|
|
247
|
+
}
|
|
248
|
+
await this.tokenUsageRecorder.recordCompletedInvocation({
|
|
249
|
+
workstationId,
|
|
250
|
+
runtime: 'claude-code',
|
|
251
|
+
sourceInvocationId: completedUsage.sourceInvocationId,
|
|
252
|
+
provider: 'claude',
|
|
253
|
+
startedAt: completedUsage.startedAt,
|
|
254
|
+
occurredAt: completedUsage.occurredAt,
|
|
255
|
+
timeSource: completedUsage.timeSource,
|
|
256
|
+
sessionId: completedUsage.sessionId ?? extractedSessionId ?? sessionId,
|
|
257
|
+
taskId: task.taskId,
|
|
258
|
+
modelUsages: [{
|
|
259
|
+
sourceModelUsageId: `${completedUsage.sourceInvocationId}:model:0`,
|
|
260
|
+
model: completedUsage.model,
|
|
261
|
+
inputTokens: completedUsage.usage.inputTokens,
|
|
262
|
+
outputTokens: completedUsage.usage.outputTokens,
|
|
263
|
+
cacheReadTokens: completedUsage.usage.cacheReadTokens,
|
|
264
|
+
cacheCreationTokens: completedUsage.usage.cacheCreationTokens,
|
|
265
|
+
}],
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
172
269
|
const action = parseClaudeLine(line);
|
|
173
270
|
if (action.type === 'tool_use' && isLocalTool(action.toolName)) {
|
|
174
|
-
yield { type: 'thinking', content:
|
|
271
|
+
yield { type: 'thinking', content: `调用本地工具: ${action.toolName}` };
|
|
175
272
|
continue;
|
|
176
273
|
}
|
|
177
274
|
switch (action.type) {
|
|
178
275
|
case 'delta':
|
|
179
276
|
accumulatedDeltaText += action.text;
|
|
180
|
-
yield {
|
|
277
|
+
yield {
|
|
278
|
+
type: 'delta',
|
|
279
|
+
content: action.text,
|
|
280
|
+
engineEvent: { type: 'content_delta', text: action.text },
|
|
281
|
+
};
|
|
181
282
|
break;
|
|
182
283
|
case 'thinking':
|
|
183
|
-
yield {
|
|
284
|
+
yield {
|
|
285
|
+
type: 'thinking',
|
|
286
|
+
content: action.text,
|
|
287
|
+
engineEvent: { type: 'thinking_delta', text: action.text },
|
|
288
|
+
};
|
|
184
289
|
break;
|
|
185
290
|
case 'tool_use':
|
|
186
291
|
if (isLocalTool(action.toolName)) {
|
|
@@ -192,10 +297,15 @@ export class ClaudeAdapter {
|
|
|
192
297
|
callId: randomUUID(),
|
|
193
298
|
name: action.toolName,
|
|
194
299
|
args: action.toolInput ?? {},
|
|
300
|
+
engineEvent: {
|
|
301
|
+
type: 'tool_call',
|
|
302
|
+
name: action.toolName,
|
|
303
|
+
input: action.toolInput,
|
|
304
|
+
},
|
|
195
305
|
};
|
|
196
306
|
}
|
|
197
307
|
break;
|
|
198
|
-
case 'result':
|
|
308
|
+
case 'result': {
|
|
199
309
|
if (action.sessionId)
|
|
200
310
|
extractedSessionId = action.sessionId;
|
|
201
311
|
if (action.result)
|
|
@@ -206,11 +316,35 @@ export class ClaudeAdapter {
|
|
|
206
316
|
output_tokens: action.usage.output_tokens,
|
|
207
317
|
};
|
|
208
318
|
}
|
|
319
|
+
// steer:本 turn 结束。有排队递话则注入下一 turn 继续;
|
|
320
|
+
// 否则关闭 stdin,CLI 处理完退出
|
|
321
|
+
const nextSteer = this.steerQueue.shift();
|
|
322
|
+
if (nextSteer && proc.stdin && !proc.stdin.destroyed && proc.stdin.writable) {
|
|
323
|
+
accumulatedDeltaText = '';
|
|
324
|
+
proc.stdin.write(JSON.stringify({
|
|
325
|
+
type: 'user',
|
|
326
|
+
message: { role: 'user', content: nextSteer },
|
|
327
|
+
}) + '\n');
|
|
328
|
+
yield {
|
|
329
|
+
type: 'thinking',
|
|
330
|
+
content: `[steer] 已注入纠偏消息(队列剩余 ${this.steerQueue.length} 条),继续执行`,
|
|
331
|
+
engineEvent: { type: 'status', text: 'steer_applied' },
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
try {
|
|
336
|
+
proc.stdin?.end();
|
|
337
|
+
}
|
|
338
|
+
catch { /* already closed */ }
|
|
339
|
+
}
|
|
209
340
|
break;
|
|
210
|
-
|
|
341
|
+
}
|
|
342
|
+
case 'error': {
|
|
211
343
|
completed = true;
|
|
212
|
-
|
|
344
|
+
const classified = classifyAdapterError({ message: action.message, aborted: signal?.aborted });
|
|
345
|
+
yield { type: 'error', message: action.message, code: classified.code, retryable: classified.retryable };
|
|
213
346
|
return;
|
|
347
|
+
}
|
|
214
348
|
case 'skip':
|
|
215
349
|
break;
|
|
216
350
|
}
|
|
@@ -221,7 +355,8 @@ export class ClaudeAdapter {
|
|
|
221
355
|
if (!completed) {
|
|
222
356
|
completed = true;
|
|
223
357
|
if (startupError) {
|
|
224
|
-
|
|
358
|
+
const classified = classifyAdapterError({ message: startupError, aborted: signal?.aborted });
|
|
359
|
+
yield { type: 'error', message: startupError, code: classified.code, retryable: classified.retryable };
|
|
225
360
|
return;
|
|
226
361
|
}
|
|
227
362
|
if (extractedSessionId || exitResult.code === 0) {
|
|
@@ -237,10 +372,13 @@ export class ClaudeAdapter {
|
|
|
237
372
|
};
|
|
238
373
|
}
|
|
239
374
|
else {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
message
|
|
243
|
-
|
|
375
|
+
const message = stderrText.trim() || exitResult.error || `Claude Code exited with code ${exitResult.code}`;
|
|
376
|
+
const classified = classifyAdapterError({
|
|
377
|
+
message,
|
|
378
|
+
exitCode: exitResult.code,
|
|
379
|
+
aborted: signal?.aborted,
|
|
380
|
+
});
|
|
381
|
+
yield { type: 'error', message, code: classified.code, retryable: classified.retryable };
|
|
244
382
|
}
|
|
245
383
|
}
|
|
246
384
|
}
|
|
@@ -253,8 +391,26 @@ export class ClaudeAdapter {
|
|
|
253
391
|
}
|
|
254
392
|
catch { /* best-effort */ }
|
|
255
393
|
}
|
|
394
|
+
this.activeStdin = null;
|
|
395
|
+
this.steerQueue = [];
|
|
256
396
|
}
|
|
257
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* 中途递话(P2.1/D5):将纠偏消息排队,当前 turn 结束(result 帧)时注入
|
|
400
|
+
* stdin 开启下一 turn(dsh steer 语义的 stream-json 落地——stream-json 协议
|
|
401
|
+
* 在 turn 边界处理排队的 user 消息)。
|
|
402
|
+
*/
|
|
403
|
+
steer(text) {
|
|
404
|
+
const stdin = this.activeStdin;
|
|
405
|
+
if (!stdin || stdin.destroyed || !stdin.writable) {
|
|
406
|
+
return { accepted: false, reason: 'no_active_run' };
|
|
407
|
+
}
|
|
408
|
+
const trimmed = text.trim();
|
|
409
|
+
if (!trimmed)
|
|
410
|
+
return { accepted: false, reason: 'empty_text' };
|
|
411
|
+
this.steerQueue.push(trimmed);
|
|
412
|
+
return { accepted: true };
|
|
413
|
+
}
|
|
258
414
|
// ── healthCheck ──
|
|
259
415
|
async healthCheck() {
|
|
260
416
|
return new Promise((resolve) => {
|
|
@@ -283,71 +439,81 @@ export class ClaudeAdapter {
|
|
|
283
439
|
async listSessions(options) {
|
|
284
440
|
try {
|
|
285
441
|
const dir = options.dir?.trim();
|
|
286
|
-
const
|
|
287
|
-
if (projectDirs.length === 0) {
|
|
288
|
-
return { sessions: [] };
|
|
289
|
-
}
|
|
290
|
-
// load session index from ~/.claude/sessions/
|
|
291
|
-
const sessionsIndex = this.loadSessionsIndex();
|
|
292
|
-
const entries = [];
|
|
293
|
-
const seen = new Set();
|
|
294
|
-
for (const projectDir of projectDirs) {
|
|
295
|
-
const files = readdirSync(projectDir.path, { withFileTypes: true })
|
|
296
|
-
.filter((d) => d.isFile() && d.name.endsWith('.jsonl'));
|
|
297
|
-
for (const file of files) {
|
|
298
|
-
const sessionId = file.name.replace(/\.jsonl$/, '');
|
|
299
|
-
if (seen.has(sessionId))
|
|
300
|
-
continue;
|
|
301
|
-
seen.add(sessionId);
|
|
302
|
-
const indexPath = join(projectDir.path, file.name);
|
|
303
|
-
const idxMeta = sessionsIndex.get(sessionId);
|
|
304
|
-
if (idxMeta) {
|
|
305
|
-
entries.push({
|
|
306
|
-
id: sessionId,
|
|
307
|
-
cwd: idxMeta.cwd,
|
|
308
|
-
projectPath: idxMeta.cwd ?? projectDir.projectPath,
|
|
309
|
-
summary: idxMeta.summary,
|
|
310
|
-
createdAt: idxMeta.createdAt,
|
|
311
|
-
lastActivityAt: idxMeta.lastActivityAt,
|
|
312
|
-
messageCount: idxMeta.messageCount,
|
|
313
|
-
status: idxMeta.status,
|
|
314
|
-
});
|
|
315
|
-
continue;
|
|
316
|
-
}
|
|
317
|
-
try {
|
|
318
|
-
const stat = readFileSync(indexPath, 'utf-8');
|
|
319
|
-
const lines = stat.trim().length > 0 ? stat.trim().split('\n') : [];
|
|
320
|
-
const firstLine = lines.length > 0 ? JSON.parse(lines[0]) : null;
|
|
321
|
-
const lastLine = lines.length > 0 ? JSON.parse(lines[lines.length - 1]) : null;
|
|
322
|
-
entries.push({
|
|
323
|
-
id: sessionId,
|
|
324
|
-
projectPath: projectDir.projectPath,
|
|
325
|
-
summary: typeof firstLine?.content === 'string'
|
|
326
|
-
? firstLine.content.slice(0, 120)
|
|
327
|
-
: undefined,
|
|
328
|
-
createdAt: firstLine?.timestamp,
|
|
329
|
-
lastActivityAt: lastLine?.timestamp,
|
|
330
|
-
messageCount: lines.length,
|
|
331
|
-
status: firstLine?.status,
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
catch {
|
|
335
|
-
entries.push({ id: sessionId, projectPath: projectDir.projectPath });
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
442
|
+
const entries = this.collectSessionEntries(dir);
|
|
339
443
|
const limit = options.limit ?? 50;
|
|
340
|
-
entries.sort((a, b) => {
|
|
341
|
-
const da = a.lastActivityAt ?? a.createdAt ?? '';
|
|
342
|
-
const db = b.lastActivityAt ?? b.createdAt ?? '';
|
|
343
|
-
return db.localeCompare(da);
|
|
344
|
-
});
|
|
345
444
|
return { sessions: entries.slice(0, limit) };
|
|
346
445
|
}
|
|
347
446
|
catch (err) {
|
|
348
447
|
return { sessions: [], error: err instanceof Error ? err.message : String(err) };
|
|
349
448
|
}
|
|
350
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* 收集给定工作目录范围内的全部会话条目(task 6.10)。
|
|
452
|
+
*
|
|
453
|
+
* 复用 listSessions 的遍历逻辑:索引元数据优先(`~/.claude/sessions/*.json`),
|
|
454
|
+
* 缺失时回退到读取 `.jsonl` 首尾行。结果按 lastActivityAt/createdAt 倒序排列。
|
|
455
|
+
* searchSessions 与 listSessions 共享该集合以保持一致行为。
|
|
456
|
+
*/
|
|
457
|
+
collectSessionEntries(dir) {
|
|
458
|
+
const projectDirs = this.resolveProjectDirs(dir);
|
|
459
|
+
if (projectDirs.length === 0) {
|
|
460
|
+
return [];
|
|
461
|
+
}
|
|
462
|
+
const sessionsIndex = this.loadSessionsIndex();
|
|
463
|
+
const entries = [];
|
|
464
|
+
const seen = new Set();
|
|
465
|
+
for (const projectDir of projectDirs) {
|
|
466
|
+
const files = readdirSync(projectDir.path, { withFileTypes: true })
|
|
467
|
+
.filter((d) => d.isFile() && d.name.endsWith('.jsonl'));
|
|
468
|
+
for (const file of files) {
|
|
469
|
+
const sessionId = file.name.replace(/\.jsonl$/, '');
|
|
470
|
+
if (seen.has(sessionId))
|
|
471
|
+
continue;
|
|
472
|
+
seen.add(sessionId);
|
|
473
|
+
const indexPath = join(projectDir.path, file.name);
|
|
474
|
+
const idxMeta = sessionsIndex.get(sessionId);
|
|
475
|
+
if (idxMeta) {
|
|
476
|
+
entries.push({
|
|
477
|
+
id: sessionId,
|
|
478
|
+
cwd: idxMeta.cwd,
|
|
479
|
+
projectPath: idxMeta.cwd ?? projectDir.projectPath,
|
|
480
|
+
summary: idxMeta.summary,
|
|
481
|
+
createdAt: idxMeta.createdAt,
|
|
482
|
+
lastActivityAt: idxMeta.lastActivityAt,
|
|
483
|
+
messageCount: idxMeta.messageCount,
|
|
484
|
+
status: idxMeta.status,
|
|
485
|
+
});
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
const stat = readFileSync(indexPath, 'utf-8');
|
|
490
|
+
const lines = stat.trim().length > 0 ? stat.trim().split('\n') : [];
|
|
491
|
+
const firstLine = lines.length > 0 ? JSON.parse(lines[0]) : null;
|
|
492
|
+
const lastLine = lines.length > 0 ? JSON.parse(lines[lines.length - 1]) : null;
|
|
493
|
+
entries.push({
|
|
494
|
+
id: sessionId,
|
|
495
|
+
projectPath: projectDir.projectPath,
|
|
496
|
+
summary: typeof firstLine?.content === 'string'
|
|
497
|
+
? firstLine.content.slice(0, 120)
|
|
498
|
+
: undefined,
|
|
499
|
+
createdAt: firstLine?.timestamp,
|
|
500
|
+
lastActivityAt: lastLine?.timestamp,
|
|
501
|
+
messageCount: lines.length,
|
|
502
|
+
status: firstLine?.status,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
entries.push({ id: sessionId, projectPath: projectDir.projectPath });
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
entries.sort((a, b) => {
|
|
511
|
+
const da = a.lastActivityAt ?? a.createdAt ?? '';
|
|
512
|
+
const db = b.lastActivityAt ?? b.createdAt ?? '';
|
|
513
|
+
return db.localeCompare(da);
|
|
514
|
+
});
|
|
515
|
+
return entries;
|
|
516
|
+
}
|
|
351
517
|
// ── getSession ──
|
|
352
518
|
async getSession(sessionId, options = {}) {
|
|
353
519
|
try {
|
|
@@ -404,6 +570,124 @@ export class ClaudeAdapter {
|
|
|
404
570
|
return this.emptySessionResult(sessionId, err instanceof Error ? err.message : String(err));
|
|
405
571
|
}
|
|
406
572
|
}
|
|
573
|
+
// ── searchSessions(task 6.10) ──
|
|
574
|
+
/**
|
|
575
|
+
* 按可见文本字段搜索项目会话。命中 summary / customTitle / firstPrompt /
|
|
576
|
+
* cwd / projectName / projectPath(不区分大小写)。
|
|
577
|
+
*
|
|
578
|
+
* 实现为 collectSessionEntries + 内存过滤:daemon 侧结果集通常很小,
|
|
579
|
+
* 无需引擎原生搜索能力;与 listSessions 共享同一数据源保证一致性。
|
|
580
|
+
* cursor 为简单整数偏移字符串。
|
|
581
|
+
*/
|
|
582
|
+
async searchSessions(options) {
|
|
583
|
+
try {
|
|
584
|
+
const needle = options.query?.trim().toLowerCase();
|
|
585
|
+
if (!needle) {
|
|
586
|
+
return { sessions: [] };
|
|
587
|
+
}
|
|
588
|
+
const dir = options.dir?.trim();
|
|
589
|
+
const all = this.collectSessionEntries(dir);
|
|
590
|
+
const matched = all.filter((entry) => this.sessionEntryMatches(entry, needle));
|
|
591
|
+
const limit = options.limit ?? 50;
|
|
592
|
+
const start = this.cursorToOffset(options.cursor);
|
|
593
|
+
const page = matched.slice(start, start + limit);
|
|
594
|
+
const hasNext = start + limit < matched.length;
|
|
595
|
+
return {
|
|
596
|
+
sessions: page,
|
|
597
|
+
...(hasNext ? { nextCursor: String(start + limit) } : {}),
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
catch (err) {
|
|
601
|
+
return { sessions: [], error: err instanceof Error ? err.message : String(err) };
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
sessionEntryMatches(entry, needle) {
|
|
605
|
+
const fields = [
|
|
606
|
+
entry.summary,
|
|
607
|
+
entry.customTitle,
|
|
608
|
+
entry.firstPrompt,
|
|
609
|
+
entry.cwd,
|
|
610
|
+
entry.projectName,
|
|
611
|
+
entry.projectPath,
|
|
612
|
+
entry.gitBranch,
|
|
613
|
+
entry.tag,
|
|
614
|
+
];
|
|
615
|
+
return fields.some((value) => typeof value === 'string' && value.toLowerCase().includes(needle));
|
|
616
|
+
}
|
|
617
|
+
cursorToOffset(cursor) {
|
|
618
|
+
if (!cursor)
|
|
619
|
+
return 0;
|
|
620
|
+
const parsed = Number.parseInt(cursor, 10);
|
|
621
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
622
|
+
}
|
|
623
|
+
// ── deleteSession(task 6.10) ──
|
|
624
|
+
/**
|
|
625
|
+
* 幂等删除会话:删除 `~/.claude/projects/<dir>/<sessionId>.jsonl`,
|
|
626
|
+
* 并清理 `~/.claude/sessions/*.json` 索引中指向该 sessionId 的条目。
|
|
627
|
+
*
|
|
628
|
+
* - 文件不存在(或从未存在)→ `already_deleted`(幂等)。
|
|
629
|
+
* - 文件存在并删除成功 → `deleted`。
|
|
630
|
+
* - 删除失败(权限等)→ `rejected`(含 error)。
|
|
631
|
+
*
|
|
632
|
+
* 仅作用于 Engine 原生 Session 文件,不影响产品 conversation/task 记录(task 9.13)。
|
|
633
|
+
*/
|
|
634
|
+
async deleteSession(sessionId, _request) {
|
|
635
|
+
try {
|
|
636
|
+
const resolved = this.resolveSessionFile(sessionId);
|
|
637
|
+
if (!resolved || !existsSync(resolved.path)) {
|
|
638
|
+
// 索引条目仍可能残留:清理之,保持幂等语义
|
|
639
|
+
this.cleanSessionsIndex(sessionId);
|
|
640
|
+
return { status: 'already_deleted' };
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
unlinkSync(resolved.path);
|
|
644
|
+
}
|
|
645
|
+
catch (err) {
|
|
646
|
+
return {
|
|
647
|
+
status: 'rejected',
|
|
648
|
+
error: `Failed to delete session file: ${err instanceof Error ? err.message : String(err)}`,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
this.cleanSessionsIndex(sessionId);
|
|
652
|
+
return { status: 'deleted' };
|
|
653
|
+
}
|
|
654
|
+
catch (err) {
|
|
655
|
+
return {
|
|
656
|
+
status: 'rejected',
|
|
657
|
+
error: err instanceof Error ? err.message : String(err),
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* 清理 `~/.claude/sessions/*.json` 索引中指向该 sessionId 的条目。
|
|
663
|
+
* 静默处理:索引不存在或解析失败不影响删除回执。
|
|
664
|
+
*/
|
|
665
|
+
cleanSessionsIndex(sessionId) {
|
|
666
|
+
try {
|
|
667
|
+
const sessionsDir = join(homedir(), '.claude', 'sessions');
|
|
668
|
+
if (!existsSync(sessionsDir))
|
|
669
|
+
return;
|
|
670
|
+
const files = readdirSync(sessionsDir, { withFileTypes: true });
|
|
671
|
+
for (const file of files) {
|
|
672
|
+
if (!file.isFile() || !file.name.endsWith('.json'))
|
|
673
|
+
continue;
|
|
674
|
+
const filePath = join(sessionsDir, file.name);
|
|
675
|
+
try {
|
|
676
|
+
const meta = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
677
|
+
if (meta?.sessionId !== sessionId)
|
|
678
|
+
continue;
|
|
679
|
+
// 该索引文件即为该会话的元数据;删除整个文件
|
|
680
|
+
unlinkSync(filePath);
|
|
681
|
+
}
|
|
682
|
+
catch {
|
|
683
|
+
/* skip malformed index entries */
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
catch {
|
|
688
|
+
/* index cleanup is best-effort */
|
|
689
|
+
}
|
|
690
|
+
}
|
|
407
691
|
// ── helpers for session queries ──
|
|
408
692
|
/** �?~/.claude/sessions/*.json 加载会话元数据索�?*/
|
|
409
693
|
loadSessionsIndex() {
|
|
@@ -678,5 +962,41 @@ export class ClaudeAdapter {
|
|
|
678
962
|
}
|
|
679
963
|
return null;
|
|
680
964
|
}
|
|
965
|
+
// 搬自 run-claude-agent.mjs:38-40,491-511
|
|
966
|
+
async *executeViaAgentSdk(task, signal) {
|
|
967
|
+
const sdkPath = process.env.CLAUDE_AGENT_SDK_PATH
|
|
968
|
+
|| '/home/node/.local/lib/node_modules/@anthropic-ai/claude-agent-sdk';
|
|
969
|
+
const require = createRequire(import.meta.url);
|
|
970
|
+
const sdk = require(sdkPath);
|
|
971
|
+
const { query } = sdk;
|
|
972
|
+
const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
|
|
973
|
+
const permissionMode = resolvePermissionMode(task);
|
|
974
|
+
const maxTurns = Math.min(task.claude?.maxTurns ?? 200, 500);
|
|
975
|
+
const allowedTools = task.claude?.tools ?? ['Skill', 'Read', 'Write', 'Bash', 'mcp__*'];
|
|
976
|
+
const queryOptions = {
|
|
977
|
+
cwd: task.workDir || process.cwd(),
|
|
978
|
+
settingSources: ['user', 'project'],
|
|
979
|
+
allowedTools,
|
|
980
|
+
permissionMode,
|
|
981
|
+
maxTurns,
|
|
982
|
+
};
|
|
983
|
+
if (task.resumeSession && task.sessionId)
|
|
984
|
+
queryOptions.resume = task.sessionId;
|
|
985
|
+
if (!isRoot)
|
|
986
|
+
queryOptions.allowDangerouslySkipPermissions = true;
|
|
987
|
+
for await (const message of query({
|
|
988
|
+
prompt: task.prompt || task.input || '',
|
|
989
|
+
options: queryOptions,
|
|
990
|
+
})) {
|
|
991
|
+
if (signal?.aborted)
|
|
992
|
+
return;
|
|
993
|
+
if (message.type === 'result' && message.result != null) {
|
|
994
|
+
yield { type: 'result', content: String(message.result) };
|
|
995
|
+
}
|
|
996
|
+
else if (message.type === 'assistant' && message.message != null) {
|
|
997
|
+
yield { type: 'delta', content: String(message.message), engineEvent: { type: 'content_delta', text: String(message.message) } };
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
681
1001
|
}
|
|
682
1002
|
//# sourceMappingURL=ClaudeAdapter.js.map
|