@rudderhq/agent-runtime-opencode-local 0.5.1 → 0.5.2-canary.1
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/server/browser-capability.test.js +12 -2
- package/dist/server/browser-capability.test.js.map +1 -1
- package/dist/server/execute.d.ts +1 -0
- package/dist/server/execute.d.ts.map +1 -1
- package/dist/server/execute.js +466 -419
- package/dist/server/execute.js.map +1 -1
- package/package.json +2 -2
- package/skills/browser/SKILL.md +172 -41
- package/skills/browser/agents/openai.yaml +12 -2
- package/skills/browser/evals/evals.json +41 -0
- package/skills/browser/references/interaction-guide.md +171 -0
- package/skills/browser/references/tool-contract.md +192 -68
- package/skills/rudder-docs/references/cli-reference.md +18 -1
- package/skills/skill-creator/CHANGELOG.md +26 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +544 -3
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/compatibility/claudecode.md +29 -0
- package/skills/skill-creator/references/compatibility/codex.md +69 -0
- package/skills/skill-creator/references/compatibility/other.md +41 -0
- package/skills/skill-creator/references/rudder.md +96 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +335 -0
- package/skills/skill-creator/scripts/improve_description.py +197 -0
- package/skills/skill-creator/scripts/model_backends.py +115 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +363 -0
- package/skills/skill-creator/scripts/run_loop.py +319 -0
- package/skills/skill-creator/scripts/utils.py +223 -0
package/dist/server/execute.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { RUDDER_MCP_MANAGED_ENV_KEYS, RUDDER_MCP_SERVER_NAME, applyRudderBrowserCapabilityEnv, inferOpenAiCompatibleBiller, pickRudderMcpManagedEnv, rudderMcpRuntimeMetadata, } from "@rudderhq/agent-runtime-utils";
|
|
1
|
+
import { RUDDER_BROWSER_MCP_SERVER_NAME, RUDDER_MCP_MANAGED_ENV_KEYS, RUDDER_MCP_SERVER_NAME, applyRudderBrowserCapabilityEnv, inferOpenAiCompatibleBiller, pickRudderMcpManagedEnv, rudderBrowserMcpRuntimeMetadata, rudderMcpRuntimeMetadata, } from "@rudderhq/agent-runtime-utils";
|
|
2
2
|
import { applyGitCredentialHelperPolicyEnv, applyGitIdentityPreparationEnv, ensureGitIdentityFileConfig } from "@rudderhq/agent-runtime-utils/git-identity";
|
|
3
|
-
import { assertRudderMcpCoreAvailable, preflightRudderMcpServer, } from "@rudderhq/agent-runtime-utils/rudder-mcp-preflight";
|
|
4
|
-
import { resolveRudderMcpCliCommand } from "@rudderhq/agent-runtime-utils/rudder-mcp-server";
|
|
3
|
+
import { assertRudderMcpCoreAvailable, preflightRudderBrowserMcpServer, preflightRudderMcpServer, } from "@rudderhq/agent-runtime-utils/rudder-mcp-preflight";
|
|
4
|
+
import { resolveRudderBrowserMcpCliCommand, resolveRudderMcpCliCommand, } from "@rudderhq/agent-runtime-utils/rudder-mcp-server";
|
|
5
5
|
import { asBoolean, asNumber, asString, asStringArray, buildRudderEnv, cleanupRetiredRudderManagedEntries, ensureAbsoluteDirectory, ensureCommandResolvable, ensurePathInEnv, ensureRudderCliInPath, ensureRudderSkillSymlink, filterRudderDesiredSkillsForBrowserCapability, formatRetiredRudderManagedEntryCleanupWarnings, joinPromptSections, loadAgentInstructionsPrefix, parseObject, prepareAgentInstructionRuntimeContext, pruneLegacyLocalCliCredentialHomeEntries, readRudderRuntimeSkillEntries, redactEnvForLogs, removeUnselectedRudderSkillSymlinks, renderTemplate, resolveLocalOperatorHome, resolveRudderDesiredSkillNames, runChildProcess, selectPromptTemplate, shouldIncludeRuntimeHeartbeatInstructions, } from "@rudderhq/agent-runtime-utils/server-utils";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
6
7
|
import fs from "node:fs/promises";
|
|
7
8
|
import os from "node:os";
|
|
8
9
|
import path from "node:path";
|
|
@@ -185,6 +186,7 @@ export async function resolveRudderOpenCodeMcpConfig(managedEnv = {}, verifiedCo
|
|
|
185
186
|
...(rudderMcp.env ?? {}),
|
|
186
187
|
...managedEnv,
|
|
187
188
|
};
|
|
189
|
+
delete env.RUDDER_BROWSER_ENABLED;
|
|
188
190
|
return {
|
|
189
191
|
type: "local",
|
|
190
192
|
command: [rudderMcp.command, ...rudderMcp.args],
|
|
@@ -192,20 +194,45 @@ export async function resolveRudderOpenCodeMcpConfig(managedEnv = {}, verifiedCo
|
|
|
192
194
|
...(Object.keys(env).length > 0 ? { environment: env } : {}),
|
|
193
195
|
};
|
|
194
196
|
}
|
|
197
|
+
export async function resolveRudderOpenCodeMcpConfigs(managedEnv = {}, verifiedCommand, verifiedBrowserCommand) {
|
|
198
|
+
const configs = {
|
|
199
|
+
[RUDDER_MCP_SERVER_NAME]: await resolveRudderOpenCodeMcpConfig(managedEnv, verifiedCommand),
|
|
200
|
+
};
|
|
201
|
+
if (managedEnv.RUDDER_BROWSER_ENABLED === "true") {
|
|
202
|
+
const browserMcp = verifiedBrowserCommand ?? await resolveRudderBrowserMcpCliCommand(__moduleDir);
|
|
203
|
+
const env = { ...(browserMcp.env ?? {}), ...managedEnv };
|
|
204
|
+
configs[RUDDER_BROWSER_MCP_SERVER_NAME] = {
|
|
205
|
+
type: "local",
|
|
206
|
+
command: [browserMcp.command, ...browserMcp.args],
|
|
207
|
+
enabled: true,
|
|
208
|
+
...(Object.keys(env).length > 0 ? { environment: env } : {}),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return configs;
|
|
212
|
+
}
|
|
195
213
|
async function ensureManagedOpenCodeConfig(input) {
|
|
196
|
-
const configDir =
|
|
214
|
+
const configDir = input.configPath
|
|
215
|
+
? path.dirname(input.configPath)
|
|
216
|
+
: path.join(input.targetHome, ".config", "opencode");
|
|
197
217
|
const existing = await fs.lstat(configDir).catch(() => null);
|
|
198
218
|
if (existing?.isSymbolicLink()) {
|
|
199
219
|
await fs.unlink(configDir);
|
|
200
220
|
}
|
|
201
221
|
await fs.mkdir(configDir, { recursive: true });
|
|
202
|
-
|
|
222
|
+
if (!input.configPath)
|
|
223
|
+
await removeManagedOpenCodeConfigExtensions(configDir);
|
|
203
224
|
const config = await readOpenCodeConfigFile(input.sourceHome);
|
|
204
|
-
config.mcp = {
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
225
|
+
config.mcp = await resolveRudderOpenCodeMcpConfigs(input.managedEnv ?? {}, input.verifiedCommand, input.verifiedBrowserCommand);
|
|
226
|
+
const configPath = input.configPath ?? path.join(configDir, MANAGED_OPENCODE_CONFIG_FILE);
|
|
227
|
+
const tempPath = `${configPath}.${randomUUID()}.tmp`;
|
|
228
|
+
try {
|
|
229
|
+
await fs.writeFile(tempPath, `${JSON.stringify(config, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
230
|
+
await fs.rename(tempPath, configPath);
|
|
231
|
+
await fs.chmod(configPath, 0o600);
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
await fs.rm(tempPath, { force: true });
|
|
235
|
+
}
|
|
209
236
|
await input.onLog("stdout", `[rudder] Wrote sanitized OpenCode config into adapter-managed runtime state ${configDir}.\n`);
|
|
210
237
|
}
|
|
211
238
|
function resolveSharedOpenCodeHomeDir(env) {
|
|
@@ -390,434 +417,454 @@ export async function execute(ctx) {
|
|
|
390
417
|
const operatorHome = resolveLocalOperatorHome(sourceEnv);
|
|
391
418
|
if (authToken)
|
|
392
419
|
env.RUDDER_API_KEY = authToken;
|
|
393
|
-
const managedHome = await prepareManagedOpenCodeHome({ ...sourceEnv, ...env }, operatorHome, onLog, agent.orgId,
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
if (typeof value === "string" && value.trim().length > 0)
|
|
424
|
-
runtimeEnv[key] = value;
|
|
425
|
-
else
|
|
420
|
+
const managedHome = await prepareManagedOpenCodeHome({ ...sourceEnv, ...env }, operatorHome, onLog, agent.orgId, {});
|
|
421
|
+
const runtimeTmpDir = path.join(managedHome, "runtime-tmp", runId);
|
|
422
|
+
await fs.mkdir(runtimeTmpDir, { recursive: true });
|
|
423
|
+
try {
|
|
424
|
+
const runConfigPath = path.join(runtimeTmpDir, MANAGED_OPENCODE_CONFIG_FILE);
|
|
425
|
+
const preparedGitIdentity = await ensureGitIdentityFileConfig({
|
|
426
|
+
cwd,
|
|
427
|
+
home: managedHome,
|
|
428
|
+
sourceEnv,
|
|
429
|
+
onLog,
|
|
430
|
+
});
|
|
431
|
+
env.HOME = operatorHome;
|
|
432
|
+
env.USERPROFILE = operatorHome;
|
|
433
|
+
env.OPENCODE_CONFIG = runConfigPath;
|
|
434
|
+
env.OPENCODE_DISABLE_CLAUDE_CODE = "true";
|
|
435
|
+
env.OPENCODE_DISABLE_CLAUDE_CODE_PROMPT = "true";
|
|
436
|
+
env.OPENCODE_DISABLE_CLAUDE_CODE_SKILLS = "true";
|
|
437
|
+
env.RUDDER_OPERATOR_HOME = operatorHome;
|
|
438
|
+
env.XDG_CONFIG_HOME = path.join(managedHome, ".config");
|
|
439
|
+
env.XDG_DATA_HOME = path.join(managedHome, ".local", "share");
|
|
440
|
+
env.XDG_CACHE_HOME = path.join(managedHome, ".cache");
|
|
441
|
+
applyGitIdentityPreparationEnv(env, preparedGitIdentity);
|
|
442
|
+
applyGitCredentialHelperPolicyEnv(env);
|
|
443
|
+
const openCodeSkillEntries = await readRudderRuntimeSkillEntries(config, __moduleDir);
|
|
444
|
+
const desiredOpenCodeSkillNames = resolveRudderDesiredSkillNames(config, openCodeSkillEntries);
|
|
445
|
+
const isChatResultRepair = context.rudderChatResultRepair === true;
|
|
446
|
+
const managedOpenCodeSkillsDir = resolveManagedOpenCodeSkillsDir(managedHome);
|
|
447
|
+
const runtimeEnv = Object.fromEntries(Object.entries(ensurePathInEnv(await ensureRudderCliInPath(__moduleDir, { ...process.env, ...env })))
|
|
448
|
+
.filter((entry) => typeof entry[1] === "string"));
|
|
449
|
+
for (const key of OPENCODE_INHERITED_ENV_BLOCKLIST) {
|
|
426
450
|
delete runtimeEnv[key];
|
|
427
|
-
}
|
|
428
|
-
const rudderMcpCommand = await resolveRudderMcpCliCommand(__moduleDir);
|
|
429
|
-
const rudderMcpPreflight = await preflightRudderMcpServer({
|
|
430
|
-
command: rudderMcpCommand,
|
|
431
|
-
runtimeEnv,
|
|
432
|
-
managedEnv: pickRudderMcpManagedEnv(env),
|
|
433
|
-
browserEnabled,
|
|
434
|
-
});
|
|
435
|
-
assertRudderMcpCoreAvailable(rudderMcpPreflight);
|
|
436
|
-
delete runtimeEnv.RUDDER_DESKTOP_CLI_ENTRY;
|
|
437
|
-
delete runtimeEnv.RUDDER_MCP_RUDDER_BIN;
|
|
438
|
-
if (browserEnabled && !rudderMcpPreflight?.browserAvailable) {
|
|
439
|
-
browserEnabled = false;
|
|
440
|
-
env.RUDDER_BROWSER_ENABLED = "false";
|
|
441
|
-
runtimeEnv.RUDDER_BROWSER_ENABLED = "false";
|
|
442
|
-
await onLog("stderr", `[rudder] ${rudderMcpPreflight?.diagnostic}\n`);
|
|
443
|
-
}
|
|
444
|
-
const effectiveDesiredOpenCodeSkillNames = filterRudderDesiredSkillsForBrowserCapability(openCodeSkillEntries, desiredOpenCodeSkillNames, browserEnabled);
|
|
445
|
-
const selectedOpenCodeSkillEntries = isChatResultRepair
|
|
446
|
-
? []
|
|
447
|
-
: openCodeSkillEntries.filter((entry) => effectiveDesiredOpenCodeSkillNames.includes(entry.key));
|
|
448
|
-
const loadedSkills = openCodeSkillEntries
|
|
449
|
-
.filter((entry) => effectiveDesiredOpenCodeSkillNames.includes(entry.key))
|
|
450
|
-
.map((entry) => ({
|
|
451
|
-
key: entry.key,
|
|
452
|
-
runtimeName: entry.runtimeName,
|
|
453
|
-
name: entry.name ?? null,
|
|
454
|
-
description: entry.description ?? null,
|
|
455
|
-
}));
|
|
456
|
-
await ensureOpenCodeSkillsInjected(onLog, managedOpenCodeSkillsDir, openCodeSkillEntries, effectiveDesiredOpenCodeSkillNames);
|
|
457
|
-
const selectedSkillPrompt = await renderSelectedOpenCodeSkillPrompt(onLog, managedOpenCodeSkillsDir, selectedOpenCodeSkillEntries);
|
|
458
|
-
await ensureManagedOpenCodeConfig({
|
|
459
|
-
sourceHome: operatorHome,
|
|
460
|
-
targetHome: managedHome,
|
|
461
|
-
managedEnv: pickRudderMcpManagedEnv(env),
|
|
462
|
-
verifiedCommand: rudderMcpCommand,
|
|
463
|
-
onLog,
|
|
464
|
-
});
|
|
465
|
-
await ensureCommandResolvable(command, cwd, runtimeEnv);
|
|
466
|
-
validateOpenCodeModelConfig({ model });
|
|
467
|
-
const configuredTimeoutSec = asNumber(config.timeoutSec, 0);
|
|
468
|
-
const graceSec = asNumber(config.graceSec, 20);
|
|
469
|
-
const extraArgs = (() => {
|
|
470
|
-
const fromExtraArgs = asStringArray(config.extraArgs);
|
|
471
|
-
if (fromExtraArgs.length > 0)
|
|
472
|
-
return fromExtraArgs;
|
|
473
|
-
return asStringArray(config.args);
|
|
474
|
-
})();
|
|
475
|
-
const runtimeSessionParams = parseObject(runtime.sessionParams);
|
|
476
|
-
const runtimeSessionId = asString(runtimeSessionParams.sessionId, runtime.sessionId ?? "");
|
|
477
|
-
const runtimeSessionCwd = asString(runtimeSessionParams.cwd, "");
|
|
478
|
-
const canResumeSession = runtimeSessionId.length > 0 &&
|
|
479
|
-
(runtimeSessionCwd.length === 0 || path.resolve(runtimeSessionCwd) === path.resolve(cwd));
|
|
480
|
-
const sessionId = canResumeSession ? runtimeSessionId : null;
|
|
481
|
-
if (runtimeSessionId && !canResumeSession) {
|
|
482
|
-
await onLog("stdout", `[rudder] OpenCode session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${cwd}".\n`);
|
|
483
|
-
}
|
|
484
|
-
const instructionsFilePath = asString(config.instructionsFilePath, "").trim();
|
|
485
|
-
const resolvedInstructionsFilePath = instructionsFilePath
|
|
486
|
-
? path.resolve(cwd, instructionsFilePath)
|
|
487
|
-
: "";
|
|
488
|
-
const instructionRuntimeContext = prepareAgentInstructionRuntimeContext(context);
|
|
489
|
-
const loadedInstructions = await loadAgentInstructionsPrefix({
|
|
490
|
-
instructionsFilePath: resolvedInstructionsFilePath,
|
|
491
|
-
includeHeartbeatInstructions: shouldIncludeRuntimeHeartbeatInstructions(context),
|
|
492
|
-
contextSectionsBeforeCurrentTime: instructionRuntimeContext.contextSectionsBeforeCurrentTime,
|
|
493
|
-
onLog,
|
|
494
|
-
});
|
|
495
|
-
const instructionsPrefix = loadedInstructions.prefix;
|
|
496
|
-
const instructionsDir = loadedInstructions.instructionsDir;
|
|
497
|
-
const commandNotes = (() => {
|
|
498
|
-
const rudderMcpNote = "Configured first-party Rudder MCP tools for OpenCode.";
|
|
499
|
-
if (!resolvedInstructionsFilePath) {
|
|
500
|
-
return [
|
|
501
|
-
...loadedInstructions.commandNotes,
|
|
502
|
-
rudderMcpNote,
|
|
503
|
-
"Prepended Rudder operating contract to stdin prompt.",
|
|
504
|
-
];
|
|
505
451
|
}
|
|
506
|
-
|
|
452
|
+
for (const key of RUDDER_MCP_MANAGED_ENV_KEYS) {
|
|
453
|
+
const value = env[key];
|
|
454
|
+
if (typeof value === "string" && value.trim().length > 0)
|
|
455
|
+
runtimeEnv[key] = value;
|
|
456
|
+
else
|
|
457
|
+
delete runtimeEnv[key];
|
|
458
|
+
}
|
|
459
|
+
const rudderMcpCommand = await resolveRudderMcpCliCommand(__moduleDir);
|
|
460
|
+
const rudderMcpPreflight = await preflightRudderMcpServer({
|
|
461
|
+
command: rudderMcpCommand,
|
|
462
|
+
runtimeEnv,
|
|
463
|
+
managedEnv: pickRudderMcpManagedEnv(env),
|
|
464
|
+
browserEnabled: false,
|
|
465
|
+
});
|
|
466
|
+
assertRudderMcpCoreAvailable(rudderMcpPreflight);
|
|
467
|
+
delete runtimeEnv.RUDDER_DESKTOP_CLI_ENTRY;
|
|
468
|
+
delete runtimeEnv.RUDDER_MCP_RUDDER_BIN;
|
|
469
|
+
const browserMcpCommand = browserEnabled ? await resolveRudderBrowserMcpCliCommand(__moduleDir) : null;
|
|
470
|
+
const browserMcpPreflight = browserMcpCommand
|
|
471
|
+
? await preflightRudderBrowserMcpServer({
|
|
472
|
+
command: browserMcpCommand,
|
|
473
|
+
runtimeEnv,
|
|
474
|
+
managedEnv: pickRudderMcpManagedEnv(env),
|
|
475
|
+
})
|
|
476
|
+
: null;
|
|
477
|
+
if (browserEnabled && !browserMcpPreflight?.browserAvailable) {
|
|
478
|
+
browserEnabled = false;
|
|
479
|
+
env.RUDDER_BROWSER_ENABLED = "false";
|
|
480
|
+
runtimeEnv.RUDDER_BROWSER_ENABLED = "false";
|
|
481
|
+
await onLog("stderr", `[rudder] ${browserMcpPreflight?.diagnostic}\n`);
|
|
482
|
+
}
|
|
483
|
+
const effectiveDesiredOpenCodeSkillNames = filterRudderDesiredSkillsForBrowserCapability(openCodeSkillEntries, desiredOpenCodeSkillNames, browserEnabled);
|
|
484
|
+
const selectedOpenCodeSkillEntries = isChatResultRepair
|
|
485
|
+
? []
|
|
486
|
+
: openCodeSkillEntries.filter((entry) => effectiveDesiredOpenCodeSkillNames.includes(entry.key));
|
|
487
|
+
const loadedSkills = openCodeSkillEntries
|
|
488
|
+
.filter((entry) => effectiveDesiredOpenCodeSkillNames.includes(entry.key))
|
|
489
|
+
.map((entry) => ({
|
|
490
|
+
key: entry.key,
|
|
491
|
+
runtimeName: entry.runtimeName,
|
|
492
|
+
name: entry.name ?? null,
|
|
493
|
+
description: entry.description ?? null,
|
|
494
|
+
}));
|
|
495
|
+
await ensureOpenCodeSkillsInjected(onLog, managedOpenCodeSkillsDir, openCodeSkillEntries, effectiveDesiredOpenCodeSkillNames);
|
|
496
|
+
const selectedSkillPrompt = await renderSelectedOpenCodeSkillPrompt(onLog, managedOpenCodeSkillsDir, selectedOpenCodeSkillEntries);
|
|
497
|
+
await ensureManagedOpenCodeConfig({
|
|
498
|
+
sourceHome: operatorHome,
|
|
499
|
+
targetHome: managedHome,
|
|
500
|
+
configPath: runConfigPath,
|
|
501
|
+
managedEnv: pickRudderMcpManagedEnv(env),
|
|
502
|
+
verifiedCommand: rudderMcpCommand,
|
|
503
|
+
verifiedBrowserCommand: browserMcpCommand ?? undefined,
|
|
504
|
+
onLog,
|
|
505
|
+
});
|
|
506
|
+
await ensureCommandResolvable(command, cwd, runtimeEnv);
|
|
507
|
+
validateOpenCodeModelConfig({ model });
|
|
508
|
+
const configuredTimeoutSec = asNumber(config.timeoutSec, 0);
|
|
509
|
+
const graceSec = asNumber(config.graceSec, 20);
|
|
510
|
+
const extraArgs = (() => {
|
|
511
|
+
const fromExtraArgs = asStringArray(config.extraArgs);
|
|
512
|
+
if (fromExtraArgs.length > 0)
|
|
513
|
+
return fromExtraArgs;
|
|
514
|
+
return asStringArray(config.args);
|
|
515
|
+
})();
|
|
516
|
+
const runtimeSessionParams = parseObject(runtime.sessionParams);
|
|
517
|
+
const runtimeSessionId = asString(runtimeSessionParams.sessionId, runtime.sessionId ?? "");
|
|
518
|
+
const runtimeSessionCwd = asString(runtimeSessionParams.cwd, "");
|
|
519
|
+
const canResumeSession = runtimeSessionId.length > 0 &&
|
|
520
|
+
(runtimeSessionCwd.length === 0 || path.resolve(runtimeSessionCwd) === path.resolve(cwd));
|
|
521
|
+
const sessionId = canResumeSession ? runtimeSessionId : null;
|
|
522
|
+
if (runtimeSessionId && !canResumeSession) {
|
|
523
|
+
await onLog("stdout", `[rudder] OpenCode session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${cwd}".\n`);
|
|
524
|
+
}
|
|
525
|
+
const instructionsFilePath = asString(config.instructionsFilePath, "").trim();
|
|
526
|
+
const resolvedInstructionsFilePath = instructionsFilePath
|
|
527
|
+
? path.resolve(cwd, instructionsFilePath)
|
|
528
|
+
: "";
|
|
529
|
+
const instructionRuntimeContext = prepareAgentInstructionRuntimeContext(context);
|
|
530
|
+
const loadedInstructions = await loadAgentInstructionsPrefix({
|
|
531
|
+
instructionsFilePath: resolvedInstructionsFilePath,
|
|
532
|
+
includeHeartbeatInstructions: shouldIncludeRuntimeHeartbeatInstructions(context),
|
|
533
|
+
contextSectionsBeforeCurrentTime: instructionRuntimeContext.contextSectionsBeforeCurrentTime,
|
|
534
|
+
onLog,
|
|
535
|
+
});
|
|
536
|
+
const instructionsPrefix = loadedInstructions.prefix;
|
|
537
|
+
const instructionsDir = loadedInstructions.instructionsDir;
|
|
538
|
+
const commandNotes = (() => {
|
|
539
|
+
const rudderMcpNote = "Configured first-party Rudder MCP tools for OpenCode.";
|
|
540
|
+
if (!resolvedInstructionsFilePath) {
|
|
541
|
+
return [
|
|
542
|
+
...loadedInstructions.commandNotes,
|
|
543
|
+
rudderMcpNote,
|
|
544
|
+
"Prepended Rudder operating contract to stdin prompt.",
|
|
545
|
+
];
|
|
546
|
+
}
|
|
547
|
+
if (instructionsPrefix.length > 0) {
|
|
548
|
+
return [
|
|
549
|
+
...loadedInstructions.commandNotes,
|
|
550
|
+
rudderMcpNote,
|
|
551
|
+
`Prepended instructions + path directive to stdin prompt (relative references from ${instructionsDir}).`,
|
|
552
|
+
];
|
|
553
|
+
}
|
|
507
554
|
return [
|
|
508
555
|
...loadedInstructions.commandNotes,
|
|
509
556
|
rudderMcpNote,
|
|
510
|
-
`Prepended instructions + path directive to stdin prompt (relative references from ${instructionsDir}).`,
|
|
511
557
|
];
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
558
|
+
})();
|
|
559
|
+
/**
|
|
560
|
+
* Final prompt assembly order is intentional and shared across runtimes:
|
|
561
|
+
* 1) optional injected instructions prefix,
|
|
562
|
+
* 2) optional bootstrap prompt (only when not resuming a prior session),
|
|
563
|
+
* 3) optional session handoff markdown,
|
|
564
|
+
* 4) heartbeat prompt selected by wake trigger (assignment, mention, retry, fallback).
|
|
565
|
+
*
|
|
566
|
+
* Prompt example (assignment wakeup):
|
|
567
|
+
* [instructions prefix]
|
|
568
|
+
* [bootstrap prompt]
|
|
569
|
+
* [session handoff note]
|
|
570
|
+
* You are agent agent-123 (Frontend Maintainer). You have been assigned to work on an issue.
|
|
571
|
+
* Issue: "Fix onboarding redirect"
|
|
572
|
+
*
|
|
573
|
+
* Reasoning: assignment/mention heartbeat templates carry issue/comment context so
|
|
574
|
+
* the agent can start useful work on turn one without spending extra tool calls on
|
|
575
|
+
* "what changed?" discovery.
|
|
576
|
+
*
|
|
577
|
+
* Traceability:
|
|
578
|
+
* - doc/engineering/DEVELOPING.md
|
|
579
|
+
*/
|
|
580
|
+
const bootstrapPromptTemplate = asString(config.bootstrapPromptTemplate, "");
|
|
581
|
+
const templateData = {
|
|
582
|
+
agentId: agent.id,
|
|
583
|
+
orgId: agent.orgId,
|
|
584
|
+
runId,
|
|
585
|
+
organization: { id: agent.orgId },
|
|
586
|
+
agent,
|
|
587
|
+
run: {
|
|
588
|
+
id: runId,
|
|
589
|
+
source: context.wakeSource ?? "on_demand",
|
|
590
|
+
wakeReason: context.wakeReason ?? null,
|
|
591
|
+
},
|
|
592
|
+
context: instructionRuntimeContext.promptContext,
|
|
593
|
+
// Issue and comment context for enriched prompts
|
|
594
|
+
issue: context.issue ?? null,
|
|
595
|
+
comment: context.comment ?? null,
|
|
549
596
|
wakeReason: context.wakeReason ?? null,
|
|
550
|
-
|
|
551
|
-
context: instructionRuntimeContext.promptContext,
|
|
552
|
-
// Issue and comment context for enriched prompts
|
|
553
|
-
issue: context.issue ?? null,
|
|
554
|
-
comment: context.comment ?? null,
|
|
555
|
-
wakeReason: context.wakeReason ?? null,
|
|
556
|
-
wakeSource: context.wakeSource ?? null,
|
|
557
|
-
};
|
|
558
|
-
const renderedPrompt = renderTemplate(promptTemplate, templateData);
|
|
559
|
-
const renderedBootstrapPrompt = !sessionId && bootstrapPromptTemplate.trim().length > 0
|
|
560
|
-
? renderTemplate(bootstrapPromptTemplate, templateData).trim()
|
|
561
|
-
: "";
|
|
562
|
-
const sessionHandoffNote = asString(context.rudderSessionHandoffMarkdown, "").trim();
|
|
563
|
-
const prompt = joinPromptSections([
|
|
564
|
-
instructionsPrefix,
|
|
565
|
-
selectedSkillPrompt,
|
|
566
|
-
renderedBootstrapPrompt,
|
|
567
|
-
sessionHandoffNote,
|
|
568
|
-
renderedPrompt,
|
|
569
|
-
]);
|
|
570
|
-
const promptMetrics = {
|
|
571
|
-
promptChars: prompt.length,
|
|
572
|
-
...loadedInstructions.metrics,
|
|
573
|
-
bootstrapPromptChars: renderedBootstrapPrompt.length,
|
|
574
|
-
sessionHandoffChars: sessionHandoffNote.length,
|
|
575
|
-
heartbeatPromptChars: renderedPrompt.length,
|
|
576
|
-
};
|
|
577
|
-
const runtimeTmpDir = path.join(managedHome, "runtime-tmp", runId);
|
|
578
|
-
await fs.mkdir(runtimeTmpDir, { recursive: true });
|
|
579
|
-
const promptFilePath = path.join(runtimeTmpDir, "rudder-prompt.md");
|
|
580
|
-
await fs.writeFile(promptFilePath, prompt, "utf8");
|
|
581
|
-
const useStdinPrompt = context.chatMode === true;
|
|
582
|
-
const hasChatModeTimeoutFallback = useStdinPrompt && configuredTimeoutSec <= 0;
|
|
583
|
-
const timeoutSec = hasChatModeTimeoutFallback
|
|
584
|
-
? CHAT_MODE_DEFAULT_TIMEOUT_SEC
|
|
585
|
-
: configuredTimeoutSec;
|
|
586
|
-
const toolLoopIdleTimeoutSec = Math.max(1, asNumber(config.toolLoopIdleTimeoutSec, asNumber(env.RUDDER_OPENCODE_TOOL_LOOP_IDLE_TIMEOUT_SEC, DEFAULT_TOOL_LOOP_IDLE_TIMEOUT_SEC)));
|
|
587
|
-
const startupIdleTimeoutSec = Math.max(1, asNumber(config.startupIdleTimeoutSec, asNumber(env.RUDDER_OPENCODE_STARTUP_IDLE_TIMEOUT_SEC, DEFAULT_STARTUP_IDLE_TIMEOUT_SEC)));
|
|
588
|
-
const effectiveCommandNotes = hasChatModeTimeoutFallback
|
|
589
|
-
? [
|
|
590
|
-
...commandNotes,
|
|
591
|
-
`Applied ${CHAT_MODE_DEFAULT_TIMEOUT_SEC}s default timeout for OpenCode chat mode because timeoutSec was unset.`,
|
|
592
|
-
]
|
|
593
|
-
: commandNotes;
|
|
594
|
-
const buildArgs = (resumeSessionId, redactPromptFile) => {
|
|
595
|
-
const args = ["run", "--pure", "--format", "json", "--dir", cwd];
|
|
596
|
-
if (!useStdinPrompt)
|
|
597
|
-
args.push(OPENCODE_PROMPT_FILE_MESSAGE);
|
|
598
|
-
if (resumeSessionId)
|
|
599
|
-
args.push("--session", resumeSessionId);
|
|
600
|
-
if (model)
|
|
601
|
-
args.push("--model", model);
|
|
602
|
-
if (variant)
|
|
603
|
-
args.push("--variant", variant);
|
|
604
|
-
if (dangerouslySkipPermissions)
|
|
605
|
-
args.push("--dangerously-skip-permissions");
|
|
606
|
-
if (extraArgs.length > 0)
|
|
607
|
-
args.push(...extraArgs);
|
|
608
|
-
if (!useStdinPrompt) {
|
|
609
|
-
args.push("--file", redactPromptFile ? `<rudder prompt file ${prompt.length} chars>` : promptFilePath);
|
|
610
|
-
}
|
|
611
|
-
return args;
|
|
612
|
-
};
|
|
613
|
-
const runAttempt = async (resumeSessionId) => {
|
|
614
|
-
const args = buildArgs(resumeSessionId, false);
|
|
615
|
-
if (onMeta) {
|
|
616
|
-
await onMeta({
|
|
617
|
-
agentRuntimeType: "opencode_local",
|
|
618
|
-
command,
|
|
619
|
-
cwd,
|
|
620
|
-
commandNotes: effectiveCommandNotes,
|
|
621
|
-
commandArgs: buildArgs(resumeSessionId, true),
|
|
622
|
-
env: redactEnvForLogs(env),
|
|
623
|
-
prompt,
|
|
624
|
-
agentInstructionStack: prompt,
|
|
625
|
-
promptMetrics,
|
|
626
|
-
loadedSkills,
|
|
627
|
-
realizedSkills: loadedSkills,
|
|
628
|
-
promptInjectedSkills: loadedSkills,
|
|
629
|
-
rudderMcp: rudderMcpRuntimeMetadata({ browserEnabled, preflight: rudderMcpPreflight }),
|
|
630
|
-
context,
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
let stdoutBuffer = "";
|
|
634
|
-
let sawTerminalContent = false;
|
|
635
|
-
let sawTerminalCompletionSummary = false;
|
|
636
|
-
let stoppedAfterTerminalText = false;
|
|
637
|
-
let stoppedAfterToolLoopIdle = false;
|
|
638
|
-
let stoppedAfterStartupIdle = false;
|
|
639
|
-
const terminalStopController = new AbortController();
|
|
640
|
-
let startupIdleTimer = null;
|
|
641
|
-
let toolLoopIdleTimer = null;
|
|
642
|
-
const clearStartupIdleTimer = () => {
|
|
643
|
-
if (!startupIdleTimer)
|
|
644
|
-
return;
|
|
645
|
-
clearTimeout(startupIdleTimer);
|
|
646
|
-
startupIdleTimer = null;
|
|
597
|
+
wakeSource: context.wakeSource ?? null,
|
|
647
598
|
};
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
599
|
+
const renderedPrompt = renderTemplate(promptTemplate, templateData);
|
|
600
|
+
const renderedBootstrapPrompt = !sessionId && bootstrapPromptTemplate.trim().length > 0
|
|
601
|
+
? renderTemplate(bootstrapPromptTemplate, templateData).trim()
|
|
602
|
+
: "";
|
|
603
|
+
const sessionHandoffNote = asString(context.rudderSessionHandoffMarkdown, "").trim();
|
|
604
|
+
const prompt = joinPromptSections([
|
|
605
|
+
instructionsPrefix,
|
|
606
|
+
selectedSkillPrompt,
|
|
607
|
+
renderedBootstrapPrompt,
|
|
608
|
+
sessionHandoffNote,
|
|
609
|
+
renderedPrompt,
|
|
610
|
+
]);
|
|
611
|
+
const promptMetrics = {
|
|
612
|
+
promptChars: prompt.length,
|
|
613
|
+
...loadedInstructions.metrics,
|
|
614
|
+
bootstrapPromptChars: renderedBootstrapPrompt.length,
|
|
615
|
+
sessionHandoffChars: sessionHandoffNote.length,
|
|
616
|
+
heartbeatPromptChars: renderedPrompt.length,
|
|
653
617
|
};
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
618
|
+
const promptFilePath = path.join(runtimeTmpDir, "rudder-prompt.md");
|
|
619
|
+
await fs.writeFile(promptFilePath, prompt, "utf8");
|
|
620
|
+
const useStdinPrompt = context.chatMode === true;
|
|
621
|
+
const hasChatModeTimeoutFallback = useStdinPrompt && configuredTimeoutSec <= 0;
|
|
622
|
+
const timeoutSec = hasChatModeTimeoutFallback
|
|
623
|
+
? CHAT_MODE_DEFAULT_TIMEOUT_SEC
|
|
624
|
+
: configuredTimeoutSec;
|
|
625
|
+
const toolLoopIdleTimeoutSec = Math.max(1, asNumber(config.toolLoopIdleTimeoutSec, asNumber(env.RUDDER_OPENCODE_TOOL_LOOP_IDLE_TIMEOUT_SEC, DEFAULT_TOOL_LOOP_IDLE_TIMEOUT_SEC)));
|
|
626
|
+
const startupIdleTimeoutSec = Math.max(1, asNumber(config.startupIdleTimeoutSec, asNumber(env.RUDDER_OPENCODE_STARTUP_IDLE_TIMEOUT_SEC, DEFAULT_STARTUP_IDLE_TIMEOUT_SEC)));
|
|
627
|
+
const effectiveCommandNotes = hasChatModeTimeoutFallback
|
|
628
|
+
? [
|
|
629
|
+
...commandNotes,
|
|
630
|
+
`Applied ${CHAT_MODE_DEFAULT_TIMEOUT_SEC}s default timeout for OpenCode chat mode because timeoutSec was unset.`,
|
|
631
|
+
]
|
|
632
|
+
: commandNotes;
|
|
633
|
+
const buildArgs = (resumeSessionId, redactPromptFile) => {
|
|
634
|
+
const args = ["run", "--pure", "--format", "json", "--dir", cwd];
|
|
635
|
+
if (!useStdinPrompt)
|
|
636
|
+
args.push(OPENCODE_PROMPT_FILE_MESSAGE);
|
|
637
|
+
if (resumeSessionId)
|
|
638
|
+
args.push("--session", resumeSessionId);
|
|
639
|
+
if (model)
|
|
640
|
+
args.push("--model", model);
|
|
641
|
+
if (variant)
|
|
642
|
+
args.push("--variant", variant);
|
|
643
|
+
if (dangerouslySkipPermissions)
|
|
644
|
+
args.push("--dangerously-skip-permissions");
|
|
645
|
+
if (extraArgs.length > 0)
|
|
646
|
+
args.push(...extraArgs);
|
|
647
|
+
if (!useStdinPrompt) {
|
|
648
|
+
args.push("--file", redactPromptFile ? `<rudder prompt file ${prompt.length} chars>` : promptFilePath);
|
|
649
|
+
}
|
|
650
|
+
return args;
|
|
661
651
|
};
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
652
|
+
const runAttempt = async (resumeSessionId) => {
|
|
653
|
+
const args = buildArgs(resumeSessionId, false);
|
|
654
|
+
if (onMeta) {
|
|
655
|
+
await onMeta({
|
|
656
|
+
agentRuntimeType: "opencode_local",
|
|
657
|
+
command,
|
|
658
|
+
cwd,
|
|
659
|
+
commandNotes: effectiveCommandNotes,
|
|
660
|
+
commandArgs: buildArgs(resumeSessionId, true),
|
|
661
|
+
env: redactEnvForLogs(env),
|
|
662
|
+
prompt,
|
|
663
|
+
agentInstructionStack: prompt,
|
|
664
|
+
promptMetrics,
|
|
665
|
+
loadedSkills,
|
|
666
|
+
realizedSkills: loadedSkills,
|
|
667
|
+
promptInjectedSkills: loadedSkills,
|
|
668
|
+
rudderMcp: rudderMcpRuntimeMetadata({ browserEnabled, preflight: rudderMcpPreflight }),
|
|
669
|
+
browserMcp: rudderBrowserMcpRuntimeMetadata({
|
|
670
|
+
available: browserEnabled,
|
|
671
|
+
preflight: browserMcpPreflight,
|
|
672
|
+
}),
|
|
673
|
+
context,
|
|
674
|
+
});
|
|
678
675
|
}
|
|
679
|
-
stdoutBuffer
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
676
|
+
let stdoutBuffer = "";
|
|
677
|
+
let sawTerminalContent = false;
|
|
678
|
+
let sawTerminalCompletionSummary = false;
|
|
679
|
+
let stoppedAfterTerminalText = false;
|
|
680
|
+
let stoppedAfterToolLoopIdle = false;
|
|
681
|
+
let stoppedAfterStartupIdle = false;
|
|
682
|
+
const terminalStopController = new AbortController();
|
|
683
|
+
let startupIdleTimer = null;
|
|
684
|
+
let toolLoopIdleTimer = null;
|
|
685
|
+
const clearStartupIdleTimer = () => {
|
|
686
|
+
if (!startupIdleTimer)
|
|
687
|
+
return;
|
|
688
|
+
clearTimeout(startupIdleTimer);
|
|
689
|
+
startupIdleTimer = null;
|
|
690
|
+
};
|
|
691
|
+
const clearToolLoopIdleTimer = () => {
|
|
692
|
+
if (!toolLoopIdleTimer)
|
|
693
|
+
return;
|
|
694
|
+
clearTimeout(toolLoopIdleTimer);
|
|
695
|
+
toolLoopIdleTimer = null;
|
|
696
|
+
};
|
|
697
|
+
const armToolLoopIdleTimer = () => {
|
|
698
|
+
clearToolLoopIdleTimer();
|
|
699
|
+
toolLoopIdleTimer = setTimeout(() => {
|
|
700
|
+
stoppedAfterToolLoopIdle = true;
|
|
701
|
+
if (!terminalStopController.signal.aborted)
|
|
702
|
+
terminalStopController.abort();
|
|
703
|
+
}, toolLoopIdleTimeoutSec * 1000);
|
|
704
|
+
};
|
|
705
|
+
startupIdleTimer = setTimeout(() => {
|
|
706
|
+
stoppedAfterStartupIdle = true;
|
|
707
|
+
if (!terminalStopController.signal.aborted)
|
|
702
708
|
terminalStopController.abort();
|
|
709
|
+
}, startupIdleTimeoutSec * 1000);
|
|
710
|
+
const abortSignals = [
|
|
711
|
+
ctx.abortSignal,
|
|
712
|
+
terminalStopController.signal,
|
|
713
|
+
].filter((signal) => Boolean(signal));
|
|
714
|
+
const combinedAbortSignal = abortSignals.length > 0
|
|
715
|
+
? AbortSignal.any(abortSignals)
|
|
716
|
+
: undefined;
|
|
717
|
+
const bufferedOnLog = async (stream, chunk) => {
|
|
718
|
+
if (stream !== "stdout") {
|
|
719
|
+
await onLog(stream, chunk);
|
|
720
|
+
return;
|
|
703
721
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
722
|
+
stdoutBuffer += chunk;
|
|
723
|
+
const lines = stdoutBuffer.split(/\r?\n/);
|
|
724
|
+
stdoutBuffer = lines.pop() ?? "";
|
|
725
|
+
for (const line of lines) {
|
|
726
|
+
const parsedLine = parseOpenCodeJsonlLine(line.trim());
|
|
727
|
+
if (parsedLine)
|
|
728
|
+
clearStartupIdleTimer();
|
|
729
|
+
if (parsedLine?.type === "assistantText" && parsedLine.text.trim().length > 0) {
|
|
730
|
+
sawTerminalContent = true;
|
|
731
|
+
}
|
|
732
|
+
const sawCompletionSummary = parsedLine?.type === "syntheticText" &&
|
|
733
|
+
parsedLine.completion &&
|
|
734
|
+
parsedLine.text.trim().length > 0;
|
|
735
|
+
if (parsedLine && parsedLine.type !== "other")
|
|
736
|
+
clearToolLoopIdleTimer();
|
|
737
|
+
if (sawCompletionSummary) {
|
|
738
|
+
sawTerminalCompletionSummary = true;
|
|
739
|
+
}
|
|
740
|
+
if (parsedLine?.type === "toolCallsStepFinish") {
|
|
741
|
+
armToolLoopIdleTimer();
|
|
742
|
+
}
|
|
743
|
+
if (parsedLine?.type === "terminalStop" && sawTerminalContent && !terminalStopController.signal.aborted) {
|
|
744
|
+
stoppedAfterTerminalText = true;
|
|
745
|
+
terminalStopController.abort();
|
|
746
|
+
}
|
|
747
|
+
if (parsedLine?.type === "terminalStop" && sawTerminalCompletionSummary && !terminalStopController.signal.aborted) {
|
|
748
|
+
stoppedAfterTerminalText = true;
|
|
749
|
+
terminalStopController.abort();
|
|
750
|
+
}
|
|
707
751
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
: proc,
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
752
|
+
await onLog(stream, chunk);
|
|
753
|
+
};
|
|
754
|
+
const proc = await runChildProcess(runId, command, args, {
|
|
755
|
+
cwd,
|
|
756
|
+
env: runtimeEnv,
|
|
757
|
+
timeoutSec,
|
|
758
|
+
graceSec,
|
|
759
|
+
stdin: useStdinPrompt ? prompt : undefined,
|
|
760
|
+
onSpawn,
|
|
761
|
+
abortSignal: combinedAbortSignal,
|
|
762
|
+
onLog: bufferedOnLog,
|
|
763
|
+
})
|
|
764
|
+
.finally(() => {
|
|
765
|
+
clearStartupIdleTimer();
|
|
766
|
+
clearToolLoopIdleTimer();
|
|
767
|
+
});
|
|
768
|
+
return {
|
|
769
|
+
proc: stoppedAfterTerminalText && !proc.timedOut && proc.signal === "SIGTERM"
|
|
770
|
+
? { ...proc, exitCode: 0, signal: null }
|
|
771
|
+
: proc,
|
|
772
|
+
rawStderr: proc.stderr,
|
|
773
|
+
parsed: parseOpenCodeJsonl(proc.stdout),
|
|
774
|
+
stoppedAfterTerminalText,
|
|
775
|
+
stoppedAfterToolLoopIdle,
|
|
776
|
+
stoppedAfterStartupIdle,
|
|
777
|
+
};
|
|
734
778
|
};
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
779
|
+
const toResult = (attempt, clearSessionOnMissingSession = false) => {
|
|
780
|
+
if (attempt.proc.timedOut) {
|
|
781
|
+
return {
|
|
782
|
+
exitCode: attempt.proc.exitCode,
|
|
783
|
+
signal: attempt.proc.signal,
|
|
784
|
+
timedOut: true,
|
|
785
|
+
errorMessage: `Timed out after ${timeoutSec}s`,
|
|
786
|
+
clearSession: clearSessionOnMissingSession,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
const resolvedSessionId = attempt.parsed.sessionId ??
|
|
790
|
+
(clearSessionOnMissingSession ? null : runtimeSessionId ?? runtime.sessionId ?? null);
|
|
791
|
+
const resolvedSessionParams = resolvedSessionId
|
|
792
|
+
? {
|
|
793
|
+
sessionId: resolvedSessionId,
|
|
794
|
+
cwd,
|
|
795
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
796
|
+
...(workspaceRepoUrl ? { repoUrl: workspaceRepoUrl } : {}),
|
|
797
|
+
...(workspaceRepoRef ? { repoRef: workspaceRepoRef } : {}),
|
|
798
|
+
}
|
|
799
|
+
: null;
|
|
800
|
+
const stderrLine = firstNonEmptyLine(attempt.proc.stderr);
|
|
801
|
+
const rawExitCode = attempt.proc.exitCode;
|
|
802
|
+
const parsedError = typeof attempt.parsed.errorMessage === "string" ? attempt.parsed.errorMessage.trim() : "";
|
|
803
|
+
const parsedSummary = attempt.parsed.summary.trim() || attempt.parsed.completionSummary?.trim() || "";
|
|
804
|
+
const startupIdle = attempt.stoppedAfterStartupIdle === true;
|
|
805
|
+
const toolLoopIdle = attempt.stoppedAfterToolLoopIdle === true;
|
|
806
|
+
const missingFinalSummary = !parsedError &&
|
|
807
|
+
!startupIdle &&
|
|
808
|
+
!toolLoopIdle &&
|
|
809
|
+
(rawExitCode ?? 0) === 0 &&
|
|
810
|
+
parsedSummary.length === 0;
|
|
811
|
+
const synthesizedExitCode = (parsedError || missingFinalSummary || startupIdle || toolLoopIdle) && (rawExitCode ?? 0) === 0 ? 1 : rawExitCode;
|
|
812
|
+
const fallbackErrorMessage = parsedError ||
|
|
813
|
+
(startupIdle
|
|
814
|
+
? `OpenCode stopped after ${startupIdleTimeoutSec}s without emitting JSON output.`
|
|
815
|
+
: "") ||
|
|
816
|
+
(toolLoopIdle
|
|
817
|
+
? `OpenCode stopped after ${toolLoopIdleTimeoutSec}s without continuing after a Rudder tool-call step.`
|
|
818
|
+
: "") ||
|
|
819
|
+
(missingFinalSummary
|
|
820
|
+
? "OpenCode completed without a final text summary; Rudder requires final text to persist a trustworthy run result."
|
|
821
|
+
: "") ||
|
|
822
|
+
stderrLine ||
|
|
823
|
+
`OpenCode exited with code ${synthesizedExitCode ?? -1}`;
|
|
824
|
+
const modelId = model || null;
|
|
738
825
|
return {
|
|
739
|
-
exitCode:
|
|
826
|
+
exitCode: synthesizedExitCode,
|
|
740
827
|
signal: attempt.proc.signal,
|
|
741
|
-
timedOut:
|
|
742
|
-
errorMessage:
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
? {
|
|
828
|
+
timedOut: false,
|
|
829
|
+
errorMessage: (synthesizedExitCode ?? 0) === 0 ? null : fallbackErrorMessage,
|
|
830
|
+
errorCode: startupIdle ? "opencode_startup_idle" : toolLoopIdle ? "opencode_tool_loop_idle" : null,
|
|
831
|
+
usage: {
|
|
832
|
+
inputTokens: attempt.parsed.usage.inputTokens,
|
|
833
|
+
outputTokens: attempt.parsed.usage.outputTokens,
|
|
834
|
+
cachedInputTokens: attempt.parsed.usage.cachedInputTokens,
|
|
835
|
+
},
|
|
750
836
|
sessionId: resolvedSessionId,
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
const synthesizedExitCode = (parsedError || missingFinalSummary || startupIdle || toolLoopIdle) && (rawExitCode ?? 0) === 0 ? 1 : rawExitCode;
|
|
769
|
-
const fallbackErrorMessage = parsedError ||
|
|
770
|
-
(startupIdle
|
|
771
|
-
? `OpenCode stopped after ${startupIdleTimeoutSec}s without emitting JSON output.`
|
|
772
|
-
: "") ||
|
|
773
|
-
(toolLoopIdle
|
|
774
|
-
? `OpenCode stopped after ${toolLoopIdleTimeoutSec}s without continuing after a Rudder tool-call step.`
|
|
775
|
-
: "") ||
|
|
776
|
-
(missingFinalSummary
|
|
777
|
-
? "OpenCode completed without a final text summary; Rudder requires final text to persist a trustworthy run result."
|
|
778
|
-
: "") ||
|
|
779
|
-
stderrLine ||
|
|
780
|
-
`OpenCode exited with code ${synthesizedExitCode ?? -1}`;
|
|
781
|
-
const modelId = model || null;
|
|
782
|
-
return {
|
|
783
|
-
exitCode: synthesizedExitCode,
|
|
784
|
-
signal: attempt.proc.signal,
|
|
785
|
-
timedOut: false,
|
|
786
|
-
errorMessage: (synthesizedExitCode ?? 0) === 0 ? null : fallbackErrorMessage,
|
|
787
|
-
errorCode: startupIdle ? "opencode_startup_idle" : toolLoopIdle ? "opencode_tool_loop_idle" : null,
|
|
788
|
-
usage: {
|
|
789
|
-
inputTokens: attempt.parsed.usage.inputTokens,
|
|
790
|
-
outputTokens: attempt.parsed.usage.outputTokens,
|
|
791
|
-
cachedInputTokens: attempt.parsed.usage.cachedInputTokens,
|
|
792
|
-
},
|
|
793
|
-
sessionId: resolvedSessionId,
|
|
794
|
-
sessionParams: resolvedSessionParams,
|
|
795
|
-
sessionDisplayId: resolvedSessionId,
|
|
796
|
-
provider: parseModelProvider(modelId),
|
|
797
|
-
biller: resolveOpenCodeBiller(runtimeEnv, parseModelProvider(modelId)),
|
|
798
|
-
model: modelId,
|
|
799
|
-
billingType: "unknown",
|
|
800
|
-
costUsd: attempt.parsed.costUsd,
|
|
801
|
-
resultJson: {
|
|
802
|
-
stdout: attempt.proc.stdout,
|
|
803
|
-
stderr: attempt.proc.stderr,
|
|
804
|
-
...(missingFinalSummary ? { summaryStatus: "missing_final_text" } : {}),
|
|
805
|
-
...(startupIdle ? { summaryStatus: "startup_idle", stoppedAfterStartupIdle: true } : {}),
|
|
806
|
-
...(toolLoopIdle ? { summaryStatus: "tool_loop_idle", stoppedAfterToolLoopIdle: true } : {}),
|
|
807
|
-
},
|
|
808
|
-
summary: parsedSummary || ((synthesizedExitCode ?? 0) === 0 ? "" : fallbackErrorMessage),
|
|
809
|
-
clearSession: Boolean(clearSessionOnMissingSession && !attempt.parsed.sessionId),
|
|
837
|
+
sessionParams: resolvedSessionParams,
|
|
838
|
+
sessionDisplayId: resolvedSessionId,
|
|
839
|
+
provider: parseModelProvider(modelId),
|
|
840
|
+
biller: resolveOpenCodeBiller(runtimeEnv, parseModelProvider(modelId)),
|
|
841
|
+
model: modelId,
|
|
842
|
+
billingType: "unknown",
|
|
843
|
+
costUsd: attempt.parsed.costUsd,
|
|
844
|
+
resultJson: {
|
|
845
|
+
stdout: attempt.proc.stdout,
|
|
846
|
+
stderr: attempt.proc.stderr,
|
|
847
|
+
...(missingFinalSummary ? { summaryStatus: "missing_final_text" } : {}),
|
|
848
|
+
...(startupIdle ? { summaryStatus: "startup_idle", stoppedAfterStartupIdle: true } : {}),
|
|
849
|
+
...(toolLoopIdle ? { summaryStatus: "tool_loop_idle", stoppedAfterToolLoopIdle: true } : {}),
|
|
850
|
+
},
|
|
851
|
+
summary: parsedSummary || ((synthesizedExitCode ?? 0) === 0 ? "" : fallbackErrorMessage),
|
|
852
|
+
clearSession: Boolean(clearSessionOnMissingSession && !attempt.parsed.sessionId),
|
|
853
|
+
};
|
|
810
854
|
};
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
855
|
+
const initial = await runAttempt(sessionId);
|
|
856
|
+
const initialFailed = !initial.proc.timedOut && ((initial.proc.exitCode ?? 0) !== 0 || Boolean(initial.parsed.errorMessage));
|
|
857
|
+
if (sessionId &&
|
|
858
|
+
initialFailed &&
|
|
859
|
+
isOpenCodeUnknownSessionError(initial.proc.stdout, initial.rawStderr)) {
|
|
860
|
+
await onLog("stdout", `[rudder] OpenCode session "${sessionId}" is unavailable; retrying with a fresh session.\n`);
|
|
861
|
+
const retry = await runAttempt(null);
|
|
862
|
+
return toResult(retry, true);
|
|
863
|
+
}
|
|
864
|
+
return toResult(initial);
|
|
865
|
+
}
|
|
866
|
+
finally {
|
|
867
|
+
await fs.rm(runtimeTmpDir, { recursive: true, force: true });
|
|
820
868
|
}
|
|
821
|
-
return toResult(initial);
|
|
822
869
|
}
|
|
823
870
|
//# sourceMappingURL=execute.js.map
|