juno-code 1.0.50 → 1.0.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -8
- package/dist/bin/cli.js +3103 -1356
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/cli.mjs +3082 -1335
- package/dist/bin/cli.mjs.map +1 -1
- package/dist/index.d.mts +26 -12
- package/dist/index.d.ts +26 -12
- package/dist/index.js +407 -67
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +405 -65
- package/dist/index.mjs.map +1 -1
- package/dist/templates/scripts/__pycache__/parallel_runner.cpython-313.pyc +0 -0
- package/dist/templates/scripts/install_requirements.sh +35 -2
- package/dist/templates/scripts/kanban.sh +11 -0
- package/dist/templates/scripts/parallel_runner.sh +602 -131
- package/dist/templates/services/README.md +23 -4
- package/dist/templates/services/__pycache__/pi.cpython-313.pyc +0 -0
- package/dist/templates/services/__pycache__/pi.cpython-38.pyc +0 -0
- package/dist/templates/services/pi.py +1034 -39
- package/dist/templates/skills/claude/ralph-loop/scripts/kanban.sh +11 -0
- package/dist/templates/skills/codex/ralph-loop/scripts/kanban.sh +11 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import * as childProcess from 'child_process';
|
|
3
4
|
import { spawn } from 'child_process';
|
|
4
5
|
import * as fs3 from 'fs';
|
|
5
6
|
import { promises } from 'fs';
|
|
@@ -11,6 +12,7 @@ import { z } from 'zod';
|
|
|
11
12
|
import * as yaml from 'js-yaml';
|
|
12
13
|
import { EventEmitter } from 'events';
|
|
13
14
|
import { execa } from 'execa';
|
|
15
|
+
import { promisify } from 'util';
|
|
14
16
|
import { v4 } from 'uuid';
|
|
15
17
|
import * as process2 from 'process';
|
|
16
18
|
|
|
@@ -34,7 +36,7 @@ var __export = (target, all) => {
|
|
|
34
36
|
var version;
|
|
35
37
|
var init_version = __esm({
|
|
36
38
|
"src/version.ts"() {
|
|
37
|
-
version = "1.0.
|
|
39
|
+
version = "1.0.53";
|
|
38
40
|
}
|
|
39
41
|
});
|
|
40
42
|
|
|
@@ -722,25 +724,28 @@ var init_shell_backend = __esm({
|
|
|
722
724
|
const scriptPath = await this.findScriptForSubagent(subagentType);
|
|
723
725
|
const result = await this.executeScript(scriptPath, request, toolId, subagentType);
|
|
724
726
|
const duration = Date.now() - startTime;
|
|
727
|
+
const structuredResult = this.buildStructuredOutput(subagentType, result);
|
|
728
|
+
const structuredPayload = this.parseStructuredResultPayload(structuredResult.content);
|
|
729
|
+
const structuredIndicatesError = structuredPayload?.is_error === true || structuredPayload?.subtype === "error";
|
|
730
|
+
const executionSucceeded = result.success && !structuredIndicatesError;
|
|
725
731
|
await this.emitProgressEvent({
|
|
726
732
|
sessionId: request.metadata?.sessionId || "unknown",
|
|
727
733
|
timestamp: /* @__PURE__ */ new Date(),
|
|
728
734
|
backend: "shell",
|
|
729
735
|
count: ++this.eventCounter,
|
|
730
736
|
type: "tool_result" /* TOOL_RESULT */,
|
|
731
|
-
content: `${request.toolName} completed successfully (${duration}ms)`,
|
|
737
|
+
content: executionSucceeded ? `${request.toolName} completed successfully (${duration}ms)` : `${request.toolName} completed with error (${duration}ms)`,
|
|
732
738
|
toolId,
|
|
733
739
|
metadata: {
|
|
734
740
|
toolName: request.toolName,
|
|
735
741
|
duration,
|
|
736
|
-
success:
|
|
742
|
+
success: executionSucceeded,
|
|
737
743
|
phase: "completion"
|
|
738
744
|
}
|
|
739
745
|
});
|
|
740
|
-
const structuredResult = this.buildStructuredOutput(subagentType, result);
|
|
741
746
|
const toolResult = {
|
|
742
747
|
content: structuredResult.content,
|
|
743
|
-
status:
|
|
748
|
+
status: executionSucceeded ? "completed" /* COMPLETED */ : "failed" /* FAILED */,
|
|
744
749
|
startTime: new Date(startTime),
|
|
745
750
|
endTime: /* @__PURE__ */ new Date(),
|
|
746
751
|
duration,
|
|
@@ -749,6 +754,9 @@ var init_shell_backend = __esm({
|
|
|
749
754
|
};
|
|
750
755
|
if (result.error) {
|
|
751
756
|
toolResult.error = new Error(result.error);
|
|
757
|
+
} else if (!executionSucceeded) {
|
|
758
|
+
const structuredErrorMessage = typeof structuredPayload?.error === "string" && structuredPayload.error || typeof structuredPayload?.result === "string" && structuredPayload.result || `${request.toolName} reported a structured error`;
|
|
759
|
+
toolResult.error = new Error(structuredErrorMessage);
|
|
752
760
|
}
|
|
753
761
|
if (structuredResult.metadata) {
|
|
754
762
|
toolResult.metadata = structuredResult.metadata;
|
|
@@ -997,6 +1005,12 @@ var init_shell_backend = __esm({
|
|
|
997
1005
|
if (isPython && subagentType === "pi" && request.arguments?.project_path) {
|
|
998
1006
|
args.push("--cd", String(request.arguments.project_path));
|
|
999
1007
|
}
|
|
1008
|
+
if (isPython && subagentType === "pi" && request.arguments?.live === true) {
|
|
1009
|
+
args.push("--live");
|
|
1010
|
+
}
|
|
1011
|
+
if (isPython && subagentType === "pi" && request.arguments?.liveInteractiveSession === true) {
|
|
1012
|
+
args.push("--live-manual");
|
|
1013
|
+
}
|
|
1000
1014
|
if (isPython && this.config.debug) {
|
|
1001
1015
|
args.push("--verbose");
|
|
1002
1016
|
}
|
|
@@ -1014,12 +1028,19 @@ var init_shell_backend = __esm({
|
|
|
1014
1028
|
`Environment variables: ${Object.keys(env2).filter((k) => k.startsWith("JUNO_") || k.startsWith("PI_")).join(", ")}`
|
|
1015
1029
|
);
|
|
1016
1030
|
}
|
|
1031
|
+
const isPiLiveMode = isPython && subagentType === "pi" && request.arguments?.live === true;
|
|
1032
|
+
const shouldAttachLiveTerminal = isPiLiveMode && process.stdout.isTTY === true;
|
|
1033
|
+
if (this.config.debug && isPiLiveMode) {
|
|
1034
|
+
engineLogger.debug(
|
|
1035
|
+
`Pi live mode stdio: ${shouldAttachLiveTerminal ? "inherit (interactive TTY or stdout-tty fallback)" : "pipe (headless/non-TTY)"}`
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1017
1038
|
const child = spawn(command, args, {
|
|
1018
1039
|
env: env2,
|
|
1019
1040
|
cwd: this.config.workingDirectory,
|
|
1020
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1041
|
+
stdio: shouldAttachLiveTerminal ? "inherit" : ["pipe", "pipe", "pipe"]
|
|
1021
1042
|
});
|
|
1022
|
-
if (child.stdin) {
|
|
1043
|
+
if (!shouldAttachLiveTerminal && child.stdin) {
|
|
1023
1044
|
child.stdin.end();
|
|
1024
1045
|
}
|
|
1025
1046
|
let stdout2 = "";
|
|
@@ -1272,8 +1293,19 @@ var init_shell_backend = __esm({
|
|
|
1272
1293
|
if (subagentType === "pi") {
|
|
1273
1294
|
const piEvent = result.subAgentResponse ?? this.extractLastJsonEvent(result.output);
|
|
1274
1295
|
if (piEvent) {
|
|
1275
|
-
|
|
1276
|
-
|
|
1296
|
+
const piNestedEvent = typeof piEvent.sub_agent_response === "object" && piEvent.sub_agent_response ? piEvent.sub_agent_response : void 0;
|
|
1297
|
+
const piSessionId = typeof piEvent.session_id === "string" && piEvent.session_id ? piEvent.session_id : typeof piEvent.sessionId === "string" && piEvent.sessionId ? piEvent.sessionId : typeof piEvent.id === "string" && piEvent.type === "session" ? piEvent.id : typeof piNestedEvent?.session_id === "string" && piNestedEvent.session_id ? piNestedEvent.session_id : typeof piNestedEvent?.sessionId === "string" && piNestedEvent.sessionId ? piNestedEvent.sessionId : typeof piNestedEvent?.id === "string" && piNestedEvent.type === "session" ? piNestedEvent.id : typeof piEvent.sub_agent_response?.session_id === "string" && piEvent.sub_agent_response.session_id ? piEvent.sub_agent_response.session_id : void 0;
|
|
1298
|
+
const sanitizedPiEvent = { ...piEvent };
|
|
1299
|
+
delete sanitizedPiEvent.messages;
|
|
1300
|
+
if (sanitizedPiEvent.sub_agent_response && typeof sanitizedPiEvent.sub_agent_response === "object") {
|
|
1301
|
+
const inner = { ...sanitizedPiEvent.sub_agent_response };
|
|
1302
|
+
delete inner.messages;
|
|
1303
|
+
delete inner.type;
|
|
1304
|
+
sanitizedPiEvent.sub_agent_response = inner;
|
|
1305
|
+
}
|
|
1306
|
+
const hasDirectResultText = typeof piEvent.result === "string";
|
|
1307
|
+
let resultText = hasDirectResultText ? piEvent.result : void 0;
|
|
1308
|
+
if (resultText === void 0 && Array.isArray(piEvent.messages)) {
|
|
1277
1309
|
for (let i = piEvent.messages.length - 1; i >= 0; i--) {
|
|
1278
1310
|
const msg = piEvent.messages[i];
|
|
1279
1311
|
if (msg?.role === "assistant") {
|
|
@@ -1293,16 +1325,11 @@ var init_shell_backend = __esm({
|
|
|
1293
1325
|
}
|
|
1294
1326
|
}
|
|
1295
1327
|
}
|
|
1296
|
-
if (resultText) {
|
|
1328
|
+
if (resultText === void 0 && typeof piEvent.error === "string") {
|
|
1329
|
+
resultText = piEvent.error;
|
|
1330
|
+
}
|
|
1331
|
+
if (resultText !== void 0) {
|
|
1297
1332
|
const isError = piEvent.is_error ?? !result.success;
|
|
1298
|
-
const sanitizedPiEvent = { ...piEvent };
|
|
1299
|
-
delete sanitizedPiEvent.messages;
|
|
1300
|
-
if (sanitizedPiEvent.sub_agent_response && typeof sanitizedPiEvent.sub_agent_response === "object") {
|
|
1301
|
-
const inner = { ...sanitizedPiEvent.sub_agent_response };
|
|
1302
|
-
delete inner.messages;
|
|
1303
|
-
delete inner.type;
|
|
1304
|
-
sanitizedPiEvent.sub_agent_response = inner;
|
|
1305
|
-
}
|
|
1306
1333
|
const usage = piEvent.usage;
|
|
1307
1334
|
const totalCostUsd = typeof piEvent.total_cost_usd === "number" ? piEvent.total_cost_usd : typeof usage?.cost?.total === "number" ? usage.cost.total : void 0;
|
|
1308
1335
|
const structuredPayload = {
|
|
@@ -1310,9 +1337,9 @@ var init_shell_backend = __esm({
|
|
|
1310
1337
|
subtype: piEvent.subtype || (isError ? "error" : "success"),
|
|
1311
1338
|
is_error: isError,
|
|
1312
1339
|
result: resultText,
|
|
1313
|
-
error: piEvent.error,
|
|
1340
|
+
error: isError ? piEvent.error ?? result.error ?? resultText : piEvent.error,
|
|
1314
1341
|
stderr: result.error,
|
|
1315
|
-
session_id:
|
|
1342
|
+
session_id: piSessionId,
|
|
1316
1343
|
exit_code: result.exitCode,
|
|
1317
1344
|
duration_ms: piEvent.duration_ms ?? result.duration,
|
|
1318
1345
|
total_cost_usd: totalCostUsd,
|
|
@@ -1330,6 +1357,57 @@ var init_shell_backend = __esm({
|
|
|
1330
1357
|
metadata
|
|
1331
1358
|
};
|
|
1332
1359
|
}
|
|
1360
|
+
const isSessionSnapshotOnly = piEvent.type === "session" || piEvent.subtype === "session";
|
|
1361
|
+
if (isSessionSnapshotOnly) {
|
|
1362
|
+
const errorMessage = result.error?.trim() || "Pi exited before emitting a terminal result event (session snapshot only).";
|
|
1363
|
+
const structuredPayload = {
|
|
1364
|
+
type: "result",
|
|
1365
|
+
subtype: "error",
|
|
1366
|
+
is_error: true,
|
|
1367
|
+
result: errorMessage,
|
|
1368
|
+
error: errorMessage,
|
|
1369
|
+
stderr: result.error,
|
|
1370
|
+
session_id: piSessionId,
|
|
1371
|
+
exit_code: result.exitCode,
|
|
1372
|
+
duration_ms: result.duration,
|
|
1373
|
+
sub_agent_response: sanitizedPiEvent
|
|
1374
|
+
};
|
|
1375
|
+
const metadata = {
|
|
1376
|
+
...piEvent ? { subAgentResponse: piEvent } : void 0,
|
|
1377
|
+
structuredOutput: true,
|
|
1378
|
+
contentType: "application/json",
|
|
1379
|
+
rawOutput: result.output
|
|
1380
|
+
};
|
|
1381
|
+
return {
|
|
1382
|
+
content: JSON.stringify(structuredPayload),
|
|
1383
|
+
metadata
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
if (!result.success) {
|
|
1387
|
+
const errorMessage = result.error?.trim() || result.output?.trim() || "Unknown error";
|
|
1388
|
+
const structuredPayload = {
|
|
1389
|
+
type: "result",
|
|
1390
|
+
subtype: "error",
|
|
1391
|
+
is_error: true,
|
|
1392
|
+
result: errorMessage,
|
|
1393
|
+
error: errorMessage,
|
|
1394
|
+
stderr: result.error,
|
|
1395
|
+
session_id: piSessionId,
|
|
1396
|
+
exit_code: result.exitCode,
|
|
1397
|
+
duration_ms: result.duration,
|
|
1398
|
+
sub_agent_response: sanitizedPiEvent
|
|
1399
|
+
};
|
|
1400
|
+
const metadata = {
|
|
1401
|
+
...piEvent ? { subAgentResponse: piEvent } : void 0,
|
|
1402
|
+
structuredOutput: true,
|
|
1403
|
+
contentType: "application/json",
|
|
1404
|
+
rawOutput: result.output
|
|
1405
|
+
};
|
|
1406
|
+
return {
|
|
1407
|
+
content: JSON.stringify(structuredPayload),
|
|
1408
|
+
metadata
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1333
1411
|
}
|
|
1334
1412
|
}
|
|
1335
1413
|
if (!result.success) {
|
|
@@ -1394,6 +1472,20 @@ var init_shell_backend = __esm({
|
|
|
1394
1472
|
}
|
|
1395
1473
|
return null;
|
|
1396
1474
|
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Parse JSON structured output payload emitted by shell service wrappers.
|
|
1477
|
+
*/
|
|
1478
|
+
parseStructuredResultPayload(content) {
|
|
1479
|
+
try {
|
|
1480
|
+
const parsed = JSON.parse(content);
|
|
1481
|
+
if (!parsed || typeof parsed !== "object") {
|
|
1482
|
+
return null;
|
|
1483
|
+
}
|
|
1484
|
+
return parsed;
|
|
1485
|
+
} catch {
|
|
1486
|
+
return null;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1397
1489
|
/**
|
|
1398
1490
|
* Extract the last valid JSON object from a script's stdout to use as a structured payload fallback.
|
|
1399
1491
|
*/
|
|
@@ -1707,6 +1799,41 @@ function getDefaultHooksJson(indent = 2) {
|
|
|
1707
1799
|
return JSON.stringify(DEFAULT_HOOKS, null, indent);
|
|
1708
1800
|
}
|
|
1709
1801
|
|
|
1802
|
+
// src/core/subagent-models.ts
|
|
1803
|
+
init_version();
|
|
1804
|
+
var SUBAGENT_DEFAULT_MODELS = {
|
|
1805
|
+
claude: ":sonnet",
|
|
1806
|
+
codex: ":codex",
|
|
1807
|
+
gemini: ":pro",
|
|
1808
|
+
cursor: "auto",
|
|
1809
|
+
pi: ":pi"
|
|
1810
|
+
};
|
|
1811
|
+
function isModelCompatibleWithSubagent(model, subagent) {
|
|
1812
|
+
if (!model.startsWith(":")) {
|
|
1813
|
+
return true;
|
|
1814
|
+
}
|
|
1815
|
+
const claudeShorthands = [":sonnet", ":haiku", ":opus"];
|
|
1816
|
+
const codexShorthands = [":codex", ":codex-mini", ":gpt-5", ":mini"];
|
|
1817
|
+
const geminiShorthands = [":pro", ":flash"];
|
|
1818
|
+
const isClaudeModel = claudeShorthands.includes(model) || model.startsWith(":claude");
|
|
1819
|
+
const isCodexModel = codexShorthands.includes(model) || model.startsWith(":gpt");
|
|
1820
|
+
const isGeminiModel = geminiShorthands.includes(model) || model.startsWith(":gemini");
|
|
1821
|
+
switch (subagent) {
|
|
1822
|
+
case "claude":
|
|
1823
|
+
return isClaudeModel || !isCodexModel && !isGeminiModel;
|
|
1824
|
+
case "codex":
|
|
1825
|
+
return isCodexModel || !isClaudeModel && !isGeminiModel;
|
|
1826
|
+
case "gemini":
|
|
1827
|
+
return isGeminiModel || !isClaudeModel && !isCodexModel;
|
|
1828
|
+
case "cursor":
|
|
1829
|
+
return true;
|
|
1830
|
+
case "pi":
|
|
1831
|
+
return true;
|
|
1832
|
+
default:
|
|
1833
|
+
return true;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1710
1837
|
// src/core/config.ts
|
|
1711
1838
|
var ENV_VAR_MAPPING = {
|
|
1712
1839
|
// Core settings
|
|
@@ -1756,6 +1883,7 @@ var JunoTaskConfigSchema = z.object({
|
|
|
1756
1883
|
defaultBackend: BackendTypeSchema.describe("Default backend to use for task execution"),
|
|
1757
1884
|
defaultMaxIterations: z.number().int().min(1).max(1e3).describe("Default maximum number of iterations for task execution"),
|
|
1758
1885
|
defaultModel: z.string().optional().describe("Default model to use for the subagent"),
|
|
1886
|
+
defaultModels: z.record(SubagentTypeSchema, z.string()).optional().describe("Optional per-subagent default model overrides"),
|
|
1759
1887
|
// Project metadata
|
|
1760
1888
|
mainTask: z.string().optional().describe("Main task objective for the project"),
|
|
1761
1889
|
// Logging settings
|
|
@@ -1811,6 +1939,7 @@ var DEFAULT_CONFIG = {
|
|
|
1811
1939
|
defaultSubagent: "claude",
|
|
1812
1940
|
defaultBackend: "shell",
|
|
1813
1941
|
defaultMaxIterations: 1,
|
|
1942
|
+
defaultModels: { ...SUBAGENT_DEFAULT_MODELS },
|
|
1814
1943
|
// Logging settings
|
|
1815
1944
|
logLevel: "info",
|
|
1816
1945
|
verbose: 1,
|
|
@@ -2126,7 +2255,7 @@ function parseEnvFileContent(content) {
|
|
|
2126
2255
|
const quote = value[0];
|
|
2127
2256
|
value = value.slice(1, -1);
|
|
2128
2257
|
if (quote === '"') {
|
|
2129
|
-
value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ");
|
|
2258
|
+
value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
2130
2259
|
}
|
|
2131
2260
|
} else {
|
|
2132
2261
|
const inlineCommentIndex = value.indexOf(" #");
|
|
@@ -2215,13 +2344,23 @@ async function ensureHooksConfig(baseDir) {
|
|
|
2215
2344
|
}
|
|
2216
2345
|
if (!existingConfig.defaultModel) {
|
|
2217
2346
|
const subagent = existingConfig.defaultSubagent || "claude";
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
existingConfig.defaultModel
|
|
2347
|
+
existingConfig.defaultModel = SUBAGENT_DEFAULT_MODELS[subagent] || SUBAGENT_DEFAULT_MODELS.claude;
|
|
2348
|
+
needsUpdate = true;
|
|
2349
|
+
}
|
|
2350
|
+
if (!existingConfig.defaultModels || typeof existingConfig.defaultModels !== "object" || Array.isArray(existingConfig.defaultModels)) {
|
|
2351
|
+
const baseDefaults = { ...SUBAGENT_DEFAULT_MODELS };
|
|
2352
|
+
const subagent = existingConfig.defaultSubagent || "claude";
|
|
2353
|
+
if (typeof existingConfig.defaultModel === "string") {
|
|
2354
|
+
baseDefaults[subagent] = existingConfig.defaultModel;
|
|
2355
|
+
}
|
|
2356
|
+
existingConfig.defaultModels = baseDefaults;
|
|
2357
|
+
needsUpdate = true;
|
|
2358
|
+
}
|
|
2359
|
+
const selectedSubagentRaw = typeof existingConfig.defaultSubagent === "string" ? existingConfig.defaultSubagent : "claude";
|
|
2360
|
+
const selectedSubagent = selectedSubagentRaw in SUBAGENT_DEFAULT_MODELS ? selectedSubagentRaw : "claude";
|
|
2361
|
+
const selectedMapModel = existingConfig.defaultModels && typeof existingConfig.defaultModels === "object" ? existingConfig.defaultModels[selectedSubagent] : void 0;
|
|
2362
|
+
if (typeof selectedMapModel === "string" && isModelCompatibleWithSubagent(selectedMapModel, selectedSubagent) && existingConfig.defaultModel !== selectedMapModel) {
|
|
2363
|
+
existingConfig.defaultModel = selectedMapModel;
|
|
2225
2364
|
needsUpdate = true;
|
|
2226
2365
|
}
|
|
2227
2366
|
if (existingConfig.defaultMaxIterations === 50) {
|
|
@@ -2528,6 +2667,178 @@ function validateHooksConfig(hooks) {
|
|
|
2528
2667
|
// src/core/engine.ts
|
|
2529
2668
|
init_advanced_logger();
|
|
2530
2669
|
init_shell_backend();
|
|
2670
|
+
|
|
2671
|
+
// src/core/prompt-command-substitution.ts
|
|
2672
|
+
init_version();
|
|
2673
|
+
var SINGLE_QUOTED_MARKER = "!'";
|
|
2674
|
+
var TRIPLE_BACKTICK_MARKER = "!```";
|
|
2675
|
+
var TRIPLE_BACKTICK_CLOSER = "```";
|
|
2676
|
+
var DEFAULT_MAX_BUFFER_BYTES = 1024 * 1024;
|
|
2677
|
+
var DEFAULT_COMMAND_TIMEOUT_MS = 3e4;
|
|
2678
|
+
var COMMAND_TIMEOUT_ENV_KEY = "JUNO_CODE_PROMPT_SUBSTITUTION_TIMEOUT_MS";
|
|
2679
|
+
function findPromptCommandSubstitutions(prompt) {
|
|
2680
|
+
const matches = [];
|
|
2681
|
+
let cursor = 0;
|
|
2682
|
+
while (cursor < prompt.length) {
|
|
2683
|
+
const singleQuotedStart = prompt.indexOf(SINGLE_QUOTED_MARKER, cursor);
|
|
2684
|
+
const tripleBacktickStart = prompt.indexOf(TRIPLE_BACKTICK_MARKER, cursor);
|
|
2685
|
+
const markerStart = chooseNearestMarker(singleQuotedStart, tripleBacktickStart);
|
|
2686
|
+
if (markerStart === null) {
|
|
2687
|
+
break;
|
|
2688
|
+
}
|
|
2689
|
+
if (markerStart === singleQuotedStart) {
|
|
2690
|
+
const parsedSingleQuoted = parseSingleQuotedSubstitution(prompt, markerStart);
|
|
2691
|
+
if (!parsedSingleQuoted) {
|
|
2692
|
+
cursor = markerStart + SINGLE_QUOTED_MARKER.length;
|
|
2693
|
+
continue;
|
|
2694
|
+
}
|
|
2695
|
+
matches.push(parsedSingleQuoted);
|
|
2696
|
+
cursor = parsedSingleQuoted.endIndex;
|
|
2697
|
+
continue;
|
|
2698
|
+
}
|
|
2699
|
+
const parsedTripleBacktick = parseTripleBacktickSubstitution(prompt, markerStart);
|
|
2700
|
+
if (!parsedTripleBacktick) {
|
|
2701
|
+
cursor = markerStart + TRIPLE_BACKTICK_MARKER.length;
|
|
2702
|
+
continue;
|
|
2703
|
+
}
|
|
2704
|
+
matches.push(parsedTripleBacktick);
|
|
2705
|
+
cursor = parsedTripleBacktick.endIndex;
|
|
2706
|
+
}
|
|
2707
|
+
return matches;
|
|
2708
|
+
}
|
|
2709
|
+
async function resolvePromptCommandSubstitutions(prompt, options) {
|
|
2710
|
+
const matches = findPromptCommandSubstitutions(prompt);
|
|
2711
|
+
if (matches.length === 0) {
|
|
2712
|
+
return prompt;
|
|
2713
|
+
}
|
|
2714
|
+
const executor = options.executor ?? createDefaultPromptCommandExecutor(options);
|
|
2715
|
+
let result = "";
|
|
2716
|
+
let cursor = 0;
|
|
2717
|
+
for (const match of matches) {
|
|
2718
|
+
result += prompt.slice(cursor, match.startIndex);
|
|
2719
|
+
const commandOutput = await executor(match.command);
|
|
2720
|
+
result += normalizeCommandOutput(commandOutput);
|
|
2721
|
+
cursor = match.endIndex;
|
|
2722
|
+
}
|
|
2723
|
+
result += prompt.slice(cursor);
|
|
2724
|
+
return result;
|
|
2725
|
+
}
|
|
2726
|
+
function chooseNearestMarker(singleQuotedStart, tripleBacktickStart) {
|
|
2727
|
+
const singleExists = singleQuotedStart >= 0;
|
|
2728
|
+
const tripleExists = tripleBacktickStart >= 0;
|
|
2729
|
+
if (!singleExists && !tripleExists) {
|
|
2730
|
+
return null;
|
|
2731
|
+
}
|
|
2732
|
+
if (!singleExists) {
|
|
2733
|
+
return tripleBacktickStart;
|
|
2734
|
+
}
|
|
2735
|
+
if (!tripleExists) {
|
|
2736
|
+
return singleQuotedStart;
|
|
2737
|
+
}
|
|
2738
|
+
return Math.min(singleQuotedStart, tripleBacktickStart);
|
|
2739
|
+
}
|
|
2740
|
+
function parseSingleQuotedSubstitution(prompt, markerStart) {
|
|
2741
|
+
const contentStart = markerStart + SINGLE_QUOTED_MARKER.length;
|
|
2742
|
+
const closingQuote = findClosingSingleQuote(prompt, contentStart);
|
|
2743
|
+
if (closingQuote < 0) {
|
|
2744
|
+
return null;
|
|
2745
|
+
}
|
|
2746
|
+
const raw = prompt.slice(markerStart, closingQuote + 1);
|
|
2747
|
+
const command = prompt.slice(contentStart, closingQuote);
|
|
2748
|
+
return {
|
|
2749
|
+
syntax: "single-quoted",
|
|
2750
|
+
startIndex: markerStart,
|
|
2751
|
+
endIndex: closingQuote + 1,
|
|
2752
|
+
command,
|
|
2753
|
+
raw
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
function findClosingSingleQuote(prompt, startIndex) {
|
|
2757
|
+
let escaped = false;
|
|
2758
|
+
for (let index = startIndex; index < prompt.length; index++) {
|
|
2759
|
+
const char = prompt[index];
|
|
2760
|
+
if (char === "'" && !escaped) {
|
|
2761
|
+
return index;
|
|
2762
|
+
}
|
|
2763
|
+
if (char === "\\" && !escaped) {
|
|
2764
|
+
escaped = true;
|
|
2765
|
+
continue;
|
|
2766
|
+
}
|
|
2767
|
+
escaped = false;
|
|
2768
|
+
}
|
|
2769
|
+
return -1;
|
|
2770
|
+
}
|
|
2771
|
+
function parseTripleBacktickSubstitution(prompt, markerStart) {
|
|
2772
|
+
const contentStart = markerStart + TRIPLE_BACKTICK_MARKER.length;
|
|
2773
|
+
const closingBackticks = prompt.indexOf(TRIPLE_BACKTICK_CLOSER, contentStart);
|
|
2774
|
+
if (closingBackticks < 0) {
|
|
2775
|
+
return null;
|
|
2776
|
+
}
|
|
2777
|
+
const raw = prompt.slice(markerStart, closingBackticks + TRIPLE_BACKTICK_CLOSER.length);
|
|
2778
|
+
const command = prompt.slice(contentStart, closingBackticks);
|
|
2779
|
+
return {
|
|
2780
|
+
syntax: "triple-backtick",
|
|
2781
|
+
startIndex: markerStart,
|
|
2782
|
+
endIndex: closingBackticks + TRIPLE_BACKTICK_CLOSER.length,
|
|
2783
|
+
command,
|
|
2784
|
+
raw
|
|
2785
|
+
};
|
|
2786
|
+
}
|
|
2787
|
+
function createDefaultPromptCommandExecutor(options) {
|
|
2788
|
+
const execFile2 = promisify(childProcess.execFile);
|
|
2789
|
+
const maxBufferBytes = options.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;
|
|
2790
|
+
const commandTimeoutMs = resolvePromptCommandTimeoutMs(options.commandTimeoutMs);
|
|
2791
|
+
const shell = process.env.SHELL || "/bin/bash";
|
|
2792
|
+
return async (command) => {
|
|
2793
|
+
const normalizedCommand = command.trim();
|
|
2794
|
+
if (!normalizedCommand) {
|
|
2795
|
+
return "";
|
|
2796
|
+
}
|
|
2797
|
+
const commandForExecution = wrapCommandForNonInteractiveExecution(normalizedCommand);
|
|
2798
|
+
try {
|
|
2799
|
+
const result = await execFile2(shell, ["-lc", commandForExecution], {
|
|
2800
|
+
cwd: options.workingDirectory,
|
|
2801
|
+
env: options.environment ?? process.env,
|
|
2802
|
+
maxBuffer: maxBufferBytes,
|
|
2803
|
+
timeout: commandTimeoutMs
|
|
2804
|
+
});
|
|
2805
|
+
const stdout2 = typeof result === "string" || Buffer.isBuffer(result) ? String(result) : String(result.stdout ?? "");
|
|
2806
|
+
return stdout2;
|
|
2807
|
+
} catch (error) {
|
|
2808
|
+
const failedCommand = normalizedCommand.replace(/\s+/g, " ").trim();
|
|
2809
|
+
const details = error && typeof error === "object" && "stderr" in error ? String(error.stderr ?? "").trim() : "";
|
|
2810
|
+
const timeoutDetected = error && typeof error === "object" && (("code" in error ? String(error.code ?? "").toUpperCase() === "ETIMEDOUT" : false) || "killed" in error && Boolean(error.killed) && String(error.signal ?? "").toUpperCase() === "SIGTERM" || "message" in error && /timed?\s*out/i.test(String(error.message ?? "")));
|
|
2811
|
+
if (timeoutDetected) {
|
|
2812
|
+
throw new Error(
|
|
2813
|
+
`Prompt command substitution timed out after ${commandTimeoutMs}ms for \`${failedCommand}\``
|
|
2814
|
+
);
|
|
2815
|
+
}
|
|
2816
|
+
const suffix = details ? `: ${details}` : "";
|
|
2817
|
+
throw new Error(`Prompt command substitution failed for \`${failedCommand}\`${suffix}`);
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
function resolvePromptCommandTimeoutMs(explicitTimeoutMs) {
|
|
2822
|
+
if (typeof explicitTimeoutMs === "number" && Number.isFinite(explicitTimeoutMs) && explicitTimeoutMs > 0) {
|
|
2823
|
+
return explicitTimeoutMs;
|
|
2824
|
+
}
|
|
2825
|
+
const envValue = process.env[COMMAND_TIMEOUT_ENV_KEY];
|
|
2826
|
+
if (envValue !== void 0) {
|
|
2827
|
+
const parsed = Number(envValue);
|
|
2828
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
2829
|
+
return parsed;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
return DEFAULT_COMMAND_TIMEOUT_MS;
|
|
2833
|
+
}
|
|
2834
|
+
function wrapCommandForNonInteractiveExecution(command) {
|
|
2835
|
+
return `(${command}) </dev/null`;
|
|
2836
|
+
}
|
|
2837
|
+
function normalizeCommandOutput(output) {
|
|
2838
|
+
return output.replace(/\r?\n$/, "");
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// src/core/engine.ts
|
|
2531
2842
|
var ExecutionStatus = /* @__PURE__ */ ((ExecutionStatus2) => {
|
|
2532
2843
|
ExecutionStatus2["PENDING"] = "pending";
|
|
2533
2844
|
ExecutionStatus2["RUNNING"] = "running";
|
|
@@ -2834,7 +3145,8 @@ var ExecutionEngine = class extends EventEmitter {
|
|
|
2834
3145
|
if (!request.requestId?.trim()) {
|
|
2835
3146
|
throw new Error("Request ID is required");
|
|
2836
3147
|
}
|
|
2837
|
-
|
|
3148
|
+
const allowEmptyInstructionForPiLiveInteractiveSession = request.subagent === "pi" && request.live === true && request.liveInteractiveSession === true && typeof request.resume === "string" && request.resume.trim().length > 0;
|
|
3149
|
+
if (!request.instruction?.trim() && !allowEmptyInstructionForPiLiveInteractiveSession) {
|
|
2838
3150
|
throw new Error("Instruction is required");
|
|
2839
3151
|
}
|
|
2840
3152
|
if (!request.subagent?.trim()) {
|
|
@@ -3072,43 +3384,62 @@ var ExecutionEngine = class extends EventEmitter {
|
|
|
3072
3384
|
engineLogger.warn("Hook START_ITERATION failed", { error, iterationNumber });
|
|
3073
3385
|
}
|
|
3074
3386
|
this.emit("iteration:start", { context, iterationNumber });
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3387
|
+
let toolRequest = null;
|
|
3388
|
+
try {
|
|
3389
|
+
const instructionTemplate = context.request.instruction;
|
|
3390
|
+
const resolvedInstruction = await resolvePromptCommandSubstitutions(instructionTemplate, {
|
|
3391
|
+
workingDirectory: context.request.workingDirectory,
|
|
3392
|
+
environment: {
|
|
3393
|
+
...process.env,
|
|
3394
|
+
JUNO_TASK_ROOT: process.env.JUNO_TASK_ROOT || context.request.workingDirectory
|
|
3395
|
+
}
|
|
3396
|
+
});
|
|
3397
|
+
this.emit("iteration:instruction-resolved", {
|
|
3398
|
+
context,
|
|
3399
|
+
iterationNumber,
|
|
3400
|
+
instruction: resolvedInstruction,
|
|
3401
|
+
templateInstruction: instructionTemplate
|
|
3402
|
+
});
|
|
3403
|
+
toolRequest = {
|
|
3404
|
+
toolName: this.getToolNameForSubagent(context.request.subagent),
|
|
3405
|
+
arguments: {
|
|
3406
|
+
instruction: resolvedInstruction,
|
|
3407
|
+
project_path: context.request.workingDirectory,
|
|
3408
|
+
...context.request.model !== void 0 && { model: context.request.model },
|
|
3409
|
+
...context.request.agents !== void 0 && { agents: context.request.agents },
|
|
3410
|
+
...context.request.tools !== void 0 && { tools: context.request.tools },
|
|
3411
|
+
...context.request.allowedTools !== void 0 && {
|
|
3412
|
+
allowedTools: context.request.allowedTools
|
|
3413
|
+
},
|
|
3414
|
+
...context.request.appendAllowedTools !== void 0 && {
|
|
3415
|
+
appendAllowedTools: context.request.appendAllowedTools
|
|
3416
|
+
},
|
|
3417
|
+
...context.request.disallowedTools !== void 0 && {
|
|
3418
|
+
disallowedTools: context.request.disallowedTools
|
|
3419
|
+
},
|
|
3420
|
+
...context.request.resume !== void 0 && { resume: context.request.resume },
|
|
3421
|
+
...context.request.continueConversation !== void 0 && {
|
|
3422
|
+
continueConversation: context.request.continueConversation
|
|
3423
|
+
},
|
|
3424
|
+
...context.request.thinking !== void 0 && { thinking: context.request.thinking },
|
|
3425
|
+
...context.request.live !== void 0 && { live: context.request.live },
|
|
3426
|
+
...context.request.liveInteractiveSession !== void 0 && {
|
|
3427
|
+
liveInteractiveSession: context.request.liveInteractiveSession
|
|
3428
|
+
},
|
|
3429
|
+
iteration: iterationNumber
|
|
3091
3430
|
},
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3431
|
+
timeout: context.request.timeoutMs || this.engineConfig.config.mcpTimeout,
|
|
3432
|
+
priority: context.request.priority || "normal",
|
|
3433
|
+
metadata: {
|
|
3434
|
+
sessionId: context.sessionContext.sessionId,
|
|
3435
|
+
iterationNumber
|
|
3095
3436
|
},
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
sessionId: context.sessionContext.sessionId,
|
|
3103
|
-
iterationNumber
|
|
3104
|
-
},
|
|
3105
|
-
progressCallback: async (event) => {
|
|
3106
|
-
context.progressEvents.push(event);
|
|
3107
|
-
context.statistics.totalProgressEvents++;
|
|
3108
|
-
await this.processProgressEvent(context, event);
|
|
3109
|
-
}
|
|
3110
|
-
};
|
|
3111
|
-
try {
|
|
3437
|
+
progressCallback: async (event) => {
|
|
3438
|
+
context.progressEvents.push(event);
|
|
3439
|
+
context.statistics.totalProgressEvents++;
|
|
3440
|
+
await this.processProgressEvent(context, event);
|
|
3441
|
+
}
|
|
3442
|
+
};
|
|
3112
3443
|
if (!this.currentBackend) {
|
|
3113
3444
|
throw new Error("No backend initialized. Call initializeBackend() first.");
|
|
3114
3445
|
}
|
|
@@ -3177,7 +3508,10 @@ var ExecutionEngine = class extends EventEmitter {
|
|
|
3177
3508
|
duration,
|
|
3178
3509
|
error: mcpError,
|
|
3179
3510
|
progressEvents: [],
|
|
3180
|
-
request: toolRequest
|
|
3511
|
+
request: toolRequest ?? {
|
|
3512
|
+
toolName: this.getToolNameForSubagent(context.request.subagent),
|
|
3513
|
+
arguments: {}
|
|
3514
|
+
}
|
|
3181
3515
|
},
|
|
3182
3516
|
progressEvents: [],
|
|
3183
3517
|
error: mcpError
|
|
@@ -3689,6 +4023,12 @@ function createExecutionRequest(options) {
|
|
|
3689
4023
|
if (options.thinking !== void 0) {
|
|
3690
4024
|
result.thinking = options.thinking;
|
|
3691
4025
|
}
|
|
4026
|
+
if (options.live !== void 0) {
|
|
4027
|
+
result.live = options.live;
|
|
4028
|
+
}
|
|
4029
|
+
if (options.liveInteractiveSession !== void 0) {
|
|
4030
|
+
result.liveInteractiveSession = options.liveInteractiveSession;
|
|
4031
|
+
}
|
|
3692
4032
|
return result;
|
|
3693
4033
|
}
|
|
3694
4034
|
|