@alvax-ai/adapter-claude-local 0.1.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.
Files changed (67) hide show
  1. package/dist/cli/format-event.d.ts +2 -0
  2. package/dist/cli/format-event.d.ts.map +1 -0
  3. package/dist/cli/format-event.js +136 -0
  4. package/dist/cli/format-event.js.map +1 -0
  5. package/dist/cli/index.d.ts +2 -0
  6. package/dist/cli/index.d.ts.map +1 -0
  7. package/dist/cli/index.js +2 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/cli/quota-probe.d.ts +3 -0
  10. package/dist/cli/quota-probe.d.ts.map +1 -0
  11. package/dist/cli/quota-probe.js +106 -0
  12. package/dist/cli/quota-probe.js.map +1 -0
  13. package/dist/index.d.ts +11 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +47 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/server/claude-config.d.ts +5 -0
  18. package/dist/server/claude-config.d.ts.map +1 -0
  19. package/dist/server/claude-config.js +106 -0
  20. package/dist/server/claude-config.js.map +1 -0
  21. package/dist/server/execute.d.ts +18 -0
  22. package/dist/server/execute.d.ts.map +1 -0
  23. package/dist/server/execute.js +700 -0
  24. package/dist/server/execute.js.map +1 -0
  25. package/dist/server/index.d.ts +9 -0
  26. package/dist/server/index.d.ts.map +1 -0
  27. package/dist/server/index.js +64 -0
  28. package/dist/server/index.js.map +1 -0
  29. package/dist/server/models.d.ts +11 -0
  30. package/dist/server/models.d.ts.map +1 -0
  31. package/dist/server/models.js +32 -0
  32. package/dist/server/models.js.map +1 -0
  33. package/dist/server/parse.d.ts +34 -0
  34. package/dist/server/parse.d.ts.map +1 -0
  35. package/dist/server/parse.js +319 -0
  36. package/dist/server/parse.js.map +1 -0
  37. package/dist/server/prompt-cache.d.ts +17 -0
  38. package/dist/server/prompt-cache.d.ts.map +1 -0
  39. package/dist/server/prompt-cache.js +125 -0
  40. package/dist/server/prompt-cache.js.map +1 -0
  41. package/dist/server/quota.d.ts +21 -0
  42. package/dist/server/quota.d.ts.map +1 -0
  43. package/dist/server/quota.js +484 -0
  44. package/dist/server/quota.js.map +1 -0
  45. package/dist/server/skills.d.ts +8 -0
  46. package/dist/server/skills.d.ts.map +1 -0
  47. package/dist/server/skills.js +97 -0
  48. package/dist/server/skills.js.map +1 -0
  49. package/dist/server/test.d.ts +3 -0
  50. package/dist/server/test.d.ts.map +1 -0
  51. package/dist/server/test.js +258 -0
  52. package/dist/server/test.js.map +1 -0
  53. package/dist/ui/build-config.d.ts +3 -0
  54. package/dist/ui/build-config.d.ts.map +1 -0
  55. package/dist/ui/build-config.js +111 -0
  56. package/dist/ui/build-config.js.map +1 -0
  57. package/dist/ui/index.d.ts +3 -0
  58. package/dist/ui/index.d.ts.map +1 -0
  59. package/dist/ui/index.js +3 -0
  60. package/dist/ui/index.js.map +1 -0
  61. package/dist/ui/parse-stdout.d.ts +3 -0
  62. package/dist/ui/parse-stdout.d.ts.map +1 -0
  63. package/dist/ui/parse-stdout.js +149 -0
  64. package/dist/ui/parse-stdout.js.map +1 -0
  65. package/package.json +60 -0
  66. package/skills/alvax/SKILL.md +448 -0
  67. package/skills/alvax/references/api-reference.md +284 -0
