@parall/claude-agent 1.30.0 → 1.32.0
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/config.d.ts.map +1 -1
- package/dist/config.js +18 -18
- package/dist/dispatch.d.ts +9 -7
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +134 -88
- package/dist/index.js +185 -134
- package/dist/output-parser.d.ts +4 -4
- package/dist/output-parser.d.ts.map +1 -1
- package/dist/output-parser.js +41 -41
- package/dist/session-manager.d.ts +2 -2
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +12 -10
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.js +9 -9
- package/package.json +4 -4
- package/src/config.ts +27 -19
- package/src/dispatch.ts +182 -121
- package/src/index.ts +214 -142
- package/src/output-parser.ts +54 -49
- package/src/session-manager.ts +31 -28
- package/src/workspace.ts +10 -10
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import * as os from
|
|
3
|
-
import { ParallAgentGateway, createPlatformConfigManager, createLogger, childLogger, isPlatformManagedProfile, parseShutdownDeadlineMs } from
|
|
4
|
-
import { ApiError, ParallClient, ParallWs } from
|
|
5
|
-
import { buildClaudeRuntimeKey, contextFilePathForSession, resolveClaudeAgentConfig, resolveWsUrl, sessionStateFilePathForRuntime, stepIdFilePathForSession, } from
|
|
6
|
-
import { ClaudeCodeAdapter } from
|
|
7
|
-
import { ClaudeSessionManager } from
|
|
8
|
-
import { ensureClaudeWorkspace } from
|
|
9
|
-
const log = createLogger(
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import { ParallAgentGateway, createPlatformConfigManager, createLogger, createOtelLogger, childLogger, isPlatformManagedProfile, isPlatformModelOverride, resolveRuntimeModel, parseShutdownDeadlineMs, parseForkDeadlineMs, parseDispatchDeadlineMs, parseProviderConfig, clearAllProviderCreds, llmSource, initAgentTelemetry, } from '@parall/agent-core';
|
|
4
|
+
import { ApiError, ParallClient, ParallWs } from '@parall/sdk';
|
|
5
|
+
import { buildClaudeRuntimeKey, contextFilePathForSession, resolveClaudeAgentConfig, resolveWsUrl, sessionStateFilePathForRuntime, stepIdFilePathForSession, } from './config.js';
|
|
6
|
+
import { ClaudeCodeAdapter } from './dispatch.js';
|
|
7
|
+
import { ClaudeSessionManager } from './session-manager.js';
|
|
8
|
+
import { ensureClaudeWorkspace } from './workspace.js';
|
|
9
|
+
const log = createLogger('claude-agent');
|
|
10
10
|
let activeLog = log;
|
|
11
11
|
async function getAgentMeWithLegacyFallback(client, orgId) {
|
|
12
12
|
try {
|
|
@@ -20,137 +20,188 @@ async function getAgentMeWithLegacyFallback(client, orgId) {
|
|
|
20
20
|
return { ...user, agent_profile: null };
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
function resolveProviderEnv() {
|
|
24
|
+
const pc = parseProviderConfig(process.env);
|
|
25
|
+
if (!pc)
|
|
26
|
+
return;
|
|
27
|
+
clearAllProviderCreds(process.env);
|
|
28
|
+
const source = llmSource(pc);
|
|
29
|
+
if (source === 'parall') {
|
|
30
|
+
process.env.ANTHROPIC_AUTH_TOKEN = process.env.PRLL_API_KEY;
|
|
31
|
+
process.env.ANTHROPIC_BASE_URL = `${process.env.PRLL_API_URL}/api/llm`;
|
|
32
|
+
process.env.PRLL_CLAUDE_ALLOW_API_KEY = '1';
|
|
31
33
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const agentLog = childLogger(log, agentUserId);
|
|
40
|
-
activeLog = agentLog;
|
|
41
|
-
ensureClaudeWorkspace(config.workspaceDir, agentLog, {
|
|
42
|
-
userId: agentUserId,
|
|
43
|
-
displayName: me.display_name,
|
|
44
|
-
description: me.agent_profile?.description ?? undefined,
|
|
45
|
-
});
|
|
46
|
-
const runtimeKey = config.runtimeKey || buildClaudeRuntimeKey(agentUserId);
|
|
47
|
-
const sessionStateFilePath = sessionStateFilePathForRuntime(config.stateDir, runtimeKey);
|
|
48
|
-
const sessionManager = new ClaudeSessionManager(runtimeKey, sessionStateFilePath, agentLog);
|
|
49
|
-
const resolvedWsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
|
|
50
|
-
const ws = new ParallWs({
|
|
51
|
-
getTicket: () => client.getWsTicket(),
|
|
52
|
-
wsUrl: resolvedWsUrl,
|
|
53
|
-
});
|
|
54
|
-
const configMgr = createPlatformConfigManager({ client, stateDir: config.stateDir, runtimeType: "claude-code", log: agentLog });
|
|
55
|
-
const platformDefaults = await configMgr.fetch();
|
|
56
|
-
let platformManaged = isPlatformManagedProfile(me.agent_profile);
|
|
57
|
-
const resolvedModel = platformManaged ? (platformDefaults.model ?? config.model) : config.model;
|
|
58
|
-
const resolvedEffort = platformManaged
|
|
59
|
-
? (platformDefaults.thinkingEffort ?? (process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined))
|
|
60
|
-
: (process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined);
|
|
61
|
-
if (platformDefaults.model)
|
|
62
|
-
agentLog.info(`platform config: model=${platformDefaults.model} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
|
|
63
|
-
if (platformDefaults.thinkingEffort)
|
|
64
|
-
agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
|
|
65
|
-
const adapter = new ClaudeCodeAdapter({
|
|
66
|
-
claudeBin: config.claudeBin,
|
|
67
|
-
claudeHome: config.claudeHome,
|
|
68
|
-
workspaceDir: config.workspaceDir,
|
|
69
|
-
model: resolvedModel,
|
|
70
|
-
permissionMode: config.permissionMode,
|
|
71
|
-
allowedTools: config.allowedTools,
|
|
72
|
-
disallowedTools: config.disallowedTools,
|
|
73
|
-
additionalDirs: config.additionalDirs,
|
|
74
|
-
appendSystemPrompt: config.appendSystemPrompt,
|
|
75
|
-
allowApiKey: config.allowApiKey,
|
|
76
|
-
sessionManager,
|
|
77
|
-
// Static Parall credentials. The long-lived subprocess must authenticate
|
|
78
|
-
// against Parall's API across many dispatches, so these cannot come from
|
|
79
|
-
// a per-dispatch DispatchContext.
|
|
80
|
-
apiUrl: config.apiUrl,
|
|
81
|
-
apiKey: config.apiKey,
|
|
82
|
-
orgId: config.orgId,
|
|
83
|
-
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
84
|
-
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
85
|
-
});
|
|
86
|
-
if (resolvedEffort) {
|
|
87
|
-
adapter.updateConfig({ effort: resolvedEffort });
|
|
34
|
+
else if (source === 'custom') {
|
|
35
|
+
if (pc.anthropic_auth_token) {
|
|
36
|
+
process.env.ANTHROPIC_AUTH_TOKEN = pc.anthropic_auth_token;
|
|
37
|
+
process.env.PRLL_CLAUDE_ALLOW_API_KEY = '1';
|
|
38
|
+
}
|
|
39
|
+
if (pc.anthropic_base_url)
|
|
40
|
+
process.env.ANTHROPIC_BASE_URL = pc.anthropic_base_url;
|
|
88
41
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
connectionLabel: resolvedWsUrl,
|
|
94
|
-
config: {
|
|
95
|
-
parall_url: config.apiUrl,
|
|
96
|
-
api_key: config.apiKey,
|
|
97
|
-
org_id: config.orgId,
|
|
98
|
-
},
|
|
99
|
-
agentUserId,
|
|
100
|
-
runtimeType: "claude-code",
|
|
101
|
-
runtimeKey,
|
|
102
|
-
runtimeRef: {
|
|
103
|
-
hostname: os.hostname(),
|
|
104
|
-
pid: process.pid,
|
|
105
|
-
workspace_dir: config.workspaceDir,
|
|
106
|
-
claude_home: config.claudeHome,
|
|
107
|
-
},
|
|
108
|
-
dispatchAdapter: adapter,
|
|
109
|
-
log: agentLog,
|
|
110
|
-
shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
|
|
111
|
-
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
112
|
-
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
113
|
-
onConfigUpdate: async () => {
|
|
114
|
-
const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
115
|
-
platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
|
|
116
|
-
const updated = await configMgr.fetch();
|
|
117
|
-
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
118
|
-
adapter.updateConfig({
|
|
119
|
-
model: platformManaged ? (updated.model ?? config.model) : (config.model ?? null),
|
|
120
|
-
effort: platformManaged ? (updated.thinkingEffort ?? localEffort) : localEffort,
|
|
121
|
-
});
|
|
122
|
-
},
|
|
123
|
-
onSessionReady: async () => {
|
|
124
|
-
const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
125
|
-
platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
|
|
126
|
-
const updated = await configMgr.fetch();
|
|
127
|
-
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
128
|
-
adapter.updateConfig({
|
|
129
|
-
model: platformManaged ? (updated.model ?? config.model) : (config.model ?? null),
|
|
130
|
-
effort: platformManaged ? (updated.thinkingEffort ?? localEffort) : localEffort,
|
|
131
|
-
});
|
|
132
|
-
},
|
|
133
|
-
// Long-lived `claude` subprocesses outlive a single dispatch, so the
|
|
134
|
-
// gateway's shuttingDown flag alone does not tear them down. Piggyback on
|
|
135
|
-
// onBeforeDisconnect to close stdin and SIGTERM any survivors after
|
|
136
|
-
// in-flight drains finish.
|
|
137
|
-
onBeforeDisconnect: () => adapter.shutdown(),
|
|
138
|
-
onNewSession: async () => {
|
|
139
|
-
sessionManager.clearMainSession();
|
|
140
|
-
await adapter.shutdown();
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
const abortController = new AbortController();
|
|
144
|
-
const abort = () => abortController.abort();
|
|
145
|
-
process.on("SIGINT", abort);
|
|
146
|
-
process.on("SIGTERM", abort);
|
|
42
|
+
}
|
|
43
|
+
async function main() {
|
|
44
|
+
const telemetry = await initAgentTelemetry('parall-claude-agent', 'claude-code');
|
|
45
|
+
activeLog = createOtelLogger('agent', 'claude-agent');
|
|
147
46
|
try {
|
|
148
|
-
|
|
149
|
-
|
|
47
|
+
resolveProviderEnv();
|
|
48
|
+
const config = resolveClaudeAgentConfig(process.env);
|
|
49
|
+
if (config.allowApiKey && (process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN)) {
|
|
50
|
+
// Surface the billing path: default is OAuth (Claude.ai subscription); this
|
|
51
|
+
// branch means the operator opted into Anthropic API pay-per-use, and the
|
|
52
|
+
// whole point of the strip default is that "silent" is the bad state.
|
|
53
|
+
activeLog.warn('PRLL_CLAUDE_ALLOW_API_KEY=1 — ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN will reach the Claude CLI; billing routes through Anthropic API pay-per-use instead of Claude.ai OAuth.');
|
|
54
|
+
}
|
|
55
|
+
const client = new ParallClient({
|
|
56
|
+
baseUrl: config.apiUrl,
|
|
57
|
+
token: config.apiKey,
|
|
58
|
+
swimlaneName: config.swimlaneName,
|
|
59
|
+
});
|
|
60
|
+
const me = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
61
|
+
const agentUserId = me.id;
|
|
62
|
+
const agentLog = childLogger(activeLog, agentUserId);
|
|
63
|
+
activeLog = agentLog;
|
|
64
|
+
ensureClaudeWorkspace(config.workspaceDir, agentLog, {
|
|
65
|
+
userId: agentUserId,
|
|
66
|
+
displayName: me.display_name,
|
|
67
|
+
description: me.agent_profile?.description ?? undefined,
|
|
68
|
+
});
|
|
69
|
+
const runtimeKey = config.runtimeKey || buildClaudeRuntimeKey(agentUserId);
|
|
70
|
+
const sessionStateFilePath = sessionStateFilePathForRuntime(config.stateDir, runtimeKey);
|
|
71
|
+
const sessionManager = new ClaudeSessionManager(runtimeKey, sessionStateFilePath, agentLog);
|
|
72
|
+
const resolvedWsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
|
|
73
|
+
const ws = new ParallWs({
|
|
74
|
+
getTicket: () => client.getWsTicket(),
|
|
75
|
+
wsUrl: resolvedWsUrl,
|
|
76
|
+
});
|
|
77
|
+
const configMgr = createPlatformConfigManager({
|
|
78
|
+
client,
|
|
79
|
+
stateDir: config.stateDir,
|
|
80
|
+
runtimeType: 'claude-code',
|
|
81
|
+
log: agentLog,
|
|
82
|
+
});
|
|
83
|
+
const platformDefaults = await configMgr.fetch();
|
|
84
|
+
let platformManaged = isPlatformManagedProfile(me.agent_profile);
|
|
85
|
+
// Model precedence: operator override (platform-managed) > env > server floor.
|
|
86
|
+
// The server delivers a model floor for Parall-proxy routes even when
|
|
87
|
+
// model_management=self, so the CLI never falls back to a built-in default
|
|
88
|
+
// the catalog might reject — but that floor must lose to an explicit env
|
|
89
|
+
// override (env > floor). A profile is an operator-override only when it is
|
|
90
|
+
// platform-managed AND not explicitly self-managed: this treats legacy hosted
|
|
91
|
+
// agents (model_management=null, resolved to platform server-side) as
|
|
92
|
+
// overrides, while excluding local self agents whose delivered model is a
|
|
93
|
+
// floor. effort stays an operator override (platform).
|
|
94
|
+
let platformModelOverride = isPlatformModelOverride(me.agent_profile);
|
|
95
|
+
const resolvedModel = resolveRuntimeModel(platformModelOverride, platformDefaults.model, config.model);
|
|
96
|
+
const resolvedEffort = platformManaged
|
|
97
|
+
? (platformDefaults.thinkingEffort ??
|
|
98
|
+
(process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined))
|
|
99
|
+
: process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || undefined;
|
|
100
|
+
if (platformDefaults.model)
|
|
101
|
+
agentLog.info(`platform config: model=${platformDefaults.model} → resolved=${resolvedModel ?? 'cli-default'} (${platformModelOverride ? 'platform-managed' : 'floor/self'})`);
|
|
102
|
+
if (platformDefaults.thinkingEffort)
|
|
103
|
+
agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort} (${platformModelOverride ? 'applied' : 'ignored, model_management=self'})`);
|
|
104
|
+
const adapter = new ClaudeCodeAdapter({
|
|
105
|
+
claudeBin: config.claudeBin,
|
|
106
|
+
claudeHome: config.claudeHome,
|
|
107
|
+
workspaceDir: config.workspaceDir,
|
|
108
|
+
model: resolvedModel,
|
|
109
|
+
permissionMode: config.permissionMode,
|
|
110
|
+
allowedTools: config.allowedTools,
|
|
111
|
+
disallowedTools: config.disallowedTools,
|
|
112
|
+
additionalDirs: config.additionalDirs,
|
|
113
|
+
appendSystemPrompt: config.appendSystemPrompt,
|
|
114
|
+
allowApiKey: config.allowApiKey,
|
|
115
|
+
sessionManager,
|
|
116
|
+
// Static Parall credentials. The long-lived subprocess must authenticate
|
|
117
|
+
// against Parall's API across many dispatches, so these cannot come from
|
|
118
|
+
// a per-dispatch DispatchContext.
|
|
119
|
+
apiUrl: config.apiUrl,
|
|
120
|
+
apiKey: config.apiKey,
|
|
121
|
+
orgId: config.orgId,
|
|
122
|
+
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
123
|
+
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
124
|
+
});
|
|
125
|
+
if (resolvedEffort) {
|
|
126
|
+
adapter.updateConfig({ effort: resolvedEffort });
|
|
127
|
+
}
|
|
128
|
+
const gateway = new ParallAgentGateway({
|
|
129
|
+
accountId: agentUserId,
|
|
130
|
+
client,
|
|
131
|
+
ws,
|
|
132
|
+
connectionLabel: resolvedWsUrl,
|
|
133
|
+
config: {
|
|
134
|
+
parall_url: config.apiUrl,
|
|
135
|
+
api_key: config.apiKey,
|
|
136
|
+
org_id: config.orgId,
|
|
137
|
+
},
|
|
138
|
+
agentUserId,
|
|
139
|
+
runtimeType: 'claude-code',
|
|
140
|
+
runtimeKey,
|
|
141
|
+
runtimeRef: {
|
|
142
|
+
hostname: os.hostname(),
|
|
143
|
+
pid: process.pid,
|
|
144
|
+
workspace_dir: config.workspaceDir,
|
|
145
|
+
claude_home: config.claudeHome,
|
|
146
|
+
},
|
|
147
|
+
dispatchAdapter: adapter,
|
|
148
|
+
log: agentLog,
|
|
149
|
+
shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
|
|
150
|
+
forkDeadlineMs: parseForkDeadlineMs(process.env.PRLL_FORK_DEADLINE_MS),
|
|
151
|
+
dispatchDeadlineMs: parseDispatchDeadlineMs(process.env.PRLL_DISPATCH_DEADLINE_MS),
|
|
152
|
+
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
153
|
+
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
154
|
+
onConfigUpdate: async () => {
|
|
155
|
+
const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
156
|
+
platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
|
|
157
|
+
platformModelOverride = isPlatformModelOverride(refreshed.agent_profile);
|
|
158
|
+
const updated = await configMgr.fetch();
|
|
159
|
+
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
160
|
+
adapter.updateConfig({
|
|
161
|
+
model: resolveRuntimeModel(platformModelOverride, updated.model, config.model) ?? null,
|
|
162
|
+
effort: platformManaged ? (updated.thinkingEffort ?? localEffort) : localEffort,
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
onSessionReady: async () => {
|
|
166
|
+
const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
167
|
+
platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
|
|
168
|
+
platformModelOverride = isPlatformModelOverride(refreshed.agent_profile);
|
|
169
|
+
const updated = await configMgr.fetch();
|
|
170
|
+
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
171
|
+
adapter.updateConfig({
|
|
172
|
+
model: resolveRuntimeModel(platformModelOverride, updated.model, config.model) ?? null,
|
|
173
|
+
effort: platformManaged ? (updated.thinkingEffort ?? localEffort) : localEffort,
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
// Long-lived `claude` subprocesses outlive a single dispatch, so the
|
|
177
|
+
// gateway's shuttingDown flag alone does not tear them down. Piggyback on
|
|
178
|
+
// onBeforeDisconnect to close stdin and SIGTERM any survivors after
|
|
179
|
+
// in-flight drains finish.
|
|
180
|
+
onBeforeDisconnect: () => adapter.shutdown(),
|
|
181
|
+
onNewSession: async () => {
|
|
182
|
+
sessionManager.clearMainSession();
|
|
183
|
+
adapter.resetProcesses();
|
|
184
|
+
},
|
|
185
|
+
onSessionStale: () => {
|
|
186
|
+
sessionManager.clearMainSession();
|
|
187
|
+
adapter.resetProcesses();
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
const abortController = new AbortController();
|
|
191
|
+
const abort = () => abortController.abort();
|
|
192
|
+
process.on('SIGINT', abort);
|
|
193
|
+
process.on('SIGTERM', abort);
|
|
194
|
+
try {
|
|
195
|
+
agentLog.info(`starting self-hosted Claude runtime for ${me.display_name} (${agentUserId})`);
|
|
196
|
+
await gateway.run(abortController.signal);
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
process.off('SIGINT', abort);
|
|
200
|
+
process.off('SIGTERM', abort);
|
|
201
|
+
}
|
|
150
202
|
}
|
|
151
203
|
finally {
|
|
152
|
-
|
|
153
|
-
process.off("SIGTERM", abort);
|
|
204
|
+
await telemetry.shutdown();
|
|
154
205
|
}
|
|
155
206
|
}
|
|
156
207
|
main().catch((err) => {
|
package/dist/output-parser.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { Readable } from
|
|
2
|
-
import type { RuntimeEvent } from
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
import type { RuntimeEvent } from '@parall/agent-core';
|
|
3
3
|
export type ClaudeParsedEvent = RuntimeEvent | {
|
|
4
|
-
type:
|
|
4
|
+
type: 'session_id';
|
|
5
5
|
sessionId: string;
|
|
6
6
|
} | {
|
|
7
|
-
type:
|
|
7
|
+
type: 'turn_end';
|
|
8
8
|
sessionId?: string;
|
|
9
9
|
isError: boolean;
|
|
10
10
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-parser.d.ts","sourceRoot":"","sources":["../src/output-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GACzB,YAAY,GACZ;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAWzC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AA4E/D,wBAAuB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"output-parser.d.ts","sourceRoot":"","sources":["../src/output-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GACzB,YAAY,GACZ;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAWzC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AA4E/D,wBAAuB,qBAAqB,CAC1C,QAAQ,EAAE,QAAQ,GACjB,cAAc,CAAC,iBAAiB,CAAC,CAiInC"}
|
package/dist/output-parser.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
function asTrimmedString(value) {
|
|
2
|
-
if (typeof value !==
|
|
2
|
+
if (typeof value !== 'string')
|
|
3
3
|
return undefined;
|
|
4
4
|
const trimmed = value.trim();
|
|
5
5
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
6
6
|
}
|
|
7
7
|
function parseEventTimestampMs(event) {
|
|
8
|
-
if (!event || typeof event !==
|
|
8
|
+
if (!event || typeof event !== 'object')
|
|
9
9
|
return undefined;
|
|
10
10
|
const timestamp = event.timestamp;
|
|
11
|
-
if (typeof timestamp !==
|
|
11
|
+
if (typeof timestamp !== 'string')
|
|
12
12
|
return undefined;
|
|
13
13
|
const ms = Date.parse(timestamp);
|
|
14
14
|
return Number.isFinite(ms) ? ms : undefined;
|
|
15
15
|
}
|
|
16
16
|
function stringifyContent(value) {
|
|
17
|
-
if (typeof value ===
|
|
17
|
+
if (typeof value === 'string')
|
|
18
18
|
return value;
|
|
19
19
|
if (Array.isArray(value)) {
|
|
20
20
|
return value
|
|
21
21
|
.map((item) => {
|
|
22
|
-
if (typeof item ===
|
|
22
|
+
if (typeof item === 'string')
|
|
23
23
|
return item;
|
|
24
|
-
if (!item || typeof item !==
|
|
25
|
-
return
|
|
24
|
+
if (!item || typeof item !== 'object')
|
|
25
|
+
return '';
|
|
26
26
|
const text = asTrimmedString(item.text);
|
|
27
27
|
if (text)
|
|
28
28
|
return text;
|
|
@@ -32,18 +32,18 @@ function stringifyContent(value) {
|
|
|
32
32
|
return JSON.stringify(item);
|
|
33
33
|
})
|
|
34
34
|
.filter(Boolean)
|
|
35
|
-
.join(
|
|
35
|
+
.join('\n');
|
|
36
36
|
}
|
|
37
|
-
if (value && typeof value ===
|
|
37
|
+
if (value && typeof value === 'object') {
|
|
38
38
|
return JSON.stringify(value);
|
|
39
39
|
}
|
|
40
|
-
return
|
|
40
|
+
return '';
|
|
41
41
|
}
|
|
42
42
|
function normalizeToolResultContent(content, fallback) {
|
|
43
43
|
const direct = stringifyContent(content).trim();
|
|
44
44
|
if (direct)
|
|
45
45
|
return direct;
|
|
46
|
-
if (fallback && typeof fallback ===
|
|
46
|
+
if (fallback && typeof fallback === 'object') {
|
|
47
47
|
const stdout = asTrimmedString(fallback.stdout);
|
|
48
48
|
const stderr = asTrimmedString(fallback.stderr);
|
|
49
49
|
if (stdout && stderr)
|
|
@@ -53,20 +53,20 @@ function normalizeToolResultContent(content, fallback) {
|
|
|
53
53
|
if (stderr)
|
|
54
54
|
return stderr;
|
|
55
55
|
}
|
|
56
|
-
return
|
|
56
|
+
return '';
|
|
57
57
|
}
|
|
58
58
|
async function* readJsonLines(readable) {
|
|
59
|
-
let buffer =
|
|
60
|
-
readable.setEncoding(
|
|
59
|
+
let buffer = '';
|
|
60
|
+
readable.setEncoding('utf8');
|
|
61
61
|
for await (const chunk of readable) {
|
|
62
62
|
buffer += chunk;
|
|
63
|
-
let newlineIndex = buffer.indexOf(
|
|
63
|
+
let newlineIndex = buffer.indexOf('\n');
|
|
64
64
|
while (newlineIndex >= 0) {
|
|
65
65
|
const line = buffer.slice(0, newlineIndex).trim();
|
|
66
66
|
buffer = buffer.slice(newlineIndex + 1);
|
|
67
67
|
if (line)
|
|
68
68
|
yield line;
|
|
69
|
-
newlineIndex = buffer.indexOf(
|
|
69
|
+
newlineIndex = buffer.indexOf('\n');
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
const tail = buffer.trim();
|
|
@@ -83,45 +83,45 @@ export async function* parseClaudeStreamJson(readable) {
|
|
|
83
83
|
catch {
|
|
84
84
|
continue;
|
|
85
85
|
}
|
|
86
|
-
if (!event || typeof event !==
|
|
86
|
+
if (!event || typeof event !== 'object')
|
|
87
87
|
continue;
|
|
88
88
|
const now = Date.now();
|
|
89
89
|
const eventTimestampMs = parseEventTimestampMs(event) ?? now;
|
|
90
90
|
const eventRecord = event;
|
|
91
|
-
if (eventRecord.type ===
|
|
91
|
+
if (eventRecord.type === 'system' && eventRecord.subtype === 'init') {
|
|
92
92
|
const sessionId = asTrimmedString(eventRecord.session_id);
|
|
93
93
|
if (sessionId) {
|
|
94
|
-
yield { type:
|
|
94
|
+
yield { type: 'session_id', sessionId };
|
|
95
95
|
}
|
|
96
96
|
continue;
|
|
97
97
|
}
|
|
98
|
-
if (eventRecord.type ===
|
|
98
|
+
if (eventRecord.type === 'assistant') {
|
|
99
99
|
if (eventRecord.error)
|
|
100
100
|
continue;
|
|
101
101
|
const message = eventRecord.message;
|
|
102
|
-
const content = message && typeof message ===
|
|
102
|
+
const content = message && typeof message === 'object'
|
|
103
103
|
? message.content
|
|
104
104
|
: undefined;
|
|
105
105
|
if (!Array.isArray(content))
|
|
106
106
|
continue;
|
|
107
107
|
for (const block of content) {
|
|
108
|
-
if (!block || typeof block !==
|
|
108
|
+
if (!block || typeof block !== 'object')
|
|
109
109
|
continue;
|
|
110
110
|
const blockRecord = block;
|
|
111
111
|
switch (blockRecord.type) {
|
|
112
|
-
case
|
|
112
|
+
case 'thinking': {
|
|
113
113
|
const text = asTrimmedString(blockRecord.thinking);
|
|
114
114
|
if (text)
|
|
115
|
-
yield { type:
|
|
115
|
+
yield { type: 'thinking', text };
|
|
116
116
|
break;
|
|
117
117
|
}
|
|
118
|
-
case
|
|
118
|
+
case 'text': {
|
|
119
119
|
const text = asTrimmedString(blockRecord.text);
|
|
120
120
|
if (text)
|
|
121
|
-
yield { type:
|
|
121
|
+
yield { type: 'text', text, project: true };
|
|
122
122
|
break;
|
|
123
123
|
}
|
|
124
|
-
case
|
|
124
|
+
case 'tool_use': {
|
|
125
125
|
const callId = asTrimmedString(blockRecord.id);
|
|
126
126
|
const toolName = asTrimmedString(blockRecord.name);
|
|
127
127
|
if (!callId || !toolName)
|
|
@@ -131,7 +131,7 @@ export async function* parseClaudeStreamJson(readable) {
|
|
|
131
131
|
startedAtMs: eventTimestampMs,
|
|
132
132
|
});
|
|
133
133
|
yield {
|
|
134
|
-
type:
|
|
134
|
+
type: 'tool_call',
|
|
135
135
|
callId,
|
|
136
136
|
toolName,
|
|
137
137
|
input: blockRecord.input ?? {},
|
|
@@ -143,18 +143,18 @@ export async function* parseClaudeStreamJson(readable) {
|
|
|
143
143
|
}
|
|
144
144
|
continue;
|
|
145
145
|
}
|
|
146
|
-
if (eventRecord.type ===
|
|
146
|
+
if (eventRecord.type === 'user') {
|
|
147
147
|
const message = eventRecord.message;
|
|
148
|
-
const content = message && typeof message ===
|
|
148
|
+
const content = message && typeof message === 'object'
|
|
149
149
|
? message.content
|
|
150
150
|
: undefined;
|
|
151
151
|
if (!Array.isArray(content))
|
|
152
152
|
continue;
|
|
153
153
|
for (const block of content) {
|
|
154
|
-
if (!block || typeof block !==
|
|
154
|
+
if (!block || typeof block !== 'object')
|
|
155
155
|
continue;
|
|
156
156
|
const blockRecord = block;
|
|
157
|
-
if (blockRecord.type !==
|
|
157
|
+
if (blockRecord.type !== 'tool_result')
|
|
158
158
|
continue;
|
|
159
159
|
const callId = asTrimmedString(blockRecord.tool_use_id);
|
|
160
160
|
if (!callId)
|
|
@@ -162,24 +162,24 @@ export async function* parseClaudeStreamJson(readable) {
|
|
|
162
162
|
const meta = toolUses.get(callId);
|
|
163
163
|
const output = normalizeToolResultContent(blockRecord.content, eventRecord.tool_use_result);
|
|
164
164
|
yield {
|
|
165
|
-
type:
|
|
165
|
+
type: 'tool_result',
|
|
166
166
|
callId,
|
|
167
|
-
toolName: meta?.toolName ??
|
|
167
|
+
toolName: meta?.toolName ?? 'unknown',
|
|
168
168
|
output,
|
|
169
|
-
...(blockRecord.is_error === true ? { error: output ||
|
|
169
|
+
...(blockRecord.is_error === true ? { error: output || 'Tool call failed' } : {}),
|
|
170
170
|
...(meta ? { durationMs: Math.max(0, eventTimestampMs - meta.startedAtMs) } : {}),
|
|
171
171
|
};
|
|
172
172
|
toolUses.delete(callId);
|
|
173
173
|
}
|
|
174
174
|
continue;
|
|
175
175
|
}
|
|
176
|
-
if (eventRecord.type ===
|
|
176
|
+
if (eventRecord.type === 'result') {
|
|
177
177
|
const isError = eventRecord.is_error === true;
|
|
178
178
|
if (isError) {
|
|
179
|
-
const message = asTrimmedString(eventRecord.result)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
yield { type:
|
|
179
|
+
const message = asTrimmedString(eventRecord.result) ||
|
|
180
|
+
asTrimmedString(eventRecord.error) ||
|
|
181
|
+
'Claude dispatch failed';
|
|
182
|
+
yield { type: 'error', message };
|
|
183
183
|
}
|
|
184
184
|
// Turn boundary: the subprocess lives across many turns, so any
|
|
185
185
|
// tool_use entries that never matched a tool_result this turn
|
|
@@ -188,7 +188,7 @@ export async function* parseClaudeStreamJson(readable) {
|
|
|
188
188
|
// the map grow unbounded or mislabel durations on id collisions.
|
|
189
189
|
toolUses.clear();
|
|
190
190
|
yield {
|
|
191
|
-
type:
|
|
191
|
+
type: 'turn_end',
|
|
192
192
|
sessionId: asTrimmedString(eventRecord.session_id),
|
|
193
193
|
isError,
|
|
194
194
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ChildProcessWithoutNullStreams } from
|
|
2
|
-
import type { ForkSessionHandle } from
|
|
1
|
+
import type { ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
|
+
import type { ForkSessionHandle } from '@parall/agent-core';
|
|
3
3
|
type ClaudeSessionManagerLogger = {
|
|
4
4
|
warn(message: string): void;
|
|
5
5
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAO5D,KAAK,0BAA0B,GAAG;IAChC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,8BAA8B,CAAC;IACrC,WAAW,EAAE,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAC7E,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAEF,qBAAa,oBAAoB;IAM7B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAP1B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IACxD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAChE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0C;gBAGjD,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,0BAA0B,YAAA;IAKtD,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpD,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAU3C,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAQrD,iBAAiB,CAAC,gBAAgB,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAYrE,WAAW,CAAC,UAAU,EAAE,MAAM;IAU9B,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAI/D,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB;IAsB/D,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,mBAAmB;IAO7D,qEAAqE;IACrE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAE5C,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAO5D,KAAK,0BAA0B,GAAG;IAChC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,8BAA8B,CAAC;IACrC,WAAW,EAAE,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAC7E,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAEF,qBAAa,oBAAoB;IAM7B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAP1B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IACxD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAChE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0C;gBAGjD,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,0BAA0B,YAAA;IAKtD,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpD,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAU3C,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAQrD,iBAAiB,CAAC,gBAAgB,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAYrE,WAAW,CAAC,UAAU,EAAE,MAAM;IAU9B,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAI/D,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB;IAsB/D,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,mBAAmB;IAO7D,qEAAqE;IACrE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAE5C,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;YAMpB,WAAW;IAmCzB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAuBvB,OAAO,CAAC,WAAW;IAmBnB,gBAAgB;IAShB,OAAO,CAAC,OAAO;IAgBf,OAAO,CAAC,OAAO;CAoBhB"}
|
package/dist/session-manager.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as path from
|
|
3
|
-
import { randomUUID } from
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
4
|
export class ClaudeSessionManager {
|
|
5
5
|
mainSessionKey;
|
|
6
6
|
stateFilePath;
|
|
@@ -20,10 +20,10 @@ export class ClaudeSessionManager {
|
|
|
20
20
|
getResumeArgs(sessionKey) {
|
|
21
21
|
const existing = this.sessionIds.get(sessionKey);
|
|
22
22
|
if (existing)
|
|
23
|
-
return [
|
|
23
|
+
return ['--resume', existing];
|
|
24
24
|
const parent = this.pendingForkParents.get(sessionKey);
|
|
25
25
|
if (parent)
|
|
26
|
-
return [
|
|
26
|
+
return ['--resume', parent, '--fork-session'];
|
|
27
27
|
return [];
|
|
28
28
|
}
|
|
29
29
|
recordSessionId(sessionKey, sessionId) {
|
|
@@ -102,7 +102,7 @@ export class ClaudeSessionManager {
|
|
|
102
102
|
if (handle.proc.exitCode === null && handle.proc.signalCode === null) {
|
|
103
103
|
this.logger?.warn(`claude-agent: SIGTERM timed out for ${sessionKey}, escalating to SIGKILL`);
|
|
104
104
|
try {
|
|
105
|
-
handle.proc.kill(
|
|
105
|
+
handle.proc.kill('SIGKILL');
|
|
106
106
|
}
|
|
107
107
|
catch (error) {
|
|
108
108
|
this.logger?.warn(`claude-agent: SIGKILL failed for ${sessionKey}: ${String(error)}`);
|
|
@@ -131,7 +131,7 @@ export class ClaudeSessionManager {
|
|
|
131
131
|
resolve(true);
|
|
132
132
|
}, ms);
|
|
133
133
|
// Don't keep the event loop alive purely on the timeout.
|
|
134
|
-
if (typeof timer.unref ===
|
|
134
|
+
if (typeof timer.unref === 'function')
|
|
135
135
|
timer.unref();
|
|
136
136
|
promise
|
|
137
137
|
.catch(() => {
|
|
@@ -157,7 +157,7 @@ export class ClaudeSessionManager {
|
|
|
157
157
|
}
|
|
158
158
|
if (handle.proc.exitCode === null && handle.proc.signalCode === null) {
|
|
159
159
|
try {
|
|
160
|
-
handle.proc.kill(
|
|
160
|
+
handle.proc.kill('SIGTERM');
|
|
161
161
|
}
|
|
162
162
|
catch (error) {
|
|
163
163
|
this.logger?.warn(`claude-agent: failed to SIGTERM claude subprocess (${reason}): ${String(error)}`);
|
|
@@ -175,9 +175,11 @@ export class ClaudeSessionManager {
|
|
|
175
175
|
}
|
|
176
176
|
restore() {
|
|
177
177
|
try {
|
|
178
|
-
const raw = fs.readFileSync(this.stateFilePath,
|
|
178
|
+
const raw = fs.readFileSync(this.stateFilePath, 'utf8');
|
|
179
179
|
const parsed = JSON.parse(raw);
|
|
180
|
-
if (parsed.runtimeKey === this.mainSessionKey &&
|
|
180
|
+
if (parsed.runtimeKey === this.mainSessionKey &&
|
|
181
|
+
typeof parsed.sessionId === 'string' &&
|
|
182
|
+
parsed.sessionId.trim()) {
|
|
181
183
|
this.sessionIds.set(this.mainSessionKey, parsed.sessionId.trim());
|
|
182
184
|
}
|
|
183
185
|
}
|