juno-code 1.0.49 → 1.0.51
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 +508 -202
- package/dist/bin/cli.d.mts +1 -1
- package/dist/bin/cli.d.ts +1 -1
- package/dist/bin/cli.js +3332 -1421
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/cli.mjs +3316 -1405
- package/dist/bin/cli.mjs.map +1 -1
- package/dist/bin/feedback-collector.js.map +1 -1
- package/dist/bin/feedback-collector.mjs.map +1 -1
- package/dist/index.d.mts +56 -19
- package/dist/index.d.ts +56 -19
- package/dist/index.js +240 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +240 -36
- package/dist/index.mjs.map +1 -1
- package/dist/templates/scripts/install_requirements.sh +55 -5
- package/dist/templates/scripts/kanban.sh +11 -0
- package/dist/templates/services/README.md +23 -4
- package/dist/templates/services/__pycache__/pi.cpython-313.pyc +0 -0
- package/dist/templates/services/pi.py +1933 -262
- package/dist/templates/skills/claude/kanban-workflow/SKILL.md +138 -0
- package/dist/templates/skills/claude/plan-kanban-tasks/SKILL.md +1 -1
- package/dist/templates/skills/claude/ralph-loop/scripts/kanban.sh +11 -0
- package/dist/templates/skills/claude/understand-project/SKILL.md +1 -1
- package/dist/templates/skills/codex/kanban-workflow/SKILL.md +139 -0
- package/dist/templates/skills/codex/plan-kanban-tasks/SKILL.md +32 -0
- package/dist/templates/skills/codex/ralph-loop/scripts/kanban.sh +11 -0
- package/dist/templates/skills/codex/understand-project/SKILL.md +46 -0
- package/dist/templates/skills/pi/kanban-workflow/SKILL.md +139 -0
- package/dist/templates/skills/pi/plan-kanban-tasks/SKILL.md +1 -1
- package/dist/templates/skills/pi/ralph-loop/SKILL.md +4 -0
- package/dist/templates/skills/pi/understand-project/SKILL.md +1 -1
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -62,7 +62,7 @@ var __export = (target, all) => {
|
|
|
62
62
|
exports.version = void 0;
|
|
63
63
|
var init_version = __esm({
|
|
64
64
|
"src/version.ts"() {
|
|
65
|
-
exports.version = "1.0.
|
|
65
|
+
exports.version = "1.0.51";
|
|
66
66
|
}
|
|
67
67
|
});
|
|
68
68
|
|
|
@@ -1013,6 +1013,9 @@ var init_shell_backend = __esm({
|
|
|
1013
1013
|
args.push("--disallowedTools");
|
|
1014
1014
|
args.push(...request.arguments.disallowedTools);
|
|
1015
1015
|
}
|
|
1016
|
+
if (isPython && request.arguments?.thinking) {
|
|
1017
|
+
args.push("--thinking", String(request.arguments.thinking));
|
|
1018
|
+
}
|
|
1016
1019
|
if (isPython && request.arguments?.resume) {
|
|
1017
1020
|
args.push("--resume", String(request.arguments.resume));
|
|
1018
1021
|
}
|
|
@@ -1022,6 +1025,9 @@ var init_shell_backend = __esm({
|
|
|
1022
1025
|
if (isPython && subagentType === "pi" && request.arguments?.project_path) {
|
|
1023
1026
|
args.push("--cd", String(request.arguments.project_path));
|
|
1024
1027
|
}
|
|
1028
|
+
if (isPython && subagentType === "pi" && request.arguments?.live === true) {
|
|
1029
|
+
args.push("--live");
|
|
1030
|
+
}
|
|
1025
1031
|
if (isPython && this.config.debug) {
|
|
1026
1032
|
args.push("--verbose");
|
|
1027
1033
|
}
|
|
@@ -1039,12 +1045,19 @@ var init_shell_backend = __esm({
|
|
|
1039
1045
|
`Environment variables: ${Object.keys(env2).filter((k) => k.startsWith("JUNO_") || k.startsWith("PI_")).join(", ")}`
|
|
1040
1046
|
);
|
|
1041
1047
|
}
|
|
1048
|
+
const isPiLiveMode = isPython && subagentType === "pi" && request.arguments?.live === true;
|
|
1049
|
+
const shouldAttachLiveTerminal = isPiLiveMode && process.stdout.isTTY === true;
|
|
1050
|
+
if (this.config.debug && isPiLiveMode) {
|
|
1051
|
+
engineLogger.debug(
|
|
1052
|
+
`Pi live mode stdio: ${shouldAttachLiveTerminal ? "inherit (interactive TTY or stdout-tty fallback)" : "pipe (headless/non-TTY)"}`
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1042
1055
|
const child = child_process.spawn(command, args, {
|
|
1043
1056
|
env: env2,
|
|
1044
1057
|
cwd: this.config.workingDirectory,
|
|
1045
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1058
|
+
stdio: shouldAttachLiveTerminal ? "inherit" : ["pipe", "pipe", "pipe"]
|
|
1046
1059
|
});
|
|
1047
|
-
if (child.stdin) {
|
|
1060
|
+
if (!shouldAttachLiveTerminal && child.stdin) {
|
|
1048
1061
|
child.stdin.end();
|
|
1049
1062
|
}
|
|
1050
1063
|
let stdout2 = "";
|
|
@@ -1116,7 +1129,8 @@ var init_shell_backend = __esm({
|
|
|
1116
1129
|
subAgentResponse = JSON.parse(captured);
|
|
1117
1130
|
}
|
|
1118
1131
|
} catch (error) {
|
|
1119
|
-
|
|
1132
|
+
const isNotFound = error?.code === "ENOENT";
|
|
1133
|
+
if (!isNotFound && this.config?.debug) {
|
|
1120
1134
|
engineLogger.warn(
|
|
1121
1135
|
`Failed to read subagent capture: ${error instanceof Error ? error.message : String(error)}`
|
|
1122
1136
|
);
|
|
@@ -1327,6 +1341,8 @@ var init_shell_backend = __esm({
|
|
|
1327
1341
|
delete inner.type;
|
|
1328
1342
|
sanitizedPiEvent.sub_agent_response = inner;
|
|
1329
1343
|
}
|
|
1344
|
+
const usage = piEvent.usage;
|
|
1345
|
+
const totalCostUsd = typeof piEvent.total_cost_usd === "number" ? piEvent.total_cost_usd : typeof usage?.cost?.total === "number" ? usage.cost.total : void 0;
|
|
1330
1346
|
const structuredPayload = {
|
|
1331
1347
|
type: "result",
|
|
1332
1348
|
subtype: piEvent.subtype || (isError ? "error" : "success"),
|
|
@@ -1337,7 +1353,8 @@ var init_shell_backend = __esm({
|
|
|
1337
1353
|
session_id: piEvent.session_id,
|
|
1338
1354
|
exit_code: result.exitCode,
|
|
1339
1355
|
duration_ms: piEvent.duration_ms ?? result.duration,
|
|
1340
|
-
|
|
1356
|
+
total_cost_usd: totalCostUsd,
|
|
1357
|
+
usage,
|
|
1341
1358
|
sub_agent_response: sanitizedPiEvent
|
|
1342
1359
|
};
|
|
1343
1360
|
const metadata = {
|
|
@@ -1728,6 +1745,16 @@ function getDefaultHooksJson(indent = 2) {
|
|
|
1728
1745
|
return JSON.stringify(DEFAULT_HOOKS, null, indent);
|
|
1729
1746
|
}
|
|
1730
1747
|
|
|
1748
|
+
// src/core/subagent-models.ts
|
|
1749
|
+
init_version();
|
|
1750
|
+
var SUBAGENT_DEFAULT_MODELS = {
|
|
1751
|
+
claude: ":sonnet",
|
|
1752
|
+
codex: ":codex",
|
|
1753
|
+
gemini: ":pro",
|
|
1754
|
+
cursor: "auto",
|
|
1755
|
+
pi: ":pi"
|
|
1756
|
+
};
|
|
1757
|
+
|
|
1731
1758
|
// src/core/config.ts
|
|
1732
1759
|
var ENV_VAR_MAPPING = {
|
|
1733
1760
|
// Core settings
|
|
@@ -1777,18 +1804,31 @@ var JunoTaskConfigSchema = zod.z.object({
|
|
|
1777
1804
|
defaultBackend: BackendTypeSchema.describe("Default backend to use for task execution"),
|
|
1778
1805
|
defaultMaxIterations: zod.z.number().int().min(1).max(1e3).describe("Default maximum number of iterations for task execution"),
|
|
1779
1806
|
defaultModel: zod.z.string().optional().describe("Default model to use for the subagent"),
|
|
1807
|
+
defaultModels: zod.z.record(SubagentTypeSchema, zod.z.string()).optional().describe("Optional per-subagent default model overrides"),
|
|
1780
1808
|
// Project metadata
|
|
1781
1809
|
mainTask: zod.z.string().optional().describe("Main task objective for the project"),
|
|
1782
1810
|
// Logging settings
|
|
1783
1811
|
logLevel: LogLevelSchema.describe("Logging level for the application"),
|
|
1784
1812
|
logFile: zod.z.string().optional().describe("Path to log file (optional)"),
|
|
1785
|
-
verbose: zod.z.
|
|
1813
|
+
verbose: zod.z.preprocess(
|
|
1814
|
+
(val) => {
|
|
1815
|
+
if (val === true) return 1;
|
|
1816
|
+
if (val === false) return 0;
|
|
1817
|
+
if (typeof val === "string") {
|
|
1818
|
+
const lower = val.toLowerCase().trim();
|
|
1819
|
+
if (lower === "true" || lower === "yes") return 1;
|
|
1820
|
+
if (lower === "false" || lower === "no") return 0;
|
|
1821
|
+
}
|
|
1822
|
+
return val;
|
|
1823
|
+
},
|
|
1824
|
+
zod.z.number().int().min(0).max(2)
|
|
1825
|
+
).describe("Verbosity level: 0=quiet, 1=normal+helping texts (default), 2=debug+hooks"),
|
|
1786
1826
|
quiet: zod.z.boolean().describe("Enable quiet mode (minimal output)"),
|
|
1787
1827
|
// MCP settings
|
|
1788
1828
|
mcpTimeout: zod.z.number().int().min(1e3).max(864e5).describe("MCP server timeout in milliseconds"),
|
|
1789
1829
|
mcpRetries: zod.z.number().int().min(0).max(10).describe("Number of retries for MCP operations"),
|
|
1790
1830
|
mcpServerPath: zod.z.string().optional().describe("Path to MCP server executable (auto-discovered if not specified)"),
|
|
1791
|
-
mcpServerName: zod.z.string().optional().describe(
|
|
1831
|
+
mcpServerName: zod.z.string().optional().describe("Named MCP server to connect to"),
|
|
1792
1832
|
// Hook settings
|
|
1793
1833
|
hookCommandTimeout: zod.z.number().int().min(1e3).max(36e5).optional().describe(
|
|
1794
1834
|
"Timeout for individual hook commands in milliseconds (default: 300000 = 5 minutes)"
|
|
@@ -1803,6 +1843,11 @@ var JunoTaskConfigSchema = zod.z.object({
|
|
|
1803
1843
|
// Paths
|
|
1804
1844
|
workingDirectory: zod.z.string().describe("Working directory for task execution"),
|
|
1805
1845
|
sessionDirectory: zod.z.string().describe("Directory for storing session data"),
|
|
1846
|
+
// Project environment bootstrap
|
|
1847
|
+
envFilePath: zod.z.string().optional().describe(
|
|
1848
|
+
"Path to the project env file loaded before execution (relative to project root or absolute)"
|
|
1849
|
+
),
|
|
1850
|
+
envFileCopied: zod.z.boolean().optional().describe("Tracks whether configured env file has been initialized from .env.juno"),
|
|
1806
1851
|
// Hooks configuration
|
|
1807
1852
|
hooks: HooksSchema.describe(
|
|
1808
1853
|
"Hook system configuration for executing commands at specific lifecycle events"
|
|
@@ -1815,16 +1860,15 @@ var DEFAULT_CONFIG = {
|
|
|
1815
1860
|
defaultSubagent: "claude",
|
|
1816
1861
|
defaultBackend: "shell",
|
|
1817
1862
|
defaultMaxIterations: 1,
|
|
1863
|
+
defaultModels: { ...SUBAGENT_DEFAULT_MODELS },
|
|
1818
1864
|
// Logging settings
|
|
1819
1865
|
logLevel: "info",
|
|
1820
|
-
verbose:
|
|
1866
|
+
verbose: 1,
|
|
1821
1867
|
quiet: false,
|
|
1822
1868
|
// MCP settings (also used by shell backend)
|
|
1823
1869
|
mcpTimeout: 432e5,
|
|
1824
1870
|
// 43200 seconds (12 hours) - default for long-running shell backend operations
|
|
1825
1871
|
mcpRetries: 3,
|
|
1826
|
-
mcpServerName: "roundtable-ai",
|
|
1827
|
-
// Default to roundtable-ai server
|
|
1828
1872
|
// Quota/hourly limit settings
|
|
1829
1873
|
onHourlyLimit: "raise",
|
|
1830
1874
|
// Default to exit immediately when hourly limit is reached
|
|
@@ -1834,6 +1878,9 @@ var DEFAULT_CONFIG = {
|
|
|
1834
1878
|
// Paths
|
|
1835
1879
|
workingDirectory: process.cwd(),
|
|
1836
1880
|
sessionDirectory: path4__namespace.join(process.cwd(), ".juno_task"),
|
|
1881
|
+
// Project environment bootstrap
|
|
1882
|
+
envFilePath: ".env.juno",
|
|
1883
|
+
envFileCopied: false,
|
|
1837
1884
|
// Hooks configuration - populated with default hooks template
|
|
1838
1885
|
hooks: getDefaultHooks()
|
|
1839
1886
|
};
|
|
@@ -1846,6 +1893,7 @@ var GLOBAL_CONFIG_FILE_NAMES = [
|
|
|
1846
1893
|
// Will look for 'junoCode' field
|
|
1847
1894
|
];
|
|
1848
1895
|
var PROJECT_CONFIG_FILE = ".juno_task/config.json";
|
|
1896
|
+
var DEFAULT_PROJECT_ENV_FILE = ".env.juno";
|
|
1849
1897
|
function resolvePath(inputPath, basePath = process.cwd()) {
|
|
1850
1898
|
if (path4__namespace.isAbsolute(inputPath)) {
|
|
1851
1899
|
return inputPath;
|
|
@@ -1867,7 +1915,12 @@ function loadConfigFromEnv() {
|
|
|
1867
1915
|
for (const [envVar, configKey] of Object.entries(ENV_VAR_MAPPING)) {
|
|
1868
1916
|
const value = process.env[envVar];
|
|
1869
1917
|
if (value !== void 0) {
|
|
1870
|
-
|
|
1918
|
+
let parsed = parseEnvValue(value);
|
|
1919
|
+
if (configKey === "verbose") {
|
|
1920
|
+
if (parsed === true) parsed = 1;
|
|
1921
|
+
else if (parsed === false) parsed = 0;
|
|
1922
|
+
}
|
|
1923
|
+
config[configKey] = parsed;
|
|
1871
1924
|
}
|
|
1872
1925
|
}
|
|
1873
1926
|
return config;
|
|
@@ -2101,6 +2154,95 @@ function validateConfig(config) {
|
|
|
2101
2154
|
throw error;
|
|
2102
2155
|
}
|
|
2103
2156
|
}
|
|
2157
|
+
function parseEnvFileContent(content) {
|
|
2158
|
+
const envVars = {};
|
|
2159
|
+
const lines = content.split(/\r?\n/);
|
|
2160
|
+
for (const rawLine of lines) {
|
|
2161
|
+
const trimmedLine = rawLine.trim();
|
|
2162
|
+
if (!trimmedLine || trimmedLine.startsWith("#")) {
|
|
2163
|
+
continue;
|
|
2164
|
+
}
|
|
2165
|
+
const line = trimmedLine.startsWith("export ") ? trimmedLine.slice(7).trim() : trimmedLine;
|
|
2166
|
+
const separatorIndex = line.indexOf("=");
|
|
2167
|
+
if (separatorIndex === -1) {
|
|
2168
|
+
continue;
|
|
2169
|
+
}
|
|
2170
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
2171
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
2172
|
+
continue;
|
|
2173
|
+
}
|
|
2174
|
+
let value = line.slice(separatorIndex + 1).trim();
|
|
2175
|
+
if ((value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) && value.length >= 2) {
|
|
2176
|
+
const quote = value[0];
|
|
2177
|
+
value = value.slice(1, -1);
|
|
2178
|
+
if (quote === '"') {
|
|
2179
|
+
value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
2180
|
+
}
|
|
2181
|
+
} else {
|
|
2182
|
+
const inlineCommentIndex = value.indexOf(" #");
|
|
2183
|
+
if (inlineCommentIndex >= 0) {
|
|
2184
|
+
value = value.slice(0, inlineCommentIndex).trimEnd();
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
envVars[key] = value;
|
|
2188
|
+
}
|
|
2189
|
+
return envVars;
|
|
2190
|
+
}
|
|
2191
|
+
async function loadEnvFileIntoProcess(envFilePath) {
|
|
2192
|
+
try {
|
|
2193
|
+
const content = await fs3.promises.readFile(envFilePath, "utf-8");
|
|
2194
|
+
const parsed = parseEnvFileContent(content);
|
|
2195
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
2196
|
+
process.env[key] = value;
|
|
2197
|
+
}
|
|
2198
|
+
} catch (error) {
|
|
2199
|
+
console.warn(`Warning: Failed to load env file ${envFilePath}: ${error}`);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
async function ensureAndLoadProjectEnv(baseDir) {
|
|
2203
|
+
const configPath = path4__namespace.join(baseDir, PROJECT_CONFIG_FILE);
|
|
2204
|
+
const defaultEnvPath = resolvePath(DEFAULT_PROJECT_ENV_FILE, baseDir);
|
|
2205
|
+
await fs__default.default.ensureFile(defaultEnvPath);
|
|
2206
|
+
let existingConfig = null;
|
|
2207
|
+
if (await fs__default.default.pathExists(configPath)) {
|
|
2208
|
+
try {
|
|
2209
|
+
existingConfig = await fs__default.default.readJson(configPath);
|
|
2210
|
+
} catch (error) {
|
|
2211
|
+
console.warn(`Warning: Failed to read ${configPath} for env bootstrap: ${error}`);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
const configuredEnvPathRaw = existingConfig && typeof existingConfig.envFilePath === "string" && existingConfig.envFilePath ? existingConfig.envFilePath : DEFAULT_PROJECT_ENV_FILE;
|
|
2215
|
+
const configuredEnvPath = resolvePath(configuredEnvPathRaw, baseDir);
|
|
2216
|
+
let envFileCopied = existingConfig && typeof existingConfig.envFileCopied === "boolean" ? existingConfig.envFileCopied : false;
|
|
2217
|
+
let needsConfigUpdate = false;
|
|
2218
|
+
if (configuredEnvPath !== defaultEnvPath) {
|
|
2219
|
+
const configuredExists = await fs__default.default.pathExists(configuredEnvPath);
|
|
2220
|
+
if (!configuredExists) {
|
|
2221
|
+
await fs__default.default.ensureDir(path4__namespace.dirname(configuredEnvPath));
|
|
2222
|
+
if (!envFileCopied) {
|
|
2223
|
+
await fs3.promises.copyFile(defaultEnvPath, configuredEnvPath);
|
|
2224
|
+
} else {
|
|
2225
|
+
await fs__default.default.ensureFile(configuredEnvPath);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
if (!envFileCopied) {
|
|
2229
|
+
envFileCopied = true;
|
|
2230
|
+
needsConfigUpdate = true;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
if (existingConfig && (needsConfigUpdate || typeof existingConfig.envFilePath !== "string" || typeof existingConfig.envFileCopied !== "boolean")) {
|
|
2234
|
+
const updatedConfig = {
|
|
2235
|
+
...existingConfig,
|
|
2236
|
+
envFilePath: configuredEnvPathRaw,
|
|
2237
|
+
envFileCopied
|
|
2238
|
+
};
|
|
2239
|
+
await fs__default.default.writeJson(configPath, updatedConfig, { spaces: 2 });
|
|
2240
|
+
}
|
|
2241
|
+
await loadEnvFileIntoProcess(defaultEnvPath);
|
|
2242
|
+
if (configuredEnvPath !== defaultEnvPath) {
|
|
2243
|
+
await loadEnvFileIntoProcess(configuredEnvPath);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2104
2246
|
async function ensureHooksConfig(baseDir) {
|
|
2105
2247
|
try {
|
|
2106
2248
|
const configDir = path4__namespace.join(baseDir, ".juno_task");
|
|
@@ -2123,19 +2265,30 @@ async function ensureHooksConfig(baseDir) {
|
|
|
2123
2265
|
}
|
|
2124
2266
|
if (!existingConfig.defaultModel) {
|
|
2125
2267
|
const subagent = existingConfig.defaultSubagent || "claude";
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
existingConfig.defaultModel
|
|
2268
|
+
existingConfig.defaultModel = SUBAGENT_DEFAULT_MODELS[subagent] || SUBAGENT_DEFAULT_MODELS.claude;
|
|
2269
|
+
needsUpdate = true;
|
|
2270
|
+
}
|
|
2271
|
+
if (!existingConfig.defaultModels || typeof existingConfig.defaultModels !== "object" || Array.isArray(existingConfig.defaultModels)) {
|
|
2272
|
+
const baseDefaults = { ...SUBAGENT_DEFAULT_MODELS };
|
|
2273
|
+
const subagent = existingConfig.defaultSubagent || "claude";
|
|
2274
|
+
if (typeof existingConfig.defaultModel === "string") {
|
|
2275
|
+
baseDefaults[subagent] = existingConfig.defaultModel;
|
|
2276
|
+
}
|
|
2277
|
+
existingConfig.defaultModels = baseDefaults;
|
|
2133
2278
|
needsUpdate = true;
|
|
2134
2279
|
}
|
|
2135
2280
|
if (existingConfig.defaultMaxIterations === 50) {
|
|
2136
2281
|
existingConfig.defaultMaxIterations = DEFAULT_CONFIG.defaultMaxIterations;
|
|
2137
2282
|
needsUpdate = true;
|
|
2138
2283
|
}
|
|
2284
|
+
if (!existingConfig.envFilePath) {
|
|
2285
|
+
existingConfig.envFilePath = DEFAULT_PROJECT_ENV_FILE;
|
|
2286
|
+
needsUpdate = true;
|
|
2287
|
+
}
|
|
2288
|
+
if (typeof existingConfig.envFileCopied !== "boolean") {
|
|
2289
|
+
existingConfig.envFileCopied = false;
|
|
2290
|
+
needsUpdate = true;
|
|
2291
|
+
}
|
|
2139
2292
|
if (needsUpdate) {
|
|
2140
2293
|
await fs__default.default.writeJson(configPath, existingConfig, { spaces: 2 });
|
|
2141
2294
|
}
|
|
@@ -2147,6 +2300,7 @@ async function ensureHooksConfig(baseDir) {
|
|
|
2147
2300
|
async function loadConfig(options = {}) {
|
|
2148
2301
|
const { baseDir = process.cwd(), configFile, cliConfig } = options;
|
|
2149
2302
|
await ensureHooksConfig(baseDir);
|
|
2303
|
+
await ensureAndLoadProjectEnv(baseDir);
|
|
2150
2304
|
const loader = new ConfigLoader(baseDir);
|
|
2151
2305
|
loader.fromEnvironment();
|
|
2152
2306
|
if (configFile) {
|
|
@@ -2179,7 +2333,7 @@ async function executeHook(hookType, hooks, context = {}, options = {}) {
|
|
|
2179
2333
|
logContext = "SYSTEM" /* SYSTEM */
|
|
2180
2334
|
} = options;
|
|
2181
2335
|
const contextLogger = logger.child(logContext);
|
|
2182
|
-
contextLogger.
|
|
2336
|
+
contextLogger.debug(`Starting hook execution: ${hookType}`, {
|
|
2183
2337
|
context,
|
|
2184
2338
|
workingDirectory: context.workingDirectory || process.cwd(),
|
|
2185
2339
|
commandTimeout,
|
|
@@ -2208,13 +2362,13 @@ async function executeHook(hookType, hooks, context = {}, options = {}) {
|
|
|
2208
2362
|
commandsFailed: 0
|
|
2209
2363
|
};
|
|
2210
2364
|
}
|
|
2211
|
-
contextLogger.
|
|
2365
|
+
contextLogger.debug(`Executing ${hook.commands.length} commands for hook ${hookType}`);
|
|
2212
2366
|
const commandResults = [];
|
|
2213
2367
|
let commandsFailed = 0;
|
|
2214
2368
|
for (let i = 0; i < hook.commands.length; i++) {
|
|
2215
2369
|
const command = hook.commands[i];
|
|
2216
2370
|
const commandStartTime = Date.now();
|
|
2217
|
-
contextLogger.
|
|
2371
|
+
contextLogger.debug(`Executing command ${i + 1}/${hook.commands.length}: ${command}`, {
|
|
2218
2372
|
commandIndex: i,
|
|
2219
2373
|
totalCommands: hook.commands.length
|
|
2220
2374
|
});
|
|
@@ -2262,7 +2416,7 @@ async function executeHook(hookType, hooks, context = {}, options = {}) {
|
|
|
2262
2416
|
};
|
|
2263
2417
|
commandResults.push(commandResult);
|
|
2264
2418
|
if (success2) {
|
|
2265
|
-
contextLogger.
|
|
2419
|
+
contextLogger.debug(`Command completed successfully`, {
|
|
2266
2420
|
command,
|
|
2267
2421
|
exitCode: result2.exitCode,
|
|
2268
2422
|
duration,
|
|
@@ -2343,7 +2497,7 @@ async function executeHook(hookType, hooks, context = {}, options = {}) {
|
|
|
2343
2497
|
commandsExecuted,
|
|
2344
2498
|
commandsFailed
|
|
2345
2499
|
};
|
|
2346
|
-
contextLogger.
|
|
2500
|
+
contextLogger.debug(`Hook execution completed`, {
|
|
2347
2501
|
hookType,
|
|
2348
2502
|
totalDuration,
|
|
2349
2503
|
commandsExecuted,
|
|
@@ -2354,7 +2508,7 @@ async function executeHook(hookType, hooks, context = {}, options = {}) {
|
|
|
2354
2508
|
}
|
|
2355
2509
|
async function executeHooks(hookTypes, hooks, context = {}, options = {}) {
|
|
2356
2510
|
const results = [];
|
|
2357
|
-
hookLogger.
|
|
2511
|
+
hookLogger.debug(`Starting batch hook execution`, {
|
|
2358
2512
|
hookTypes,
|
|
2359
2513
|
context
|
|
2360
2514
|
});
|
|
@@ -2365,7 +2519,7 @@ async function executeHooks(hookTypes, hooks, context = {}, options = {}) {
|
|
|
2365
2519
|
const totalSuccess = results.every((r) => r.success);
|
|
2366
2520
|
const totalCommands = results.reduce((sum, r) => sum + r.commandsExecuted, 0);
|
|
2367
2521
|
const totalFailed = results.reduce((sum, r) => sum + r.commandsFailed, 0);
|
|
2368
|
-
hookLogger.
|
|
2522
|
+
hookLogger.debug(`Batch hook execution completed`, {
|
|
2369
2523
|
hookTypes,
|
|
2370
2524
|
totalHooks: results.length,
|
|
2371
2525
|
totalCommands,
|
|
@@ -2657,6 +2811,30 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
2657
2811
|
*/
|
|
2658
2812
|
setupProgressTracking() {
|
|
2659
2813
|
}
|
|
2814
|
+
/**
|
|
2815
|
+
* Display hook execution output to stderr at verbose level 2 (debug+hooks).
|
|
2816
|
+
* At level 2: shows hook name, command stdout/stderr output.
|
|
2817
|
+
* At level 0-1: output is suppressed (hooks still execute).
|
|
2818
|
+
*/
|
|
2819
|
+
displayHookOutput(hookResult) {
|
|
2820
|
+
if (this.engineConfig.config.verbose < 2) return;
|
|
2821
|
+
for (const cmdResult of hookResult.commandResults) {
|
|
2822
|
+
const prefix = `[hook:${hookResult.hookType}]`;
|
|
2823
|
+
if (cmdResult.stdout) {
|
|
2824
|
+
for (const line of cmdResult.stdout.split("\n")) {
|
|
2825
|
+
if (line.trim()) console.error(`${prefix} ${line}`);
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
if (cmdResult.stderr) {
|
|
2829
|
+
for (const line of cmdResult.stderr.split("\n")) {
|
|
2830
|
+
if (line.trim()) console.error(`${prefix} ${line}`);
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
if (!cmdResult.success) {
|
|
2834
|
+
console.error(`${prefix} command failed (exit ${cmdResult.exitCode}): ${cmdResult.command}`);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2660
2838
|
/**
|
|
2661
2839
|
* Initialize backend for execution request.
|
|
2662
2840
|
* Directly creates and configures a ShellBackend (no factory indirection).
|
|
@@ -2671,11 +2849,14 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
2671
2849
|
backend.configure({
|
|
2672
2850
|
workingDirectory: request.workingDirectory,
|
|
2673
2851
|
servicesPath: `${process.env.HOME || process.env.USERPROFILE}/.juno_code/services`,
|
|
2674
|
-
debug: this.engineConfig.config.verbose,
|
|
2852
|
+
debug: this.engineConfig.config.verbose >= 2,
|
|
2675
2853
|
timeout: request.timeoutMs || this.engineConfig.config.mcpTimeout || 432e5,
|
|
2676
2854
|
enableJsonStreaming: true,
|
|
2677
|
-
outputRawJson: this.engineConfig.config.verbose,
|
|
2678
|
-
environment:
|
|
2855
|
+
outputRawJson: this.engineConfig.config.verbose >= 1,
|
|
2856
|
+
environment: {
|
|
2857
|
+
...process.env,
|
|
2858
|
+
JUNO_TASK_ROOT: process.env.JUNO_TASK_ROOT || request.workingDirectory
|
|
2859
|
+
},
|
|
2679
2860
|
sessionId: request.requestId
|
|
2680
2861
|
});
|
|
2681
2862
|
await backend.initialize();
|
|
@@ -2697,7 +2878,7 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
2697
2878
|
this.emit("progress:error", { event, error });
|
|
2698
2879
|
}
|
|
2699
2880
|
});
|
|
2700
|
-
engineLogger.
|
|
2881
|
+
engineLogger.debug(`Initialized ${backend.name} backend for execution`);
|
|
2701
2882
|
}
|
|
2702
2883
|
/**
|
|
2703
2884
|
* Validate execution request parameters
|
|
@@ -2800,10 +2981,13 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
2800
2981
|
async executeInternal(context) {
|
|
2801
2982
|
context.status = "running" /* RUNNING */;
|
|
2802
2983
|
context.sessionContext = { ...context.sessionContext, state: "active" };
|
|
2984
|
+
if (!process.env.JUNO_TASK_ROOT) {
|
|
2985
|
+
process.env.JUNO_TASK_ROOT = context.request.workingDirectory;
|
|
2986
|
+
}
|
|
2803
2987
|
await this.initializeBackend(context.request);
|
|
2804
2988
|
try {
|
|
2805
2989
|
if (this.engineConfig.config.hooks && !this.engineConfig.config.skipHooks) {
|
|
2806
|
-
await executeHook(
|
|
2990
|
+
const hookResult = await executeHook(
|
|
2807
2991
|
"START_RUN",
|
|
2808
2992
|
this.engineConfig.config.hooks,
|
|
2809
2993
|
{
|
|
@@ -2823,6 +3007,7 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
2823
3007
|
commandTimeout: this.engineConfig.config.hookCommandTimeout
|
|
2824
3008
|
}
|
|
2825
3009
|
);
|
|
3010
|
+
this.displayHookOutput(hookResult);
|
|
2826
3011
|
}
|
|
2827
3012
|
} catch (error) {
|
|
2828
3013
|
engineLogger.warn("Hook START_RUN failed", { error });
|
|
@@ -2846,7 +3031,7 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
2846
3031
|
}
|
|
2847
3032
|
try {
|
|
2848
3033
|
if (this.engineConfig.config.hooks && !this.engineConfig.config.skipHooks) {
|
|
2849
|
-
await executeHook(
|
|
3034
|
+
const hookResult = await executeHook(
|
|
2850
3035
|
"END_RUN",
|
|
2851
3036
|
this.engineConfig.config.hooks,
|
|
2852
3037
|
{
|
|
@@ -2868,6 +3053,7 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
2868
3053
|
commandTimeout: this.engineConfig.config.hookCommandTimeout
|
|
2869
3054
|
}
|
|
2870
3055
|
);
|
|
3056
|
+
this.displayHookOutput(hookResult);
|
|
2871
3057
|
}
|
|
2872
3058
|
} catch (error) {
|
|
2873
3059
|
engineLogger.warn("Hook END_RUN failed", { error });
|
|
@@ -2912,7 +3098,7 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
2912
3098
|
const iterationStart = /* @__PURE__ */ new Date();
|
|
2913
3099
|
try {
|
|
2914
3100
|
if (this.engineConfig.config.hooks && !this.engineConfig.config.skipHooks) {
|
|
2915
|
-
await executeHook(
|
|
3101
|
+
const hookResult = await executeHook(
|
|
2916
3102
|
"START_ITERATION",
|
|
2917
3103
|
this.engineConfig.config.hooks,
|
|
2918
3104
|
{
|
|
@@ -2933,6 +3119,7 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
2933
3119
|
commandTimeout: this.engineConfig.config.hookCommandTimeout
|
|
2934
3120
|
}
|
|
2935
3121
|
);
|
|
3122
|
+
this.displayHookOutput(hookResult);
|
|
2936
3123
|
}
|
|
2937
3124
|
} catch (error) {
|
|
2938
3125
|
engineLogger.warn("Hook START_ITERATION failed", { error, iterationNumber });
|
|
@@ -2959,6 +3146,8 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
2959
3146
|
...context.request.continueConversation !== void 0 && {
|
|
2960
3147
|
continueConversation: context.request.continueConversation
|
|
2961
3148
|
},
|
|
3149
|
+
...context.request.thinking !== void 0 && { thinking: context.request.thinking },
|
|
3150
|
+
...context.request.live !== void 0 && { live: context.request.live },
|
|
2962
3151
|
iteration: iterationNumber
|
|
2963
3152
|
},
|
|
2964
3153
|
timeout: context.request.timeoutMs || this.engineConfig.config.mcpTimeout,
|
|
@@ -2995,7 +3184,7 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
2995
3184
|
this.emit("iteration:complete", { context, iterationResult });
|
|
2996
3185
|
try {
|
|
2997
3186
|
if (this.engineConfig.config.hooks && !this.engineConfig.config.skipHooks) {
|
|
2998
|
-
await executeHook(
|
|
3187
|
+
const hookResult = await executeHook(
|
|
2999
3188
|
"END_ITERATION",
|
|
3000
3189
|
this.engineConfig.config.hooks,
|
|
3001
3190
|
{
|
|
@@ -3017,6 +3206,7 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
3017
3206
|
commandTimeout: this.engineConfig.config.hookCommandTimeout
|
|
3018
3207
|
}
|
|
3019
3208
|
);
|
|
3209
|
+
this.displayHookOutput(hookResult);
|
|
3020
3210
|
}
|
|
3021
3211
|
} catch (error) {
|
|
3022
3212
|
engineLogger.warn("Hook END_ITERATION failed", { error, iterationNumber });
|
|
@@ -3051,7 +3241,7 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
3051
3241
|
this.emit("iteration:error", { context, iterationResult });
|
|
3052
3242
|
try {
|
|
3053
3243
|
if (this.engineConfig.config.hooks && !this.engineConfig.config.skipHooks) {
|
|
3054
|
-
await executeHook(
|
|
3244
|
+
const hookResult = await executeHook(
|
|
3055
3245
|
"END_ITERATION",
|
|
3056
3246
|
this.engineConfig.config.hooks,
|
|
3057
3247
|
{
|
|
@@ -3074,6 +3264,7 @@ var ExecutionEngine = class extends events.EventEmitter {
|
|
|
3074
3264
|
commandTimeout: this.engineConfig.config.hookCommandTimeout
|
|
3075
3265
|
}
|
|
3076
3266
|
);
|
|
3267
|
+
this.displayHookOutput(hookResult);
|
|
3077
3268
|
}
|
|
3078
3269
|
} catch (hookError) {
|
|
3079
3270
|
engineLogger.warn("Hook END_ITERATION failed", { error: hookError, iterationNumber });
|
|
@@ -3549,6 +3740,12 @@ function createExecutionRequest(options) {
|
|
|
3549
3740
|
if (options.continueConversation !== void 0) {
|
|
3550
3741
|
result.continueConversation = options.continueConversation;
|
|
3551
3742
|
}
|
|
3743
|
+
if (options.thinking !== void 0) {
|
|
3744
|
+
result.thinking = options.thinking;
|
|
3745
|
+
}
|
|
3746
|
+
if (options.live !== void 0) {
|
|
3747
|
+
result.live = options.live;
|
|
3748
|
+
}
|
|
3552
3749
|
return result;
|
|
3553
3750
|
}
|
|
3554
3751
|
|
|
@@ -4228,9 +4425,16 @@ function validateEnvironmentVars(envVars) {
|
|
|
4228
4425
|
case "JUNO_TASK_DEFAULT_MAX_ITERATIONS":
|
|
4229
4426
|
config.defaultMaxIterations = validateIterations(parseInt(value, 10));
|
|
4230
4427
|
break;
|
|
4231
|
-
case "JUNO_TASK_VERBOSE":
|
|
4232
|
-
|
|
4428
|
+
case "JUNO_TASK_VERBOSE": {
|
|
4429
|
+
const lower = value.toLowerCase();
|
|
4430
|
+
if (lower === "true" || lower === "yes") config.verbose = 1;
|
|
4431
|
+
else if (lower === "false" || lower === "no") config.verbose = 0;
|
|
4432
|
+
else {
|
|
4433
|
+
const n = Number(value);
|
|
4434
|
+
config.verbose = !isNaN(n) && n >= 0 && n <= 2 ? Math.floor(n) : 1;
|
|
4435
|
+
}
|
|
4233
4436
|
break;
|
|
4437
|
+
}
|
|
4234
4438
|
case "JUNO_TASK_QUIET":
|
|
4235
4439
|
config.quiet = value.toLowerCase() === "true";
|
|
4236
4440
|
break;
|