@kmugalkhod/lightcode 0.6.0 → 0.7.1
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 +119 -91
- package/cli.js +633 -236
- package/package.json +1 -1
- package/server.js +220 -104
package/cli.js
CHANGED
|
@@ -124,7 +124,7 @@ var init_logger = __esm(() => {
|
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
// packages/shared/src/version.ts
|
|
127
|
-
var lightcodeVersion = "0.
|
|
127
|
+
var lightcodeVersion = "0.7.1";
|
|
128
128
|
|
|
129
129
|
// packages/shared/src/index.ts
|
|
130
130
|
var productName = "Lightcode";
|
|
@@ -1053,7 +1053,7 @@ var init_slash_page_menu = __esm(() => {
|
|
|
1053
1053
|
});
|
|
1054
1054
|
|
|
1055
1055
|
// packages/ai/src/constants.ts
|
|
1056
|
-
var MAX_TOOL_TEXT_OUTPUT_CHARS = 12000, DEFAULT_TOOL_TEXT_OUTPUT_CHARS = 6000, MAX_TOOL_LIST_ENTRIES = 200, MAX_TOOL_SEARCH_RESULTS = 200, ANTHROPIC_TOOL_OPTIONAL_PARAMETER_BUDGET = 24;
|
|
1056
|
+
var MAX_TOOL_TEXT_OUTPUT_CHARS = 12000, DEFAULT_TOOL_TEXT_OUTPUT_CHARS = 6000, MAX_TOOL_LIST_ENTRIES = 200, MAX_TOOL_SEARCH_RESULTS = 200, ANTHROPIC_TOOL_OPTIONAL_PARAMETER_BUDGET = 24, TODO_CONTENT_MAX_CHARS = 200, TODO_ACTIVE_FORM_MAX_CHARS = 80, PROGRESS_NOTE_PRUNE_MIN_CHARS = 200;
|
|
1057
1057
|
|
|
1058
1058
|
// packages/ai/src/coding-agent-modes.ts
|
|
1059
1059
|
import { z } from "zod";
|
|
@@ -1970,20 +1970,22 @@ var init_schema10 = __esm(() => {
|
|
|
1970
1970
|
todoPrioritySchema = z14.enum(["low", "medium", "high"]);
|
|
1971
1971
|
todoItemSchema = z14.object({
|
|
1972
1972
|
id: z14.string().min(1).max(120).optional(),
|
|
1973
|
-
content: z14.string().min(1).max(
|
|
1973
|
+
content: z14.string().min(1).max(TODO_CONTENT_MAX_CHARS),
|
|
1974
1974
|
status: todoStatusSchema,
|
|
1975
1975
|
priority: todoPrioritySchema.optional().default("medium"),
|
|
1976
|
-
notes: z14.string().max(1000).optional()
|
|
1976
|
+
notes: z14.string().max(1000).optional(),
|
|
1977
|
+
activeForm: z14.string().min(1).max(TODO_ACTIVE_FORM_MAX_CHARS).optional()
|
|
1977
1978
|
});
|
|
1978
1979
|
todoItemProviderSchema = z14.object({
|
|
1979
|
-
content: z14.string().min(1).max(
|
|
1980
|
-
status: todoStatusSchema
|
|
1980
|
+
content: z14.string().min(1).max(TODO_CONTENT_MAX_CHARS),
|
|
1981
|
+
status: todoStatusSchema,
|
|
1982
|
+
activeForm: z14.string().min(1).max(TODO_ACTIVE_FORM_MAX_CHARS).optional().describe(`Short present-tense active label for this task, e.g. "Adding tests" or "Editing schema". Set it primarily for the in_progress item so the UI can show what's actively happening; the UI falls back to content when omitted.`)
|
|
1981
1983
|
});
|
|
1982
1984
|
todoWriteInputSchema = z14.object({
|
|
1983
1985
|
todos: z14.array(todoItemSchema).max(50)
|
|
1984
1986
|
});
|
|
1985
1987
|
todoWriteProviderInputSchema = z14.object({
|
|
1986
|
-
todos: z14.array(todoItemProviderSchema)
|
|
1988
|
+
todos: z14.array(todoItemProviderSchema).max(20)
|
|
1987
1989
|
});
|
|
1988
1990
|
todoWriteOutputSchema = z14.object({
|
|
1989
1991
|
sessionId: z14.string(),
|
|
@@ -1995,7 +1997,8 @@ var init_schema10 = __esm(() => {
|
|
|
1995
1997
|
inProgress: z14.number().int().nonnegative(),
|
|
1996
1998
|
completed: z14.number().int().nonnegative(),
|
|
1997
1999
|
canceled: z14.number().int().nonnegative()
|
|
1998
|
-
})
|
|
2000
|
+
}),
|
|
2001
|
+
warnings: z14.array(z14.string()).optional()
|
|
1999
2002
|
});
|
|
2000
2003
|
});
|
|
2001
2004
|
|
|
@@ -2927,14 +2930,139 @@ ${environmentContext.trim()}` : "");
|
|
|
2927
2930
|
var cwdPromptPlaceholder = "{cwd}", defaultCodingAgentSystemPrompt, toolCallingDisciplineAddendum;
|
|
2928
2931
|
var init_coding_agent_prompt = __esm(() => {
|
|
2929
2932
|
init_coding_agent_modes();
|
|
2930
|
-
defaultCodingAgentSystemPrompt = "You are a coding agent working inside the user's project at this working directory: {cwd}. " + 'You can already see the project \u2014 proactively read it. When the user asks you to review, explain, or work on "my code"/"this", inspect the working directory yourself with list_files, glob_search, grep, and read_file; NEVER ask the user to paste or share code that lives in this directory \u2014 open it. ' + "Use tools for filesystem and codebase tasks instead of guessing, and only interact with files under the working directory. " + "Prefer dedicated tools \u2014 glob_search for paths, grep for text search, structured git tools for repository inspection \u2014 and bash only when those are not enough. " + `
|
|
2933
|
+
defaultCodingAgentSystemPrompt = "You are a coding agent working inside the user's project at this working directory: {cwd}. " + 'You can already see the project \u2014 proactively read it. When the user asks you to review, explain, or work on "my code"/"this", inspect the working directory yourself with list_files, glob_search, grep, and read_file; NEVER ask the user to paste or share code that lives in this directory \u2014 open it. ' + "Use tools for filesystem and codebase tasks instead of guessing, and only interact with files under the working directory. " + "Prefer dedicated tools \u2014 glob_search for paths, grep for text search, structured git tools for repository inspection \u2014 and bash only when those are not enough. " + `Inspect context before risky operations. Progress notes: before a major tool action you may emit ONE short note (one sentence, ~15 words) saying what you are about to do. After a tool returns, mention a finding only if it changes your plan, and state it as fact grounded in what the tool actually returned \u2014 cite the file/line/symbol. NEVER claim you found, changed, or fixed something before a tool result confirms it. Skip notes for routine reads, and do not restate the todo list in prose.
|
|
2934
|
+
|
|
2935
|
+
` + `Task list discipline \u2014 use the todo_write tool to keep a visible, evolving checklist:
|
|
2936
|
+
` + `- Create the list FIRST. For any task with roughly three or more distinct steps, or any multi-file or multi-stage work, call todo_write as your very first action \u2014 before reading or editing \u2014 with every step listed and all of them set to pending.
|
|
2937
|
+
` + `- Keep exactly ONE item in_progress at a time. Mark an item in_progress immediately before you start it; never have two items in_progress at once.
|
|
2938
|
+
` + `- Mark an item completed only after a tool result confirms it is actually done (test passed, file written, build clean) \u2014 not on intent. Do this the MOMENT it is confirmed; do not batch completions or wait until the end of the turn.
|
|
2939
|
+
` + `- Completions are monotonic: once a task is completed, do not silently move it back to pending or in_progress. Reopen it only if it genuinely regressed.
|
|
2940
|
+
` + `- Every todo_write call is a FULL REWRITE of the list: always send every task with its current status (re-state the completed and pending ones too \u2014 never send only the changed item). Keep each item terse (a short label, not a paragraph).
|
|
2941
|
+
` + `- Skip the list for trivial, single-step, or purely conversational requests \u2014 a checklist there is just noise.
|
|
2931
2942
|
|
|
2932
2943
|
` + `You are an agent \u2014 keep working until the request is fully resolved. Never stop with a partial answer; if you say you will do something, do it in the same turn by calling a tool. Do not finish while any todo is pending or in_progress \u2014 end your turn only when the task is complete or you are blocked on input that only the user can provide.
|
|
2933
2944
|
|
|
2945
|
+
` + `When the task is complete, end with a short summary (a few lines) grounded in the todos you actually completed and the real file diffs \u2014 name the files you edited, do not list steps you did not verify, and do not repeat the todo card verbatim. If anything is incomplete or you are blocked, say so plainly.
|
|
2946
|
+
|
|
2934
2947
|
` + "Long-running processes (dev servers, watchers): never run them as plain foreground bash \u2014 the bash tool enforces a timeout and will kill them. Start them detached with output redirected to a log " + '(Windows: start /B cmd /c "npm run dev > dev.log 2>&1"; Unix: nohup npm run dev > dev.log 2>&1 &), then read the log to confirm startup and the actual port (do not assume the default). ' + "Never kill processes or free ports you did not open yourself.";
|
|
2935
2948
|
toolCallingDisciplineAddendum = "Invoke tools only through the function-calling interface. " + "Never write tool calls as XML or plain text such as <tool_call>, <function_call>, <invoke>, or bare JSON in your message. " + "Tool arguments must be valid JSON matching the tool's schema. " + "Call one tool at a time and wait for its result before deciding the next step.";
|
|
2936
2949
|
});
|
|
2937
2950
|
|
|
2951
|
+
// packages/ai/src/common/coerce-tool-input.ts
|
|
2952
|
+
function coercePrimitive(current, expected) {
|
|
2953
|
+
if (current === undefined || current === null) {
|
|
2954
|
+
return NO_CHANGE;
|
|
2955
|
+
}
|
|
2956
|
+
switch (expected) {
|
|
2957
|
+
case "number": {
|
|
2958
|
+
if (typeof current === "string") {
|
|
2959
|
+
const trimmed = current.trim();
|
|
2960
|
+
if (trimmed !== "" && Number.isFinite(Number(trimmed))) {
|
|
2961
|
+
return Number(trimmed);
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
return NO_CHANGE;
|
|
2965
|
+
}
|
|
2966
|
+
case "boolean": {
|
|
2967
|
+
if (typeof current === "string") {
|
|
2968
|
+
const lowered = current.trim().toLowerCase();
|
|
2969
|
+
if (lowered === "true" || lowered === "1" || lowered === "yes") {
|
|
2970
|
+
return true;
|
|
2971
|
+
}
|
|
2972
|
+
if (lowered === "false" || lowered === "0" || lowered === "no") {
|
|
2973
|
+
return false;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
if (current === 1)
|
|
2977
|
+
return true;
|
|
2978
|
+
if (current === 0)
|
|
2979
|
+
return false;
|
|
2980
|
+
return NO_CHANGE;
|
|
2981
|
+
}
|
|
2982
|
+
case "string": {
|
|
2983
|
+
if (typeof current === "number" || typeof current === "boolean") {
|
|
2984
|
+
return String(current);
|
|
2985
|
+
}
|
|
2986
|
+
return NO_CHANGE;
|
|
2987
|
+
}
|
|
2988
|
+
case "object":
|
|
2989
|
+
case "array": {
|
|
2990
|
+
if (typeof current === "string") {
|
|
2991
|
+
try {
|
|
2992
|
+
const parsed = JSON.parse(current);
|
|
2993
|
+
const isArray = Array.isArray(parsed);
|
|
2994
|
+
const matches = expected === "array" ? isArray : parsed !== null && typeof parsed === "object" && !isArray;
|
|
2995
|
+
if (matches) {
|
|
2996
|
+
return parsed;
|
|
2997
|
+
}
|
|
2998
|
+
} catch {}
|
|
2999
|
+
}
|
|
3000
|
+
return NO_CHANGE;
|
|
3001
|
+
}
|
|
3002
|
+
default:
|
|
3003
|
+
return NO_CHANGE;
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
function coerceAtPath(root, path4, expected) {
|
|
3007
|
+
if (path4.length === 0 || root === null || typeof root !== "object") {
|
|
3008
|
+
return false;
|
|
3009
|
+
}
|
|
3010
|
+
let parent = root;
|
|
3011
|
+
for (let index = 0;index < path4.length - 1; index += 1) {
|
|
3012
|
+
const next = parent[path4[index]];
|
|
3013
|
+
if (next === null || typeof next !== "object") {
|
|
3014
|
+
return false;
|
|
3015
|
+
}
|
|
3016
|
+
parent = next;
|
|
3017
|
+
}
|
|
3018
|
+
const key = path4[path4.length - 1];
|
|
3019
|
+
const coerced = coercePrimitive(parent[key], expected);
|
|
3020
|
+
if (coerced === NO_CHANGE) {
|
|
3021
|
+
return false;
|
|
3022
|
+
}
|
|
3023
|
+
parent[key] = coerced;
|
|
3024
|
+
return true;
|
|
3025
|
+
}
|
|
3026
|
+
function coerceToolInputToSchema(value, schema) {
|
|
3027
|
+
if (value === null || typeof value !== "object") {
|
|
3028
|
+
return null;
|
|
3029
|
+
}
|
|
3030
|
+
let current;
|
|
3031
|
+
try {
|
|
3032
|
+
current = structuredClone(value);
|
|
3033
|
+
} catch {
|
|
3034
|
+
current = value;
|
|
3035
|
+
}
|
|
3036
|
+
for (let pass = 0;pass < MAX_PASSES; pass += 1) {
|
|
3037
|
+
const result = schema.safeParse(current);
|
|
3038
|
+
if (result.success) {
|
|
3039
|
+
return result.data;
|
|
3040
|
+
}
|
|
3041
|
+
let changed = false;
|
|
3042
|
+
for (const issue of result.error.issues) {
|
|
3043
|
+
if (issue.code !== "invalid_type") {
|
|
3044
|
+
continue;
|
|
3045
|
+
}
|
|
3046
|
+
const expected = issue.expected;
|
|
3047
|
+
if (typeof expected !== "string") {
|
|
3048
|
+
continue;
|
|
3049
|
+
}
|
|
3050
|
+
if (coerceAtPath(current, issue.path, expected)) {
|
|
3051
|
+
changed = true;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
if (!changed) {
|
|
3055
|
+
break;
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
const final = schema.safeParse(current);
|
|
3059
|
+
return final.success ? final.data : null;
|
|
3060
|
+
}
|
|
3061
|
+
var NO_CHANGE, MAX_PASSES = 4;
|
|
3062
|
+
var init_coerce_tool_input = __esm(() => {
|
|
3063
|
+
NO_CHANGE = Symbol("no-change");
|
|
3064
|
+
});
|
|
3065
|
+
|
|
2938
3066
|
// packages/ai/src/providers/tool-call-json-repair.ts
|
|
2939
3067
|
import { parsePartialJson } from "ai";
|
|
2940
3068
|
async function repairToolJson(raw) {
|
|
@@ -3129,15 +3257,19 @@ async function repairCodingAgentToolCall({
|
|
|
3129
3257
|
if (!repairedInput || typeof repairedInput !== "object") {
|
|
3130
3258
|
return null;
|
|
3131
3259
|
}
|
|
3260
|
+
const schema = codingToolProviderInputSchemas[toolCall.toolName];
|
|
3261
|
+
const coerced = schema ? coerceToolInputToSchema(repairedInput, schema) : null;
|
|
3132
3262
|
return {
|
|
3133
3263
|
...toolCall,
|
|
3134
|
-
input: JSON.stringify(repairedInput)
|
|
3264
|
+
input: JSON.stringify(coerced ?? repairedInput)
|
|
3135
3265
|
};
|
|
3136
3266
|
}
|
|
3137
3267
|
return null;
|
|
3138
3268
|
}
|
|
3139
3269
|
var toolNameAliases;
|
|
3140
3270
|
var init_coding_agent_tool_repair = __esm(() => {
|
|
3271
|
+
init_agent_tools();
|
|
3272
|
+
init_coerce_tool_input();
|
|
3141
3273
|
init_tool_call_json_repair();
|
|
3142
3274
|
toolNameAliases = {
|
|
3143
3275
|
read: "read_file",
|
|
@@ -3226,19 +3358,6 @@ function getLastUserText(messages, prompt) {
|
|
|
3226
3358
|
}
|
|
3227
3359
|
return collectTextFromUnknown(prompt).trim();
|
|
3228
3360
|
}
|
|
3229
|
-
function pushUniqueTool(tools, toolName) {
|
|
3230
|
-
if (!tools.includes(toolName)) {
|
|
3231
|
-
tools.push(toolName);
|
|
3232
|
-
}
|
|
3233
|
-
}
|
|
3234
|
-
function includesAny(text, tokens) {
|
|
3235
|
-
return tokens.some((token) => {
|
|
3236
|
-
if (token.includes("://") || token.includes(" ")) {
|
|
3237
|
-
return text.includes(token);
|
|
3238
|
-
}
|
|
3239
|
-
return new RegExp(`(^|[^a-z0-9])${token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}([^a-z0-9]|$)`).test(text);
|
|
3240
|
-
});
|
|
3241
|
-
}
|
|
3242
3361
|
function selectCodingAgentIntentTools({
|
|
3243
3362
|
mode,
|
|
3244
3363
|
prompt,
|
|
@@ -3249,75 +3368,7 @@ function selectCodingAgentIntentTools({
|
|
|
3249
3368
|
if (!normalizedText || casualPromptPattern.test(normalizedText)) {
|
|
3250
3369
|
return [];
|
|
3251
3370
|
}
|
|
3252
|
-
|
|
3253
|
-
const hasGitIntent = includesAny(normalizedText, [
|
|
3254
|
-
"git",
|
|
3255
|
-
"diff",
|
|
3256
|
-
"status",
|
|
3257
|
-
"commit",
|
|
3258
|
-
"log",
|
|
3259
|
-
"show",
|
|
3260
|
-
"revision",
|
|
3261
|
-
"changes"
|
|
3262
|
-
]);
|
|
3263
|
-
if (hasGitIntent) {
|
|
3264
|
-
if (includesAny(normalizedText, ["status", "state", "dirty", "working tree"]) || !includesAny(normalizedText, ["diff", "log", "show", "history", "commit"])) {
|
|
3265
|
-
pushUniqueTool(situational, "git_status");
|
|
3266
|
-
}
|
|
3267
|
-
if (includesAny(normalizedText, ["diff", "changes", "patch"])) {
|
|
3268
|
-
pushUniqueTool(situational, "git_diff");
|
|
3269
|
-
}
|
|
3270
|
-
if (includesAny(normalizedText, ["log", "history", "commits"])) {
|
|
3271
|
-
pushUniqueTool(situational, "git_log");
|
|
3272
|
-
}
|
|
3273
|
-
if (includesAny(normalizedText, ["show", "revision", "commit"])) {
|
|
3274
|
-
pushUniqueTool(situational, "git_show");
|
|
3275
|
-
}
|
|
3276
|
-
}
|
|
3277
|
-
if (mode === "build" && includesAny(normalizedText, ["todo", "tasks", "checklist", "steps", "epic", "ticket"])) {
|
|
3278
|
-
pushUniqueTool(situational, "todo_write");
|
|
3279
|
-
}
|
|
3280
|
-
if (includesAny(normalizedText, ["tool_search", "tool search", "find tool", "available tool"])) {
|
|
3281
|
-
pushUniqueTool(situational, "tool_search");
|
|
3282
|
-
}
|
|
3283
|
-
if (includesAny(normalizedText, ["skill", "skills", "load skill"])) {
|
|
3284
|
-
pushUniqueTool(situational, "skill");
|
|
3285
|
-
}
|
|
3286
|
-
if (includesAny(normalizedText, ["mcp", "resource", "resources", "mcp tool", "mcp server"])) {
|
|
3287
|
-
pushUniqueTool(situational, "list_mcp_resources");
|
|
3288
|
-
if (includesAny(normalizedText, ["read", "resource", "resources"])) {
|
|
3289
|
-
pushUniqueTool(situational, "read_mcp_resource");
|
|
3290
|
-
}
|
|
3291
|
-
if (mode === "build" && includesAny(normalizedText, ["call", "execute", "run"])) {
|
|
3292
|
-
pushUniqueTool(situational, "call_mcp_tool");
|
|
3293
|
-
}
|
|
3294
|
-
}
|
|
3295
|
-
if (includesAny(normalizedText, [
|
|
3296
|
-
"http://",
|
|
3297
|
-
"https://",
|
|
3298
|
-
"url",
|
|
3299
|
-
"fetch",
|
|
3300
|
-
"website",
|
|
3301
|
-
"web",
|
|
3302
|
-
"online",
|
|
3303
|
-
"search internet",
|
|
3304
|
-
"latest"
|
|
3305
|
-
])) {
|
|
3306
|
-
pushUniqueTool(situational, "web_fetch");
|
|
3307
|
-
pushUniqueTool(situational, "web_search");
|
|
3308
|
-
}
|
|
3309
|
-
if (mode === "plan" && includesAny(normalizedText, ["question", "ask", "clarify"])) {
|
|
3310
|
-
pushUniqueTool(situational, "request_user_input");
|
|
3311
|
-
}
|
|
3312
|
-
const ordered = [...baseReadTools, ...situational];
|
|
3313
|
-
if (mode === "build") {
|
|
3314
|
-
ordered.push("write_file", "edit_file", "bash");
|
|
3315
|
-
}
|
|
3316
|
-
const selected = [];
|
|
3317
|
-
for (const toolName of ordered) {
|
|
3318
|
-
pushUniqueTool(selected, toolName);
|
|
3319
|
-
}
|
|
3320
|
-
return selected.slice(0, maxProviderActiveTools);
|
|
3371
|
+
return [...getCodingAgentModeDefinition(mode).activeTools];
|
|
3321
3372
|
}
|
|
3322
3373
|
function limitProviderActiveTools(tools, maxTools = maxProviderActiveTools) {
|
|
3323
3374
|
if (tools.length <= maxTools) {
|
|
@@ -3326,15 +3377,10 @@ function limitProviderActiveTools(tools, maxTools = maxProviderActiveTools) {
|
|
|
3326
3377
|
const toolSet = new Set(tools);
|
|
3327
3378
|
return providerToolPriority.filter((toolName) => toolSet.has(toolName)).slice(0, maxTools);
|
|
3328
3379
|
}
|
|
3329
|
-
var casualPromptPattern,
|
|
3380
|
+
var casualPromptPattern, maxProviderActiveTools = 24, providerToolPriority;
|
|
3330
3381
|
var init_intent_tool_selection = __esm(() => {
|
|
3382
|
+
init_coding_agent_modes();
|
|
3331
3383
|
casualPromptPattern = /^(hi|hello|hey|how are you|how r you|what'?s up|whats up|sup|thanks|thank you|ok|okay|yes|no|nice|cool|great)[\s?!.,]*$/i;
|
|
3332
|
-
baseReadTools = [
|
|
3333
|
-
"list_files",
|
|
3334
|
-
"glob_search",
|
|
3335
|
-
"read_file",
|
|
3336
|
-
"grep"
|
|
3337
|
-
];
|
|
3338
3384
|
providerToolPriority = [
|
|
3339
3385
|
"list_files",
|
|
3340
3386
|
"glob_search",
|
|
@@ -4038,6 +4084,16 @@ function getPartState(part) {
|
|
|
4038
4084
|
const state = isRecord3(part) ? Reflect.get(part, "state") : undefined;
|
|
4039
4085
|
return typeof state === "string" ? state : null;
|
|
4040
4086
|
}
|
|
4087
|
+
function getPlainTextPartContent(part) {
|
|
4088
|
+
if (!isRecord3(part) || Reflect.get(part, "type") !== "text") {
|
|
4089
|
+
return null;
|
|
4090
|
+
}
|
|
4091
|
+
const text = Reflect.get(part, "text");
|
|
4092
|
+
return typeof text === "string" ? text : null;
|
|
4093
|
+
}
|
|
4094
|
+
function messageHasToolPart(message) {
|
|
4095
|
+
return message.parts.some((part) => getToolNameFromPart(part) !== null);
|
|
4096
|
+
}
|
|
4041
4097
|
function computePruneCutoffIndex(messages, preserveRecentMessages, quantizeUserTurns = DEFAULT_PRUNE_QUANTIZE_USER_TURNS) {
|
|
4042
4098
|
const candidate = messages.length - preserveRecentMessages;
|
|
4043
4099
|
if (candidate <= 0) {
|
|
@@ -4087,15 +4143,29 @@ function pruneToolOutputs(messages, options) {
|
|
|
4087
4143
|
});
|
|
4088
4144
|
let elidedToolOutputs = 0;
|
|
4089
4145
|
let dedupedFileReads = 0;
|
|
4146
|
+
let elidedProgressNotes = 0;
|
|
4090
4147
|
let savedChars = 0;
|
|
4091
4148
|
const prunedMessages = messages.map((message, messageIndex) => {
|
|
4092
4149
|
if (messageIndex >= cutoff) {
|
|
4093
4150
|
return message;
|
|
4094
4151
|
}
|
|
4152
|
+
const noteCandidate = message.role === "assistant" && messageHasToolPart(message);
|
|
4095
4153
|
let changed = false;
|
|
4096
4154
|
const parts = message.parts.map((part, partIndex) => {
|
|
4097
4155
|
const toolName = getToolNameFromPart(part);
|
|
4098
4156
|
if (!toolName || getPartState(part) !== "output-available") {
|
|
4157
|
+
if (noteCandidate && toolName === null) {
|
|
4158
|
+
const text = getPlainTextPartContent(part);
|
|
4159
|
+
if (text !== null && text !== PROGRESS_NOTE_ELISION_STUB && text.length > PROGRESS_NOTE_PRUNE_MIN_CHARS) {
|
|
4160
|
+
elidedProgressNotes += 1;
|
|
4161
|
+
savedChars += Math.max(0, text.length - PROGRESS_NOTE_ELISION_STUB.length);
|
|
4162
|
+
changed = true;
|
|
4163
|
+
return {
|
|
4164
|
+
...part,
|
|
4165
|
+
text: PROGRESS_NOTE_ELISION_STUB
|
|
4166
|
+
};
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4099
4169
|
return part;
|
|
4100
4170
|
}
|
|
4101
4171
|
const output = isRecord3(part) ? Reflect.get(part, "output") : undefined;
|
|
@@ -4112,7 +4182,7 @@ function pruneToolOutputs(messages, options) {
|
|
|
4112
4182
|
}
|
|
4113
4183
|
const stub = {
|
|
4114
4184
|
lightcodeElided: true,
|
|
4115
|
-
note: isStaleRead ? "Earlier read of this file elided;
|
|
4185
|
+
note: isStaleRead ? "Earlier read of this file elided; its current content is already shown later in this conversation \u2014 do not read it again." : "Large tool output elided to preserve context budget. Re-run the tool only if this output is needed again.",
|
|
4116
4186
|
originalChars: serializedOutput.length
|
|
4117
4187
|
};
|
|
4118
4188
|
if (isStaleRead) {
|
|
@@ -4130,10 +4200,11 @@ function pruneToolOutputs(messages, options) {
|
|
|
4130
4200
|
messages: prunedMessages,
|
|
4131
4201
|
elidedToolOutputs,
|
|
4132
4202
|
dedupedFileReads,
|
|
4203
|
+
elidedProgressNotes,
|
|
4133
4204
|
savedChars
|
|
4134
4205
|
};
|
|
4135
4206
|
}
|
|
4136
|
-
var DEFAULT_PRUNE_MIN_OUTPUT_CHARS = 2000, DEFAULT_PRUNE_QUANTIZE_USER_TURNS = 4;
|
|
4207
|
+
var DEFAULT_PRUNE_MIN_OUTPUT_CHARS = 2000, PROGRESS_NOTE_ELISION_STUB = "[progress note elided]", DEFAULT_PRUNE_QUANTIZE_USER_TURNS = 4;
|
|
4137
4208
|
var init_tier1_prune = __esm(() => {
|
|
4138
4209
|
init_estimate();
|
|
4139
4210
|
init_message_parts();
|
|
@@ -6050,6 +6121,57 @@ function getExportPath(sessionTitle, sessionId) {
|
|
|
6050
6121
|
}
|
|
6051
6122
|
var init_export_session_markdown = () => {};
|
|
6052
6123
|
|
|
6124
|
+
// packages/ai/src/tool-call-repeat-guard.ts
|
|
6125
|
+
function stableStringify(value) {
|
|
6126
|
+
if (value === null || typeof value !== "object") {
|
|
6127
|
+
return JSON.stringify(value) ?? "null";
|
|
6128
|
+
}
|
|
6129
|
+
if (Array.isArray(value)) {
|
|
6130
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
6131
|
+
}
|
|
6132
|
+
const record = value;
|
|
6133
|
+
const entries = Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`);
|
|
6134
|
+
return `{${entries.join(",")}}`;
|
|
6135
|
+
}
|
|
6136
|
+
function assertNotRepeatingToolCall(toolName, input, turnKey) {
|
|
6137
|
+
if (!turnKey || !guardedTools.has(toolName)) {
|
|
6138
|
+
return;
|
|
6139
|
+
}
|
|
6140
|
+
const signature = `${toolName}:${stableStringify(input)}`;
|
|
6141
|
+
const previous = lastCallByTurn.get(turnKey);
|
|
6142
|
+
const count = previous && previous.signature === signature ? previous.count + 1 : 1;
|
|
6143
|
+
lastCallByTurn.set(turnKey, { signature, count });
|
|
6144
|
+
if (lastCallByTurn.size > MAX_TURNS_TRACKED) {
|
|
6145
|
+
const oldest = lastCallByTurn.keys().next().value;
|
|
6146
|
+
if (oldest !== undefined) {
|
|
6147
|
+
lastCallByTurn.delete(oldest);
|
|
6148
|
+
}
|
|
6149
|
+
}
|
|
6150
|
+
if (count >= REPEAT_THRESHOLD) {
|
|
6151
|
+
throw new RepeatedToolCallError(`You have called ${toolName} with identical arguments ${count} times in a row and the result has not changed. ` + "Stop repeating it \u2014 use the information already in this conversation and take the next concrete action " + "(make your edit, or call a different tool).");
|
|
6152
|
+
}
|
|
6153
|
+
}
|
|
6154
|
+
var guardedTools, REPEAT_THRESHOLD = 3, MAX_TURNS_TRACKED = 8, RepeatedToolCallError, lastCallByTurn;
|
|
6155
|
+
var init_tool_call_repeat_guard = __esm(() => {
|
|
6156
|
+
guardedTools = new Set([
|
|
6157
|
+
"read_file",
|
|
6158
|
+
"list_files",
|
|
6159
|
+
"glob_search",
|
|
6160
|
+
"grep",
|
|
6161
|
+
"git_status",
|
|
6162
|
+
"git_diff",
|
|
6163
|
+
"git_log",
|
|
6164
|
+
"git_show"
|
|
6165
|
+
]);
|
|
6166
|
+
RepeatedToolCallError = class RepeatedToolCallError extends Error {
|
|
6167
|
+
constructor(message) {
|
|
6168
|
+
super(message);
|
|
6169
|
+
this.name = "RepeatedToolCallError";
|
|
6170
|
+
}
|
|
6171
|
+
};
|
|
6172
|
+
lastCallByTurn = new Map;
|
|
6173
|
+
});
|
|
6174
|
+
|
|
6053
6175
|
// packages/ai/src/common/resolve-within-workspace.ts
|
|
6054
6176
|
import path9 from "path";
|
|
6055
6177
|
import {
|
|
@@ -7280,6 +7402,47 @@ function getTodoFilePath(sessionId, workspaceContext) {
|
|
|
7280
7402
|
allowMissing: true
|
|
7281
7403
|
});
|
|
7282
7404
|
}
|
|
7405
|
+
function truncateContent(content) {
|
|
7406
|
+
if (content.length <= TODO_CONTENT_MAX_CHARS) {
|
|
7407
|
+
return content;
|
|
7408
|
+
}
|
|
7409
|
+
return `${content.slice(0, TODO_CONTENT_MAX_CHARS - 1).trimEnd()}\u2026`;
|
|
7410
|
+
}
|
|
7411
|
+
function validateTodoInvariants(todos, previousTodos) {
|
|
7412
|
+
const warnings = [];
|
|
7413
|
+
let normalized = todos.map((todo) => {
|
|
7414
|
+
const capped = truncateContent(todo.content);
|
|
7415
|
+
if (capped !== todo.content) {
|
|
7416
|
+
warnings.push(`Todo "${capped}" was longer than ${TODO_CONTENT_MAX_CHARS} characters and was truncated; keep items terse.`);
|
|
7417
|
+
}
|
|
7418
|
+
return capped === todo.content ? todo : { ...todo, content: capped };
|
|
7419
|
+
});
|
|
7420
|
+
const inProgress = normalized.filter((todo) => todo.status === "in_progress");
|
|
7421
|
+
if (inProgress.length > 1) {
|
|
7422
|
+
let seen = false;
|
|
7423
|
+
normalized = normalized.map((todo) => {
|
|
7424
|
+
if (todo.status !== "in_progress") {
|
|
7425
|
+
return todo;
|
|
7426
|
+
}
|
|
7427
|
+
if (!seen) {
|
|
7428
|
+
seen = true;
|
|
7429
|
+
return todo;
|
|
7430
|
+
}
|
|
7431
|
+
return { ...todo, status: "pending" };
|
|
7432
|
+
});
|
|
7433
|
+
warnings.push(`${inProgress.length} tasks were in_progress; kept "${inProgress[0].content}" and reset the rest to pending. Keep exactly one task in_progress.`);
|
|
7434
|
+
} else if (inProgress.length === 0 && normalized.some((todo) => todo.status === "pending")) {
|
|
7435
|
+
warnings.push("No task is in_progress but pending work remains; mark the next task in_progress before starting it.");
|
|
7436
|
+
}
|
|
7437
|
+
const previouslyCompleted = new Map(previousTodos.filter((todo) => todo.status === "completed").map((todo) => [todo.id ?? todo.content, todo]));
|
|
7438
|
+
for (const todo of normalized) {
|
|
7439
|
+
const key = todo.id ?? todo.content;
|
|
7440
|
+
if (previouslyCompleted.has(key) && todo.status !== "completed" && todo.status !== "canceled") {
|
|
7441
|
+
warnings.push(`Task "${todo.content}" was completed earlier but is now ${todo.status}; completions should be monotonic. Reopen only if it genuinely regressed.`);
|
|
7442
|
+
}
|
|
7443
|
+
}
|
|
7444
|
+
return { todos: normalized, warnings };
|
|
7445
|
+
}
|
|
7283
7446
|
function summarizeTodos(todos) {
|
|
7284
7447
|
return {
|
|
7285
7448
|
total: todos.length,
|
|
@@ -7307,17 +7470,20 @@ async function executeTodoWrite(input, {
|
|
|
7307
7470
|
} = {}) {
|
|
7308
7471
|
const parsedInput = todoWriteInputSchema.parse(input);
|
|
7309
7472
|
const todoFilePath = getTodoFilePath(sessionId, workspaceContext);
|
|
7310
|
-
const
|
|
7473
|
+
const submitted = parsedInput.todos.map((todo, index) => todoItemSchema.parse({
|
|
7311
7474
|
...todo,
|
|
7312
7475
|
id: todo.id ?? `todo-${index + 1}`
|
|
7313
7476
|
}));
|
|
7477
|
+
const previousTodos = await loadSessionTodos({ sessionId, workspaceContext });
|
|
7478
|
+
const { todos, warnings } = validateTodoInvariants(submitted, previousTodos);
|
|
7314
7479
|
await mkdir(path14.dirname(todoFilePath), { recursive: true });
|
|
7315
7480
|
await writeFile(todoFilePath, JSON.stringify(todos, null, 2), "utf8");
|
|
7316
7481
|
return todoWriteOutputSchema.parse({
|
|
7317
7482
|
sessionId: safeTodoSessionId(sessionId),
|
|
7318
7483
|
path: toWorkspaceRelativePath(todoFilePath, workspaceContext),
|
|
7319
7484
|
todos,
|
|
7320
|
-
summary: summarizeTodos(todos)
|
|
7485
|
+
summary: summarizeTodos(todos),
|
|
7486
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
7321
7487
|
});
|
|
7322
7488
|
}
|
|
7323
7489
|
var defaultSessionId = "default";
|
|
@@ -7758,12 +7924,22 @@ function assertToolPermission(toolName, input, options) {
|
|
|
7758
7924
|
}
|
|
7759
7925
|
}
|
|
7760
7926
|
function parseCodingToolInput(toolName, rawInput) {
|
|
7761
|
-
|
|
7927
|
+
const schema6 = codingToolInputSchemas[toolName];
|
|
7928
|
+
const parsed = schema6.safeParse(rawInput);
|
|
7929
|
+
if (parsed.success) {
|
|
7930
|
+
return parsed.data;
|
|
7931
|
+
}
|
|
7932
|
+
const coerced = coerceToolInputToSchema(rawInput, schema6);
|
|
7933
|
+
if (coerced !== null) {
|
|
7934
|
+
return coerced;
|
|
7935
|
+
}
|
|
7936
|
+
return schema6.parse(rawInput);
|
|
7762
7937
|
}
|
|
7763
7938
|
async function executeCodingTool(toolName, input, options) {
|
|
7764
7939
|
const workspaceContext = getWorkspaceContext(options);
|
|
7765
7940
|
const validatedInput = parseCodingToolInput(toolName, input);
|
|
7766
7941
|
assertToolPermission(toolName, validatedInput, options);
|
|
7942
|
+
assertNotRepeatingToolCall(toolName, validatedInput, options?.turnKey);
|
|
7767
7943
|
const runtime4 = codingToolRuntimes[toolName];
|
|
7768
7944
|
const rawOutput = await runtime4(validatedInput, { workspaceContext, options });
|
|
7769
7945
|
return codingToolOutputSchemas[toolName].parse(rawOutput);
|
|
@@ -7771,6 +7947,8 @@ async function executeCodingTool(toolName, input, options) {
|
|
|
7771
7947
|
var codingToolRuntimes;
|
|
7772
7948
|
var init_runtime_registry = __esm(() => {
|
|
7773
7949
|
init_agent_tools();
|
|
7950
|
+
init_coerce_tool_input();
|
|
7951
|
+
init_tool_call_repeat_guard();
|
|
7774
7952
|
init_runtime4();
|
|
7775
7953
|
init_runtime6();
|
|
7776
7954
|
init_runtime7();
|
|
@@ -8072,6 +8250,7 @@ __export(exports_runtime, {
|
|
|
8072
8250
|
WORKSPACE: () => WORKSPACE,
|
|
8073
8251
|
PermissionPolicy: () => PermissionPolicy,
|
|
8074
8252
|
PermissionDeniedError: () => PermissionDeniedError,
|
|
8253
|
+
PROGRESS_NOTE_ELISION_STUB: () => PROGRESS_NOTE_ELISION_STUB,
|
|
8075
8254
|
McpServerManager: () => McpServerManager,
|
|
8076
8255
|
MAX_TOOL_TEXT_OUTPUT_CHARS: () => MAX_TOOL_TEXT_OUTPUT_CHARS,
|
|
8077
8256
|
MAX_TOOL_SEARCH_RESULTS: () => MAX_TOOL_SEARCH_RESULTS,
|
|
@@ -8236,6 +8415,24 @@ async function runCopyAction({
|
|
|
8236
8415
|
const copied = await copyToClipboard(payload);
|
|
8237
8416
|
notify(copied ? `Copied ${label} to clipboard (${payload.length} chars).` : "Copy failed: clipboard unavailable in this terminal.", copied ? "info" : "error");
|
|
8238
8417
|
}
|
|
8418
|
+
async function runSkillsAction({
|
|
8419
|
+
notify
|
|
8420
|
+
}) {
|
|
8421
|
+
try {
|
|
8422
|
+
const { listSkills: listSkills2 } = await Promise.resolve().then(() => (init_runtime17(), exports_runtime));
|
|
8423
|
+
const skills = listSkills2({ cwd: process.cwd() });
|
|
8424
|
+
if (skills.length === 0) {
|
|
8425
|
+
notify("No skills found. Add one at .lightcode/skills/<name>/SKILL.md (or ~/.lightcode/skills/ for all projects).");
|
|
8426
|
+
return;
|
|
8427
|
+
}
|
|
8428
|
+
const lines = skills.map((skill) => `\u2022 ${skill.name}${skill.description ? ` \u2014 ${skill.description}` : ""} (${skill.source})`);
|
|
8429
|
+
notify(`Available skills (ask the agent to use one by name):
|
|
8430
|
+
${lines.join(`
|
|
8431
|
+
`)}`);
|
|
8432
|
+
} catch (error) {
|
|
8433
|
+
notify(`Could not list skills: ${error instanceof Error ? error.message : "unknown error"}`, "error");
|
|
8434
|
+
}
|
|
8435
|
+
}
|
|
8239
8436
|
function findChatSlashAction(text) {
|
|
8240
8437
|
const command = text.trim().split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
8241
8438
|
return chatSlashActions.find((action) => action.shortcut === command) ?? null;
|
|
@@ -8294,6 +8491,14 @@ var init_chat_slash_actions = __esm(() => {
|
|
|
8294
8491
|
shortcut: "/undo",
|
|
8295
8492
|
run: runUndoAction
|
|
8296
8493
|
},
|
|
8494
|
+
{
|
|
8495
|
+
kind: "chat-action",
|
|
8496
|
+
id: "skills",
|
|
8497
|
+
label: "List skills",
|
|
8498
|
+
description: "Show the skills the agent can load in this workspace",
|
|
8499
|
+
shortcut: "/skills",
|
|
8500
|
+
run: runSkillsAction
|
|
8501
|
+
},
|
|
8297
8502
|
{
|
|
8298
8503
|
kind: "chat-action",
|
|
8299
8504
|
id: "permission",
|
|
@@ -9298,7 +9503,7 @@ function useCodingSessionChat({
|
|
|
9298
9503
|
sendMessage,
|
|
9299
9504
|
setMessages
|
|
9300
9505
|
]);
|
|
9301
|
-
const submitInput = useCallback((text) => {
|
|
9506
|
+
const submitInput = useCallback((text, files) => {
|
|
9302
9507
|
if (canRetryRecoverableResponse && isRetryCommand(text)) {
|
|
9303
9508
|
retryRecoverableResponse();
|
|
9304
9509
|
return;
|
|
@@ -9322,7 +9527,7 @@ function useCodingSessionChat({
|
|
|
9322
9527
|
}
|
|
9323
9528
|
setToolExecutionError(null);
|
|
9324
9529
|
advanceTurnKey();
|
|
9325
|
-
sendMessage({ text });
|
|
9530
|
+
sendMessage({ text, files });
|
|
9326
9531
|
}, [
|
|
9327
9532
|
advanceTurnKey,
|
|
9328
9533
|
pendingApprovals.length,
|
|
@@ -10579,83 +10784,61 @@ function getOutputPreviewLines(part, maxLines) {
|
|
|
10579
10784
|
return value.split(/\r?\n/).slice(0, maxLines).map((line) => truncateInline2(line, OUTPUT_LINE_MAX_CHARS));
|
|
10580
10785
|
}
|
|
10581
10786
|
}
|
|
10582
|
-
const matches = Reflect.get(output, "matches");
|
|
10583
|
-
if (Array.isArray(matches)) {
|
|
10584
|
-
return [`${matches.length} match${matches.length === 1 ? "" : "es"}`];
|
|
10585
|
-
}
|
|
10586
10787
|
return [];
|
|
10587
10788
|
}
|
|
10588
|
-
function basename(filePath) {
|
|
10589
|
-
const parts = filePath.split(/[\\/]/).filter(Boolean);
|
|
10590
|
-
return parts[parts.length - 1] ?? filePath;
|
|
10591
|
-
}
|
|
10592
|
-
function countTextLines(text) {
|
|
10593
|
-
const trimmed = text.replace(/[\r\n]+$/, "");
|
|
10594
|
-
return trimmed.length === 0 ? 0 : trimmed.split(/\r?\n/).length;
|
|
10595
|
-
}
|
|
10596
10789
|
function countDiffStats(diff) {
|
|
10597
10790
|
let added = 0;
|
|
10598
10791
|
let removed = 0;
|
|
10599
|
-
for (const line of diff.split(
|
|
10792
|
+
for (const line of diff.split(`
|
|
10793
|
+
`)) {
|
|
10600
10794
|
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
10601
|
-
added
|
|
10795
|
+
added++;
|
|
10602
10796
|
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
10603
|
-
removed
|
|
10797
|
+
removed++;
|
|
10604
10798
|
}
|
|
10605
10799
|
}
|
|
10606
10800
|
return { added, removed };
|
|
10607
10801
|
}
|
|
10608
|
-
function
|
|
10609
|
-
|
|
10802
|
+
function basename(path16) {
|
|
10803
|
+
const normalized = path16.replaceAll("\\", "/");
|
|
10804
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
10805
|
+
return lastSlash === -1 ? normalized : normalized.slice(lastSlash + 1);
|
|
10610
10806
|
}
|
|
10611
10807
|
function summarizeToolOutput(part) {
|
|
10808
|
+
if (part.state === "output-error") {
|
|
10809
|
+
return { text: "Failed to execute tool", isError: true };
|
|
10810
|
+
}
|
|
10811
|
+
if (part.state === "output-denied") {
|
|
10812
|
+
return { text: "Execution denied", isError: true };
|
|
10813
|
+
}
|
|
10612
10814
|
if (part.state !== "output-available") {
|
|
10613
10815
|
return null;
|
|
10614
10816
|
}
|
|
10615
10817
|
const output = part.output;
|
|
10616
10818
|
if (typeof output === "string") {
|
|
10617
|
-
const lines =
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
if (!output || typeof output !== "object") {
|
|
10621
|
-
return null;
|
|
10622
|
-
}
|
|
10623
|
-
const exitCode = Reflect.get(output, "exitCode");
|
|
10624
|
-
if (typeof exitCode === "number") {
|
|
10625
|
-
const stdout = Reflect.get(output, "stdout");
|
|
10626
|
-
const stdoutText = typeof stdout === "string" ? stdout : "";
|
|
10627
|
-
const testMatch = /(\d+)\s+(passed|failed|passing|failing)/i.exec(stdoutText);
|
|
10628
|
-
if (testMatch) {
|
|
10629
|
-
return { text: `Tests: ${testMatch[0]}`, isError: exitCode !== 0 };
|
|
10819
|
+
const lines = output.trim().split(/\r?\n/);
|
|
10820
|
+
if (lines.length === 0 || !lines[0]) {
|
|
10821
|
+
return { text: "No output generated" };
|
|
10630
10822
|
}
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
return { text: `exit ${exitCode}${tail}`, isError: exitCode !== 0 };
|
|
10634
|
-
}
|
|
10635
|
-
for (const key of ["content", "text"]) {
|
|
10636
|
-
const value = Reflect.get(output, key);
|
|
10637
|
-
if (typeof value === "string" && value.trim()) {
|
|
10638
|
-
return { text: pluralize(countTextLines(value), "line"), isError: false };
|
|
10823
|
+
if (lines.length === 1) {
|
|
10824
|
+
return null;
|
|
10639
10825
|
}
|
|
10826
|
+
return { text: `${lines.length} lines of output` };
|
|
10640
10827
|
}
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
return { text: pluralize(matches.length, "match"), isError: false };
|
|
10828
|
+
if (!output || typeof output !== "object") {
|
|
10829
|
+
return { text: "Completed" };
|
|
10644
10830
|
}
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
}
|
|
10831
|
+
const keys = Object.keys(output).filter((k) => k !== "error");
|
|
10832
|
+
const hasError = Boolean(Reflect.get(output, "error"));
|
|
10833
|
+
if (keys.includes("matches") && Reflect.get(output, "matches") instanceof Array) {
|
|
10834
|
+
const count = Reflect.get(output, "matches").length;
|
|
10835
|
+
return { text: `Found ${count} match${count === 1 ? "" : "es"}`, isError: hasError };
|
|
10651
10836
|
}
|
|
10652
|
-
|
|
10653
|
-
const
|
|
10654
|
-
|
|
10655
|
-
return { text: truncateInline2(value, 100), isError: false };
|
|
10656
|
-
}
|
|
10837
|
+
if (keys.includes("entries") && Reflect.get(output, "entries") instanceof Array) {
|
|
10838
|
+
const count = Reflect.get(output, "entries").length;
|
|
10839
|
+
return { text: `Listed ${count} file${count === 1 ? "" : "s"}`, isError: hasError };
|
|
10657
10840
|
}
|
|
10658
|
-
return
|
|
10841
|
+
return { text: "Completed", isError: hasError };
|
|
10659
10842
|
}
|
|
10660
10843
|
function ChatMessageToolInvocationPart({
|
|
10661
10844
|
part,
|
|
@@ -10680,9 +10863,19 @@ function ChatMessageToolInvocationPart({
|
|
|
10680
10863
|
const hasCollapsedRawOutput = !fileEditDiff && !expandedToolOutput && getOutputPreviewLines(part, 1).length > 0;
|
|
10681
10864
|
return /* @__PURE__ */ jsxDEV12("box", {
|
|
10682
10865
|
flexDirection: "column",
|
|
10866
|
+
paddingLeft: 1,
|
|
10867
|
+
paddingRight: 1,
|
|
10868
|
+
paddingBottom: 1,
|
|
10869
|
+
paddingTop: 1,
|
|
10870
|
+
borderStyle: borderStyleFor.chrome,
|
|
10871
|
+
border: ["left"],
|
|
10872
|
+
borderColor: stateColor,
|
|
10873
|
+
backgroundColor: cliTheme.surfaces.inset,
|
|
10874
|
+
gap: fileEditDiff || rawPreviewLines.length > 0 ? 1 : 0,
|
|
10683
10875
|
children: [
|
|
10684
10876
|
/* @__PURE__ */ jsxDEV12("text", {
|
|
10685
10877
|
fg: stateColor,
|
|
10878
|
+
attributes: TextAttributes9.BOLD,
|
|
10686
10879
|
children: line
|
|
10687
10880
|
}, undefined, false, undefined, this),
|
|
10688
10881
|
secondaryParams ? /* @__PURE__ */ jsxDEV12("text", {
|
|
@@ -10797,6 +10990,14 @@ function ChatMessage({ message, pendingApprovalIds }) {
|
|
|
10797
10990
|
width: "100%",
|
|
10798
10991
|
flexDirection: "column",
|
|
10799
10992
|
gap: space.xs,
|
|
10993
|
+
paddingLeft: 1,
|
|
10994
|
+
paddingRight: 1,
|
|
10995
|
+
paddingTop: 1,
|
|
10996
|
+
paddingBottom: 1,
|
|
10997
|
+
borderStyle: borderStyleFor.chrome,
|
|
10998
|
+
border: ["left"],
|
|
10999
|
+
borderColor: roleStyle.borderColor,
|
|
11000
|
+
backgroundColor: roleStyle.backgroundColor,
|
|
10800
11001
|
children: [
|
|
10801
11002
|
/* @__PURE__ */ jsxDEV13("text", {
|
|
10802
11003
|
fg: roleStyle.labelColor,
|
|
@@ -10806,8 +11007,8 @@ function ChatMessage({ message, pendingApprovalIds }) {
|
|
|
10806
11007
|
/* @__PURE__ */ jsxDEV13("box", {
|
|
10807
11008
|
width: "100%",
|
|
10808
11009
|
flexDirection: "column",
|
|
10809
|
-
gap: space.
|
|
10810
|
-
|
|
11010
|
+
gap: space.md,
|
|
11011
|
+
paddingTop: space.xs,
|
|
10811
11012
|
children: renderedParts
|
|
10812
11013
|
}, undefined, false, undefined, this)
|
|
10813
11014
|
]
|
|
@@ -10872,7 +11073,7 @@ function ChatShell({
|
|
|
10872
11073
|
/* @__PURE__ */ jsxDEV14("box", {
|
|
10873
11074
|
width: "100%",
|
|
10874
11075
|
flexGrow: 1,
|
|
10875
|
-
borderStyle:
|
|
11076
|
+
borderStyle: borderStyleFor.card,
|
|
10876
11077
|
borderColor: cliTheme.borders.default,
|
|
10877
11078
|
backgroundColor: cliTheme.surfaces.inset,
|
|
10878
11079
|
children: /* @__PURE__ */ jsxDEV14("scrollbox", {
|
|
@@ -11131,6 +11332,74 @@ var init_permission_mode_selector = __esm(() => {
|
|
|
11131
11332
|
];
|
|
11132
11333
|
});
|
|
11133
11334
|
|
|
11335
|
+
// apps/cli/src/utils/clipboard-image.ts
|
|
11336
|
+
import { spawn } from "child_process";
|
|
11337
|
+
import { mkdtemp, readFile as readFile3, rm } from "fs/promises";
|
|
11338
|
+
import { tmpdir } from "os";
|
|
11339
|
+
import path16 from "path";
|
|
11340
|
+
async function readClipboardImage() {
|
|
11341
|
+
if (process.platform !== "darwin") {
|
|
11342
|
+
return null;
|
|
11343
|
+
}
|
|
11344
|
+
let dir = null;
|
|
11345
|
+
try {
|
|
11346
|
+
dir = await mkdtemp(path16.join(tmpdir(), "lightcode-clip-"));
|
|
11347
|
+
const file = path16.join(dir, "clipboard.png");
|
|
11348
|
+
const wrote = await writeClipboardPngViaOsascript(file);
|
|
11349
|
+
if (!wrote) {
|
|
11350
|
+
return null;
|
|
11351
|
+
}
|
|
11352
|
+
const bytes = await readFile3(file);
|
|
11353
|
+
if (bytes.byteLength === 0) {
|
|
11354
|
+
return null;
|
|
11355
|
+
}
|
|
11356
|
+
return {
|
|
11357
|
+
mediaType: "image/png",
|
|
11358
|
+
base64: bytes.toString("base64"),
|
|
11359
|
+
byteLength: bytes.byteLength
|
|
11360
|
+
};
|
|
11361
|
+
} catch {
|
|
11362
|
+
return null;
|
|
11363
|
+
} finally {
|
|
11364
|
+
if (dir) {
|
|
11365
|
+
await rm(dir, { recursive: true, force: true }).catch(() => {});
|
|
11366
|
+
}
|
|
11367
|
+
}
|
|
11368
|
+
}
|
|
11369
|
+
function writeClipboardPngViaOsascript(filePath) {
|
|
11370
|
+
const script = [
|
|
11371
|
+
"try",
|
|
11372
|
+
" set thePng to (the clipboard as \xABclass PNGf\xBB)",
|
|
11373
|
+
` set fp to open for access POSIX file ${JSON.stringify(filePath)} with write permission`,
|
|
11374
|
+
" set eof fp to 0",
|
|
11375
|
+
" write thePng to fp",
|
|
11376
|
+
" close access fp",
|
|
11377
|
+
' return "OK"',
|
|
11378
|
+
"on error",
|
|
11379
|
+
" try",
|
|
11380
|
+
" close access fp",
|
|
11381
|
+
" end try",
|
|
11382
|
+
' return "NONE"',
|
|
11383
|
+
"end try"
|
|
11384
|
+
];
|
|
11385
|
+
const args = [];
|
|
11386
|
+
for (const line of script) {
|
|
11387
|
+
args.push("-e", line);
|
|
11388
|
+
}
|
|
11389
|
+
return new Promise((resolve) => {
|
|
11390
|
+
let stdout = "";
|
|
11391
|
+
const child = spawn("osascript", args, {
|
|
11392
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
11393
|
+
});
|
|
11394
|
+
child.stdout?.on("data", (chunk) => {
|
|
11395
|
+
stdout += chunk.toString();
|
|
11396
|
+
});
|
|
11397
|
+
child.on("error", () => resolve(false));
|
|
11398
|
+
child.on("close", () => resolve(stdout.trim() === "OK"));
|
|
11399
|
+
});
|
|
11400
|
+
}
|
|
11401
|
+
var init_clipboard_image = () => {};
|
|
11402
|
+
|
|
11134
11403
|
// apps/cli/src/utils/fuzzy-match.ts
|
|
11135
11404
|
function fuzzyScore(query, candidate) {
|
|
11136
11405
|
const normalizedQuery = query.toLowerCase();
|
|
@@ -11166,10 +11435,68 @@ var init_fuzzy_match = __esm(() => {
|
|
|
11166
11435
|
BOUNDARY_CHARS = new Set(["/", "\\", ".", "-", "_"]);
|
|
11167
11436
|
});
|
|
11168
11437
|
|
|
11438
|
+
// apps/cli/src/utils/image-attachments.ts
|
|
11439
|
+
function formatImageChip(index) {
|
|
11440
|
+
return `[Image #${index}]`;
|
|
11441
|
+
}
|
|
11442
|
+
function clipboardImageToFilePart(index, image) {
|
|
11443
|
+
return {
|
|
11444
|
+
type: "file",
|
|
11445
|
+
mediaType: image.mediaType,
|
|
11446
|
+
filename: `pasted-image-${index}.png`,
|
|
11447
|
+
url: `data:${image.mediaType};base64,${image.base64}`
|
|
11448
|
+
};
|
|
11449
|
+
}
|
|
11450
|
+
function stripImageChips(text) {
|
|
11451
|
+
return text.replace(IMAGE_CHIP_PATTERN, "").replace(/ {2,}/g, " ");
|
|
11452
|
+
}
|
|
11453
|
+
var IMAGE_CHIP_PATTERN;
|
|
11454
|
+
var init_image_attachments = __esm(() => {
|
|
11455
|
+
IMAGE_CHIP_PATTERN = /\[Image #(\d+)\]/g;
|
|
11456
|
+
});
|
|
11457
|
+
|
|
11458
|
+
// apps/cli/src/utils/paste-placeholders.ts
|
|
11459
|
+
function countLines(text) {
|
|
11460
|
+
if (text.length === 0) {
|
|
11461
|
+
return 0;
|
|
11462
|
+
}
|
|
11463
|
+
return text.split(/\r\n|\r|\n/).length;
|
|
11464
|
+
}
|
|
11465
|
+
function shouldCollapsePaste(text) {
|
|
11466
|
+
return countLines(text) > PASTE_PLACEHOLDER_LINE_THRESHOLD || text.length > PASTE_PLACEHOLDER_CHAR_THRESHOLD;
|
|
11467
|
+
}
|
|
11468
|
+
function formatPastePlaceholder(index, text) {
|
|
11469
|
+
return `[Pasted text #${index} +${countLines(text)} lines]`;
|
|
11470
|
+
}
|
|
11471
|
+
function expandPastePlaceholders(text, store) {
|
|
11472
|
+
return text.replace(PASTE_PLACEHOLDER_PATTERN, (match, rawIndex) => {
|
|
11473
|
+
const stored = store.get(Number(rawIndex));
|
|
11474
|
+
return stored ?? match;
|
|
11475
|
+
});
|
|
11476
|
+
}
|
|
11477
|
+
var PASTE_PLACEHOLDER_LINE_THRESHOLD = 5, PASTE_PLACEHOLDER_CHAR_THRESHOLD = 400, PASTE_PLACEHOLDER_PATTERN;
|
|
11478
|
+
var init_paste_placeholders = __esm(() => {
|
|
11479
|
+
PASTE_PLACEHOLDER_PATTERN = /\[Pasted text #(\d+) \+\d+ lines\]/g;
|
|
11480
|
+
});
|
|
11481
|
+
|
|
11169
11482
|
// apps/cli/src/components/chat/chat-text-area.tsx
|
|
11170
|
-
import {
|
|
11483
|
+
import {
|
|
11484
|
+
decodePasteBytes,
|
|
11485
|
+
stripAnsiSequences,
|
|
11486
|
+
TextAttributes as TextAttributes13
|
|
11487
|
+
} from "@opentui/core";
|
|
11488
|
+
import { appendFileSync as appendFileSync2 } from "fs";
|
|
11171
11489
|
import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2, useState as useState5 } from "react";
|
|
11172
11490
|
import { jsxDEV as jsxDEV16 } from "@opentui/react/jsx-dev-runtime";
|
|
11491
|
+
function pasteDebug(message) {
|
|
11492
|
+
if (!process.env.LIGHTCODE_PASTE_DEBUG) {
|
|
11493
|
+
return;
|
|
11494
|
+
}
|
|
11495
|
+
try {
|
|
11496
|
+
appendFileSync2("/tmp/lightcode-paste-debug.log", `${message}
|
|
11497
|
+
`);
|
|
11498
|
+
} catch {}
|
|
11499
|
+
}
|
|
11173
11500
|
function getTrailingMentionQuery(text) {
|
|
11174
11501
|
const match = text.match(trailingMentionPattern);
|
|
11175
11502
|
return match ? match[1] : null;
|
|
@@ -11191,9 +11518,66 @@ function ChatTextArea({
|
|
|
11191
11518
|
const textareaRef = useRef2(null);
|
|
11192
11519
|
const lastManualNewlineAt = useRef2(0);
|
|
11193
11520
|
const wasSlashMenuOpen = useRef2(false);
|
|
11521
|
+
const pasteStore = useRef2(new Map);
|
|
11522
|
+
const pasteCounter = useRef2(0);
|
|
11523
|
+
const imageStore = useRef2(new Map);
|
|
11524
|
+
const imageCounter = useRef2(0);
|
|
11194
11525
|
const [mentionQuery, setMentionQuery] = useState5(null);
|
|
11195
11526
|
const [mentionSelected, setMentionSelected] = useState5(0);
|
|
11196
11527
|
const isFocused = focused && !disabled;
|
|
11528
|
+
const attachClipboardImage = useCallback3(() => {
|
|
11529
|
+
readClipboardImage().then((image) => {
|
|
11530
|
+
pasteDebug(`clipboard read: ${image ? `image ${image.byteLength} bytes` : "no image"}`);
|
|
11531
|
+
const textarea = textareaRef.current;
|
|
11532
|
+
if (!image || !textarea) {
|
|
11533
|
+
return;
|
|
11534
|
+
}
|
|
11535
|
+
const index = imageCounter.current + 1;
|
|
11536
|
+
imageCounter.current = index;
|
|
11537
|
+
imageStore.current.set(index, clipboardImageToFilePart(index, image));
|
|
11538
|
+
textarea.insertText(formatImageChip(index));
|
|
11539
|
+
onTextChange?.(textarea.plainText);
|
|
11540
|
+
});
|
|
11541
|
+
}, [onTextChange]);
|
|
11542
|
+
const handlePaste = useCallback3((event) => {
|
|
11543
|
+
const pasted = stripAnsiSequences(decodePasteBytes(event.bytes));
|
|
11544
|
+
pasteDebug(`paste fired: bytes=${event.bytes?.length ?? "?"} decodedLen=${pasted.length} empty=${pasted.trim().length === 0}`);
|
|
11545
|
+
const textarea = textareaRef.current;
|
|
11546
|
+
if (!textarea) {
|
|
11547
|
+
return;
|
|
11548
|
+
}
|
|
11549
|
+
if (pasted.trim().length === 0) {
|
|
11550
|
+
event.preventDefault();
|
|
11551
|
+
attachClipboardImage();
|
|
11552
|
+
return;
|
|
11553
|
+
}
|
|
11554
|
+
if (!shouldCollapsePaste(pasted)) {
|
|
11555
|
+
return;
|
|
11556
|
+
}
|
|
11557
|
+
event.preventDefault();
|
|
11558
|
+
const index = pasteCounter.current + 1;
|
|
11559
|
+
pasteCounter.current = index;
|
|
11560
|
+
pasteStore.current.set(index, pasted);
|
|
11561
|
+
textarea.insertText(formatPastePlaceholder(index, pasted));
|
|
11562
|
+
const next = textarea.plainText;
|
|
11563
|
+
if (mentionCandidates) {
|
|
11564
|
+
setMentionQuery(getTrailingMentionQuery(next));
|
|
11565
|
+
setMentionSelected(0);
|
|
11566
|
+
}
|
|
11567
|
+
onTextChange?.(next);
|
|
11568
|
+
}, [mentionCandidates, onTextChange]);
|
|
11569
|
+
useEffect3(() => {
|
|
11570
|
+
const textarea = textareaRef.current;
|
|
11571
|
+
if (!textarea) {
|
|
11572
|
+
return;
|
|
11573
|
+
}
|
|
11574
|
+
textarea.onPaste = handlePaste;
|
|
11575
|
+
return () => {
|
|
11576
|
+
if (textarea.onPaste === handlePaste) {
|
|
11577
|
+
textarea.onPaste = undefined;
|
|
11578
|
+
}
|
|
11579
|
+
};
|
|
11580
|
+
}, [handlePaste]);
|
|
11197
11581
|
const mentionMatches = mentionQuery !== null && mentionCandidates && mentionCandidates.length > 0 ? fuzzyFilter(mentionQuery, mentionCandidates) : [];
|
|
11198
11582
|
const selectedMentionIndex = Math.min(mentionSelected, Math.max(mentionMatches.length - 1, 0));
|
|
11199
11583
|
const acceptMention = useCallback3((mentionPath) => {
|
|
@@ -11217,6 +11601,12 @@ function ChatTextArea({
|
|
|
11217
11601
|
textareaRef.current?.newLine();
|
|
11218
11602
|
return;
|
|
11219
11603
|
}
|
|
11604
|
+
if (event.name === "v" && event.ctrl) {
|
|
11605
|
+
event.preventDefault();
|
|
11606
|
+
pasteDebug("ctrl+v: grab clipboard image");
|
|
11607
|
+
attachClipboardImage();
|
|
11608
|
+
return;
|
|
11609
|
+
}
|
|
11220
11610
|
if (mentionMatches.length === 0) {
|
|
11221
11611
|
return;
|
|
11222
11612
|
}
|
|
@@ -11234,18 +11624,24 @@ function ChatTextArea({
|
|
|
11234
11624
|
setMentionQuery(null);
|
|
11235
11625
|
setMentionSelected(0);
|
|
11236
11626
|
}
|
|
11237
|
-
}, [acceptMention, mentionMatches, selectedMentionIndex]);
|
|
11627
|
+
}, [acceptMention, attachClipboardImage, mentionMatches, selectedMentionIndex]);
|
|
11238
11628
|
const handleSubmit = useCallback3(() => {
|
|
11239
11629
|
if (Date.now() - lastManualNewlineAt.current < 100 || disabled) {
|
|
11240
11630
|
return;
|
|
11241
11631
|
}
|
|
11242
11632
|
const rawText = textareaRef.current?.plainText ?? "";
|
|
11243
|
-
const
|
|
11244
|
-
|
|
11633
|
+
const expandedText = stripImageChips(expandPastePlaceholders(rawText, pasteStore.current));
|
|
11634
|
+
const submittedText = trimOnSubmit ? expandedText.trim() : expandedText;
|
|
11635
|
+
const files = [...imageStore.current.values()];
|
|
11636
|
+
if (!allowEmpty && submittedText.length === 0 && files.length === 0) {
|
|
11245
11637
|
return;
|
|
11246
11638
|
}
|
|
11247
|
-
onSubmit(submittedText);
|
|
11639
|
+
onSubmit(submittedText, files.length > 0 ? files : undefined);
|
|
11248
11640
|
textareaRef.current?.setText("");
|
|
11641
|
+
pasteStore.current.clear();
|
|
11642
|
+
pasteCounter.current = 0;
|
|
11643
|
+
imageStore.current.clear();
|
|
11644
|
+
imageCounter.current = 0;
|
|
11249
11645
|
setMentionQuery(null);
|
|
11250
11646
|
setMentionSelected(0);
|
|
11251
11647
|
onTextChange?.("");
|
|
@@ -11280,7 +11676,7 @@ function ChatTextArea({
|
|
|
11280
11676
|
beforeInput,
|
|
11281
11677
|
mentionMatches.length > 0 ? /* @__PURE__ */ jsxDEV16("box", {
|
|
11282
11678
|
flexDirection: "column",
|
|
11283
|
-
borderStyle:
|
|
11679
|
+
borderStyle: borderStyleFor.card,
|
|
11284
11680
|
borderColor: cliTheme.overlay.border,
|
|
11285
11681
|
backgroundColor: cliTheme.overlay.surface,
|
|
11286
11682
|
paddingX: 1,
|
|
@@ -11300,7 +11696,7 @@ function ChatTextArea({
|
|
|
11300
11696
|
}, undefined, true, undefined, this) : null,
|
|
11301
11697
|
/* @__PURE__ */ jsxDEV16("box", {
|
|
11302
11698
|
flexDirection: "column",
|
|
11303
|
-
borderStyle:
|
|
11699
|
+
borderStyle: borderStyleFor.card,
|
|
11304
11700
|
borderColor: isFocused ? cliTheme.input.focusedBorder : cliTheme.input.blurredBorder,
|
|
11305
11701
|
backgroundColor: cliTheme.input.container,
|
|
11306
11702
|
paddingX: 1,
|
|
@@ -11335,7 +11731,10 @@ function ChatTextArea({
|
|
|
11335
11731
|
var trailingMentionPattern, textareaKeyBindings;
|
|
11336
11732
|
var init_chat_text_area = __esm(() => {
|
|
11337
11733
|
init_cli_theme();
|
|
11734
|
+
init_clipboard_image();
|
|
11338
11735
|
init_fuzzy_match();
|
|
11736
|
+
init_image_attachments();
|
|
11737
|
+
init_paste_placeholders();
|
|
11339
11738
|
trailingMentionPattern = /(?:^|\s)@([^\s@]*)$/;
|
|
11340
11739
|
textareaKeyBindings = [
|
|
11341
11740
|
{ name: "return", ctrl: true, action: "newline" },
|
|
@@ -11366,6 +11765,8 @@ function ChatTodoStatusCard({ todos }) {
|
|
|
11366
11765
|
return null;
|
|
11367
11766
|
}
|
|
11368
11767
|
const completedCount = todos.filter((todo) => todo.status === "completed").length;
|
|
11768
|
+
const percent = todos.length > 0 ? Math.round(completedCount / todos.length * 100) : 0;
|
|
11769
|
+
const progressText = completedCount === 0 ? "Not started" : `${completedCount}/${todos.length} done (${percent}%)`;
|
|
11369
11770
|
return /* @__PURE__ */ jsxDEV17("box", {
|
|
11370
11771
|
width: "100%",
|
|
11371
11772
|
flexDirection: "column",
|
|
@@ -11388,47 +11789,43 @@ function ChatTodoStatusCard({ todos }) {
|
|
|
11388
11789
|
}, undefined, false, undefined, this),
|
|
11389
11790
|
/* @__PURE__ */ jsxDEV17("text", {
|
|
11390
11791
|
fg: cliTheme.text.muted,
|
|
11391
|
-
children:
|
|
11392
|
-
|
|
11393
|
-
"/",
|
|
11394
|
-
todos.length,
|
|
11395
|
-
" done"
|
|
11396
|
-
]
|
|
11397
|
-
}, undefined, true, undefined, this)
|
|
11792
|
+
children: progressText
|
|
11793
|
+
}, undefined, false, undefined, this)
|
|
11398
11794
|
]
|
|
11399
11795
|
}, undefined, true, undefined, this),
|
|
11400
11796
|
/* @__PURE__ */ jsxDEV17("box", {
|
|
11401
11797
|
width: "100%",
|
|
11402
11798
|
flexDirection: "column",
|
|
11403
|
-
children: todos.
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
11799
|
+
children: todos.map((todo, index) => {
|
|
11800
|
+
const isActive = todo.status === "in_progress";
|
|
11801
|
+
const label = isActive && todo.activeForm ? todo.activeForm : todo.content;
|
|
11802
|
+
return /* @__PURE__ */ jsxDEV17("box", {
|
|
11803
|
+
width: "100%",
|
|
11804
|
+
flexDirection: "row",
|
|
11805
|
+
gap: 1,
|
|
11806
|
+
children: [
|
|
11807
|
+
/* @__PURE__ */ jsxDEV17("text", {
|
|
11808
|
+
fg: getStatusColor(todo.status),
|
|
11809
|
+
children: todo.status === "completed" ? "[x]" : "[ ]"
|
|
11810
|
+
}, undefined, false, undefined, this),
|
|
11811
|
+
/* @__PURE__ */ jsxDEV17("text", {
|
|
11812
|
+
fg: cliTheme.text.primary,
|
|
11813
|
+
children: truncateInline2(label)
|
|
11814
|
+
}, undefined, false, undefined, this),
|
|
11815
|
+
isActive && todo.activeForm ? /* @__PURE__ */ jsxDEV17("text", {
|
|
11816
|
+
fg: cliTheme.text.muted,
|
|
11817
|
+
attributes: TextAttributes14.DIM,
|
|
11818
|
+
children: truncateInline2(todo.content)
|
|
11819
|
+
}, undefined, false, undefined, this) : null,
|
|
11820
|
+
/* @__PURE__ */ jsxDEV17("text", {
|
|
11821
|
+
fg: getStatusColor(todo.status),
|
|
11822
|
+
attributes: TextAttributes14.DIM,
|
|
11823
|
+
children: todoStatusLabel[todo.status]
|
|
11824
|
+
}, undefined, false, undefined, this)
|
|
11825
|
+
]
|
|
11826
|
+
}, todo.id ?? `${todo.content}-${index}`, true, undefined, this);
|
|
11827
|
+
})
|
|
11828
|
+
}, undefined, false, undefined, this)
|
|
11432
11829
|
]
|
|
11433
11830
|
}, undefined, true, undefined, this);
|
|
11434
11831
|
}
|
|
@@ -11903,7 +12300,7 @@ var init_chat_context_utils = __esm(() => {
|
|
|
11903
12300
|
|
|
11904
12301
|
// apps/cli/src/utils/file-mentions.ts
|
|
11905
12302
|
import { promises as fs6 } from "fs";
|
|
11906
|
-
import
|
|
12303
|
+
import path17 from "path";
|
|
11907
12304
|
function extractFileMentions(text) {
|
|
11908
12305
|
const mentions = new Set;
|
|
11909
12306
|
for (const match of text.matchAll(mentionTokenPattern)) {
|
|
@@ -11925,8 +12322,8 @@ async function appendMentionAttachments(text, cwd) {
|
|
|
11925
12322
|
}
|
|
11926
12323
|
const attachmentBlocks = [];
|
|
11927
12324
|
for (const mentionPath of mentionPaths) {
|
|
11928
|
-
const absolutePath =
|
|
11929
|
-
if (!absolutePath.startsWith(
|
|
12325
|
+
const absolutePath = path17.resolve(cwd, mentionPath);
|
|
12326
|
+
if (!absolutePath.startsWith(path17.resolve(cwd))) {
|
|
11930
12327
|
continue;
|
|
11931
12328
|
}
|
|
11932
12329
|
try {
|
|
@@ -12379,24 +12776,26 @@ function ChatScreen() {
|
|
|
12379
12776
|
setSlashMenuQuery,
|
|
12380
12777
|
setSlashMenuSelected
|
|
12381
12778
|
]);
|
|
12382
|
-
const submitChatInput = useCallback4((text) => {
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12779
|
+
const submitChatInput = useCallback4((text, files) => {
|
|
12780
|
+
if (!files || files.length === 0) {
|
|
12781
|
+
const action = findChatSlashAction(text);
|
|
12782
|
+
if (action) {
|
|
12783
|
+
closeSlashMenu();
|
|
12784
|
+
if (action.id === "permission") {
|
|
12785
|
+
setPermissionSelectorOpen(true);
|
|
12786
|
+
return;
|
|
12787
|
+
}
|
|
12788
|
+
runChatSlashAction(action, parseChatSlashArgs(text));
|
|
12789
|
+
return;
|
|
12790
|
+
}
|
|
12791
|
+
if (navigateIfSlashRoute(text)) {
|
|
12388
12792
|
return;
|
|
12389
12793
|
}
|
|
12390
|
-
runChatSlashAction(action, parseChatSlashArgs(text));
|
|
12391
|
-
return;
|
|
12392
|
-
}
|
|
12393
|
-
if (navigateIfSlashRoute(text)) {
|
|
12394
|
-
return;
|
|
12395
12794
|
}
|
|
12396
12795
|
setActionNotice(null);
|
|
12397
12796
|
(async () => {
|
|
12398
12797
|
const expandedText = await appendMentionAttachments(text, process.cwd());
|
|
12399
|
-
submitInput(expandedText);
|
|
12798
|
+
submitInput(expandedText, files);
|
|
12400
12799
|
})();
|
|
12401
12800
|
}, [closeSlashMenu, navigateIfSlashRoute, runChatSlashAction, submitInput]);
|
|
12402
12801
|
const modeDefinition = codingAgentModes[mode];
|
|
@@ -14102,13 +14501,13 @@ import { TextAttributes as TextAttributes21 } from "@opentui/core";
|
|
|
14102
14501
|
import { useKeyboard as useKeyboard9 } from "@opentui/react";
|
|
14103
14502
|
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
14104
14503
|
import os5 from "os";
|
|
14105
|
-
import
|
|
14504
|
+
import path18 from "path";
|
|
14106
14505
|
import { useCallback as useCallback7, useState as useState18 } from "react";
|
|
14107
14506
|
import { useNavigate as useNavigate4 } from "react-router";
|
|
14108
14507
|
import { jsxDEV as jsxDEV31 } from "@opentui/react/jsx-dev-runtime";
|
|
14109
14508
|
function getUserSettingsPath() {
|
|
14110
14509
|
const configured = process.env.LIGHTCODE_USER_CONFIG?.trim();
|
|
14111
|
-
return configured ??
|
|
14510
|
+
return configured ?? path18.join(os5.homedir(), ".lightcode", "settings.json");
|
|
14112
14511
|
}
|
|
14113
14512
|
function mergeUserSettings(update) {
|
|
14114
14513
|
const settingsPath = getUserSettingsPath();
|
|
@@ -14121,7 +14520,7 @@ function mergeUserSettings(update) {
|
|
|
14121
14520
|
}
|
|
14122
14521
|
} catch {}
|
|
14123
14522
|
}
|
|
14124
|
-
mkdirSync5(
|
|
14523
|
+
mkdirSync5(path18.dirname(settingsPath), { recursive: true });
|
|
14125
14524
|
writeFileSync3(settingsPath, `${JSON.stringify({ ...existing, ...update }, null, 2)}
|
|
14126
14525
|
`, "utf8");
|
|
14127
14526
|
return settingsPath;
|
|
@@ -15028,9 +15427,9 @@ function AppContent() {
|
|
|
15028
15427
|
const inChatSession = location.pathname.startsWith("/sessions/");
|
|
15029
15428
|
const inputHostsSlashMenu = location.pathname === "/" || location.pathname === "/home" || inChatSession;
|
|
15030
15429
|
const handleAction = (action) => {
|
|
15031
|
-
const
|
|
15032
|
-
if (
|
|
15033
|
-
navigate(
|
|
15430
|
+
const path19 = getPathFromAction(action);
|
|
15431
|
+
if (path19) {
|
|
15432
|
+
navigate(path19);
|
|
15034
15433
|
return;
|
|
15035
15434
|
}
|
|
15036
15435
|
switch (action) {
|
|
@@ -15186,11 +15585,11 @@ function AppContent() {
|
|
|
15186
15585
|
/* @__PURE__ */ jsxDEV34("box", {
|
|
15187
15586
|
flexDirection: "row",
|
|
15188
15587
|
justifyContent: "space-between",
|
|
15189
|
-
paddingLeft:
|
|
15190
|
-
paddingRight:
|
|
15191
|
-
|
|
15192
|
-
|
|
15193
|
-
|
|
15588
|
+
paddingLeft: 2,
|
|
15589
|
+
paddingRight: 2,
|
|
15590
|
+
paddingTop: 1,
|
|
15591
|
+
paddingBottom: 1,
|
|
15592
|
+
backgroundColor: cliTheme.surfaces.panel,
|
|
15194
15593
|
children: [
|
|
15195
15594
|
/* @__PURE__ */ jsxDEV34("box", {
|
|
15196
15595
|
flexDirection: "row",
|
|
@@ -15337,11 +15736,9 @@ function AppContent() {
|
|
|
15337
15736
|
flexDirection: "row",
|
|
15338
15737
|
justifyContent: "center",
|
|
15339
15738
|
gap: 2,
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
border: ["top"],
|
|
15344
|
-
borderColor: cliTheme.borders.default,
|
|
15739
|
+
paddingX: 2,
|
|
15740
|
+
paddingY: 1,
|
|
15741
|
+
backgroundColor: cliTheme.surfaces.panel,
|
|
15345
15742
|
children: /* @__PURE__ */ jsxDEV34("text", {
|
|
15346
15743
|
fg: ctrlCNotice ? cliTheme.semantic.warning : cliTheme.text.muted,
|
|
15347
15744
|
children: ctrlCNotice ?? getFooterStatus()
|