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.js CHANGED
@@ -2,7 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  var chalk = require('chalk');
5
- var child_process = require('child_process');
5
+ var childProcess = require('child_process');
6
6
  var fs3 = require('fs');
7
7
  var path4 = require('path');
8
8
  var os2 = require('os');
@@ -11,6 +11,7 @@ var zod = require('zod');
11
11
  var yaml = require('js-yaml');
12
12
  var events = require('events');
13
13
  var execa = require('execa');
14
+ var util = require('util');
14
15
  var uuid = require('uuid');
15
16
  var process2 = require('process');
16
17
 
@@ -35,6 +36,7 @@ function _interopNamespace(e) {
35
36
  }
36
37
 
37
38
  var chalk__default = /*#__PURE__*/_interopDefault(chalk);
39
+ var childProcess__namespace = /*#__PURE__*/_interopNamespace(childProcess);
38
40
  var fs3__namespace = /*#__PURE__*/_interopNamespace(fs3);
39
41
  var path4__namespace = /*#__PURE__*/_interopNamespace(path4);
40
42
  var os2__namespace = /*#__PURE__*/_interopNamespace(os2);
@@ -62,7 +64,7 @@ var __export = (target, all) => {
62
64
  exports.version = void 0;
63
65
  var init_version = __esm({
64
66
  "src/version.ts"() {
65
- exports.version = "1.0.50";
67
+ exports.version = "1.0.53";
66
68
  }
67
69
  });
68
70
 
@@ -750,25 +752,28 @@ var init_shell_backend = __esm({
750
752
  const scriptPath = await this.findScriptForSubagent(subagentType);
751
753
  const result = await this.executeScript(scriptPath, request, toolId, subagentType);
752
754
  const duration = Date.now() - startTime;
755
+ const structuredResult = this.buildStructuredOutput(subagentType, result);
756
+ const structuredPayload = this.parseStructuredResultPayload(structuredResult.content);
757
+ const structuredIndicatesError = structuredPayload?.is_error === true || structuredPayload?.subtype === "error";
758
+ const executionSucceeded = result.success && !structuredIndicatesError;
753
759
  await this.emitProgressEvent({
754
760
  sessionId: request.metadata?.sessionId || "unknown",
755
761
  timestamp: /* @__PURE__ */ new Date(),
756
762
  backend: "shell",
757
763
  count: ++this.eventCounter,
758
764
  type: "tool_result" /* TOOL_RESULT */,
759
- content: `${request.toolName} completed successfully (${duration}ms)`,
765
+ content: executionSucceeded ? `${request.toolName} completed successfully (${duration}ms)` : `${request.toolName} completed with error (${duration}ms)`,
760
766
  toolId,
761
767
  metadata: {
762
768
  toolName: request.toolName,
763
769
  duration,
764
- success: result.success,
770
+ success: executionSucceeded,
765
771
  phase: "completion"
766
772
  }
767
773
  });
768
- const structuredResult = this.buildStructuredOutput(subagentType, result);
769
774
  const toolResult = {
770
775
  content: structuredResult.content,
771
- status: result.success ? "completed" /* COMPLETED */ : "failed" /* FAILED */,
776
+ status: executionSucceeded ? "completed" /* COMPLETED */ : "failed" /* FAILED */,
772
777
  startTime: new Date(startTime),
773
778
  endTime: /* @__PURE__ */ new Date(),
774
779
  duration,
@@ -777,6 +782,9 @@ var init_shell_backend = __esm({
777
782
  };
778
783
  if (result.error) {
779
784
  toolResult.error = new Error(result.error);
785
+ } else if (!executionSucceeded) {
786
+ const structuredErrorMessage = typeof structuredPayload?.error === "string" && structuredPayload.error || typeof structuredPayload?.result === "string" && structuredPayload.result || `${request.toolName} reported a structured error`;
787
+ toolResult.error = new Error(structuredErrorMessage);
780
788
  }
781
789
  if (structuredResult.metadata) {
782
790
  toolResult.metadata = structuredResult.metadata;
@@ -1025,6 +1033,12 @@ var init_shell_backend = __esm({
1025
1033
  if (isPython && subagentType === "pi" && request.arguments?.project_path) {
1026
1034
  args.push("--cd", String(request.arguments.project_path));
1027
1035
  }
1036
+ if (isPython && subagentType === "pi" && request.arguments?.live === true) {
1037
+ args.push("--live");
1038
+ }
1039
+ if (isPython && subagentType === "pi" && request.arguments?.liveInteractiveSession === true) {
1040
+ args.push("--live-manual");
1041
+ }
1028
1042
  if (isPython && this.config.debug) {
1029
1043
  args.push("--verbose");
1030
1044
  }
@@ -1042,12 +1056,19 @@ var init_shell_backend = __esm({
1042
1056
  `Environment variables: ${Object.keys(env2).filter((k) => k.startsWith("JUNO_") || k.startsWith("PI_")).join(", ")}`
1043
1057
  );
1044
1058
  }
1045
- const child = child_process.spawn(command, args, {
1059
+ const isPiLiveMode = isPython && subagentType === "pi" && request.arguments?.live === true;
1060
+ const shouldAttachLiveTerminal = isPiLiveMode && process.stdout.isTTY === true;
1061
+ if (this.config.debug && isPiLiveMode) {
1062
+ engineLogger.debug(
1063
+ `Pi live mode stdio: ${shouldAttachLiveTerminal ? "inherit (interactive TTY or stdout-tty fallback)" : "pipe (headless/non-TTY)"}`
1064
+ );
1065
+ }
1066
+ const child = childProcess.spawn(command, args, {
1046
1067
  env: env2,
1047
1068
  cwd: this.config.workingDirectory,
1048
- stdio: ["pipe", "pipe", "pipe"]
1069
+ stdio: shouldAttachLiveTerminal ? "inherit" : ["pipe", "pipe", "pipe"]
1049
1070
  });
1050
- if (child.stdin) {
1071
+ if (!shouldAttachLiveTerminal && child.stdin) {
1051
1072
  child.stdin.end();
1052
1073
  }
1053
1074
  let stdout2 = "";
@@ -1300,8 +1321,19 @@ var init_shell_backend = __esm({
1300
1321
  if (subagentType === "pi") {
1301
1322
  const piEvent = result.subAgentResponse ?? this.extractLastJsonEvent(result.output);
1302
1323
  if (piEvent) {
1303
- let resultText = piEvent.result;
1304
- if (!resultText && Array.isArray(piEvent.messages)) {
1324
+ const piNestedEvent = typeof piEvent.sub_agent_response === "object" && piEvent.sub_agent_response ? piEvent.sub_agent_response : void 0;
1325
+ 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;
1326
+ const sanitizedPiEvent = { ...piEvent };
1327
+ delete sanitizedPiEvent.messages;
1328
+ if (sanitizedPiEvent.sub_agent_response && typeof sanitizedPiEvent.sub_agent_response === "object") {
1329
+ const inner = { ...sanitizedPiEvent.sub_agent_response };
1330
+ delete inner.messages;
1331
+ delete inner.type;
1332
+ sanitizedPiEvent.sub_agent_response = inner;
1333
+ }
1334
+ const hasDirectResultText = typeof piEvent.result === "string";
1335
+ let resultText = hasDirectResultText ? piEvent.result : void 0;
1336
+ if (resultText === void 0 && Array.isArray(piEvent.messages)) {
1305
1337
  for (let i = piEvent.messages.length - 1; i >= 0; i--) {
1306
1338
  const msg = piEvent.messages[i];
1307
1339
  if (msg?.role === "assistant") {
@@ -1321,16 +1353,11 @@ var init_shell_backend = __esm({
1321
1353
  }
1322
1354
  }
1323
1355
  }
1324
- if (resultText) {
1356
+ if (resultText === void 0 && typeof piEvent.error === "string") {
1357
+ resultText = piEvent.error;
1358
+ }
1359
+ if (resultText !== void 0) {
1325
1360
  const isError = piEvent.is_error ?? !result.success;
1326
- const sanitizedPiEvent = { ...piEvent };
1327
- delete sanitizedPiEvent.messages;
1328
- if (sanitizedPiEvent.sub_agent_response && typeof sanitizedPiEvent.sub_agent_response === "object") {
1329
- const inner = { ...sanitizedPiEvent.sub_agent_response };
1330
- delete inner.messages;
1331
- delete inner.type;
1332
- sanitizedPiEvent.sub_agent_response = inner;
1333
- }
1334
1361
  const usage = piEvent.usage;
1335
1362
  const totalCostUsd = typeof piEvent.total_cost_usd === "number" ? piEvent.total_cost_usd : typeof usage?.cost?.total === "number" ? usage.cost.total : void 0;
1336
1363
  const structuredPayload = {
@@ -1338,9 +1365,9 @@ var init_shell_backend = __esm({
1338
1365
  subtype: piEvent.subtype || (isError ? "error" : "success"),
1339
1366
  is_error: isError,
1340
1367
  result: resultText,
1341
- error: piEvent.error,
1368
+ error: isError ? piEvent.error ?? result.error ?? resultText : piEvent.error,
1342
1369
  stderr: result.error,
1343
- session_id: piEvent.session_id,
1370
+ session_id: piSessionId,
1344
1371
  exit_code: result.exitCode,
1345
1372
  duration_ms: piEvent.duration_ms ?? result.duration,
1346
1373
  total_cost_usd: totalCostUsd,
@@ -1358,6 +1385,57 @@ var init_shell_backend = __esm({
1358
1385
  metadata
1359
1386
  };
1360
1387
  }
1388
+ const isSessionSnapshotOnly = piEvent.type === "session" || piEvent.subtype === "session";
1389
+ if (isSessionSnapshotOnly) {
1390
+ const errorMessage = result.error?.trim() || "Pi exited before emitting a terminal result event (session snapshot only).";
1391
+ const structuredPayload = {
1392
+ type: "result",
1393
+ subtype: "error",
1394
+ is_error: true,
1395
+ result: errorMessage,
1396
+ error: errorMessage,
1397
+ stderr: result.error,
1398
+ session_id: piSessionId,
1399
+ exit_code: result.exitCode,
1400
+ duration_ms: result.duration,
1401
+ sub_agent_response: sanitizedPiEvent
1402
+ };
1403
+ const metadata = {
1404
+ ...piEvent ? { subAgentResponse: piEvent } : void 0,
1405
+ structuredOutput: true,
1406
+ contentType: "application/json",
1407
+ rawOutput: result.output
1408
+ };
1409
+ return {
1410
+ content: JSON.stringify(structuredPayload),
1411
+ metadata
1412
+ };
1413
+ }
1414
+ if (!result.success) {
1415
+ const errorMessage = result.error?.trim() || result.output?.trim() || "Unknown error";
1416
+ const structuredPayload = {
1417
+ type: "result",
1418
+ subtype: "error",
1419
+ is_error: true,
1420
+ result: errorMessage,
1421
+ error: errorMessage,
1422
+ stderr: result.error,
1423
+ session_id: piSessionId,
1424
+ exit_code: result.exitCode,
1425
+ duration_ms: result.duration,
1426
+ sub_agent_response: sanitizedPiEvent
1427
+ };
1428
+ const metadata = {
1429
+ ...piEvent ? { subAgentResponse: piEvent } : void 0,
1430
+ structuredOutput: true,
1431
+ contentType: "application/json",
1432
+ rawOutput: result.output
1433
+ };
1434
+ return {
1435
+ content: JSON.stringify(structuredPayload),
1436
+ metadata
1437
+ };
1438
+ }
1361
1439
  }
1362
1440
  }
1363
1441
  if (!result.success) {
@@ -1422,6 +1500,20 @@ var init_shell_backend = __esm({
1422
1500
  }
1423
1501
  return null;
1424
1502
  }
1503
+ /**
1504
+ * Parse JSON structured output payload emitted by shell service wrappers.
1505
+ */
1506
+ parseStructuredResultPayload(content) {
1507
+ try {
1508
+ const parsed = JSON.parse(content);
1509
+ if (!parsed || typeof parsed !== "object") {
1510
+ return null;
1511
+ }
1512
+ return parsed;
1513
+ } catch {
1514
+ return null;
1515
+ }
1516
+ }
1425
1517
  /**
1426
1518
  * Extract the last valid JSON object from a script's stdout to use as a structured payload fallback.
1427
1519
  */
@@ -1735,6 +1827,41 @@ function getDefaultHooksJson(indent = 2) {
1735
1827
  return JSON.stringify(DEFAULT_HOOKS, null, indent);
1736
1828
  }
1737
1829
 
1830
+ // src/core/subagent-models.ts
1831
+ init_version();
1832
+ var SUBAGENT_DEFAULT_MODELS = {
1833
+ claude: ":sonnet",
1834
+ codex: ":codex",
1835
+ gemini: ":pro",
1836
+ cursor: "auto",
1837
+ pi: ":pi"
1838
+ };
1839
+ function isModelCompatibleWithSubagent(model, subagent) {
1840
+ if (!model.startsWith(":")) {
1841
+ return true;
1842
+ }
1843
+ const claudeShorthands = [":sonnet", ":haiku", ":opus"];
1844
+ const codexShorthands = [":codex", ":codex-mini", ":gpt-5", ":mini"];
1845
+ const geminiShorthands = [":pro", ":flash"];
1846
+ const isClaudeModel = claudeShorthands.includes(model) || model.startsWith(":claude");
1847
+ const isCodexModel = codexShorthands.includes(model) || model.startsWith(":gpt");
1848
+ const isGeminiModel = geminiShorthands.includes(model) || model.startsWith(":gemini");
1849
+ switch (subagent) {
1850
+ case "claude":
1851
+ return isClaudeModel || !isCodexModel && !isGeminiModel;
1852
+ case "codex":
1853
+ return isCodexModel || !isClaudeModel && !isGeminiModel;
1854
+ case "gemini":
1855
+ return isGeminiModel || !isClaudeModel && !isCodexModel;
1856
+ case "cursor":
1857
+ return true;
1858
+ case "pi":
1859
+ return true;
1860
+ default:
1861
+ return true;
1862
+ }
1863
+ }
1864
+
1738
1865
  // src/core/config.ts
1739
1866
  var ENV_VAR_MAPPING = {
1740
1867
  // Core settings
@@ -1784,6 +1911,7 @@ var JunoTaskConfigSchema = zod.z.object({
1784
1911
  defaultBackend: BackendTypeSchema.describe("Default backend to use for task execution"),
1785
1912
  defaultMaxIterations: zod.z.number().int().min(1).max(1e3).describe("Default maximum number of iterations for task execution"),
1786
1913
  defaultModel: zod.z.string().optional().describe("Default model to use for the subagent"),
1914
+ defaultModels: zod.z.record(SubagentTypeSchema, zod.z.string()).optional().describe("Optional per-subagent default model overrides"),
1787
1915
  // Project metadata
1788
1916
  mainTask: zod.z.string().optional().describe("Main task objective for the project"),
1789
1917
  // Logging settings
@@ -1839,6 +1967,7 @@ var DEFAULT_CONFIG = {
1839
1967
  defaultSubagent: "claude",
1840
1968
  defaultBackend: "shell",
1841
1969
  defaultMaxIterations: 1,
1970
+ defaultModels: { ...SUBAGENT_DEFAULT_MODELS },
1842
1971
  // Logging settings
1843
1972
  logLevel: "info",
1844
1973
  verbose: 1,
@@ -2154,7 +2283,7 @@ function parseEnvFileContent(content) {
2154
2283
  const quote = value[0];
2155
2284
  value = value.slice(1, -1);
2156
2285
  if (quote === '"') {
2157
- value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ");
2286
+ value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
2158
2287
  }
2159
2288
  } else {
2160
2289
  const inlineCommentIndex = value.indexOf(" #");
@@ -2243,13 +2372,23 @@ async function ensureHooksConfig(baseDir) {
2243
2372
  }
2244
2373
  if (!existingConfig.defaultModel) {
2245
2374
  const subagent = existingConfig.defaultSubagent || "claude";
2246
- const modelDefaults = {
2247
- claude: ":sonnet",
2248
- codex: ":codex",
2249
- gemini: ":pro",
2250
- cursor: "auto"
2251
- };
2252
- existingConfig.defaultModel = modelDefaults[subagent] || ":sonnet";
2375
+ existingConfig.defaultModel = SUBAGENT_DEFAULT_MODELS[subagent] || SUBAGENT_DEFAULT_MODELS.claude;
2376
+ needsUpdate = true;
2377
+ }
2378
+ if (!existingConfig.defaultModels || typeof existingConfig.defaultModels !== "object" || Array.isArray(existingConfig.defaultModels)) {
2379
+ const baseDefaults = { ...SUBAGENT_DEFAULT_MODELS };
2380
+ const subagent = existingConfig.defaultSubagent || "claude";
2381
+ if (typeof existingConfig.defaultModel === "string") {
2382
+ baseDefaults[subagent] = existingConfig.defaultModel;
2383
+ }
2384
+ existingConfig.defaultModels = baseDefaults;
2385
+ needsUpdate = true;
2386
+ }
2387
+ const selectedSubagentRaw = typeof existingConfig.defaultSubagent === "string" ? existingConfig.defaultSubagent : "claude";
2388
+ const selectedSubagent = selectedSubagentRaw in SUBAGENT_DEFAULT_MODELS ? selectedSubagentRaw : "claude";
2389
+ const selectedMapModel = existingConfig.defaultModels && typeof existingConfig.defaultModels === "object" ? existingConfig.defaultModels[selectedSubagent] : void 0;
2390
+ if (typeof selectedMapModel === "string" && isModelCompatibleWithSubagent(selectedMapModel, selectedSubagent) && existingConfig.defaultModel !== selectedMapModel) {
2391
+ existingConfig.defaultModel = selectedMapModel;
2253
2392
  needsUpdate = true;
2254
2393
  }
2255
2394
  if (existingConfig.defaultMaxIterations === 50) {
@@ -2556,6 +2695,178 @@ function validateHooksConfig(hooks) {
2556
2695
  // src/core/engine.ts
2557
2696
  init_advanced_logger();
2558
2697
  init_shell_backend();
2698
+
2699
+ // src/core/prompt-command-substitution.ts
2700
+ init_version();
2701
+ var SINGLE_QUOTED_MARKER = "!'";
2702
+ var TRIPLE_BACKTICK_MARKER = "!```";
2703
+ var TRIPLE_BACKTICK_CLOSER = "```";
2704
+ var DEFAULT_MAX_BUFFER_BYTES = 1024 * 1024;
2705
+ var DEFAULT_COMMAND_TIMEOUT_MS = 3e4;
2706
+ var COMMAND_TIMEOUT_ENV_KEY = "JUNO_CODE_PROMPT_SUBSTITUTION_TIMEOUT_MS";
2707
+ function findPromptCommandSubstitutions(prompt) {
2708
+ const matches = [];
2709
+ let cursor = 0;
2710
+ while (cursor < prompt.length) {
2711
+ const singleQuotedStart = prompt.indexOf(SINGLE_QUOTED_MARKER, cursor);
2712
+ const tripleBacktickStart = prompt.indexOf(TRIPLE_BACKTICK_MARKER, cursor);
2713
+ const markerStart = chooseNearestMarker(singleQuotedStart, tripleBacktickStart);
2714
+ if (markerStart === null) {
2715
+ break;
2716
+ }
2717
+ if (markerStart === singleQuotedStart) {
2718
+ const parsedSingleQuoted = parseSingleQuotedSubstitution(prompt, markerStart);
2719
+ if (!parsedSingleQuoted) {
2720
+ cursor = markerStart + SINGLE_QUOTED_MARKER.length;
2721
+ continue;
2722
+ }
2723
+ matches.push(parsedSingleQuoted);
2724
+ cursor = parsedSingleQuoted.endIndex;
2725
+ continue;
2726
+ }
2727
+ const parsedTripleBacktick = parseTripleBacktickSubstitution(prompt, markerStart);
2728
+ if (!parsedTripleBacktick) {
2729
+ cursor = markerStart + TRIPLE_BACKTICK_MARKER.length;
2730
+ continue;
2731
+ }
2732
+ matches.push(parsedTripleBacktick);
2733
+ cursor = parsedTripleBacktick.endIndex;
2734
+ }
2735
+ return matches;
2736
+ }
2737
+ async function resolvePromptCommandSubstitutions(prompt, options) {
2738
+ const matches = findPromptCommandSubstitutions(prompt);
2739
+ if (matches.length === 0) {
2740
+ return prompt;
2741
+ }
2742
+ const executor = options.executor ?? createDefaultPromptCommandExecutor(options);
2743
+ let result = "";
2744
+ let cursor = 0;
2745
+ for (const match of matches) {
2746
+ result += prompt.slice(cursor, match.startIndex);
2747
+ const commandOutput = await executor(match.command);
2748
+ result += normalizeCommandOutput(commandOutput);
2749
+ cursor = match.endIndex;
2750
+ }
2751
+ result += prompt.slice(cursor);
2752
+ return result;
2753
+ }
2754
+ function chooseNearestMarker(singleQuotedStart, tripleBacktickStart) {
2755
+ const singleExists = singleQuotedStart >= 0;
2756
+ const tripleExists = tripleBacktickStart >= 0;
2757
+ if (!singleExists && !tripleExists) {
2758
+ return null;
2759
+ }
2760
+ if (!singleExists) {
2761
+ return tripleBacktickStart;
2762
+ }
2763
+ if (!tripleExists) {
2764
+ return singleQuotedStart;
2765
+ }
2766
+ return Math.min(singleQuotedStart, tripleBacktickStart);
2767
+ }
2768
+ function parseSingleQuotedSubstitution(prompt, markerStart) {
2769
+ const contentStart = markerStart + SINGLE_QUOTED_MARKER.length;
2770
+ const closingQuote = findClosingSingleQuote(prompt, contentStart);
2771
+ if (closingQuote < 0) {
2772
+ return null;
2773
+ }
2774
+ const raw = prompt.slice(markerStart, closingQuote + 1);
2775
+ const command = prompt.slice(contentStart, closingQuote);
2776
+ return {
2777
+ syntax: "single-quoted",
2778
+ startIndex: markerStart,
2779
+ endIndex: closingQuote + 1,
2780
+ command,
2781
+ raw
2782
+ };
2783
+ }
2784
+ function findClosingSingleQuote(prompt, startIndex) {
2785
+ let escaped = false;
2786
+ for (let index = startIndex; index < prompt.length; index++) {
2787
+ const char = prompt[index];
2788
+ if (char === "'" && !escaped) {
2789
+ return index;
2790
+ }
2791
+ if (char === "\\" && !escaped) {
2792
+ escaped = true;
2793
+ continue;
2794
+ }
2795
+ escaped = false;
2796
+ }
2797
+ return -1;
2798
+ }
2799
+ function parseTripleBacktickSubstitution(prompt, markerStart) {
2800
+ const contentStart = markerStart + TRIPLE_BACKTICK_MARKER.length;
2801
+ const closingBackticks = prompt.indexOf(TRIPLE_BACKTICK_CLOSER, contentStart);
2802
+ if (closingBackticks < 0) {
2803
+ return null;
2804
+ }
2805
+ const raw = prompt.slice(markerStart, closingBackticks + TRIPLE_BACKTICK_CLOSER.length);
2806
+ const command = prompt.slice(contentStart, closingBackticks);
2807
+ return {
2808
+ syntax: "triple-backtick",
2809
+ startIndex: markerStart,
2810
+ endIndex: closingBackticks + TRIPLE_BACKTICK_CLOSER.length,
2811
+ command,
2812
+ raw
2813
+ };
2814
+ }
2815
+ function createDefaultPromptCommandExecutor(options) {
2816
+ const execFile2 = util.promisify(childProcess__namespace.execFile);
2817
+ const maxBufferBytes = options.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;
2818
+ const commandTimeoutMs = resolvePromptCommandTimeoutMs(options.commandTimeoutMs);
2819
+ const shell = process.env.SHELL || "/bin/bash";
2820
+ return async (command) => {
2821
+ const normalizedCommand = command.trim();
2822
+ if (!normalizedCommand) {
2823
+ return "";
2824
+ }
2825
+ const commandForExecution = wrapCommandForNonInteractiveExecution(normalizedCommand);
2826
+ try {
2827
+ const result = await execFile2(shell, ["-lc", commandForExecution], {
2828
+ cwd: options.workingDirectory,
2829
+ env: options.environment ?? process.env,
2830
+ maxBuffer: maxBufferBytes,
2831
+ timeout: commandTimeoutMs
2832
+ });
2833
+ const stdout2 = typeof result === "string" || Buffer.isBuffer(result) ? String(result) : String(result.stdout ?? "");
2834
+ return stdout2;
2835
+ } catch (error) {
2836
+ const failedCommand = normalizedCommand.replace(/\s+/g, " ").trim();
2837
+ const details = error && typeof error === "object" && "stderr" in error ? String(error.stderr ?? "").trim() : "";
2838
+ 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 ?? "")));
2839
+ if (timeoutDetected) {
2840
+ throw new Error(
2841
+ `Prompt command substitution timed out after ${commandTimeoutMs}ms for \`${failedCommand}\``
2842
+ );
2843
+ }
2844
+ const suffix = details ? `: ${details}` : "";
2845
+ throw new Error(`Prompt command substitution failed for \`${failedCommand}\`${suffix}`);
2846
+ }
2847
+ };
2848
+ }
2849
+ function resolvePromptCommandTimeoutMs(explicitTimeoutMs) {
2850
+ if (typeof explicitTimeoutMs === "number" && Number.isFinite(explicitTimeoutMs) && explicitTimeoutMs > 0) {
2851
+ return explicitTimeoutMs;
2852
+ }
2853
+ const envValue = process.env[COMMAND_TIMEOUT_ENV_KEY];
2854
+ if (envValue !== void 0) {
2855
+ const parsed = Number(envValue);
2856
+ if (Number.isFinite(parsed) && parsed > 0) {
2857
+ return parsed;
2858
+ }
2859
+ }
2860
+ return DEFAULT_COMMAND_TIMEOUT_MS;
2861
+ }
2862
+ function wrapCommandForNonInteractiveExecution(command) {
2863
+ return `(${command}) </dev/null`;
2864
+ }
2865
+ function normalizeCommandOutput(output) {
2866
+ return output.replace(/\r?\n$/, "");
2867
+ }
2868
+
2869
+ // src/core/engine.ts
2559
2870
  var ExecutionStatus = /* @__PURE__ */ ((ExecutionStatus2) => {
2560
2871
  ExecutionStatus2["PENDING"] = "pending";
2561
2872
  ExecutionStatus2["RUNNING"] = "running";
@@ -2862,7 +3173,8 @@ var ExecutionEngine = class extends events.EventEmitter {
2862
3173
  if (!request.requestId?.trim()) {
2863
3174
  throw new Error("Request ID is required");
2864
3175
  }
2865
- if (!request.instruction?.trim()) {
3176
+ const allowEmptyInstructionForPiLiveInteractiveSession = request.subagent === "pi" && request.live === true && request.liveInteractiveSession === true && typeof request.resume === "string" && request.resume.trim().length > 0;
3177
+ if (!request.instruction?.trim() && !allowEmptyInstructionForPiLiveInteractiveSession) {
2866
3178
  throw new Error("Instruction is required");
2867
3179
  }
2868
3180
  if (!request.subagent?.trim()) {
@@ -3100,43 +3412,62 @@ var ExecutionEngine = class extends events.EventEmitter {
3100
3412
  engineLogger.warn("Hook START_ITERATION failed", { error, iterationNumber });
3101
3413
  }
3102
3414
  this.emit("iteration:start", { context, iterationNumber });
3103
- const toolRequest = {
3104
- toolName: this.getToolNameForSubagent(context.request.subagent),
3105
- arguments: {
3106
- instruction: context.request.instruction,
3107
- project_path: context.request.workingDirectory,
3108
- ...context.request.model !== void 0 && { model: context.request.model },
3109
- ...context.request.agents !== void 0 && { agents: context.request.agents },
3110
- ...context.request.tools !== void 0 && { tools: context.request.tools },
3111
- ...context.request.allowedTools !== void 0 && {
3112
- allowedTools: context.request.allowedTools
3113
- },
3114
- ...context.request.appendAllowedTools !== void 0 && {
3115
- appendAllowedTools: context.request.appendAllowedTools
3116
- },
3117
- ...context.request.disallowedTools !== void 0 && {
3118
- disallowedTools: context.request.disallowedTools
3415
+ let toolRequest = null;
3416
+ try {
3417
+ const instructionTemplate = context.request.instruction;
3418
+ const resolvedInstruction = await resolvePromptCommandSubstitutions(instructionTemplate, {
3419
+ workingDirectory: context.request.workingDirectory,
3420
+ environment: {
3421
+ ...process.env,
3422
+ JUNO_TASK_ROOT: process.env.JUNO_TASK_ROOT || context.request.workingDirectory
3423
+ }
3424
+ });
3425
+ this.emit("iteration:instruction-resolved", {
3426
+ context,
3427
+ iterationNumber,
3428
+ instruction: resolvedInstruction,
3429
+ templateInstruction: instructionTemplate
3430
+ });
3431
+ toolRequest = {
3432
+ toolName: this.getToolNameForSubagent(context.request.subagent),
3433
+ arguments: {
3434
+ instruction: resolvedInstruction,
3435
+ project_path: context.request.workingDirectory,
3436
+ ...context.request.model !== void 0 && { model: context.request.model },
3437
+ ...context.request.agents !== void 0 && { agents: context.request.agents },
3438
+ ...context.request.tools !== void 0 && { tools: context.request.tools },
3439
+ ...context.request.allowedTools !== void 0 && {
3440
+ allowedTools: context.request.allowedTools
3441
+ },
3442
+ ...context.request.appendAllowedTools !== void 0 && {
3443
+ appendAllowedTools: context.request.appendAllowedTools
3444
+ },
3445
+ ...context.request.disallowedTools !== void 0 && {
3446
+ disallowedTools: context.request.disallowedTools
3447
+ },
3448
+ ...context.request.resume !== void 0 && { resume: context.request.resume },
3449
+ ...context.request.continueConversation !== void 0 && {
3450
+ continueConversation: context.request.continueConversation
3451
+ },
3452
+ ...context.request.thinking !== void 0 && { thinking: context.request.thinking },
3453
+ ...context.request.live !== void 0 && { live: context.request.live },
3454
+ ...context.request.liveInteractiveSession !== void 0 && {
3455
+ liveInteractiveSession: context.request.liveInteractiveSession
3456
+ },
3457
+ iteration: iterationNumber
3119
3458
  },
3120
- ...context.request.resume !== void 0 && { resume: context.request.resume },
3121
- ...context.request.continueConversation !== void 0 && {
3122
- continueConversation: context.request.continueConversation
3459
+ timeout: context.request.timeoutMs || this.engineConfig.config.mcpTimeout,
3460
+ priority: context.request.priority || "normal",
3461
+ metadata: {
3462
+ sessionId: context.sessionContext.sessionId,
3463
+ iterationNumber
3123
3464
  },
3124
- ...context.request.thinking !== void 0 && { thinking: context.request.thinking },
3125
- iteration: iterationNumber
3126
- },
3127
- timeout: context.request.timeoutMs || this.engineConfig.config.mcpTimeout,
3128
- priority: context.request.priority || "normal",
3129
- metadata: {
3130
- sessionId: context.sessionContext.sessionId,
3131
- iterationNumber
3132
- },
3133
- progressCallback: async (event) => {
3134
- context.progressEvents.push(event);
3135
- context.statistics.totalProgressEvents++;
3136
- await this.processProgressEvent(context, event);
3137
- }
3138
- };
3139
- try {
3465
+ progressCallback: async (event) => {
3466
+ context.progressEvents.push(event);
3467
+ context.statistics.totalProgressEvents++;
3468
+ await this.processProgressEvent(context, event);
3469
+ }
3470
+ };
3140
3471
  if (!this.currentBackend) {
3141
3472
  throw new Error("No backend initialized. Call initializeBackend() first.");
3142
3473
  }
@@ -3205,7 +3536,10 @@ var ExecutionEngine = class extends events.EventEmitter {
3205
3536
  duration,
3206
3537
  error: mcpError,
3207
3538
  progressEvents: [],
3208
- request: toolRequest
3539
+ request: toolRequest ?? {
3540
+ toolName: this.getToolNameForSubagent(context.request.subagent),
3541
+ arguments: {}
3542
+ }
3209
3543
  },
3210
3544
  progressEvents: [],
3211
3545
  error: mcpError
@@ -3717,6 +4051,12 @@ function createExecutionRequest(options) {
3717
4051
  if (options.thinking !== void 0) {
3718
4052
  result.thinking = options.thinking;
3719
4053
  }
4054
+ if (options.live !== void 0) {
4055
+ result.live = options.live;
4056
+ }
4057
+ if (options.liveInteractiveSession !== void 0) {
4058
+ result.liveInteractiveSession = options.liveInteractiveSession;
4059
+ }
3720
4060
  return result;
3721
4061
  }
3722
4062