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/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.50";
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: result.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: result.success ? "completed" /* COMPLETED */ : "failed" /* FAILED */,
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
- let resultText = piEvent.result;
1276
- if (!resultText && Array.isArray(piEvent.messages)) {
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: piEvent.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
- const modelDefaults = {
2219
- claude: ":sonnet",
2220
- codex: ":codex",
2221
- gemini: ":pro",
2222
- cursor: "auto"
2223
- };
2224
- existingConfig.defaultModel = modelDefaults[subagent] || ":sonnet";
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
- if (!request.instruction?.trim()) {
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
- const toolRequest = {
3076
- toolName: this.getToolNameForSubagent(context.request.subagent),
3077
- arguments: {
3078
- instruction: context.request.instruction,
3079
- project_path: context.request.workingDirectory,
3080
- ...context.request.model !== void 0 && { model: context.request.model },
3081
- ...context.request.agents !== void 0 && { agents: context.request.agents },
3082
- ...context.request.tools !== void 0 && { tools: context.request.tools },
3083
- ...context.request.allowedTools !== void 0 && {
3084
- allowedTools: context.request.allowedTools
3085
- },
3086
- ...context.request.appendAllowedTools !== void 0 && {
3087
- appendAllowedTools: context.request.appendAllowedTools
3088
- },
3089
- ...context.request.disallowedTools !== void 0 && {
3090
- disallowedTools: context.request.disallowedTools
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
- ...context.request.resume !== void 0 && { resume: context.request.resume },
3093
- ...context.request.continueConversation !== void 0 && {
3094
- continueConversation: context.request.continueConversation
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
- ...context.request.thinking !== void 0 && { thinking: context.request.thinking },
3097
- iteration: iterationNumber
3098
- },
3099
- timeout: context.request.timeoutMs || this.engineConfig.config.mcpTimeout,
3100
- priority: context.request.priority || "normal",
3101
- metadata: {
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