@kimbho/kimbho-cli 0.1.8 → 0.1.11
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.cjs +832 -132
- package/dist/index.cjs.map +4 -4
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3346,7 +3346,7 @@ var {
|
|
|
3346
3346
|
// package.json
|
|
3347
3347
|
var package_default = {
|
|
3348
3348
|
name: "@kimbho/kimbho-cli",
|
|
3349
|
-
version: "0.1.
|
|
3349
|
+
version: "0.1.11",
|
|
3350
3350
|
description: "Kimbho CLI is a terminal-native coding agent for planning, execution, and verification.",
|
|
3351
3351
|
type: "module",
|
|
3352
3352
|
engines: {
|
|
@@ -7996,6 +7996,178 @@ var SessionSnapshotSchema = external_exports.object({
|
|
|
7996
7996
|
events: external_exports.array(SessionEventSchema).default([])
|
|
7997
7997
|
});
|
|
7998
7998
|
|
|
7999
|
+
// ../core/dist/jsonish.js
|
|
8000
|
+
function stripCodeFences(raw) {
|
|
8001
|
+
const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
8002
|
+
return (fenced?.[1] ?? raw).trim();
|
|
8003
|
+
}
|
|
8004
|
+
function normalizeSmartQuotes(value) {
|
|
8005
|
+
return value.replace(/[\u201c\u201d]/g, '"').replace(/[\u2018\u2019]/g, "'");
|
|
8006
|
+
}
|
|
8007
|
+
function maybeUnwrapQuotedJson(value) {
|
|
8008
|
+
const trimmed = value.trim();
|
|
8009
|
+
if (!trimmed.startsWith('"')) {
|
|
8010
|
+
return trimmed;
|
|
8011
|
+
}
|
|
8012
|
+
try {
|
|
8013
|
+
const parsed = JSON.parse(trimmed);
|
|
8014
|
+
return typeof parsed === "string" ? parsed.trim() : trimmed;
|
|
8015
|
+
} catch {
|
|
8016
|
+
return trimmed;
|
|
8017
|
+
}
|
|
8018
|
+
}
|
|
8019
|
+
function findBalancedObject(value) {
|
|
8020
|
+
for (let start = 0; start < value.length; start += 1) {
|
|
8021
|
+
if (value[start] !== "{") {
|
|
8022
|
+
continue;
|
|
8023
|
+
}
|
|
8024
|
+
let depth = 0;
|
|
8025
|
+
let inString = false;
|
|
8026
|
+
let escaping = false;
|
|
8027
|
+
for (let index = start; index < value.length; index += 1) {
|
|
8028
|
+
const character = value[index];
|
|
8029
|
+
if (escaping) {
|
|
8030
|
+
escaping = false;
|
|
8031
|
+
continue;
|
|
8032
|
+
}
|
|
8033
|
+
if (character === "\\") {
|
|
8034
|
+
escaping = true;
|
|
8035
|
+
continue;
|
|
8036
|
+
}
|
|
8037
|
+
if (character === '"') {
|
|
8038
|
+
inString = !inString;
|
|
8039
|
+
continue;
|
|
8040
|
+
}
|
|
8041
|
+
if (inString) {
|
|
8042
|
+
continue;
|
|
8043
|
+
}
|
|
8044
|
+
if (character === "{") {
|
|
8045
|
+
depth += 1;
|
|
8046
|
+
continue;
|
|
8047
|
+
}
|
|
8048
|
+
if (character === "}") {
|
|
8049
|
+
depth -= 1;
|
|
8050
|
+
if (depth === 0) {
|
|
8051
|
+
return value.slice(start, index + 1);
|
|
8052
|
+
}
|
|
8053
|
+
}
|
|
8054
|
+
}
|
|
8055
|
+
}
|
|
8056
|
+
return null;
|
|
8057
|
+
}
|
|
8058
|
+
function removeTrailingCommas(value) {
|
|
8059
|
+
let output = "";
|
|
8060
|
+
let inString = false;
|
|
8061
|
+
let escaping = false;
|
|
8062
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
8063
|
+
const character = value[index];
|
|
8064
|
+
if (escaping) {
|
|
8065
|
+
output += character;
|
|
8066
|
+
escaping = false;
|
|
8067
|
+
continue;
|
|
8068
|
+
}
|
|
8069
|
+
if (character === "\\") {
|
|
8070
|
+
output += character;
|
|
8071
|
+
escaping = true;
|
|
8072
|
+
continue;
|
|
8073
|
+
}
|
|
8074
|
+
if (character === '"') {
|
|
8075
|
+
output += character;
|
|
8076
|
+
inString = !inString;
|
|
8077
|
+
continue;
|
|
8078
|
+
}
|
|
8079
|
+
if (!inString && character === ",") {
|
|
8080
|
+
let lookahead = index + 1;
|
|
8081
|
+
while (lookahead < value.length && /\s/.test(value[lookahead] ?? "")) {
|
|
8082
|
+
lookahead += 1;
|
|
8083
|
+
}
|
|
8084
|
+
if (value[lookahead] === "}" || value[lookahead] === "]") {
|
|
8085
|
+
continue;
|
|
8086
|
+
}
|
|
8087
|
+
}
|
|
8088
|
+
output += character;
|
|
8089
|
+
}
|
|
8090
|
+
return output;
|
|
8091
|
+
}
|
|
8092
|
+
function escapeControlCharsInStrings(value) {
|
|
8093
|
+
let output = "";
|
|
8094
|
+
let inString = false;
|
|
8095
|
+
let escaping = false;
|
|
8096
|
+
for (const character of value) {
|
|
8097
|
+
if (escaping) {
|
|
8098
|
+
output += character;
|
|
8099
|
+
escaping = false;
|
|
8100
|
+
continue;
|
|
8101
|
+
}
|
|
8102
|
+
if (character === "\\") {
|
|
8103
|
+
output += character;
|
|
8104
|
+
escaping = true;
|
|
8105
|
+
continue;
|
|
8106
|
+
}
|
|
8107
|
+
if (character === '"') {
|
|
8108
|
+
output += character;
|
|
8109
|
+
inString = !inString;
|
|
8110
|
+
continue;
|
|
8111
|
+
}
|
|
8112
|
+
if (inString && character === "\n") {
|
|
8113
|
+
output += "\\n";
|
|
8114
|
+
continue;
|
|
8115
|
+
}
|
|
8116
|
+
if (inString && character === "\r") {
|
|
8117
|
+
output += "\\r";
|
|
8118
|
+
continue;
|
|
8119
|
+
}
|
|
8120
|
+
if (inString && character === " ") {
|
|
8121
|
+
output += "\\t";
|
|
8122
|
+
continue;
|
|
8123
|
+
}
|
|
8124
|
+
output += character;
|
|
8125
|
+
}
|
|
8126
|
+
return output;
|
|
8127
|
+
}
|
|
8128
|
+
function parseCandidate(candidate) {
|
|
8129
|
+
return JSON.parse(candidate);
|
|
8130
|
+
}
|
|
8131
|
+
function extractJsonObjectish(raw, context) {
|
|
8132
|
+
const stripped = maybeUnwrapQuotedJson(normalizeSmartQuotes(stripCodeFences(raw)));
|
|
8133
|
+
const extracted = findBalancedObject(stripped) ?? stripped;
|
|
8134
|
+
const candidates = Array.from(new Set([
|
|
8135
|
+
stripped,
|
|
8136
|
+
extracted,
|
|
8137
|
+
removeTrailingCommas(extracted),
|
|
8138
|
+
escapeControlCharsInStrings(removeTrailingCommas(extracted))
|
|
8139
|
+
].filter((candidate) => candidate.trim().length > 0)));
|
|
8140
|
+
let lastError;
|
|
8141
|
+
for (const candidate of candidates) {
|
|
8142
|
+
try {
|
|
8143
|
+
const parsed = parseCandidate(candidate);
|
|
8144
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8145
|
+
throw new Error(`Expected a JSON object in the ${context}.`);
|
|
8146
|
+
}
|
|
8147
|
+
return parsed;
|
|
8148
|
+
} catch (error) {
|
|
8149
|
+
lastError = error;
|
|
8150
|
+
}
|
|
8151
|
+
}
|
|
8152
|
+
if (lastError instanceof Error) {
|
|
8153
|
+
throw lastError;
|
|
8154
|
+
}
|
|
8155
|
+
throw new Error(`Expected a JSON object in the ${context}.`);
|
|
8156
|
+
}
|
|
8157
|
+
function summarizeStructuredOutputError(context, error) {
|
|
8158
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8159
|
+
if (/Expected a JSON object/i.test(message)) {
|
|
8160
|
+
return `${context} returned non-machine-readable output.`;
|
|
8161
|
+
}
|
|
8162
|
+
if (/Unexpected end of JSON input/i.test(message)) {
|
|
8163
|
+
return `${context} returned truncated JSON.`;
|
|
8164
|
+
}
|
|
8165
|
+
if (/JSON|property|position/i.test(message)) {
|
|
8166
|
+
return `${context} returned malformed JSON.`;
|
|
8167
|
+
}
|
|
8168
|
+
return `${context} returned invalid structured output.`;
|
|
8169
|
+
}
|
|
8170
|
+
|
|
7999
8171
|
// ../core/dist/config/config.js
|
|
8000
8172
|
var import_promises = require("node:fs/promises");
|
|
8001
8173
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
@@ -8415,6 +8587,10 @@ async function requestJson(url, init, timeoutMs = 3e4) {
|
|
|
8415
8587
|
}
|
|
8416
8588
|
return response.json();
|
|
8417
8589
|
}
|
|
8590
|
+
function isBadRequestError(error) {
|
|
8591
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8592
|
+
return /Request failed with 400\b/i.test(message);
|
|
8593
|
+
}
|
|
8418
8594
|
function trimTrailingSlash2(value) {
|
|
8419
8595
|
return value.replace(/\/+$/, "");
|
|
8420
8596
|
}
|
|
@@ -8796,20 +8972,39 @@ var OpenAICompatibleProvider = class {
|
|
|
8796
8972
|
}
|
|
8797
8973
|
const apiKey = resolveApiKey(this.definition);
|
|
8798
8974
|
const model = requireModel(this.definition, input);
|
|
8799
|
-
const
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8975
|
+
const requestBody = {
|
|
8976
|
+
model,
|
|
8977
|
+
messages: toChatMessages(input),
|
|
8978
|
+
...input.responseFormat === "json_object" ? {
|
|
8979
|
+
response_format: {
|
|
8980
|
+
type: "json_object"
|
|
8981
|
+
}
|
|
8982
|
+
} : {},
|
|
8983
|
+
...typeof input.temperature === "number" ? {
|
|
8984
|
+
temperature: input.temperature
|
|
8985
|
+
} : {},
|
|
8986
|
+
...typeof input.maxTokens === "number" ? {
|
|
8987
|
+
max_tokens: input.maxTokens
|
|
8988
|
+
} : {}
|
|
8989
|
+
};
|
|
8990
|
+
let response;
|
|
8991
|
+
try {
|
|
8992
|
+
response = await requestJson(`${this.definition.baseUrl}/chat/completions`, {
|
|
8993
|
+
method: "POST",
|
|
8994
|
+
headers: buildProviderHeaders(this.definition, apiKey),
|
|
8995
|
+
body: JSON.stringify(requestBody)
|
|
8996
|
+
}, GENERATION_TIMEOUT_MS);
|
|
8997
|
+
} catch (error) {
|
|
8998
|
+
if (!input.responseFormat || !isBadRequestError(error)) {
|
|
8999
|
+
throw error;
|
|
9000
|
+
}
|
|
9001
|
+
const { response_format: _ignored, ...fallbackBody } = requestBody;
|
|
9002
|
+
response = await requestJson(`${this.definition.baseUrl}/chat/completions`, {
|
|
9003
|
+
method: "POST",
|
|
9004
|
+
headers: buildProviderHeaders(this.definition, apiKey),
|
|
9005
|
+
body: JSON.stringify(fallbackBody)
|
|
9006
|
+
}, GENERATION_TIMEOUT_MS);
|
|
9007
|
+
}
|
|
8813
9008
|
const choices = Array.isArray(response.choices) ? response.choices : [];
|
|
8814
9009
|
const firstChoice = choices.length > 0 && typeof choices[0] === "object" && choices[0] !== null ? choices[0] : null;
|
|
8815
9010
|
const message = firstChoice && typeof firstChoice.message === "object" && firstChoice.message !== null ? firstChoice.message : null;
|
|
@@ -8852,26 +9047,46 @@ var OllamaProvider = class {
|
|
|
8852
9047
|
async generateText(input) {
|
|
8853
9048
|
const baseUrl = this.definition.baseUrl ?? "http://localhost:11434";
|
|
8854
9049
|
const model = requireModel(this.definition, input);
|
|
8855
|
-
const
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
},
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
}
|
|
8874
|
-
|
|
9050
|
+
const requestBody = {
|
|
9051
|
+
model,
|
|
9052
|
+
prompt: input.userPrompt ?? toPromptText(input),
|
|
9053
|
+
...input.systemPrompt ? {
|
|
9054
|
+
system: input.systemPrompt
|
|
9055
|
+
} : {},
|
|
9056
|
+
stream: false,
|
|
9057
|
+
...input.responseFormat === "json_object" ? {
|
|
9058
|
+
format: "json"
|
|
9059
|
+
} : {},
|
|
9060
|
+
...typeof input.temperature === "number" ? {
|
|
9061
|
+
options: {
|
|
9062
|
+
temperature: input.temperature
|
|
9063
|
+
}
|
|
9064
|
+
} : {}
|
|
9065
|
+
};
|
|
9066
|
+
let response;
|
|
9067
|
+
try {
|
|
9068
|
+
response = await requestJson(`${baseUrl}/api/generate`, {
|
|
9069
|
+
method: "POST",
|
|
9070
|
+
headers: {
|
|
9071
|
+
"content-type": "application/json",
|
|
9072
|
+
...this.definition.headers
|
|
9073
|
+
},
|
|
9074
|
+
body: JSON.stringify(requestBody)
|
|
9075
|
+
}, GENERATION_TIMEOUT_MS);
|
|
9076
|
+
} catch (error) {
|
|
9077
|
+
if (!input.responseFormat || !isBadRequestError(error)) {
|
|
9078
|
+
throw error;
|
|
9079
|
+
}
|
|
9080
|
+
const { format: _ignored, ...fallbackBody } = requestBody;
|
|
9081
|
+
response = await requestJson(`${baseUrl}/api/generate`, {
|
|
9082
|
+
method: "POST",
|
|
9083
|
+
headers: {
|
|
9084
|
+
"content-type": "application/json",
|
|
9085
|
+
...this.definition.headers
|
|
9086
|
+
},
|
|
9087
|
+
body: JSON.stringify(fallbackBody)
|
|
9088
|
+
}, GENERATION_TIMEOUT_MS);
|
|
9089
|
+
}
|
|
8875
9090
|
const usage = extractUsage(response);
|
|
8876
9091
|
return usage ? {
|
|
8877
9092
|
text: typeof response.response === "string" ? response.response : "",
|
|
@@ -10475,7 +10690,7 @@ async function generateNextPrisma(cwd, projectName) {
|
|
|
10475
10690
|
]
|
|
10476
10691
|
}),
|
|
10477
10692
|
"next-env.d.ts": '/// <reference types="next" />\n/// <reference types="next/image-types/global" />\n',
|
|
10478
|
-
"next.config.
|
|
10693
|
+
"next.config.mjs": "/** @type {import('next').NextConfig} */\nconst nextConfig = {};\n\nexport default nextConfig;\n",
|
|
10479
10694
|
".env.example": 'DATABASE_URL="postgresql://postgres:postgres@localhost:5432/app"\n',
|
|
10480
10695
|
"prisma/schema.prisma": [
|
|
10481
10696
|
"generator client {",
|
|
@@ -10507,6 +10722,8 @@ async function generateNextPrisma(cwd, projectName) {
|
|
|
10507
10722
|
"}"
|
|
10508
10723
|
].join("\n"),
|
|
10509
10724
|
"src/app/page.tsx": [
|
|
10725
|
+
`const title = "${title}";`,
|
|
10726
|
+
"",
|
|
10510
10727
|
"export default function HomePage() {",
|
|
10511
10728
|
" return (",
|
|
10512
10729
|
" <main style={{ fontFamily: 'Georgia, serif', padding: '4rem 1.5rem', maxWidth: 960, margin: '0 auto' }}>",
|
|
@@ -10873,6 +11090,29 @@ var READ_ONLY_SHELL_PREFIXES = [
|
|
|
10873
11090
|
"pnpm ls",
|
|
10874
11091
|
"yarn list"
|
|
10875
11092
|
];
|
|
11093
|
+
var VERIFICATION_SHELL_PREFIXES = [
|
|
11094
|
+
"npm test",
|
|
11095
|
+
"npm run test",
|
|
11096
|
+
"npm run build",
|
|
11097
|
+
"npm run lint",
|
|
11098
|
+
"pnpm test",
|
|
11099
|
+
"pnpm run test",
|
|
11100
|
+
"pnpm build",
|
|
11101
|
+
"pnpm run build",
|
|
11102
|
+
"pnpm lint",
|
|
11103
|
+
"pnpm run lint",
|
|
11104
|
+
"yarn test",
|
|
11105
|
+
"yarn build",
|
|
11106
|
+
"yarn lint",
|
|
11107
|
+
"bun test",
|
|
11108
|
+
"bun run test",
|
|
11109
|
+
"bun run build",
|
|
11110
|
+
"bun run lint",
|
|
11111
|
+
"tsc",
|
|
11112
|
+
"vitest",
|
|
11113
|
+
"jest",
|
|
11114
|
+
"next build"
|
|
11115
|
+
];
|
|
10876
11116
|
var DESTRUCTIVE_SHELL_PATTERNS = [
|
|
10877
11117
|
/\brm\s+-rf\b/i,
|
|
10878
11118
|
/\brm\s+-fr\b/i,
|
|
@@ -10894,6 +11134,10 @@ function isReadOnlyShellCommand(command) {
|
|
|
10894
11134
|
const normalized = command.trim().toLowerCase();
|
|
10895
11135
|
return READ_ONLY_SHELL_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(prefix));
|
|
10896
11136
|
}
|
|
11137
|
+
function isVerificationShellCommand(command) {
|
|
11138
|
+
const normalized = command.trim().toLowerCase();
|
|
11139
|
+
return VERIFICATION_SHELL_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix} `));
|
|
11140
|
+
}
|
|
10897
11141
|
function isDestructiveShellCommand(command) {
|
|
10898
11142
|
return DESTRUCTIVE_SHELL_PATTERNS.some((pattern) => pattern.test(command));
|
|
10899
11143
|
}
|
|
@@ -10963,7 +11207,7 @@ function enforceToolPolicy(toolId, input, descriptor, context) {
|
|
|
10963
11207
|
const approvalMode = context.approvalMode ?? "manual";
|
|
10964
11208
|
const command = typeof input.command === "string" ? input.command : "";
|
|
10965
11209
|
if (sandboxMode === "read-only") {
|
|
10966
|
-
if (toolId === "shell.exec" && isReadOnlyShellCommand(command)) {
|
|
11210
|
+
if (toolId === "shell.exec" && (isReadOnlyShellCommand(command) || isVerificationShellCommand(command))) {
|
|
10967
11211
|
return null;
|
|
10968
11212
|
}
|
|
10969
11213
|
if (descriptor.permission !== "safe") {
|
|
@@ -10975,6 +11219,9 @@ function enforceToolPolicy(toolId, input, descriptor, context) {
|
|
|
10975
11219
|
return new ToolApprovalRequiredError(createApprovalRequest(toolId, input, descriptor, context, `Approval required for destructive shell command: ${command}`));
|
|
10976
11220
|
}
|
|
10977
11221
|
}
|
|
11222
|
+
if (toolId === "shell.exec" && isVerificationShellCommand(command)) {
|
|
11223
|
+
return null;
|
|
11224
|
+
}
|
|
10978
11225
|
if ((toolId === "file.write" || toolId === "file.patch") && sandboxMode === "workspace-write") {
|
|
10979
11226
|
const targets = extractProspectiveWriteTargets(toolId, input, context.cwd);
|
|
10980
11227
|
const protectedTargets = targets.filter((target) => isProtectedWritePath(target));
|
|
@@ -11884,16 +12131,6 @@ function truncateForModel(value) {
|
|
|
11884
12131
|
return `${value.slice(0, MAX_TOOL_OUTPUT_CHARS)}
|
|
11885
12132
|
... [truncated]`;
|
|
11886
12133
|
}
|
|
11887
|
-
function extractJsonObject(raw) {
|
|
11888
|
-
const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
11889
|
-
const candidate = (fenced?.[1] ?? raw).trim();
|
|
11890
|
-
const firstBrace = candidate.indexOf("{");
|
|
11891
|
-
const lastBrace = candidate.lastIndexOf("}");
|
|
11892
|
-
if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
|
|
11893
|
-
throw new Error("Expected a JSON object in the model response.");
|
|
11894
|
-
}
|
|
11895
|
-
return JSON.parse(candidate.slice(firstBrace, lastBrace + 1));
|
|
11896
|
-
}
|
|
11897
12134
|
function renderToolResultForModel(result) {
|
|
11898
12135
|
const sections = [
|
|
11899
12136
|
`tool: ${result.toolId}`,
|
|
@@ -12009,6 +12246,7 @@ function buildSystemPrompt(agent, task, request, allowedTools, plan) {
|
|
|
12009
12246
|
`- Use browser.open, browser.inspect, browser.click, browser.fill, and browser.close when a web UI needs browser-level verification.`,
|
|
12010
12247
|
`- Use process.start/process.logs/process.stop for long-running dev servers or watchers when they are available.`,
|
|
12011
12248
|
`- Keep paths relative to the workspace.`,
|
|
12249
|
+
`- In an existing repo, inspect and change the likely source files before running build or test verification. Do not front-load verification unless you are confirming an already-completed change or diagnosing a failure.`,
|
|
12012
12250
|
`- After changing code, run verification with tests.run or shell.exec when appropriate.`,
|
|
12013
12251
|
`- Do not claim success unless the task acceptance criteria are satisfied.`,
|
|
12014
12252
|
`- If the task is underspecified, make a pragmatic implementation choice and continue.`
|
|
@@ -12061,6 +12299,9 @@ function summarizeSavedSessionContext(sessionId, session) {
|
|
|
12061
12299
|
...notes
|
|
12062
12300
|
];
|
|
12063
12301
|
}
|
|
12302
|
+
function summarizeModelActionError(error) {
|
|
12303
|
+
return summarizeStructuredOutputError("Model action", error);
|
|
12304
|
+
}
|
|
12064
12305
|
function buildInitialUserPrompt(task, request) {
|
|
12065
12306
|
return [
|
|
12066
12307
|
`Complete this task in the workspace.`,
|
|
@@ -12071,6 +12312,114 @@ function buildInitialUserPrompt(task, request) {
|
|
|
12071
12312
|
`Choose the next single action now.`
|
|
12072
12313
|
].join("\n");
|
|
12073
12314
|
}
|
|
12315
|
+
function extractCodeFences(raw) {
|
|
12316
|
+
const matches = Array.from(raw.matchAll(/```([a-z0-9_-]*)\s*\n([\s\S]*?)```/gi));
|
|
12317
|
+
return matches.map((match) => ({
|
|
12318
|
+
language: (match[1] ?? "").trim().toLowerCase(),
|
|
12319
|
+
code: (match[2] ?? "").trim()
|
|
12320
|
+
})).filter((match) => match.code.length > 0);
|
|
12321
|
+
}
|
|
12322
|
+
function normalizeWritableTaskFiles(task) {
|
|
12323
|
+
return Array.from(new Set(task.filesLikelyTouched.map((filePath) => filePath.trim()).filter((filePath) => filePath.length > 0 && !filePath.endsWith("/") && !filePath.includes("*") && !filePath.startsWith(".kimbho/"))));
|
|
12324
|
+
}
|
|
12325
|
+
function extractMentionedFilePaths(raw, task) {
|
|
12326
|
+
const mentioned = /* @__PURE__ */ new Set();
|
|
12327
|
+
const normalizedTaskFiles = normalizeWritableTaskFiles(task);
|
|
12328
|
+
const directMatches = raw.match(/([A-Za-z0-9_./-]+\.(?:tsx|ts|jsx|js|css|html|md|json|sql|prisma))/g) ?? [];
|
|
12329
|
+
for (const match of directMatches) {
|
|
12330
|
+
mentioned.add(match);
|
|
12331
|
+
}
|
|
12332
|
+
for (const candidate of normalizedTaskFiles) {
|
|
12333
|
+
const basename = import_node_path9.default.basename(candidate);
|
|
12334
|
+
if (raw.includes(candidate) || raw.includes(basename)) {
|
|
12335
|
+
mentioned.add(candidate);
|
|
12336
|
+
}
|
|
12337
|
+
}
|
|
12338
|
+
return Array.from(mentioned);
|
|
12339
|
+
}
|
|
12340
|
+
function inferFallbackFilePath(raw, task, fence) {
|
|
12341
|
+
const mentioned = extractMentionedFilePaths(raw, task);
|
|
12342
|
+
if (mentioned.length === 1) {
|
|
12343
|
+
return mentioned[0] ?? null;
|
|
12344
|
+
}
|
|
12345
|
+
const writableFiles = normalizeWritableTaskFiles(task);
|
|
12346
|
+
const sourceFiles = writableFiles.filter((filePath) => /\.[a-z0-9]+$/i.test(filePath));
|
|
12347
|
+
if (sourceFiles.length === 1) {
|
|
12348
|
+
return sourceFiles[0] ?? null;
|
|
12349
|
+
}
|
|
12350
|
+
if (fence.language === "html") {
|
|
12351
|
+
return sourceFiles.find((filePath) => filePath.endsWith(".html")) ?? null;
|
|
12352
|
+
}
|
|
12353
|
+
if (fence.language === "css") {
|
|
12354
|
+
return sourceFiles.find((filePath) => filePath.endsWith(".css")) ?? null;
|
|
12355
|
+
}
|
|
12356
|
+
if ([
|
|
12357
|
+
"tsx",
|
|
12358
|
+
"ts",
|
|
12359
|
+
"jsx",
|
|
12360
|
+
"js",
|
|
12361
|
+
"typescript",
|
|
12362
|
+
"javascript"
|
|
12363
|
+
].includes(fence.language)) {
|
|
12364
|
+
return sourceFiles.find((filePath) => /\.(tsx?|jsx?)$/i.test(filePath)) ?? null;
|
|
12365
|
+
}
|
|
12366
|
+
if (fence.language === "json") {
|
|
12367
|
+
return sourceFiles.find((filePath) => filePath.endsWith(".json")) ?? null;
|
|
12368
|
+
}
|
|
12369
|
+
return sourceFiles[0] ?? null;
|
|
12370
|
+
}
|
|
12371
|
+
function extractCommandFromResponse(raw) {
|
|
12372
|
+
const trimmed = raw.trim();
|
|
12373
|
+
if (/^(npm|pnpm|yarn|bun|npx|tsx|tsc|vitest|jest|next)\b/i.test(trimmed) && !trimmed.includes("\n")) {
|
|
12374
|
+
return trimmed;
|
|
12375
|
+
}
|
|
12376
|
+
const bashFence = extractCodeFences(raw).find((fence) => [
|
|
12377
|
+
"bash",
|
|
12378
|
+
"sh",
|
|
12379
|
+
"shell",
|
|
12380
|
+
"zsh"
|
|
12381
|
+
].includes(fence.language));
|
|
12382
|
+
if (!bashFence) {
|
|
12383
|
+
return null;
|
|
12384
|
+
}
|
|
12385
|
+
const lines = bashFence.code.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
12386
|
+
return lines.length === 1 ? lines[0] ?? null : null;
|
|
12387
|
+
}
|
|
12388
|
+
function inferFallbackActionFromResponse(raw, task, allowedTools) {
|
|
12389
|
+
if (allowedTools.includes("shell.exec")) {
|
|
12390
|
+
const command = extractCommandFromResponse(raw);
|
|
12391
|
+
if (command) {
|
|
12392
|
+
return {
|
|
12393
|
+
type: "tool",
|
|
12394
|
+
tool: "shell.exec",
|
|
12395
|
+
input: {
|
|
12396
|
+
command
|
|
12397
|
+
},
|
|
12398
|
+
reason: "Run the command inferred from the model response."
|
|
12399
|
+
};
|
|
12400
|
+
}
|
|
12401
|
+
}
|
|
12402
|
+
if (!allowedTools.includes("file.write")) {
|
|
12403
|
+
return null;
|
|
12404
|
+
}
|
|
12405
|
+
const fences = extractCodeFences(raw);
|
|
12406
|
+
for (const fence of fences) {
|
|
12407
|
+
const filePath = inferFallbackFilePath(raw, task, fence);
|
|
12408
|
+
if (!filePath) {
|
|
12409
|
+
continue;
|
|
12410
|
+
}
|
|
12411
|
+
return {
|
|
12412
|
+
type: "tool",
|
|
12413
|
+
tool: "file.write",
|
|
12414
|
+
input: {
|
|
12415
|
+
path: filePath,
|
|
12416
|
+
content: fence.code
|
|
12417
|
+
},
|
|
12418
|
+
reason: `Apply the drafted contents for ${filePath} inferred from the model response.`
|
|
12419
|
+
};
|
|
12420
|
+
}
|
|
12421
|
+
return null;
|
|
12422
|
+
}
|
|
12074
12423
|
function buildToolResultUserMessage(step, result) {
|
|
12075
12424
|
return [
|
|
12076
12425
|
`Step ${step} tool result:`,
|
|
@@ -12568,17 +12917,44 @@ var AutonomousTaskExecutor = class {
|
|
|
12568
12917
|
let responseText = "";
|
|
12569
12918
|
let parsedAction = null;
|
|
12570
12919
|
for (let attempt = 0; attempt <= MAX_PARSE_RETRIES; attempt += 1) {
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
...typeof brain.settings.maxTokens === "number" ? {
|
|
12579
|
-
maxTokens: brain.settings.maxTokens
|
|
12580
|
-
} : {}
|
|
12920
|
+
await emitProgress({
|
|
12921
|
+
type: "task-note",
|
|
12922
|
+
sessionId,
|
|
12923
|
+
taskId: task.id,
|
|
12924
|
+
agentRole: task.agentRole,
|
|
12925
|
+
step,
|
|
12926
|
+
message: attempt === 0 ? "Thinking through the next safe action." : `Re-asking the model for a stricter machine-readable action (${attempt + 1}/${MAX_PARSE_RETRIES + 1}).`
|
|
12581
12927
|
});
|
|
12928
|
+
let response;
|
|
12929
|
+
try {
|
|
12930
|
+
response = await brain.client.generateText({
|
|
12931
|
+
model: brain.model,
|
|
12932
|
+
systemPrompt: buildSystemPrompt(agent, task, request, allowedTools, options.plan),
|
|
12933
|
+
messages,
|
|
12934
|
+
responseFormat: "json_object",
|
|
12935
|
+
...typeof brain.settings.temperature === "number" ? {
|
|
12936
|
+
temperature: brain.settings.temperature
|
|
12937
|
+
} : {},
|
|
12938
|
+
...typeof brain.settings.maxTokens === "number" ? {
|
|
12939
|
+
maxTokens: brain.settings.maxTokens
|
|
12940
|
+
} : {}
|
|
12941
|
+
});
|
|
12942
|
+
} catch (error) {
|
|
12943
|
+
transcript.push({
|
|
12944
|
+
step,
|
|
12945
|
+
response: "",
|
|
12946
|
+
runtimeNote: `Model request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
12947
|
+
});
|
|
12948
|
+
const transcriptPath2 = await writeTranscriptArtifact(request.cwd, sessionId, task.id, transcript);
|
|
12949
|
+
artifacts.add(transcriptPath2);
|
|
12950
|
+
return {
|
|
12951
|
+
status: "blocked",
|
|
12952
|
+
summary: `Model request failed before ${task.id} could choose a safe action: ${error instanceof Error ? error.message : String(error)}`,
|
|
12953
|
+
toolResults,
|
|
12954
|
+
artifacts: Array.from(artifacts),
|
|
12955
|
+
usage: usageTotals
|
|
12956
|
+
};
|
|
12957
|
+
}
|
|
12582
12958
|
if (options.signal?.aborted) {
|
|
12583
12959
|
const transcriptPath2 = await writeTranscriptArtifact(request.cwd, sessionId, task.id, transcript);
|
|
12584
12960
|
artifacts.add(transcriptPath2);
|
|
@@ -12607,11 +12983,46 @@ var AutonomousTaskExecutor = class {
|
|
|
12607
12983
|
} : {}
|
|
12608
12984
|
});
|
|
12609
12985
|
try {
|
|
12610
|
-
parsedAction = actionSchema.parse(
|
|
12986
|
+
parsedAction = actionSchema.parse(extractJsonObjectish(response.text, "model response"));
|
|
12611
12987
|
break;
|
|
12612
12988
|
} catch (error) {
|
|
12989
|
+
const parseSummary = summarizeModelActionError(error);
|
|
12990
|
+
transcript.push({
|
|
12991
|
+
step,
|
|
12992
|
+
response: response.text,
|
|
12993
|
+
runtimeNote: `${parseSummary} ${error instanceof Error ? error.message : String(error)}`
|
|
12994
|
+
});
|
|
12613
12995
|
if (attempt === MAX_PARSE_RETRIES) {
|
|
12614
|
-
|
|
12996
|
+
const inferredFallbackAction = inferFallbackActionFromResponse(response.text, task, allowedTools);
|
|
12997
|
+
if (inferredFallbackAction) {
|
|
12998
|
+
parsedAction = inferredFallbackAction;
|
|
12999
|
+
await emitProgress({
|
|
13000
|
+
type: "task-note",
|
|
13001
|
+
sessionId,
|
|
13002
|
+
taskId: task.id,
|
|
13003
|
+
agentRole: task.agentRole,
|
|
13004
|
+
step,
|
|
13005
|
+
message: `Model stayed out of structured mode; inferred ${inferredFallbackAction.tool} from the response.`
|
|
13006
|
+
});
|
|
13007
|
+
break;
|
|
13008
|
+
}
|
|
13009
|
+
const transcriptPath2 = await writeTranscriptArtifact(request.cwd, sessionId, task.id, transcript);
|
|
13010
|
+
artifacts.add(transcriptPath2);
|
|
13011
|
+
await emitProgress({
|
|
13012
|
+
type: "task-note",
|
|
13013
|
+
sessionId,
|
|
13014
|
+
taskId: task.id,
|
|
13015
|
+
agentRole: task.agentRole,
|
|
13016
|
+
step,
|
|
13017
|
+
message: "Model stayed out of structured mode after multiple retries."
|
|
13018
|
+
});
|
|
13019
|
+
return {
|
|
13020
|
+
status: "blocked",
|
|
13021
|
+
summary: `${parseSummary} The task stopped before a safe tool action could be chosen.`,
|
|
13022
|
+
toolResults,
|
|
13023
|
+
artifacts: Array.from(artifacts),
|
|
13024
|
+
usage: usageTotals
|
|
13025
|
+
};
|
|
12615
13026
|
}
|
|
12616
13027
|
await emitProgress({
|
|
12617
13028
|
type: "task-note",
|
|
@@ -12619,7 +13030,7 @@ var AutonomousTaskExecutor = class {
|
|
|
12619
13030
|
taskId: task.id,
|
|
12620
13031
|
agentRole: task.agentRole,
|
|
12621
13032
|
step,
|
|
12622
|
-
message: `
|
|
13033
|
+
message: `Reformatting model output to match Kimbho's action schema (${attempt + 1}/${MAX_PARSE_RETRIES + 1}).`
|
|
12623
13034
|
});
|
|
12624
13035
|
messages.push({
|
|
12625
13036
|
role: "assistant",
|
|
@@ -12628,9 +13039,11 @@ var AutonomousTaskExecutor = class {
|
|
|
12628
13039
|
messages.push({
|
|
12629
13040
|
role: "user",
|
|
12630
13041
|
content: [
|
|
12631
|
-
"Your previous response
|
|
13042
|
+
"Your previous response did not parse as a valid JSON object.",
|
|
13043
|
+
parseSummary,
|
|
12632
13044
|
error instanceof Error ? error.message : String(error),
|
|
12633
|
-
"Return exactly one valid JSON object matching the required action schema."
|
|
13045
|
+
"Return exactly one valid JSON object matching the required action schema.",
|
|
13046
|
+
"Do not include prose, markdown fences, comments, or trailing explanations."
|
|
12634
13047
|
].join("\n")
|
|
12635
13048
|
});
|
|
12636
13049
|
}
|
|
@@ -12850,16 +13263,27 @@ function foundationMilestone(shape) {
|
|
|
12850
13263
|
"docs/",
|
|
12851
13264
|
"packages/"
|
|
12852
13265
|
], "medium"),
|
|
12853
|
-
buildTask("t3-scaffold", "Scaffold the repository foundation", "Create the workspace layout, TypeScript build config, package manifests, and initial CLI entry points.", shape === "cli-agent" ? "backend-specialist" : "infra-specialist", "scaffold", [
|
|
13266
|
+
buildTask("t3-scaffold", "Scaffold the repository foundation", shape === "static-site" ? "Create or adapt the primary landing-page entrypoint, styles, and workspace shell needed for the requested site." : "Create the workspace layout, TypeScript build config, package manifests, and initial CLI entry points.", shape === "cli-agent" ? "backend-specialist" : "infra-specialist", "scaffold", [
|
|
12854
13267
|
"t2-architecture"
|
|
12855
13268
|
], [
|
|
12856
|
-
"The monorepo or app workspace builds cleanly.",
|
|
12857
|
-
"Core commands or entry points exist."
|
|
12858
|
-
], [
|
|
12859
|
-
"package manifests",
|
|
12860
|
-
"TypeScript config",
|
|
12861
|
-
"CLI or app shell"
|
|
13269
|
+
shape === "static-site" ? "The primary page entry exists and renders the requested brand direction." : "The monorepo or app workspace builds cleanly.",
|
|
13270
|
+
shape === "static-site" ? "Core layout files or styles exist for the landing page." : "Core commands or entry points exist."
|
|
12862
13271
|
], [
|
|
13272
|
+
...shape === "static-site" ? [
|
|
13273
|
+
"Page entrypoint",
|
|
13274
|
+
"Stylesheet or layout shell"
|
|
13275
|
+
] : [
|
|
13276
|
+
"package manifests",
|
|
13277
|
+
"TypeScript config",
|
|
13278
|
+
"CLI or app shell"
|
|
13279
|
+
]
|
|
13280
|
+
], shape === "static-site" ? [
|
|
13281
|
+
"src/app/page.tsx",
|
|
13282
|
+
"src/app/layout.tsx",
|
|
13283
|
+
"src/pages/index.tsx",
|
|
13284
|
+
"index.html",
|
|
13285
|
+
"styles.css"
|
|
13286
|
+
] : [
|
|
12863
13287
|
"package.json",
|
|
12864
13288
|
"packages/",
|
|
12865
13289
|
"src/"
|
|
@@ -12882,6 +13306,9 @@ function implementationMilestone(shape) {
|
|
|
12882
13306
|
], [
|
|
12883
13307
|
"Customized landing page"
|
|
12884
13308
|
], [
|
|
13309
|
+
"src/app/page.tsx",
|
|
13310
|
+
"src/app/layout.tsx",
|
|
13311
|
+
"src/pages/index.tsx",
|
|
12885
13312
|
"index.html",
|
|
12886
13313
|
"styles.css",
|
|
12887
13314
|
"src/"
|
|
@@ -13147,16 +13574,6 @@ function createPlan(input) {
|
|
|
13147
13574
|
var DEFAULT_PLANNER_TEMPERATURE = 0.1;
|
|
13148
13575
|
var DEFAULT_PLANNER_MAX_TOKENS = 2400;
|
|
13149
13576
|
var MAX_REPLAN_CONTEXT_CHARS = 8e3;
|
|
13150
|
-
function extractJsonObject2(raw) {
|
|
13151
|
-
const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
13152
|
-
const candidate = (fenced?.[1] ?? raw).trim();
|
|
13153
|
-
const firstBrace = candidate.indexOf("{");
|
|
13154
|
-
const lastBrace = candidate.lastIndexOf("}");
|
|
13155
|
-
if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
|
|
13156
|
-
throw new Error("Expected a JSON object in the planner response.");
|
|
13157
|
-
}
|
|
13158
|
-
return JSON.parse(candidate.slice(firstBrace, lastBrace + 1));
|
|
13159
|
-
}
|
|
13160
13577
|
function validateTaskGraph(plan) {
|
|
13161
13578
|
const taskIds = /* @__PURE__ */ new Set();
|
|
13162
13579
|
for (const task of flattenPlanTasks(plan)) {
|
|
@@ -13300,7 +13717,7 @@ async function generatePlannerPlan(request, seedPlan, invocation, userPrompt, pr
|
|
|
13300
13717
|
temperature: invocation.temperature ?? DEFAULT_PLANNER_TEMPERATURE,
|
|
13301
13718
|
maxTokens: invocation.maxTokens ?? DEFAULT_PLANNER_MAX_TOKENS
|
|
13302
13719
|
});
|
|
13303
|
-
const parsed =
|
|
13720
|
+
const parsed = extractJsonObjectish(response.text, "planner response");
|
|
13304
13721
|
const candidatePlan = KimbhoPlanSchema.parse(parsed);
|
|
13305
13722
|
const normalized = normalizeCandidatePlan(request, seedPlan, candidatePlan, true, preserveStatusesFrom);
|
|
13306
13723
|
return {
|
|
@@ -13318,7 +13735,7 @@ async function generatePlannerPlan(request, seedPlan, invocation, userPrompt, pr
|
|
|
13318
13735
|
return {
|
|
13319
13736
|
plan: preserveStatusesFrom ? preserveTaskStatuses(preserveStatusesFrom, seedPlan) : seedPlan,
|
|
13320
13737
|
source: "fallback",
|
|
13321
|
-
warning:
|
|
13738
|
+
warning: summarizeStructuredOutputError("Planner", error),
|
|
13322
13739
|
...invocation.modelLabel ? {
|
|
13323
13740
|
modelLabel: invocation.modelLabel
|
|
13324
13741
|
} : {}
|
|
@@ -13361,16 +13778,6 @@ function truncate2(value, maxChars = MAX_EXPANSION_CONTEXT_CHARS) {
|
|
|
13361
13778
|
return `${value.slice(0, maxChars)}
|
|
13362
13779
|
... [truncated]`;
|
|
13363
13780
|
}
|
|
13364
|
-
function extractJsonObject3(raw) {
|
|
13365
|
-
const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
13366
|
-
const candidate = (fenced?.[1] ?? raw).trim();
|
|
13367
|
-
const firstBrace = candidate.indexOf("{");
|
|
13368
|
-
const lastBrace = candidate.lastIndexOf("}");
|
|
13369
|
-
if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
|
|
13370
|
-
throw new Error("Expected a JSON object in the task expansion response.");
|
|
13371
|
-
}
|
|
13372
|
-
return JSON.parse(candidate.slice(firstBrace, lastBrace + 1));
|
|
13373
|
-
}
|
|
13374
13781
|
function slugify(value) {
|
|
13375
13782
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
|
|
13376
13783
|
}
|
|
@@ -13676,7 +14083,7 @@ async function generateExpandedTasks(request, plan, task, invocation, repoSummar
|
|
|
13676
14083
|
temperature: invocation.temperature ?? DEFAULT_EXPANDER_TEMPERATURE,
|
|
13677
14084
|
maxTokens: invocation.maxTokens ?? DEFAULT_EXPANDER_MAX_TOKENS
|
|
13678
14085
|
});
|
|
13679
|
-
const parsed =
|
|
14086
|
+
const parsed = extractJsonObjectish(response.text, "task expansion response");
|
|
13680
14087
|
const payload = TaskExpansionPayloadSchema.parse(parsed);
|
|
13681
14088
|
const normalizedTasks = normalizeExpandedTasks(plan, task, payload, "model");
|
|
13682
14089
|
const note = `Planner expanded ${task.id} into ${normalizedTasks.length} subtasks via ${invocation.modelLabel ?? "planner model"}: ${normalizedTasks.map((candidate) => candidate.id).join(", ")}.`;
|
|
@@ -13715,7 +14122,7 @@ async function generateExpandedTasks(request, plan, task, invocation, repoSummar
|
|
|
13715
14122
|
expandedTaskId: task.id,
|
|
13716
14123
|
createdTaskIds: fallbackTasks.map((candidate) => candidate.id),
|
|
13717
14124
|
note,
|
|
13718
|
-
warning:
|
|
14125
|
+
warning: summarizeStructuredOutputError("Planner task expansion", error),
|
|
13719
14126
|
...invocation.modelLabel ? {
|
|
13720
14127
|
modelLabel: invocation.modelLabel
|
|
13721
14128
|
} : {}
|
|
@@ -15446,14 +15853,25 @@ var ExecutionOrchestrator = class {
|
|
|
15446
15853
|
async executeRepoAnalysisTask(sessionId, task, request, emitProgress, signal) {
|
|
15447
15854
|
const context = { cwd: request.cwd };
|
|
15448
15855
|
const toolResults = [];
|
|
15449
|
-
const probes =
|
|
15856
|
+
const probes = [
|
|
15450
15857
|
{ toolId: "repo.index", input: {} }
|
|
15451
|
-
] : [
|
|
15452
|
-
{ toolId: "repo.index", input: {} },
|
|
15453
|
-
{ toolId: "git.status", input: {} },
|
|
15454
|
-
{ toolId: "file.read", input: { path: "package.json" } },
|
|
15455
|
-
{ toolId: "file.read", input: { path: "README.md" } }
|
|
15456
15858
|
];
|
|
15859
|
+
if (request.workspaceState !== "empty") {
|
|
15860
|
+
const candidatePaths = await Promise.all([
|
|
15861
|
+
(0, import_promises11.access)(import_node_path11.default.join(request.cwd, ".git")).then(() => true).catch(() => false),
|
|
15862
|
+
(0, import_promises11.access)(import_node_path11.default.join(request.cwd, "package.json")).then(() => true).catch(() => false),
|
|
15863
|
+
(0, import_promises11.access)(import_node_path11.default.join(request.cwd, "README.md")).then(() => true).catch(() => false)
|
|
15864
|
+
]);
|
|
15865
|
+
if (candidatePaths[0]) {
|
|
15866
|
+
probes.push({ toolId: "git.status", input: {} });
|
|
15867
|
+
}
|
|
15868
|
+
if (candidatePaths[1]) {
|
|
15869
|
+
probes.push({ toolId: "file.read", input: { path: "package.json" } });
|
|
15870
|
+
}
|
|
15871
|
+
if (candidatePaths[2]) {
|
|
15872
|
+
probes.push({ toolId: "file.read", input: { path: "README.md" } });
|
|
15873
|
+
}
|
|
15874
|
+
}
|
|
15457
15875
|
for (const probe of probes) {
|
|
15458
15876
|
if (emitProgress) {
|
|
15459
15877
|
await emitProgress({
|
|
@@ -15570,6 +15988,7 @@ var ExecutionOrchestrator = class {
|
|
|
15570
15988
|
input.systemPrompt
|
|
15571
15989
|
].filter(Boolean).join("\n\n"),
|
|
15572
15990
|
userPrompt: input.userPrompt,
|
|
15991
|
+
responseFormat: "json_object",
|
|
15573
15992
|
...typeof input.temperature === "number" ? {
|
|
15574
15993
|
temperature: input.temperature
|
|
15575
15994
|
} : {},
|
|
@@ -15587,7 +16006,7 @@ var ExecutionOrchestrator = class {
|
|
|
15587
16006
|
if (result.warning) {
|
|
15588
16007
|
return {
|
|
15589
16008
|
plan,
|
|
15590
|
-
note:
|
|
16009
|
+
note: `${result.warning} Keeping the current task graph.`
|
|
15591
16010
|
};
|
|
15592
16011
|
}
|
|
15593
16012
|
return {
|
|
@@ -15653,6 +16072,7 @@ var ExecutionOrchestrator = class {
|
|
|
15653
16072
|
input.systemPrompt
|
|
15654
16073
|
].filter(Boolean).join("\n\n"),
|
|
15655
16074
|
userPrompt: input.userPrompt,
|
|
16075
|
+
responseFormat: "json_object",
|
|
15656
16076
|
...typeof input.temperature === "number" ? {
|
|
15657
16077
|
temperature: input.temperature
|
|
15658
16078
|
} : {},
|
|
@@ -16330,6 +16750,7 @@ async function generatePlanForRequest(request) {
|
|
|
16330
16750
|
input.systemPrompt
|
|
16331
16751
|
].filter(Boolean).join("\n\n"),
|
|
16332
16752
|
userPrompt: input.userPrompt,
|
|
16753
|
+
responseFormat: "json_object",
|
|
16333
16754
|
...typeof input.temperature === "number" ? {
|
|
16334
16755
|
temperature: input.temperature
|
|
16335
16756
|
} : {},
|
|
@@ -16359,7 +16780,8 @@ function renderPlanGenerationNotes(result) {
|
|
|
16359
16780
|
lines.push(`planner tokens: ${result.usage.inputTokens} in / ${result.usage.outputTokens} out`);
|
|
16360
16781
|
}
|
|
16361
16782
|
if (result.warning) {
|
|
16362
|
-
|
|
16783
|
+
const warning = result.source === "fallback" ? `${summarizeStructuredOutputError("Planner", new Error(result.warning))} Using the safe default plan.` : result.warning;
|
|
16784
|
+
lines.push(`planner note: ${warning}`);
|
|
16363
16785
|
}
|
|
16364
16786
|
return lines;
|
|
16365
16787
|
}
|
|
@@ -17037,6 +17459,7 @@ function createProgram(onOpenShell) {
|
|
|
17037
17459
|
}
|
|
17038
17460
|
|
|
17039
17461
|
// src/shell.ts
|
|
17462
|
+
var import_node_readline = require("node:readline");
|
|
17040
17463
|
var import_promises14 = require("node:readline/promises");
|
|
17041
17464
|
var import_node_process12 = __toESM(require("node:process"), 1);
|
|
17042
17465
|
var AMBER = "\x1B[38;5;214m";
|
|
@@ -17045,7 +17468,9 @@ var BOLD = "\x1B[1m";
|
|
|
17045
17468
|
var DIM = "\x1B[2m";
|
|
17046
17469
|
var RESET = "\x1B[0m";
|
|
17047
17470
|
var TOP_LEVEL_COMMANDS = /* @__PURE__ */ new Set([
|
|
17471
|
+
"approval",
|
|
17048
17472
|
"approve",
|
|
17473
|
+
"approve-all",
|
|
17049
17474
|
"agents",
|
|
17050
17475
|
"brain",
|
|
17051
17476
|
"brains",
|
|
@@ -17089,6 +17514,20 @@ var MAX_CHAT_MESSAGES = 12;
|
|
|
17089
17514
|
var DEFAULT_MAX_AUTO_TASKS = 3;
|
|
17090
17515
|
var DEFAULT_MAX_AGENT_STEPS = 8;
|
|
17091
17516
|
var DEFAULT_MAX_REPAIR_ATTEMPTS2 = 2;
|
|
17517
|
+
var SPINNER_FRAMES = [
|
|
17518
|
+
"-",
|
|
17519
|
+
"\\",
|
|
17520
|
+
"|",
|
|
17521
|
+
"/"
|
|
17522
|
+
];
|
|
17523
|
+
var IDLE_STATUS_LABELS = [
|
|
17524
|
+
"thinking",
|
|
17525
|
+
"musing",
|
|
17526
|
+
"discombobulating",
|
|
17527
|
+
"assembling",
|
|
17528
|
+
"drafting",
|
|
17529
|
+
"tinkering"
|
|
17530
|
+
];
|
|
17092
17531
|
var EXECUTION_PREFIXES = [
|
|
17093
17532
|
"build ",
|
|
17094
17533
|
"create ",
|
|
@@ -17148,6 +17587,71 @@ function createExecutionTelemetry() {
|
|
|
17148
17587
|
outputTokens: 0
|
|
17149
17588
|
};
|
|
17150
17589
|
}
|
|
17590
|
+
var ShellActivityIndicator = class {
|
|
17591
|
+
interval = null;
|
|
17592
|
+
frameIndex = 0;
|
|
17593
|
+
label;
|
|
17594
|
+
activeLine = false;
|
|
17595
|
+
constructor(label) {
|
|
17596
|
+
this.label = label;
|
|
17597
|
+
}
|
|
17598
|
+
start() {
|
|
17599
|
+
if (!import_node_process12.default.stdout.isTTY || this.interval) {
|
|
17600
|
+
return;
|
|
17601
|
+
}
|
|
17602
|
+
this.activeLine = true;
|
|
17603
|
+
this.render();
|
|
17604
|
+
this.interval = setInterval(() => {
|
|
17605
|
+
this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
|
|
17606
|
+
this.render();
|
|
17607
|
+
}, 120);
|
|
17608
|
+
this.interval.unref?.();
|
|
17609
|
+
}
|
|
17610
|
+
update(label) {
|
|
17611
|
+
this.label = label;
|
|
17612
|
+
if (this.interval) {
|
|
17613
|
+
this.render();
|
|
17614
|
+
}
|
|
17615
|
+
}
|
|
17616
|
+
suspend() {
|
|
17617
|
+
if (!import_node_process12.default.stdout.isTTY) {
|
|
17618
|
+
return;
|
|
17619
|
+
}
|
|
17620
|
+
this.clear();
|
|
17621
|
+
}
|
|
17622
|
+
resume() {
|
|
17623
|
+
if (!import_node_process12.default.stdout.isTTY || !this.interval) {
|
|
17624
|
+
return;
|
|
17625
|
+
}
|
|
17626
|
+
this.render();
|
|
17627
|
+
}
|
|
17628
|
+
stop() {
|
|
17629
|
+
if (this.interval) {
|
|
17630
|
+
clearInterval(this.interval);
|
|
17631
|
+
this.interval = null;
|
|
17632
|
+
}
|
|
17633
|
+
this.clear();
|
|
17634
|
+
this.activeLine = false;
|
|
17635
|
+
}
|
|
17636
|
+
render() {
|
|
17637
|
+
if (!import_node_process12.default.stdout.isTTY) {
|
|
17638
|
+
return;
|
|
17639
|
+
}
|
|
17640
|
+
const frame = color(AMBER, SPINNER_FRAMES[this.frameIndex]);
|
|
17641
|
+
const status = color(BOLD, this.label);
|
|
17642
|
+
const raw = `${frame} ${status}${color(DIM, "...")}`;
|
|
17643
|
+
this.clear();
|
|
17644
|
+
(0, import_node_readline.cursorTo)(import_node_process12.default.stdout, 0);
|
|
17645
|
+
import_node_process12.default.stdout.write(raw);
|
|
17646
|
+
}
|
|
17647
|
+
clear() {
|
|
17648
|
+
if (!import_node_process12.default.stdout.isTTY || !this.activeLine) {
|
|
17649
|
+
return;
|
|
17650
|
+
}
|
|
17651
|
+
(0, import_node_readline.cursorTo)(import_node_process12.default.stdout, 0);
|
|
17652
|
+
(0, import_node_readline.clearLine)(import_node_process12.default.stdout, 0);
|
|
17653
|
+
}
|
|
17654
|
+
};
|
|
17151
17655
|
function renderExecutionTelemetry(telemetry, startedAt) {
|
|
17152
17656
|
return `telemetry: ${((Date.now() - startedAt) / 1e3).toFixed(1)}s | ${telemetry.toolCalls} tools | ${telemetry.modelCalls} model calls | ${telemetry.inputTokens} in / ${telemetry.outputTokens} out`;
|
|
17153
17657
|
}
|
|
@@ -17337,7 +17841,9 @@ function renderHelp() {
|
|
|
17337
17841
|
"/plan <goal> Create a structured implementation plan.",
|
|
17338
17842
|
"/run <goal> Start a Kimbho execution session for a goal.",
|
|
17339
17843
|
"/resume Show the latest saved session.",
|
|
17844
|
+
"/approval [mode] Show or set approval mode: manual or auto.",
|
|
17340
17845
|
"/approve [id] Approve a pending risky action and continue the session.",
|
|
17846
|
+
"/approve-all Approve all pending actions in the current session.",
|
|
17341
17847
|
"/deny [id] Deny a pending risky action.",
|
|
17342
17848
|
"/agents Inspect agent roles and the active session.",
|
|
17343
17849
|
"/review Review the current git diff and summarize risk.",
|
|
@@ -17362,7 +17868,7 @@ function renderStartupCard(cwd, state) {
|
|
|
17362
17868
|
renderCardLine("approval", state.approvalMode),
|
|
17363
17869
|
renderCardLine("sandbox", state.sandboxMode),
|
|
17364
17870
|
renderCardLine("preset", state.stackPreset),
|
|
17365
|
-
renderCardLine("shortcuts", "/ask /run /
|
|
17871
|
+
renderCardLine("shortcuts", "/ask /run /approval /approve-all /models /quit")
|
|
17366
17872
|
];
|
|
17367
17873
|
if (!state.configured) {
|
|
17368
17874
|
cardLines.push("setup: run /init or /providers add <template> to create .kimbho/config.json");
|
|
@@ -17446,6 +17952,106 @@ function renderEventType(type) {
|
|
|
17446
17952
|
function renderInlineMarkdown(value) {
|
|
17447
17953
|
return value.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, url) => `${color(CYAN, label)} ${color(DIM, `(${url})`)}`).replace(/`([^`]+)`/g, (_match, code) => color(CYAN, code)).replace(/\*\*([^*]+)\*\*/g, (_match, boldText) => color(BOLD, boldText)).replace(/\*([^*]+)\*/g, (_match, italicText) => color(DIM, italicText));
|
|
17448
17954
|
}
|
|
17955
|
+
function hashString(value) {
|
|
17956
|
+
let hash = 0;
|
|
17957
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
17958
|
+
hash = (hash << 5) - hash + value.charCodeAt(index);
|
|
17959
|
+
hash |= 0;
|
|
17960
|
+
}
|
|
17961
|
+
return Math.abs(hash);
|
|
17962
|
+
}
|
|
17963
|
+
function pickStatusLabel(message) {
|
|
17964
|
+
const lower = message.toLowerCase();
|
|
17965
|
+
const choose = (labels) => labels[hashString(message) % labels.length];
|
|
17966
|
+
if (lower.includes("synthesizing") || lower.includes("architecture brief")) {
|
|
17967
|
+
return choose([
|
|
17968
|
+
"planning",
|
|
17969
|
+
"mapping",
|
|
17970
|
+
"sketching"
|
|
17971
|
+
]);
|
|
17972
|
+
}
|
|
17973
|
+
if (lower.includes("repo context") || lower.includes("repo analysis") || lower.includes("deterministic repo context")) {
|
|
17974
|
+
return choose([
|
|
17975
|
+
"orienting",
|
|
17976
|
+
"surveying",
|
|
17977
|
+
"mapping"
|
|
17978
|
+
]);
|
|
17979
|
+
}
|
|
17980
|
+
if (lower.includes("using ") && lower.includes(" via ")) {
|
|
17981
|
+
return choose([
|
|
17982
|
+
"warming up",
|
|
17983
|
+
"locking in",
|
|
17984
|
+
"switching gears"
|
|
17985
|
+
]);
|
|
17986
|
+
}
|
|
17987
|
+
if (lower.includes("reformatting model output") || lower.includes("structured mode")) {
|
|
17988
|
+
return choose([
|
|
17989
|
+
"reformatting",
|
|
17990
|
+
"straightening",
|
|
17991
|
+
"coaxing"
|
|
17992
|
+
]);
|
|
17993
|
+
}
|
|
17994
|
+
if (lower.includes("expanded") || lower.includes("revised the task graph") || lower.includes("keeping the current task graph")) {
|
|
17995
|
+
return choose([
|
|
17996
|
+
"replanning",
|
|
17997
|
+
"reshaping",
|
|
17998
|
+
"rerouting"
|
|
17999
|
+
]);
|
|
18000
|
+
}
|
|
18001
|
+
if (lower.includes("approval")) {
|
|
18002
|
+
return choose([
|
|
18003
|
+
"waiting",
|
|
18004
|
+
"holding",
|
|
18005
|
+
"pausing"
|
|
18006
|
+
]);
|
|
18007
|
+
}
|
|
18008
|
+
if (lower.includes("integration") || lower.includes("worktree")) {
|
|
18009
|
+
return choose([
|
|
18010
|
+
"reconciling",
|
|
18011
|
+
"merging",
|
|
18012
|
+
"untangling"
|
|
18013
|
+
]);
|
|
18014
|
+
}
|
|
18015
|
+
return choose([
|
|
18016
|
+
"thinking",
|
|
18017
|
+
"musing",
|
|
18018
|
+
"assembling",
|
|
18019
|
+
"checking"
|
|
18020
|
+
]);
|
|
18021
|
+
}
|
|
18022
|
+
function pickIdleStatusLabel(seed) {
|
|
18023
|
+
return IDLE_STATUS_LABELS[hashString(seed) % IDLE_STATUS_LABELS.length];
|
|
18024
|
+
}
|
|
18025
|
+
function statusLabelForEvent(event) {
|
|
18026
|
+
switch (event.type) {
|
|
18027
|
+
case "task-note":
|
|
18028
|
+
return pickStatusLabel(event.message);
|
|
18029
|
+
case "task-started":
|
|
18030
|
+
return pickStatusLabel(event.task.title);
|
|
18031
|
+
case "tool-started":
|
|
18032
|
+
return pickStatusLabel(event.reason ?? event.toolId);
|
|
18033
|
+
case "model-usage":
|
|
18034
|
+
return "thinking";
|
|
18035
|
+
case "approval-requested":
|
|
18036
|
+
return "waiting";
|
|
18037
|
+
case "approval-resolved":
|
|
18038
|
+
return "continuing";
|
|
18039
|
+
case "task-finished":
|
|
18040
|
+
return event.status === "handoff" ? "rerouting" : "settling";
|
|
18041
|
+
default:
|
|
18042
|
+
return "thinking";
|
|
18043
|
+
}
|
|
18044
|
+
}
|
|
18045
|
+
function simplifyTaskNote(message) {
|
|
18046
|
+
const trimmed = message.trim();
|
|
18047
|
+
if (trimmed.startsWith("Using ") && trimmed.includes(" via ")) {
|
|
18048
|
+
return trimmed.replace(/^Using\s+/, "");
|
|
18049
|
+
}
|
|
18050
|
+
if (trimmed.startsWith("Reformatting model output")) {
|
|
18051
|
+
return trimmed;
|
|
18052
|
+
}
|
|
18053
|
+
return trimmed;
|
|
18054
|
+
}
|
|
17449
18055
|
function renderTerminalMarkdown(markdown) {
|
|
17450
18056
|
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
17451
18057
|
const output = [];
|
|
@@ -17658,7 +18264,7 @@ function renderLiveExecutionEvent(event) {
|
|
|
17658
18264
|
];
|
|
17659
18265
|
case "task-note":
|
|
17660
18266
|
return [
|
|
17661
|
-
`${color(
|
|
18267
|
+
`${color(AMBER, `[${pickStatusLabel(event.message)}]`)} ${simplifyTaskNote(event.message)}`
|
|
17662
18268
|
];
|
|
17663
18269
|
case "approval-requested":
|
|
17664
18270
|
return [
|
|
@@ -17791,6 +18397,8 @@ async function handleChatPrompt(cwd, prompt, runtime) {
|
|
|
17791
18397
|
}
|
|
17792
18398
|
]);
|
|
17793
18399
|
let result;
|
|
18400
|
+
const activity = new ShellActivityIndicator(pickIdleStatusLabel(prompt));
|
|
18401
|
+
activity.start();
|
|
17794
18402
|
try {
|
|
17795
18403
|
result = await brain.client.generateText({
|
|
17796
18404
|
model: brain.model,
|
|
@@ -17806,9 +18414,11 @@ async function handleChatPrompt(cwd, prompt, runtime) {
|
|
|
17806
18414
|
} : {}
|
|
17807
18415
|
});
|
|
17808
18416
|
} catch (error) {
|
|
18417
|
+
activity.stop();
|
|
17809
18418
|
const message = error instanceof Error ? error.message : String(error);
|
|
17810
18419
|
throw new Error(`Chat failed for ${brain.role} via ${brain.provider.id}/${brain.model}: ${message}`);
|
|
17811
18420
|
}
|
|
18421
|
+
activity.stop();
|
|
17812
18422
|
const nextConversation = trimConversation([
|
|
17813
18423
|
...messages,
|
|
17814
18424
|
{
|
|
@@ -17833,13 +18443,29 @@ async function runGoalExecution(cwd, goal, runtime) {
|
|
|
17833
18443
|
workspaceState: workspace.workspaceState,
|
|
17834
18444
|
constraints: []
|
|
17835
18445
|
};
|
|
17836
|
-
const
|
|
18446
|
+
const startedAt = Date.now();
|
|
18447
|
+
const telemetry = createExecutionTelemetry();
|
|
18448
|
+
const controller = new AbortController();
|
|
18449
|
+
runtime.activeExecution = {
|
|
18450
|
+
controller,
|
|
18451
|
+
label: goal
|
|
18452
|
+
};
|
|
18453
|
+
const planningSpinner = new ShellActivityIndicator("planning");
|
|
18454
|
+
console.log(color(DIM, `Working on: ${goal}`));
|
|
18455
|
+
for (const note of workspace.notes) {
|
|
18456
|
+
console.log(color(DIM, note));
|
|
18457
|
+
}
|
|
18458
|
+
planningSpinner.start();
|
|
18459
|
+
let planResult;
|
|
18460
|
+
try {
|
|
18461
|
+
planResult = await generatePlanForRequest(request);
|
|
18462
|
+
} finally {
|
|
18463
|
+
planningSpinner.stop();
|
|
18464
|
+
}
|
|
17837
18465
|
const plan = planResult.plan;
|
|
17838
18466
|
const planPath = await savePlan(plan, request.cwd);
|
|
17839
18467
|
const envelope = orchestrator.buildEnvelope(request, plan);
|
|
17840
18468
|
const initialSnapshot = orchestrator.createSessionSnapshot(envelope);
|
|
17841
|
-
const startedAt = Date.now();
|
|
17842
|
-
const telemetry = createExecutionTelemetry();
|
|
17843
18469
|
const liveBoard = createLiveRunBoard(
|
|
17844
18470
|
initialSnapshot.id,
|
|
17845
18471
|
goal,
|
|
@@ -17849,21 +18475,14 @@ async function runGoalExecution(cwd, goal, runtime) {
|
|
|
17849
18475
|
DEFAULT_MAX_AGENT_STEPS,
|
|
17850
18476
|
DEFAULT_MAX_REPAIR_ATTEMPTS2
|
|
17851
18477
|
);
|
|
17852
|
-
const controller = new AbortController();
|
|
17853
|
-
runtime.activeExecution = {
|
|
17854
|
-
controller,
|
|
17855
|
-
label: goal
|
|
17856
|
-
};
|
|
17857
|
-
console.log(color(DIM, `Working on: ${goal}`));
|
|
17858
|
-
for (const note of workspace.notes) {
|
|
17859
|
-
console.log(color(DIM, note));
|
|
17860
|
-
}
|
|
17861
18478
|
for (const line of renderPlanGenerationNotes(planResult)) {
|
|
17862
18479
|
console.log(color(DIM, line));
|
|
17863
18480
|
}
|
|
17864
18481
|
console.log(renderShellPlanPreview(plan).join("\n"));
|
|
17865
18482
|
console.log(renderRunStartCard(liveBoard));
|
|
17866
18483
|
console.log("");
|
|
18484
|
+
const activity = new ShellActivityIndicator("starting");
|
|
18485
|
+
activity.start();
|
|
17867
18486
|
let snapshot;
|
|
17868
18487
|
try {
|
|
17869
18488
|
snapshot = await orchestrator.continueSession(initialSnapshot, {
|
|
@@ -17881,6 +18500,8 @@ async function runGoalExecution(cwd, goal, runtime) {
|
|
|
17881
18500
|
telemetry.inputTokens += event.usage?.inputTokens ?? 0;
|
|
17882
18501
|
telemetry.outputTokens += event.usage?.outputTokens ?? 0;
|
|
17883
18502
|
}
|
|
18503
|
+
activity.update(statusLabelForEvent(event));
|
|
18504
|
+
activity.suspend();
|
|
17884
18505
|
for (const line of renderLiveExecutionEvent(event)) {
|
|
17885
18506
|
console.log(line);
|
|
17886
18507
|
}
|
|
@@ -17889,12 +18510,15 @@ async function runGoalExecution(cwd, goal, runtime) {
|
|
|
17889
18510
|
console.log(line);
|
|
17890
18511
|
}
|
|
17891
18512
|
}
|
|
18513
|
+
activity.resume();
|
|
17892
18514
|
}
|
|
17893
18515
|
});
|
|
17894
18516
|
} catch (error) {
|
|
18517
|
+
activity.stop();
|
|
17895
18518
|
const message = error instanceof Error ? error.message : String(error);
|
|
17896
18519
|
throw new Error(`Execution failed while working on "${goal}": ${message}`);
|
|
17897
18520
|
} finally {
|
|
18521
|
+
activity.stop();
|
|
17898
18522
|
runtime.activeExecution = null;
|
|
17899
18523
|
}
|
|
17900
18524
|
const sessionPath = await saveSession(snapshot, request.cwd);
|
|
@@ -17928,6 +18552,8 @@ async function resumeGoalExecution(cwd, runtime) {
|
|
|
17928
18552
|
label: session.id
|
|
17929
18553
|
};
|
|
17930
18554
|
console.log(renderRunStartCard(liveBoard));
|
|
18555
|
+
const activity = new ShellActivityIndicator("resuming");
|
|
18556
|
+
activity.start();
|
|
17931
18557
|
let snapshot;
|
|
17932
18558
|
try {
|
|
17933
18559
|
snapshot = await new ExecutionOrchestrator().continueSession(session, {
|
|
@@ -17945,6 +18571,8 @@ async function resumeGoalExecution(cwd, runtime) {
|
|
|
17945
18571
|
telemetry.inputTokens += event.usage?.inputTokens ?? 0;
|
|
17946
18572
|
telemetry.outputTokens += event.usage?.outputTokens ?? 0;
|
|
17947
18573
|
}
|
|
18574
|
+
activity.update(statusLabelForEvent(event));
|
|
18575
|
+
activity.suspend();
|
|
17948
18576
|
for (const line of renderLiveExecutionEvent(event)) {
|
|
17949
18577
|
console.log(line);
|
|
17950
18578
|
}
|
|
@@ -17953,12 +18581,15 @@ async function resumeGoalExecution(cwd, runtime) {
|
|
|
17953
18581
|
console.log(line);
|
|
17954
18582
|
}
|
|
17955
18583
|
}
|
|
18584
|
+
activity.resume();
|
|
17956
18585
|
}
|
|
17957
18586
|
});
|
|
17958
18587
|
} catch (error) {
|
|
18588
|
+
activity.stop();
|
|
17959
18589
|
const message = error instanceof Error ? error.message : String(error);
|
|
17960
18590
|
throw new Error(`Resume failed for ${session.id}: ${message}`);
|
|
17961
18591
|
} finally {
|
|
18592
|
+
activity.stop();
|
|
17962
18593
|
runtime.activeExecution = null;
|
|
17963
18594
|
}
|
|
17964
18595
|
const sessionPath = await saveSession(snapshot, cwd);
|
|
@@ -17967,29 +18598,42 @@ async function resumeGoalExecution(cwd, runtime) {
|
|
|
17967
18598
|
console.log(color(DIM, renderExecutionTelemetry(telemetry, startedAt)));
|
|
17968
18599
|
console.log(renderShellSessionSummary(snapshot, null, sessionPath));
|
|
17969
18600
|
}
|
|
17970
|
-
function
|
|
18601
|
+
function resolveApprovalChoices(snapshot, requestedId, options = {}) {
|
|
17971
18602
|
if (snapshot.pendingApprovals.length === 0) {
|
|
17972
18603
|
throw new Error("No pending approvals in the current session.");
|
|
17973
18604
|
}
|
|
18605
|
+
if (requestedId === "all") {
|
|
18606
|
+
if (!options.allowAll) {
|
|
18607
|
+
throw new Error("Use /approve-all to approve every pending action.");
|
|
18608
|
+
}
|
|
18609
|
+
return [
|
|
18610
|
+
...snapshot.pendingApprovals
|
|
18611
|
+
];
|
|
18612
|
+
}
|
|
17974
18613
|
if (requestedId) {
|
|
17975
18614
|
const approval = snapshot.pendingApprovals.find((candidate) => candidate.id === requestedId);
|
|
17976
18615
|
if (!approval) {
|
|
17977
18616
|
throw new Error(`No pending approval found for "${requestedId}".`);
|
|
17978
18617
|
}
|
|
17979
|
-
return
|
|
18618
|
+
return [
|
|
18619
|
+
approval
|
|
18620
|
+
];
|
|
17980
18621
|
}
|
|
17981
18622
|
if (snapshot.pendingApprovals.length > 1) {
|
|
17982
|
-
throw new Error("Multiple pending approvals exist. Use /approve <approval-id
|
|
18623
|
+
throw new Error("Multiple pending approvals exist. Use /approve <approval-id>, /deny <approval-id>, or /approve-all.");
|
|
17983
18624
|
}
|
|
17984
|
-
return
|
|
18625
|
+
return [
|
|
18626
|
+
snapshot.pendingApprovals[0]
|
|
18627
|
+
];
|
|
17985
18628
|
}
|
|
17986
|
-
async function resolvePendingApproval(cwd, runtime, decision, approvalId) {
|
|
18629
|
+
async function resolvePendingApproval(cwd, runtime, decision, approvalId, options = {}) {
|
|
17987
18630
|
const session = await loadLatestSession(cwd);
|
|
17988
18631
|
if (!session) {
|
|
17989
18632
|
throw new Error("No saved session found. Run a goal first.");
|
|
17990
18633
|
}
|
|
17991
|
-
const
|
|
17992
|
-
|
|
18634
|
+
const approvals = resolveApprovalChoices(session, approvalId, options);
|
|
18635
|
+
const label = approvals.length === 1 ? `${approvals[0].toolId} for ${approvals[0].taskId}` : `${approvals.length} pending actions`;
|
|
18636
|
+
console.log(color(DIM, `${decision === "approve" ? "Approving" : "Denying"} ${label}...`));
|
|
17993
18637
|
const startedAt = Date.now();
|
|
17994
18638
|
const telemetry = createExecutionTelemetry();
|
|
17995
18639
|
const liveBoard = createLiveRunBoard(
|
|
@@ -18005,21 +18649,21 @@ async function resolvePendingApproval(cwd, runtime, decision, approvalId) {
|
|
|
18005
18649
|
const controller = new AbortController();
|
|
18006
18650
|
runtime.activeExecution = {
|
|
18007
18651
|
controller,
|
|
18008
|
-
label: `${session.id}:${
|
|
18652
|
+
label: approvals.length === 1 ? `${session.id}:${approvals[0].id}` : `${session.id}:batch-approval`
|
|
18009
18653
|
};
|
|
18010
18654
|
console.log(renderRunStartCard(liveBoard));
|
|
18655
|
+
const activity = new ShellActivityIndicator(decision === "approve" ? "approving" : "denying");
|
|
18656
|
+
activity.start();
|
|
18011
18657
|
let snapshot;
|
|
18012
18658
|
try {
|
|
18013
18659
|
snapshot = await new ExecutionOrchestrator().continueSession(session, {
|
|
18014
18660
|
maxAutoTasks: DEFAULT_MAX_AUTO_TASKS,
|
|
18015
18661
|
maxAgentSteps: DEFAULT_MAX_AGENT_STEPS,
|
|
18016
18662
|
maxRepairAttempts: DEFAULT_MAX_REPAIR_ATTEMPTS2,
|
|
18017
|
-
approvalDecisions:
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
}
|
|
18022
|
-
],
|
|
18663
|
+
approvalDecisions: approvals.map((approval) => ({
|
|
18664
|
+
approvalId: approval.id,
|
|
18665
|
+
decision
|
|
18666
|
+
})),
|
|
18023
18667
|
signal: controller.signal,
|
|
18024
18668
|
onProgress: async (event) => {
|
|
18025
18669
|
updateLiveRunBoard(liveBoard, event);
|
|
@@ -18031,6 +18675,8 @@ async function resolvePendingApproval(cwd, runtime, decision, approvalId) {
|
|
|
18031
18675
|
telemetry.inputTokens += event.usage?.inputTokens ?? 0;
|
|
18032
18676
|
telemetry.outputTokens += event.usage?.outputTokens ?? 0;
|
|
18033
18677
|
}
|
|
18678
|
+
activity.update(statusLabelForEvent(event));
|
|
18679
|
+
activity.suspend();
|
|
18034
18680
|
for (const line of renderLiveExecutionEvent(event)) {
|
|
18035
18681
|
console.log(line);
|
|
18036
18682
|
}
|
|
@@ -18039,12 +18685,15 @@ async function resolvePendingApproval(cwd, runtime, decision, approvalId) {
|
|
|
18039
18685
|
console.log(line);
|
|
18040
18686
|
}
|
|
18041
18687
|
}
|
|
18688
|
+
activity.resume();
|
|
18042
18689
|
}
|
|
18043
18690
|
});
|
|
18044
18691
|
} catch (error) {
|
|
18692
|
+
activity.stop();
|
|
18045
18693
|
const message = error instanceof Error ? error.message : String(error);
|
|
18046
|
-
throw new Error(`Approval resolution failed for ${approval.id}: ${message}`);
|
|
18694
|
+
throw new Error(`Approval resolution failed for ${approvals.map((approval) => approval.id).join(", ")}: ${message}`);
|
|
18047
18695
|
} finally {
|
|
18696
|
+
activity.stop();
|
|
18048
18697
|
runtime.activeExecution = null;
|
|
18049
18698
|
}
|
|
18050
18699
|
const sessionPath = await saveSession(snapshot, cwd);
|
|
@@ -18060,6 +18709,28 @@ async function printLatestPlanSummary(cwd) {
|
|
|
18060
18709
|
}
|
|
18061
18710
|
console.log(renderShellPlanSummary(plan).join("\n"));
|
|
18062
18711
|
}
|
|
18712
|
+
async function handleApprovalModeCommand(cwd, tokens) {
|
|
18713
|
+
const config = await loadConfig(cwd);
|
|
18714
|
+
if (!config) {
|
|
18715
|
+
throw new Error("No config found. Run /init or /providers add <template> first.");
|
|
18716
|
+
}
|
|
18717
|
+
const subcommand = tokens[1]?.trim().toLowerCase();
|
|
18718
|
+
if (!subcommand || subcommand === "status") {
|
|
18719
|
+
console.log(`approval mode: ${config.approvalMode}`);
|
|
18720
|
+
console.log("manual = ask before risky actions");
|
|
18721
|
+
console.log("auto = approve non-destructive actions automatically");
|
|
18722
|
+
return;
|
|
18723
|
+
}
|
|
18724
|
+
if (subcommand !== "manual" && subcommand !== "auto") {
|
|
18725
|
+
throw new Error("Usage: /approval [manual|auto|status]");
|
|
18726
|
+
}
|
|
18727
|
+
const outputPath = await saveConfig({
|
|
18728
|
+
...config,
|
|
18729
|
+
approvalMode: subcommand
|
|
18730
|
+
}, cwd);
|
|
18731
|
+
console.log(`Updated ${outputPath}`);
|
|
18732
|
+
console.log(`approval mode: ${subcommand}`);
|
|
18733
|
+
}
|
|
18063
18734
|
async function createPlanOnly(cwd, goal) {
|
|
18064
18735
|
const request = {
|
|
18065
18736
|
goal,
|
|
@@ -18068,7 +18739,14 @@ async function createPlanOnly(cwd, goal) {
|
|
|
18068
18739
|
workspaceState: await inferPlanningWorkspaceState(cwd, goal),
|
|
18069
18740
|
constraints: []
|
|
18070
18741
|
};
|
|
18071
|
-
const
|
|
18742
|
+
const activity = new ShellActivityIndicator("planning");
|
|
18743
|
+
activity.start();
|
|
18744
|
+
let planResult;
|
|
18745
|
+
try {
|
|
18746
|
+
planResult = await generatePlanForRequest(request);
|
|
18747
|
+
} finally {
|
|
18748
|
+
activity.stop();
|
|
18749
|
+
}
|
|
18072
18750
|
const plan = planResult.plan;
|
|
18073
18751
|
const planPath = await savePlan(plan, cwd);
|
|
18074
18752
|
for (const line of renderPlanGenerationNotes(planResult)) {
|
|
@@ -18647,13 +19325,35 @@ async function handleShellCommand(cwd, input, state, runtime, execute) {
|
|
|
18647
19325
|
await resumeGoalExecution(cwd, runtime);
|
|
18648
19326
|
return cwd;
|
|
18649
19327
|
}
|
|
19328
|
+
if (head === "approval") {
|
|
19329
|
+
await handleApprovalModeCommand(cwd, [
|
|
19330
|
+
"approval",
|
|
19331
|
+
...tokens.slice(1)
|
|
19332
|
+
]);
|
|
19333
|
+
return cwd;
|
|
19334
|
+
}
|
|
19335
|
+
if (head === "approve-all") {
|
|
19336
|
+
await resolvePendingApproval(
|
|
19337
|
+
cwd,
|
|
19338
|
+
runtime,
|
|
19339
|
+
"approve",
|
|
19340
|
+
"all",
|
|
19341
|
+
{
|
|
19342
|
+
allowAll: true
|
|
19343
|
+
}
|
|
19344
|
+
);
|
|
19345
|
+
return cwd;
|
|
19346
|
+
}
|
|
18650
19347
|
if (head === "approve" || head === "deny") {
|
|
18651
19348
|
const approvalId = tokens[1]?.trim();
|
|
18652
19349
|
await resolvePendingApproval(
|
|
18653
19350
|
cwd,
|
|
18654
19351
|
runtime,
|
|
18655
19352
|
head === "approve" ? "approve" : "deny",
|
|
18656
|
-
approvalId && approvalId.length > 0 ? approvalId : void 0
|
|
19353
|
+
approvalId && approvalId.length > 0 ? approvalId : void 0,
|
|
19354
|
+
{
|
|
19355
|
+
allowAll: false
|
|
19356
|
+
}
|
|
18657
19357
|
);
|
|
18658
19358
|
return cwd;
|
|
18659
19359
|
}
|