@@ -0,0 +1,700 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { adapterExecutionTargetIsRemote, adapterExecutionTargetRemoteCwd, adapterExecutionTargetSessionIdentity, adapterExecutionTargetSessionMatches, adapterExecutionTargetUsesManagedHome, adapterExecutionTargetUsesAlvaxBridge, describeAdapterExecutionTarget, ensureAdapterExecutionTargetCommandResolvable, ensureAdapterExecutionTargetRuntimeCommandInstalled, prepareAdapterExecutionTargetRuntime, readAdapterExecutionTarget, resolveAdapterExecutionTargetCommandForLogs, runAdapterExecutionTargetProcess, runAdapterExecutionTargetShellCommand, startAdapterExecutionTargetAlvaxBridge, } from "@alvax-ai/adapter-utils/execution-target";
5
+ import { asString, asNumber, asBoolean, asStringArray, parseObject, parseJson, applyAlvaxWorkspaceEnv, buildAlvaxEnv, readAlvaxRuntimeSkillEntries, readAlvaxIssueWorkModeFromContext, joinPromptSections, buildInvocationEnvForLogs, ensureAbsoluteDirectory, ensurePathInEnv, renderTemplate, renderAlvaxWakePrompt, shapeAlvaxWorkspaceEnvForExecution, stringifyAlvaxWakePayload, DEFAULT_ALVAX_AGENT_PROMPT_TEMPLATE, } from "@alvax-ai/adapter-utils/server-utils";
6
+ import { shellQuote } from "@alvax-ai/adapter-utils/ssh";
7
+ import { parseClaudeStreamJson, describeClaudeFailure, detectClaudeLoginRequired, extractClaudeRetryNotBefore, isClaudeMaxTurnsResult, isClaudeTransientUpstreamError, isClaudeUnknownSessionError, } from "./parse.js";
8
+ import { prepareClaudeConfigSeed } from "./claude-config.js";
9
+ import { resolveClaudeDesiredSkillNames } from "./skills.js";
10
+ import { isBedrockModelId } from "./models.js";
11
+ import { prepareClaudePromptBundle } from "./prompt-cache.js";
12
+ import { SANDBOX_INSTALL_COMMAND } from "../index.js";
13
+ const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
14
+ function buildLoginResult(input) {
15
+ return {
16
+ exitCode: input.proc.exitCode,
17
+ signal: input.proc.signal,
18
+ timedOut: input.proc.timedOut,
19
+ stdout: input.proc.stdout,
20
+ stderr: input.proc.stderr,
21
+ loginUrl: input.loginUrl,
22
+ };
23
+ }
24
+ function hasNonEmptyEnvValue(env, key) {
25
+ const raw = env[key];
26
+ return typeof raw === "string" && raw.trim().length > 0;
27
+ }
28
+ function isBedrockAuth(env) {
29
+ return (env.CLAUDE_CODE_USE_BEDROCK === "1" ||
30
+ env.CLAUDE_CODE_USE_BEDROCK === "true" ||
31
+ hasNonEmptyEnvValue(env, "ANTHROPIC_BEDROCK_BASE_URL"));
32
+ }
33
+ function resolveClaudeBillingType(env) {
34
+ if (isBedrockAuth(env))
35
+ return "metered_api";
36
+ return hasNonEmptyEnvValue(env, "ANTHROPIC_API_KEY") ? "api" : "subscription";
37
+ }
38
+ async function buildClaudeRuntimeConfig(input) {
39
+ const { runId, agent, config, context, runtimeCommandSpec, executionTarget, authToken } = input;
40
+ const onLog = input.onLog ?? (async () => { });
41
+ const command = asString(config.command, "claude");
42
+ const workspaceContext = parseObject(context.alvaxWorkspace);
43
+ const workspaceCwd = asString(workspaceContext.cwd, "");
44
+ const workspaceSource = asString(workspaceContext.source, "");
45
+ const workspaceStrategy = asString(workspaceContext.strategy, "");
46
+ const workspaceId = asString(workspaceContext.workspaceId, "") || null;
47
+ const workspaceRepoUrl = asString(workspaceContext.repoUrl, "") || null;
48
+ const workspaceRepoRef = asString(workspaceContext.repoRef, "") || null;
49
+ const workspaceBranch = asString(workspaceContext.branchName, "") || null;
50
+ const workspaceWorktreePath = asString(workspaceContext.worktreePath, "") || null;
51
+ const agentHome = asString(workspaceContext.agentHome, "") || null;
52
+ const workspaceHints = Array.isArray(context.alvaxWorkspaces)
53
+ ? context.alvaxWorkspaces.filter((value) => typeof value === "object" && value !== null)
54
+ : [];
55
+ const runtimeServiceIntents = Array.isArray(context.alvaxRuntimeServiceIntents)
56
+ ? context.alvaxRuntimeServiceIntents.filter((value) => typeof value === "object" && value !== null)
57
+ : [];
58
+ const runtimeServices = Array.isArray(context.alvaxRuntimeServices)
59
+ ? context.alvaxRuntimeServices.filter((value) => typeof value === "object" && value !== null)
60
+ : [];
61
+ const runtimePrimaryUrl = asString(context.alvaxRuntimePrimaryUrl, "");
62
+ const configuredCwd = asString(config.cwd, "");
63
+ const useConfiguredInsteadOfAgentHome = workspaceSource === "agent_home" && configuredCwd.length > 0;
64
+ const effectiveWorkspaceCwd = useConfiguredInsteadOfAgentHome ? "" : workspaceCwd;
65
+ const cwd = effectiveWorkspaceCwd || configuredCwd || process.cwd();
66
+ const executionTargetIsRemote = adapterExecutionTargetIsRemote(executionTarget);
67
+ const effectiveExecutionCwd = adapterExecutionTargetRemoteCwd(executionTarget, cwd);
68
+ const shapedWorkspaceEnv = shapeAlvaxWorkspaceEnvForExecution({
69
+ workspaceCwd: effectiveWorkspaceCwd,
70
+ workspaceWorktreePath,
71
+ workspaceHints,
72
+ executionTargetIsRemote,
73
+ executionCwd: effectiveExecutionCwd,
74
+ });
75
+ await ensureAbsoluteDirectory(cwd, { createIfMissing: true });
76
+ const envConfig = parseObject(config.env);
77
+ const hasExplicitApiKey = typeof envConfig.ALVAX_API_KEY === "string" && envConfig.ALVAX_API_KEY.trim().length > 0;
78
+ const env = { ...buildAlvaxEnv(agent) };
79
+ env.ALVAX_RUN_ID = runId;
80
+ if (!executionTargetIsRemote) {
81
+ const companyRoot = typeof context.alvaxCompanyRoot === "string" ? context.alvaxCompanyRoot.trim() : "";
82
+ const companyBriefPath = typeof context.alvaxCompanyBriefPath === "string" ? context.alvaxCompanyBriefPath.trim() : "";
83
+ if (companyRoot)
84
+ env.ALVAX_COMPANY_ROOT = companyRoot;
85
+ if (companyBriefPath)
86
+ env.ALVAX_COMPANY_BRIEF_PATH = companyBriefPath;
87
+ }
88
+ const wakeTaskId = (typeof context.taskId === "string" && context.taskId.trim().length > 0 && context.taskId.trim()) ||
89
+ (typeof context.issueId === "string" && context.issueId.trim().length > 0 && context.issueId.trim()) ||
90
+ null;
91
+ const wakeReason = typeof context.wakeReason === "string" && context.wakeReason.trim().length > 0
92
+ ? context.wakeReason.trim()
93
+ : null;
94
+ const wakeCommentId = (typeof context.wakeCommentId === "string" && context.wakeCommentId.trim().length > 0 && context.wakeCommentId.trim()) ||
95
+ (typeof context.commentId === "string" && context.commentId.trim().length > 0 && context.commentId.trim()) ||
96
+ null;
97
+ const approvalId = typeof context.approvalId === "string" && context.approvalId.trim().length > 0
98
+ ? context.approvalId.trim()
99
+ : null;
100
+ const approvalStatus = typeof context.approvalStatus === "string" && context.approvalStatus.trim().length > 0
101
+ ? context.approvalStatus.trim()
102
+ : null;
103
+ const linkedIssueIds = Array.isArray(context.issueIds)
104
+ ? context.issueIds.filter((value) => typeof value === "string" && value.trim().length > 0)
105
+ : [];
106
+ const wakePayloadJson = stringifyAlvaxWakePayload(context.alvaxWake);
107
+ const issueWorkMode = readAlvaxIssueWorkModeFromContext(context);
108
+ if (wakeTaskId) {
109
+ env.ALVAX_TASK_ID = wakeTaskId;
110
+ }
111
+ if (issueWorkMode) {
112
+ env.ALVAX_ISSUE_WORK_MODE = issueWorkMode;
113
+ }
114
+ if (wakeReason) {
115
+ env.ALVAX_WAKE_REASON = wakeReason;
116
+ }
117
+ if (wakeCommentId) {
118
+ env.ALVAX_WAKE_COMMENT_ID = wakeCommentId;
119
+ }
120
+ if (approvalId) {
121
+ env.ALVAX_APPROVAL_ID = approvalId;
122
+ }
123
+ if (approvalStatus) {
124
+ env.ALVAX_APPROVAL_STATUS = approvalStatus;
125
+ }
126
+ if (linkedIssueIds.length > 0) {
127
+ env.ALVAX_LINKED_ISSUE_IDS = linkedIssueIds.join(",");
128
+ }
129
+ if (wakePayloadJson) {
130
+ env.ALVAX_WAKE_PAYLOAD_JSON = wakePayloadJson;
131
+ }
132
+ applyAlvaxWorkspaceEnv(env, {
133
+ workspaceCwd: shapedWorkspaceEnv.workspaceCwd,
134
+ workspaceSource,
135
+ workspaceStrategy,
136
+ workspaceId,
137
+ workspaceRepoUrl,
138
+ workspaceRepoRef,
139
+ workspaceBranch,
140
+ workspaceWorktreePath: shapedWorkspaceEnv.workspaceWorktreePath,
141
+ agentHome,
142
+ });
143
+ if (shapedWorkspaceEnv.workspaceHints.length > 0) {
144
+ env.ALVAX_WORKSPACES_JSON = JSON.stringify(shapedWorkspaceEnv.workspaceHints);
145
+ }
146
+ if (runtimeServiceIntents.length > 0) {
147
+ env.ALVAX_RUNTIME_SERVICE_INTENTS_JSON = JSON.stringify(runtimeServiceIntents);
148
+ }
149
+ if (runtimeServices.length > 0) {
150
+ env.ALVAX_RUNTIME_SERVICES_JSON = JSON.stringify(runtimeServices);
151
+ }
152
+ if (runtimePrimaryUrl) {
153
+ env.ALVAX_RUNTIME_PRIMARY_URL = runtimePrimaryUrl;
154
+ }
155
+ for (const [key, value] of Object.entries(envConfig)) {
156
+ if (typeof value === "string")
157
+ env[key] = value;
158
+ }
159
+ if (!hasExplicitApiKey && authToken) {
160
+ env.ALVAX_API_KEY = authToken;
161
+ }
162
+ const runtimeEnv = Object.fromEntries(Object.entries(ensurePathInEnv({ ...process.env, ...env })).filter((entry) => typeof entry[1] === "string"));
163
+ const timeoutSec = asNumber(config.timeoutSec, 0);
164
+ const graceSec = asNumber(config.graceSec, 20);
165
+ await ensureAdapterExecutionTargetRuntimeCommandInstalled({
166
+ runId,
167
+ target: executionTarget,
168
+ installCommand: runtimeCommandSpec?.installCommand,
169
+ detectCommand: runtimeCommandSpec?.detectCommand,
170
+ cwd,
171
+ env: runtimeEnv,
172
+ timeoutSec,
173
+ graceSec,
174
+ onLog,
175
+ });
176
+ await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv, { installCommand: SANDBOX_INSTALL_COMMAND });
177
+ const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
178
+ const loggedEnv = buildInvocationEnvForLogs(env, {
179
+ runtimeEnv,
180
+ includeRuntimeKeys: ["HOME", "CLAUDE_CONFIG_DIR"],
181
+ resolvedCommand,
182
+ });
183
+ const extraArgs = (() => {
184
+ const fromExtraArgs = asStringArray(config.extraArgs);
185
+ if (fromExtraArgs.length > 0)
186
+ return fromExtraArgs;
187
+ return asStringArray(config.args);
188
+ })();
189
+ return {
190
+ command,
191
+ resolvedCommand,
192
+ cwd,
193
+ workspaceId,
194
+ workspaceRepoUrl,
195
+ workspaceRepoRef,
196
+ env,
197
+ loggedEnv,
198
+ timeoutSec,
199
+ graceSec,
200
+ extraArgs,
201
+ };
202
+ }
203
+ export async function runClaudeLogin(input) {
204
+ const onLog = input.onLog ?? (async () => { });
205
+ const runtime = await buildClaudeRuntimeConfig({
206
+ runId: input.runId,
207
+ agent: input.agent,
208
+ config: input.config,
209
+ context: input.context ?? {},
210
+ authToken: input.authToken,
211
+ });
212
+ const proc = await runAdapterExecutionTargetProcess(input.runId, null, runtime.command, ["login"], {
213
+ cwd: runtime.cwd,
214
+ env: runtime.env,
215
+ timeoutSec: runtime.timeoutSec,
216
+ graceSec: runtime.graceSec,
217
+ onLog,
218
+ });
219
+ const loginMeta = detectClaudeLoginRequired({
220
+ parsed: null,
221
+ stdout: proc.stdout,
222
+ stderr: proc.stderr,
223
+ });
224
+ return buildLoginResult({
225
+ proc,
226
+ loginUrl: loginMeta.loginUrl,
227
+ });
228
+ }
229
+ export async function execute(ctx) {
230
+ const { runId, agent, runtime, config, context, onLog, onMeta, onSpawn, authToken } = ctx;
231
+ const executionTarget = readAdapterExecutionTarget({
232
+ executionTarget: ctx.executionTarget,
233
+ legacyRemoteExecution: ctx.executionTransport?.remoteExecution,
234
+ });
235
+ const executionTargetIsRemote = adapterExecutionTargetIsRemote(executionTarget);
236
+ const promptTemplate = asString(config.promptTemplate, DEFAULT_ALVAX_AGENT_PROMPT_TEMPLATE);
237
+ const model = asString(config.model, "");
238
+ const effort = asString(config.effort, "");
239
+ const chrome = asBoolean(config.chrome, false);
240
+ const maxTurns = asNumber(config.maxTurnsPerRun, 0);
241
+ const dangerouslySkipPermissions = asBoolean(config.dangerouslySkipPermissions, true);
242
+ const configEnv = parseObject(config.env);
243
+ const hasExplicitClaudeConfigDir = typeof configEnv.CLAUDE_CONFIG_DIR === "string" && configEnv.CLAUDE_CONFIG_DIR.trim().length > 0;
244
+ const instructionsFilePath = asString(config.instructionsFilePath, "").trim();
245
+ const instructionsFileDir = instructionsFilePath ? `${path.dirname(instructionsFilePath)}/` : "";
246
+ const runtimeConfig = await buildClaudeRuntimeConfig({
247
+ runId,
248
+ agent,
249
+ config,
250
+ context,
251
+ runtimeCommandSpec: ctx.runtimeCommandSpec,
252
+ executionTarget,
253
+ authToken,
254
+ onLog,
255
+ });
256
+ const { command, resolvedCommand, cwd, workspaceId, workspaceRepoUrl, workspaceRepoRef, env, loggedEnv: initialLoggedEnv, timeoutSec, graceSec, extraArgs, } = runtimeConfig;
257
+ let loggedEnv = initialLoggedEnv;
258
+ const effectiveExecutionCwd = adapterExecutionTargetRemoteCwd(executionTarget, cwd);
259
+ const terminalResultCleanupGraceMs = Math.max(0, asNumber(config.terminalResultCleanupGraceMs, 5_000));
260
+ const effectiveEnv = Object.fromEntries(Object.entries({ ...process.env, ...env }).filter((entry) => typeof entry[1] === "string"));
261
+ const billingType = resolveClaudeBillingType(effectiveEnv);
262
+ const claudeSkillEntries = await readAlvaxRuntimeSkillEntries(config, __moduleDir);
263
+ const desiredSkillNames = new Set(resolveClaudeDesiredSkillNames(config, claudeSkillEntries));
264
+ // When instructionsFilePath is configured, build a stable content-addressed
265
+ // file that includes both the file content and the path directive, so we only
266
+ // need --append-system-prompt-file (Claude CLI forbids using both flags together).
267
+ let combinedInstructionsContents = null;
268
+ if (instructionsFilePath) {
269
+ try {
270
+ const instructionsContent = await fs.readFile(instructionsFilePath, "utf-8");
271
+ const pathDirective = `\nThe above agent instructions were loaded from ${instructionsFilePath}. ` +
272
+ `Resolve any relative file references from ${instructionsFileDir}. ` +
273
+ `This base directory is authoritative for sibling instruction files such as ` +
274
+ `./HEARTBEAT.md, ./SOUL.md, and ./TOOLS.md; do not resolve those from the parent agent directory.`;
275
+ combinedInstructionsContents = instructionsContent + pathDirective;
276
+ }
277
+ catch (err) {
278
+ const reason = err instanceof Error ? err.message : String(err);
279
+ await onLog("stderr", `[alvax] Warning: could not read agent instructions file "${instructionsFilePath}": ${reason}\n`);
280
+ }
281
+ }
282
+ const promptBundle = await prepareClaudePromptBundle({
283
+ companyId: agent.companyId,
284
+ skills: claudeSkillEntries.filter((entry) => desiredSkillNames.has(entry.key)),
285
+ instructionsContents: combinedInstructionsContents,
286
+ onLog,
287
+ });
288
+ const useManagedRemoteClaudeConfig = executionTargetIsRemote &&
289
+ adapterExecutionTargetUsesManagedHome(executionTarget) &&
290
+ !hasExplicitClaudeConfigDir;
291
+ const claudeConfigSeedDir = useManagedRemoteClaudeConfig
292
+ ? await prepareClaudeConfigSeed(process.env, onLog, agent.companyId)
293
+ : null;
294
+ const preparedExecutionTargetRuntime = executionTargetIsRemote
295
+ ? await (async () => {
296
+ await onLog("stdout", `[alvax] Syncing workspace and Claude runtime assets to ${describeAdapterExecutionTarget(executionTarget)}.\n`);
297
+ return await prepareAdapterExecutionTargetRuntime({
298
+ target: executionTarget,
299
+ adapterKey: "claude",
300
+ workspaceLocalDir: cwd,
301
+ installCommand: SANDBOX_INSTALL_COMMAND,
302
+ detectCommand: command,
303
+ assets: [
304
+ {
305
+ key: "skills",
306
+ localDir: promptBundle.addDir,
307
+ followSymlinks: true,
308
+ },
309
+ ...(claudeConfigSeedDir
310
+ ? [{
311
+ key: "config-seed",
312
+ localDir: claudeConfigSeedDir,
313
+ followSymlinks: true,
314
+ }]
315
+ : []),
316
+ ],
317
+ });
318
+ })()
319
+ : null;
320
+ const restoreRemoteWorkspace = preparedExecutionTargetRuntime
321
+ ? () => preparedExecutionTargetRuntime.restoreWorkspace()
322
+ : null;
323
+ const effectivePromptBundleAddDir = executionTargetIsRemote
324
+ ? preparedExecutionTargetRuntime?.assetDirs.skills ??
325
+ path.posix.join(effectiveExecutionCwd, ".alvax-runtime", "claude", "skills")
326
+ : promptBundle.addDir;
327
+ const effectiveInstructionsFilePath = promptBundle.instructionsFilePath
328
+ ? executionTargetIsRemote
329
+ ? path.posix.join(effectivePromptBundleAddDir, path.basename(promptBundle.instructionsFilePath))
330
+ : promptBundle.instructionsFilePath
331
+ : undefined;
332
+ const remoteClaudeRuntimeRoot = executionTargetIsRemote
333
+ ? preparedExecutionTargetRuntime?.runtimeRootDir ??
334
+ path.posix.join(effectiveExecutionCwd, ".alvax-runtime", "claude")
335
+ : null;
336
+ const remoteClaudeConfigSeedDir = claudeConfigSeedDir && remoteClaudeRuntimeRoot
337
+ ? preparedExecutionTargetRuntime?.assetDirs["config-seed"] ??
338
+ path.posix.join(remoteClaudeRuntimeRoot, "config-seed")
339
+ : null;
340
+ const remoteClaudeConfigDir = useManagedRemoteClaudeConfig && remoteClaudeRuntimeRoot
341
+ ? path.posix.join(remoteClaudeRuntimeRoot, "config")
342
+ : null;
343
+ if (remoteClaudeConfigDir && remoteClaudeConfigSeedDir) {
344
+ env.CLAUDE_CONFIG_DIR = remoteClaudeConfigDir;
345
+ loggedEnv.CLAUDE_CONFIG_DIR = remoteClaudeConfigDir;
346
+ await onLog("stdout", `[alvax] Materializing Claude auth/config into ${remoteClaudeConfigDir}.\n`);
347
+ await runAdapterExecutionTargetShellCommand(runId, executionTarget, `mkdir -p ${shellQuote(remoteClaudeConfigDir)} && ` +
348
+ `if [ -d ${shellQuote(remoteClaudeConfigSeedDir)} ]; then ` +
349
+ `cp -R ${shellQuote(`${remoteClaudeConfigSeedDir}/.`)} ${shellQuote(remoteClaudeConfigDir)}/; ` +
350
+ `fi`, {
351
+ cwd,
352
+ env,
353
+ timeoutSec: Math.max(timeoutSec, 15),
354
+ graceSec,
355
+ onLog,
356
+ });
357
+ }
358
+ let alvaxBridge = null;
359
+ if (executionTargetIsRemote && adapterExecutionTargetUsesAlvaxBridge(executionTarget)) {
360
+ alvaxBridge = await startAdapterExecutionTargetAlvaxBridge({
361
+ runId,
362
+ target: executionTarget,
363
+ runtimeRootDir: preparedExecutionTargetRuntime?.runtimeRootDir,
364
+ adapterKey: "claude",
365
+ hostApiToken: env.ALVAX_API_KEY,
366
+ onLog,
367
+ });
368
+ if (alvaxBridge) {
369
+ Object.assign(env, alvaxBridge.env);
370
+ const runtimeEnv = ensurePathInEnv({ ...process.env, ...env });
371
+ loggedEnv = buildInvocationEnvForLogs(env, {
372
+ runtimeEnv,
373
+ includeRuntimeKeys: ["HOME", "CLAUDE_CONFIG_DIR"],
374
+ resolvedCommand,
375
+ });
376
+ if (remoteClaudeConfigDir) {
377
+ loggedEnv.CLAUDE_CONFIG_DIR = remoteClaudeConfigDir;
378
+ }
379
+ }
380
+ }
381
+ const runtimeSessionParams = parseObject(runtime.sessionParams);
382
+ const runtimeSessionId = asString(runtimeSessionParams.sessionId, runtime.sessionId ?? "");
383
+ const runtimeSessionCwd = asString(runtimeSessionParams.cwd, "");
384
+ const runtimeRemoteExecution = parseObject(runtimeSessionParams.remoteExecution);
385
+ const runtimePromptBundleKey = asString(runtimeSessionParams.promptBundleKey, "");
386
+ const hasMatchingPromptBundle = runtimePromptBundleKey.length === 0 || runtimePromptBundleKey === promptBundle.bundleKey;
387
+ const canResumeSession = runtimeSessionId.length > 0 &&
388
+ hasMatchingPromptBundle &&
389
+ (runtimeSessionCwd.length === 0 || path.resolve(runtimeSessionCwd) === path.resolve(effectiveExecutionCwd)) &&
390
+ adapterExecutionTargetSessionMatches(runtimeRemoteExecution, executionTarget);
391
+ const sessionId = canResumeSession ? runtimeSessionId : null;
392
+ if (executionTargetIsRemote &&
393
+ runtimeSessionId &&
394
+ !canResumeSession) {
395
+ await onLog("stdout", `[alvax] Claude session "${runtimeSessionId}" does not match the current remote execution identity and will not be resumed in "${effectiveExecutionCwd}". Starting a fresh remote session.\n`);
396
+ }
397
+ else if (runtimeSessionId &&
398
+ runtimeSessionCwd.length > 0 &&
399
+ path.resolve(runtimeSessionCwd) !== path.resolve(effectiveExecutionCwd)) {
400
+ await onLog("stdout", `[alvax] Claude session "${runtimeSessionId}" does not match the current remote execution identity and will not be resumed in "${effectiveExecutionCwd}". Starting a fresh remote session.\n`);
401
+ }
402
+ else if (runtimeSessionId && !canResumeSession) {
403
+ await onLog("stdout", `[alvax] Claude session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${effectiveExecutionCwd}".\n`);
404
+ }
405
+ if (runtimeSessionId && runtimePromptBundleKey.length > 0 && runtimePromptBundleKey !== promptBundle.bundleKey) {
406
+ await onLog("stdout", `[alvax] Claude session "${runtimeSessionId}" was saved for prompt bundle "${runtimePromptBundleKey}" and will not be resumed with "${promptBundle.bundleKey}".\n`);
407
+ }
408
+ const bootstrapPromptTemplate = asString(config.bootstrapPromptTemplate, "");
409
+ const templateData = {
410
+ agentId: agent.id,
411
+ companyId: agent.companyId,
412
+ runId,
413
+ company: { id: agent.companyId },
414
+ agent,
415
+ run: { id: runId, source: "on_demand" },
416
+ context,
417
+ };
418
+ const renderedBootstrapPrompt = !sessionId && bootstrapPromptTemplate.trim().length > 0
419
+ ? renderTemplate(bootstrapPromptTemplate, templateData).trim()
420
+ : "";
421
+ const wakePrompt = renderAlvaxWakePrompt(context.alvaxWake, { resumedSession: Boolean(sessionId) });
422
+ const shouldUseResumeDeltaPrompt = Boolean(sessionId) && wakePrompt.length > 0;
423
+ const renderedPrompt = shouldUseResumeDeltaPrompt ? "" : renderTemplate(promptTemplate, templateData);
424
+ const sessionHandoffNote = asString(context.alvaxSessionHandoffMarkdown, "").trim();
425
+ const taskContextNote = asString(context.alvaxTaskMarkdown, "").trim();
426
+ const prompt = joinPromptSections([
427
+ renderedBootstrapPrompt,
428
+ wakePrompt,
429
+ sessionHandoffNote,
430
+ taskContextNote,
431
+ renderedPrompt,
432
+ ]);
433
+ const promptMetrics = {
434
+ promptChars: prompt.length,
435
+ bootstrapPromptChars: renderedBootstrapPrompt.length,
436
+ wakePromptChars: wakePrompt.length,
437
+ sessionHandoffChars: sessionHandoffNote.length,
438
+ taskContextChars: taskContextNote.length,
439
+ heartbeatPromptChars: renderedPrompt.length,
440
+ };
441
+ const buildClaudeArgs = (resumeSessionId, attemptInstructionsFilePath) => {
442
+ const args = ["--print", "-", "--output-format", "stream-json", "--verbose"];
443
+ if (resumeSessionId)
444
+ args.push("--resume", resumeSessionId);
445
+ if (dangerouslySkipPermissions)
446
+ args.push("--dangerously-skip-permissions");
447
+ if (chrome)
448
+ args.push("--chrome");
449
+ // For Bedrock: only pass --model when the ID is a Bedrock-native identifier
450
+ // (e.g. "us.anthropic.*" or ARN). Anthropic-style IDs like "claude-opus-4-6" are invalid
451
+ // on Bedrock, so skip them and let the CLI use its own configured model.
452
+ if (model && (!isBedrockAuth(effectiveEnv) || isBedrockModelId(model))) {
453
+ args.push("--model", model);
454
+ }
455
+ if (effort)
456
+ args.push("--effort", effort);
457
+ if (maxTurns > 0)
458
+ args.push("--max-turns", String(maxTurns));
459
+ // On resumed sessions the instructions are already in the session cache;
460
+ // re-injecting them via --append-system-prompt-file wastes 5-10K tokens
461
+ // per heartbeat and the Claude CLI may reject the combination outright.
462
+ if (attemptInstructionsFilePath && !resumeSessionId) {
463
+ args.push("--append-system-prompt-file", attemptInstructionsFilePath);
464
+ }
465
+ args.push("--add-dir", effectivePromptBundleAddDir);
466
+ if (extraArgs.length > 0)
467
+ args.push(...extraArgs);
468
+ return args;
469
+ };
470
+ const parseFallbackErrorMessage = (proc) => {
471
+ const stderrLine = proc.stderr
472
+ .split(/\r?\n/)
473
+ .map((line) => line.trim())
474
+ .find(Boolean) ?? "";
475
+ if ((proc.exitCode ?? 0) === 0) {
476
+ return "Failed to parse claude JSON output";
477
+ }
478
+ return stderrLine
479
+ ? `Claude exited with code ${proc.exitCode ?? -1}: ${stderrLine}`
480
+ : `Claude exited with code ${proc.exitCode ?? -1}`;
481
+ };
482
+ const runAttempt = async (resumeSessionId) => {
483
+ const attemptInstructionsFilePath = resumeSessionId ? undefined : effectiveInstructionsFilePath;
484
+ const args = buildClaudeArgs(resumeSessionId, attemptInstructionsFilePath);
485
+ const commandNotes = [];
486
+ if (!resumeSessionId) {
487
+ commandNotes.push(`Using stable Claude prompt bundle ${promptBundle.bundleKey}.`);
488
+ }
489
+ if (attemptInstructionsFilePath && !resumeSessionId) {
490
+ commandNotes.push(`Injected agent instructions via --append-system-prompt-file ${instructionsFilePath} (with path directive appended)`);
491
+ }
492
+ if (onMeta) {
493
+ await onMeta({
494
+ adapterType: "claude_local",
495
+ command: resolvedCommand,
496
+ cwd: effectiveExecutionCwd,
497
+ commandArgs: args,
498
+ commandNotes,
499
+ env: loggedEnv,
500
+ prompt,
501
+ promptMetrics,
502
+ context,
503
+ });
504
+ }
505
+ const proc = await runAdapterExecutionTargetProcess(runId, executionTarget, command, args, {
506
+ cwd,
507
+ env,
508
+ stdin: prompt,
509
+ timeoutSec,
510
+ graceSec,
511
+ onSpawn,
512
+ onLog,
513
+ terminalResultCleanup: {
514
+ graceMs: terminalResultCleanupGraceMs,
515
+ hasTerminalResult: ({ stdout }) => parseClaudeStreamJson(stdout).resultJson !== null,
516
+ },
517
+ });
518
+ const parsedStream = parseClaudeStreamJson(proc.stdout);
519
+ const parsed = parsedStream.resultJson ?? parseJson(proc.stdout);
520
+ return { proc, parsedStream, parsed };
521
+ };
522
+ const toAdapterResult = (attempt, opts) => {
523
+ const { proc, parsedStream, parsed } = attempt;
524
+ const loginMeta = detectClaudeLoginRequired({
525
+ parsed,
526
+ stdout: proc.stdout,
527
+ stderr: proc.stderr,
528
+ });
529
+ const errorMeta = loginMeta.loginUrl != null
530
+ ? {
531
+ loginUrl: loginMeta.loginUrl,
532
+ }
533
+ : undefined;
534
+ if (proc.timedOut) {
535
+ return {
536
+ exitCode: proc.exitCode,
537
+ signal: proc.signal,
538
+ timedOut: true,
539
+ errorMessage: `Timed out after ${timeoutSec}s`,
540
+ errorCode: "timeout",
541
+ errorMeta,
542
+ clearSession: Boolean(opts.clearSessionOnMissingSession),
543
+ };
544
+ }
545
+ if (!parsed) {
546
+ const fallbackErrorMessage = parseFallbackErrorMessage(proc);
547
+ const transientUpstream = !loginMeta.requiresLogin &&
548
+ (proc.exitCode ?? 0) !== 0 &&
549
+ isClaudeTransientUpstreamError({
550
+ parsed: null,
551
+ stdout: proc.stdout,
552
+ stderr: proc.stderr,
553
+ errorMessage: fallbackErrorMessage,
554
+ });
555
+ const transientRetryNotBefore = transientUpstream
556
+ ? extractClaudeRetryNotBefore({
557
+ parsed: null,
558
+ stdout: proc.stdout,
559
+ stderr: proc.stderr,
560
+ errorMessage: fallbackErrorMessage,
561
+ })
562
+ : null;
563
+ const errorCode = loginMeta.requiresLogin
564
+ ? "claude_auth_required"
565
+ : transientUpstream
566
+ ? "claude_transient_upstream"
567
+ : null;
568
+ return {
569
+ exitCode: proc.exitCode,
570
+ signal: proc.signal,
571
+ timedOut: false,
572
+ errorMessage: fallbackErrorMessage,
573
+ errorCode,
574
+ errorFamily: transientUpstream ? "transient_upstream" : null,
575
+ retryNotBefore: transientRetryNotBefore ? transientRetryNotBefore.toISOString() : null,
576
+ errorMeta,
577
+ resultJson: {
578
+ stdout: proc.stdout,
579
+ stderr: proc.stderr,
580
+ ...(transientUpstream ? { errorFamily: "transient_upstream" } : {}),
581
+ ...(transientRetryNotBefore
582
+ ? { retryNotBefore: transientRetryNotBefore.toISOString() }
583
+ : {}),
584
+ ...(transientRetryNotBefore
585
+ ? { transientRetryNotBefore: transientRetryNotBefore.toISOString() }
586
+ : {}),
587
+ },
588
+ clearSession: Boolean(opts.clearSessionOnMissingSession),
589
+ };
590
+ }
591
+ const usage = parsedStream.usage ??
592
+ (() => {
593
+ const usageObj = parseObject(parsed.usage);
594
+ return {
595
+ inputTokens: asNumber(usageObj.input_tokens, 0),
596
+ cachedInputTokens: asNumber(usageObj.cache_read_input_tokens, 0),
597
+ outputTokens: asNumber(usageObj.output_tokens, 0),
598
+ };
599
+ })();
600
+ const resolvedSessionId = parsedStream.sessionId ??
601
+ (asString(parsed.session_id, opts.fallbackSessionId ?? "") || opts.fallbackSessionId);
602
+ const resolvedSessionParams = resolvedSessionId
603
+ ? {
604
+ sessionId: resolvedSessionId,
605
+ cwd: effectiveExecutionCwd,
606
+ promptBundleKey: promptBundle.bundleKey,
607
+ ...(executionTargetIsRemote
608
+ ? {
609
+ remoteExecution: adapterExecutionTargetSessionIdentity(executionTarget),
610
+ }
611
+ : {}),
612
+ ...(workspaceId ? { workspaceId } : {}),
613
+ ...(workspaceRepoUrl ? { repoUrl: workspaceRepoUrl } : {}),
614
+ ...(workspaceRepoRef ? { repoRef: workspaceRepoRef } : {}),
615
+ }
616
+ : null;
617
+ const clearSessionForMaxTurns = isClaudeMaxTurnsResult(parsed);
618
+ const parsedIsError = asBoolean(parsed.is_error, false);
619
+ const failed = (proc.exitCode ?? 0) !== 0 || parsedIsError;
620
+ const errorMessage = failed
621
+ ? describeClaudeFailure(parsed) ?? `Claude exited with code ${proc.exitCode ?? -1}`
622
+ : null;
623
+ const transientUpstream = failed &&
624
+ !loginMeta.requiresLogin &&
625
+ !clearSessionForMaxTurns &&
626
+ isClaudeTransientUpstreamError({
627
+ parsed,
628
+ stdout: proc.stdout,
629
+ stderr: proc.stderr,
630
+ errorMessage,
631
+ });
632
+ const transientRetryNotBefore = transientUpstream
633
+ ? extractClaudeRetryNotBefore({
634
+ parsed,
635
+ stdout: proc.stdout,
636
+ stderr: proc.stderr,
637
+ errorMessage,
638
+ })
639
+ : null;
640
+ const resolvedErrorCode = loginMeta.requiresLogin
641
+ ? "claude_auth_required"
642
+ : failed && clearSessionForMaxTurns
643
+ ? "max_turns_exhausted"
644
+ : transientUpstream
645
+ ? "claude_transient_upstream"
646
+ : null;
647
+ const mergedResultJson = {
648
+ ...parsed,
649
+ ...(failed && clearSessionForMaxTurns ? { stopReason: "max_turns_exhausted" } : {}),
650
+ ...(transientUpstream ? { errorFamily: "transient_upstream" } : {}),
651
+ ...(transientRetryNotBefore ? { retryNotBefore: transientRetryNotBefore.toISOString() } : {}),
652
+ ...(transientRetryNotBefore ? { transientRetryNotBefore: transientRetryNotBefore.toISOString() } : {}),
653
+ };
654
+ return {
655
+ exitCode: proc.exitCode,
656
+ signal: proc.signal,
657
+ timedOut: false,
658
+ errorMessage,
659
+ errorCode: resolvedErrorCode,
660
+ errorFamily: transientUpstream ? "transient_upstream" : null,
661
+ retryNotBefore: transientRetryNotBefore ? transientRetryNotBefore.toISOString() : null,
662
+ errorMeta,
663
+ usage,
664
+ sessionId: resolvedSessionId,
665
+ sessionParams: resolvedSessionParams,
666
+ sessionDisplayId: resolvedSessionId,
667
+ provider: "anthropic",
668
+ biller: isBedrockAuth(effectiveEnv) ? "aws_bedrock" : "anthropic",
669
+ model: parsedStream.model || asString(parsed.model, model),
670
+ billingType,
671
+ costUsd: parsedStream.costUsd ?? asNumber(parsed.total_cost_usd, 0),
672
+ resultJson: mergedResultJson,
673
+ summary: parsedStream.summary || asString(parsed.result, ""),
674
+ clearSession: clearSessionForMaxTurns || Boolean(opts.clearSessionOnMissingSession && !resolvedSessionId),
675
+ };
676
+ };
677
+ try {
678
+ const initial = await runAttempt(sessionId ?? null);
679
+ if (sessionId &&
680
+ !initial.proc.timedOut &&
681
+ (initial.proc.exitCode ?? 0) !== 0 &&
682
+ initial.parsed &&
683
+ isClaudeUnknownSessionError(initial.parsed)) {
684
+ await onLog("stdout", `[alvax] Claude resume session "${sessionId}" is unavailable; retrying with a fresh session.\n`);
685
+ const retry = await runAttempt(null);
686
+ return toAdapterResult(retry, { fallbackSessionId: null, clearSessionOnMissingSession: true });
687
+ }
688
+ return toAdapterResult(initial, { fallbackSessionId: runtimeSessionId || runtime.sessionId });
689
+ }
690
+ finally {
691
+ if (alvaxBridge) {
692
+ await alvaxBridge.stop();
693
+ }
694
+ if (restoreRemoteWorkspace) {
695
+ await onLog("stdout", `[alvax] Restoring workspace changes from ${describeAdapterExecutionTarget(executionTarget)}.\n`);
696
+ await restoreRemoteWorkspace();
697
+ }
698
+ }
699
+ }
700
+ //# sourceMappingURL=execute.js.map