tarsk 0.5.59 → 0.5.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +862 -144
- package/dist/public/assets/account-view-DPM8TZiH.js +1 -0
- package/dist/public/assets/api-BcqwPTwD.js +1 -0
- package/dist/public/assets/{async-error-boundary-CaRNnJZe.js → async-error-boundary-B56Jpu_o.js} +1 -1
- package/dist/public/assets/browser-tab-k2H96rcs.js +8 -0
- package/dist/public/assets/{card-DFACZp3F.js → card-C2MSBS4a.js} +1 -1
- package/dist/public/assets/commit-dialog-CPsT5k3n.js +1 -0
- package/dist/public/assets/context-menu-7zA2_Vvh.js +1 -0
- package/dist/public/assets/create-repo-dialog-wC-DyHKe.js +1 -0
- package/dist/public/assets/dialogs-config-C7xqDv5K.js +51 -0
- package/dist/public/assets/diff-view-dAnk7skm.js +4 -0
- package/dist/public/assets/{explorer-tab-context-DCq1BBX_.js → explorer-tab-context-gi63e_bv.js} +1 -1
- package/dist/public/assets/explorer-tab-view-ai_mDxBQ.js +2 -0
- package/dist/public/assets/explorer-tree-BZFGE0sU.js +1 -0
- package/dist/public/assets/explorer-view-D6MhosMA.js +75 -0
- package/dist/public/assets/git-history-dialog-Dd_DmfZC.js +1 -0
- package/dist/public/assets/git-ops-button-BhHIYXxb.js +2 -0
- package/dist/public/assets/history-view-DVqIaJOR.js +16 -0
- package/dist/public/assets/index-CyxfX_DC.css +1 -0
- package/dist/public/assets/index-D2AOIdVd.js +75 -0
- package/dist/public/assets/mcp-server-card-CuBKoFtN.js +1 -0
- package/dist/public/assets/merged-pr-dialog-M4vwv3lT.js +1 -0
- package/dist/public/assets/model-list-controls-DBAyi2PU.js +1 -0
- package/dist/public/assets/{monaco-CMkNoWsM.js → monaco-D3l3Gbj2.js} +1 -1
- package/dist/public/assets/onboarding-BpwQ9yz5.js +1 -0
- package/dist/public/assets/project-env-vars-view-Cs3LO_18.js +1 -0
- package/dist/public/assets/project-model-aliases-view-Z5b4ZC1k.js +1 -0
- package/dist/public/assets/project-settings-view-BOO6FS8b.js +1 -0
- package/dist/public/assets/project-tools-view-cjwDYCta.js +1 -0
- package/dist/public/assets/providers-list-view-BBGnB3Rq.js +1 -0
- package/dist/public/assets/pull-request-dialog-VOoEmwTG.js +1 -0
- package/dist/public/assets/pull-with-changes-dialog-CoHIwHJZ.js +1 -0
- package/dist/public/assets/push-before-pr-dialog-9fV4cFz5.js +1 -0
- package/dist/public/assets/react-vendor-XZeum4Wo.js +14 -0
- package/dist/public/assets/settings-general-view-CDr9M3SH.js +1 -0
- package/dist/public/assets/settings-instructions-view-B2mWcZiS.js +1 -0
- package/dist/public/assets/settings-list-JqM9DgX9.js +1 -0
- package/dist/public/assets/settings-logs-view-BjlFrCXc.js +5 -0
- package/dist/public/assets/settings-mcp-servers-view-BmVtKqmN.js +5 -0
- package/dist/public/assets/settings-models-view-E-HNZWxO.js +1 -0
- package/dist/public/assets/settings-rules-view-CdOk3Osl.js +8 -0
- package/dist/public/assets/settings-skills-view-Cn-GySxU.js +3 -0
- package/dist/public/assets/settings-slash-commands-view-CqYBr6_A.js +1 -0
- package/dist/public/assets/settings-statistics-view-B92b4bKS.js +1 -0
- package/dist/public/assets/settings-subagents-view-AL0iV3gX.js +2 -0
- package/dist/public/assets/settings-system-prompt-view-B6CBgNW3.js +1 -0
- package/dist/public/assets/settings-telegram-view-DHQxRoFn.js +1 -0
- package/dist/public/assets/settings-view-CBLOR6Gr.js +2 -0
- package/dist/public/assets/{skeleton-DBWV1Usf.js → skeleton-CK0EzL_G.js} +1 -1
- package/dist/public/assets/slug-utils-CdbHvCiP.js +1 -0
- package/dist/public/assets/tasks-view-D1nPjESr.js +2 -0
- package/dist/public/assets/{terminal-panel-Dlon-VDl.js → terminal-panel-m9GEHtY2.js} +2 -2
- package/dist/public/assets/{ui-components-Dd2afm0c.js → ui-components-BuIiDdYp.js} +1 -1
- package/dist/public/assets/{use-deferred-search-Cu-o4nau.js → use-deferred-search-hIqqinof.js} +1 -1
- package/dist/public/assets/use-installed-ai-files-C1Uta9US.js +1 -0
- package/dist/public/assets/{use-settings-page-loading-D1JlmLmD.js → use-settings-page-loading-DtwNt6_e.js} +1 -1
- package/dist/public/assets/{utils-Dt07QR3w.js → utils-B1IYtes_.js} +1 -1
- package/dist/public/assets/{web-CdkfJJZ3.js → web-BF1VNxf3.js} +1 -1
- package/dist/public/assets/{web-DeCAC9ap.js → web-BGxrdTO9.js} +1 -1
- package/dist/public/assets/{web-DsLUxLTR.js → web-DrKfK3e-.js} +1 -1
- package/dist/public/assets/{web-CiaJzwav.js → web-LjAIxEG5.js} +1 -1
- package/dist/public/index.html +9 -9
- package/package.json +1 -1
- package/dist/public/assets/account-view-Cc6oDwk4.js +0 -1
- package/dist/public/assets/api-CtfZCd77.js +0 -1
- package/dist/public/assets/browser-tab-BfAt-Sho.js +0 -8
- package/dist/public/assets/commit-dialog-Ck0r2v9S.js +0 -1
- package/dist/public/assets/context-menu-CBvTcHUI.js +0 -1
- package/dist/public/assets/create-repo-dialog-CBvRaov7.js +0 -1
- package/dist/public/assets/dialogs-config-C2KFQf9x.js +0 -51
- package/dist/public/assets/diff-view-DDECIdhE.js +0 -4
- package/dist/public/assets/explorer-tab-view-72Krb0Ir.js +0 -2
- package/dist/public/assets/explorer-tree-CcFgAYMn.js +0 -1
- package/dist/public/assets/explorer-view-DsNxf6nu.js +0 -75
- package/dist/public/assets/git-history-dialog--akFTQOd.js +0 -1
- package/dist/public/assets/git-ops-button-CKGLh3Zk.js +0 -2
- package/dist/public/assets/history-view-IcXfB6BD.js +0 -15
- package/dist/public/assets/index-BmpLvHah.js +0 -75
- package/dist/public/assets/index-r8I03VVW.css +0 -1
- package/dist/public/assets/mcp-server-card-DDmDAlY8.js +0 -1
- package/dist/public/assets/merged-pr-dialog-CiwhRqFv.js +0 -1
- package/dist/public/assets/model-list-controls-D5IaOffK.js +0 -1
- package/dist/public/assets/onboarding-CFIVEI92.js +0 -1
- package/dist/public/assets/project-settings-view-Klpu7p3U.js +0 -1
- package/dist/public/assets/providers-list-view-Df-ONKwe.js +0 -1
- package/dist/public/assets/pull-request-dialog-DVYZgMuN.js +0 -1
- package/dist/public/assets/pull-with-changes-dialog-CHHJ0gpA.js +0 -1
- package/dist/public/assets/push-before-pr-dialog-DoNMB3C8.js +0 -1
- package/dist/public/assets/react-vendor-tSKmVA4e.js +0 -14
- package/dist/public/assets/settings-general-view-yDDYKQmO.js +0 -1
- package/dist/public/assets/settings-instructions-view-y9bUZtKn.js +0 -1
- package/dist/public/assets/settings-list-j_NDw-aW.js +0 -1
- package/dist/public/assets/settings-logs-view-BzrjJRk6.js +0 -5
- package/dist/public/assets/settings-mcp-servers-view-NuKeyQ92.js +0 -5
- package/dist/public/assets/settings-models-view-64UG4njI.js +0 -1
- package/dist/public/assets/settings-rules-view-DdxIfxki.js +0 -8
- package/dist/public/assets/settings-skills-view-DoVxhA-A.js +0 -3
- package/dist/public/assets/settings-slash-commands-view-vm0HNVw1.js +0 -1
- package/dist/public/assets/settings-statistics-view-CKc3bD1x.js +0 -1
- package/dist/public/assets/settings-subagents-view-DuHK-7uc.js +0 -2
- package/dist/public/assets/settings-system-prompt-view-1clIREZ5.js +0 -1
- package/dist/public/assets/settings-telegram-view-DCPC5GxD.js +0 -1
- package/dist/public/assets/settings-view-BweC2YuW.js +0 -2
- package/dist/public/assets/slug-utils-D8SJJo7H.js +0 -1
- package/dist/public/assets/tasks-view-Cg9N8GZb.js +0 -2
- package/dist/public/assets/use-installed-ai-files-X8NXRdGD.js +0 -1
package/dist/index.js
CHANGED
|
@@ -636,8 +636,8 @@ var init_observations = __esm({
|
|
|
636
636
|
"use strict";
|
|
637
637
|
OBSERVATION_ID_PREFIX = "obs_";
|
|
638
638
|
OBSERVATION_EXTERNALIZE_MIN_BYTES = 8 * 1024;
|
|
639
|
-
OBSERVATION_PREVIEW_MAX_BYTES =
|
|
640
|
-
OBSERVATION_PREVIEW_MAX_LINES =
|
|
639
|
+
OBSERVATION_PREVIEW_MAX_BYTES = 10 * 1024;
|
|
640
|
+
OBSERVATION_PREVIEW_MAX_LINES = 250;
|
|
641
641
|
OBSERVATION_RETRIEVAL_DEFAULT_LINES = 200;
|
|
642
642
|
OBSERVATION_RETRIEVAL_MAX_LINES = 2e3;
|
|
643
643
|
OBSERVATION_RETRIEVAL_MAX_BYTES = 50 * 1024;
|
|
@@ -648,7 +648,7 @@ var init_observations = __esm({
|
|
|
648
648
|
function isMicrocompactEligibleTool(toolName) {
|
|
649
649
|
return MICROCOMPACT_ELIGIBLE_TOOL_SET.has(toolName);
|
|
650
650
|
}
|
|
651
|
-
var MICROCOMPACT_KEEP_RECENT, MICROCOMPACT_KEEP_TOKENS, MICROCOMPACT_TRIGGER_MARGIN, MICROCOMPACT_MIN_SAVE_TOKENS, MICROCOMPACT_DIGEST_MIN_CHARS, MICROCOMPACT_MARKER_PREFIX, MICROCOMPACT_ELIGIBLE_TOOLS, MICROCOMPACT_ELIGIBLE_TOOL_SET;
|
|
651
|
+
var MICROCOMPACT_KEEP_RECENT, MICROCOMPACT_KEEP_TOKENS, MICROCOMPACT_TRIGGER_MARGIN, MICROCOMPACT_MIN_SAVE_TOKENS, MICROCOMPACT_DIGEST_MIN_CHARS, MICROCOMPACT_PROACTIVE_TURN_THRESHOLD, MICROCOMPACT_PROACTIVE_PAYLOAD_TOKENS, MICROCOMPACT_PROACTIVE_MIN_SAVE_TOKENS, MICROCOMPACT_MARKER_PREFIX, MICROCOMPACT_ELIGIBLE_TOOLS, MICROCOMPACT_ELIGIBLE_TOOL_SET;
|
|
652
652
|
var init_microcompact = __esm({
|
|
653
653
|
"../shared/dist/microcompact.js"() {
|
|
654
654
|
"use strict";
|
|
@@ -657,6 +657,9 @@ var init_microcompact = __esm({
|
|
|
657
657
|
MICROCOMPACT_TRIGGER_MARGIN = 2e4;
|
|
658
658
|
MICROCOMPACT_MIN_SAVE_TOKENS = 2e4;
|
|
659
659
|
MICROCOMPACT_DIGEST_MIN_CHARS = 2e3;
|
|
660
|
+
MICROCOMPACT_PROACTIVE_TURN_THRESHOLD = 15;
|
|
661
|
+
MICROCOMPACT_PROACTIVE_PAYLOAD_TOKENS = 5e4;
|
|
662
|
+
MICROCOMPACT_PROACTIVE_MIN_SAVE_TOKENS = 5e3;
|
|
660
663
|
MICROCOMPACT_MARKER_PREFIX = "[Tool result compacted]";
|
|
661
664
|
MICROCOMPACT_ELIGIBLE_TOOLS = [
|
|
662
665
|
"read",
|
|
@@ -1102,6 +1105,77 @@ var init_tool_display = __esm({
|
|
|
1102
1105
|
}
|
|
1103
1106
|
});
|
|
1104
1107
|
|
|
1108
|
+
// ../shared/dist/tool-call-group.js
|
|
1109
|
+
function pluralize(count, singular, plural) {
|
|
1110
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
1111
|
+
}
|
|
1112
|
+
function categoryPhrases(verb, singular, plural) {
|
|
1113
|
+
return {
|
|
1114
|
+
formatPhrase: (count) => verb === null ? pluralize(count, singular, plural) : `${verb} ${pluralize(count, singular, plural)}`,
|
|
1115
|
+
formatNeutral: (count) => pluralize(count, singular, plural)
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
var otherToolCallGroupCategory, toolCallGroupCategories, categoryByToolName;
|
|
1119
|
+
var init_tool_call_group = __esm({
|
|
1120
|
+
"../shared/dist/tool-call-group.js"() {
|
|
1121
|
+
"use strict";
|
|
1122
|
+
otherToolCallGroupCategory = {
|
|
1123
|
+
id: "other",
|
|
1124
|
+
toolNames: [],
|
|
1125
|
+
...categoryPhrases(null, "tool call", "tool calls")
|
|
1126
|
+
};
|
|
1127
|
+
toolCallGroupCategories = [
|
|
1128
|
+
{
|
|
1129
|
+
id: "files",
|
|
1130
|
+
toolNames: ["edit", "write"],
|
|
1131
|
+
...categoryPhrases("edited", "file", "files")
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
id: "commands",
|
|
1135
|
+
toolNames: ["bash", "execute_skill_script", "run_js"],
|
|
1136
|
+
...categoryPhrases("ran", "command", "commands")
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
id: "reads",
|
|
1140
|
+
toolNames: ["read", "get_observation"],
|
|
1141
|
+
...categoryPhrases("read", "file", "files")
|
|
1142
|
+
},
|
|
1143
|
+
{
|
|
1144
|
+
id: "searches",
|
|
1145
|
+
toolNames: ["grep", "find"],
|
|
1146
|
+
...categoryPhrases(null, "search", "searches")
|
|
1147
|
+
},
|
|
1148
|
+
{
|
|
1149
|
+
id: "webSearches",
|
|
1150
|
+
toolNames: ["web_search"],
|
|
1151
|
+
...categoryPhrases(null, "web search", "web searches")
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
id: "pages",
|
|
1155
|
+
toolNames: ["fetch"],
|
|
1156
|
+
...categoryPhrases("fetched", "page", "pages")
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
id: "browserActions",
|
|
1160
|
+
toolNames: ["browser"],
|
|
1161
|
+
...categoryPhrases(null, "browser action", "browser actions")
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
id: "images",
|
|
1165
|
+
toolNames: ["generate_image"],
|
|
1166
|
+
...categoryPhrases("generated", "image", "images")
|
|
1167
|
+
},
|
|
1168
|
+
{
|
|
1169
|
+
id: "subagents",
|
|
1170
|
+
toolNames: ["agent"],
|
|
1171
|
+
...categoryPhrases(null, "subagent", "subagents")
|
|
1172
|
+
},
|
|
1173
|
+
otherToolCallGroupCategory
|
|
1174
|
+
];
|
|
1175
|
+
categoryByToolName = new Map(toolCallGroupCategories.flatMap((category) => category.toolNames.map((name) => [name, category])));
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1105
1179
|
// ../shared/dist/project-tools.js
|
|
1106
1180
|
function normalizeDisabledTools(names) {
|
|
1107
1181
|
if (!Array.isArray(names)) {
|
|
@@ -1241,12 +1315,12 @@ function getFileExtension(filename) {
|
|
|
1241
1315
|
return "";
|
|
1242
1316
|
}
|
|
1243
1317
|
const trimmed = filename.trim();
|
|
1244
|
-
const
|
|
1245
|
-
const dotIndex =
|
|
1246
|
-
if (dotIndex <= 0 || dotIndex ===
|
|
1318
|
+
const basename8 = trimmed.split(/[/\\]/).pop() ?? trimmed;
|
|
1319
|
+
const dotIndex = basename8.lastIndexOf(".");
|
|
1320
|
+
if (dotIndex <= 0 || dotIndex === basename8.length - 1) {
|
|
1247
1321
|
return "";
|
|
1248
1322
|
}
|
|
1249
|
-
return
|
|
1323
|
+
return basename8.slice(dotIndex + 1).toLowerCase();
|
|
1250
1324
|
}
|
|
1251
1325
|
function getExplorerFileMediaType(path8, name) {
|
|
1252
1326
|
const ext = getFileExtension(path8) || (name ? getFileExtension(name) : "");
|
|
@@ -2346,6 +2420,7 @@ var init_dist = __esm({
|
|
|
2346
2420
|
init_typography();
|
|
2347
2421
|
init_font_scale();
|
|
2348
2422
|
init_tool_display();
|
|
2423
|
+
init_tool_call_group();
|
|
2349
2424
|
init_project_tools();
|
|
2350
2425
|
init_providers();
|
|
2351
2426
|
init_web_fetch();
|
|
@@ -6837,7 +6912,8 @@ var init_command_pattern = __esm({
|
|
|
6837
6912
|
// src/tools/path-utils.ts
|
|
6838
6913
|
import { accessSync, constants } from "node:fs";
|
|
6839
6914
|
import * as os from "node:os";
|
|
6840
|
-
import { isAbsolute, resolve as resolvePath, relative } from "node:path";
|
|
6915
|
+
import { basename, isAbsolute, resolve as resolvePath, relative } from "node:path";
|
|
6916
|
+
import { globSync } from "glob";
|
|
6841
6917
|
function normalizeUnicodeSpaces(str) {
|
|
6842
6918
|
return str.replace(UNICODE_SPACES, " ");
|
|
6843
6919
|
}
|
|
@@ -6872,6 +6948,41 @@ function findMatchingSuffixInCwd(resolvedPath2, cwd) {
|
|
|
6872
6948
|
}
|
|
6873
6949
|
return void 0;
|
|
6874
6950
|
}
|
|
6951
|
+
function findFilesByBasenameInCwd(fileBasename, cwd, limit) {
|
|
6952
|
+
if (!fileBasename || fileBasename === "." || fileBasename === "..") {
|
|
6953
|
+
return [];
|
|
6954
|
+
}
|
|
6955
|
+
try {
|
|
6956
|
+
const found = globSync(`**/${fileBasename}`, {
|
|
6957
|
+
cwd,
|
|
6958
|
+
dot: false,
|
|
6959
|
+
ignore: ["**/node_modules/**", "**/.git/**"],
|
|
6960
|
+
nodir: true
|
|
6961
|
+
});
|
|
6962
|
+
return found.slice(0, limit).map((match) => match.replace(/\\/g, "/"));
|
|
6963
|
+
} catch {
|
|
6964
|
+
return [];
|
|
6965
|
+
}
|
|
6966
|
+
}
|
|
6967
|
+
function collectFileNotFoundHints(resolvedPath2, cwd) {
|
|
6968
|
+
const requestedRelative = relative(cwd, resolvedPath2).replace(/\\/g, "/");
|
|
6969
|
+
const suffixMatch = findMatchingSuffixInCwd(resolvedPath2, cwd);
|
|
6970
|
+
if (suffixMatch && suffixMatch !== requestedRelative && suffixMatch !== ".") {
|
|
6971
|
+
return [`Try: ${suffixMatch}`];
|
|
6972
|
+
}
|
|
6973
|
+
const matches = findFilesByBasenameInCwd(
|
|
6974
|
+
basename(resolvedPath2),
|
|
6975
|
+
cwd,
|
|
6976
|
+
BASENAME_SEARCH_LIMIT
|
|
6977
|
+
).filter((match) => match !== requestedRelative);
|
|
6978
|
+
if (matches.length === 1) {
|
|
6979
|
+
return [`Try: ${matches[0]}`];
|
|
6980
|
+
}
|
|
6981
|
+
if (matches.length > 1) {
|
|
6982
|
+
return [`Similar files: ${matches.join(", ")}`];
|
|
6983
|
+
}
|
|
6984
|
+
return [];
|
|
6985
|
+
}
|
|
6875
6986
|
function collectPathSuggestionHints(resolvedPath2, cwd) {
|
|
6876
6987
|
const hints = ["Use a path relative to the project instead of an absolute path."];
|
|
6877
6988
|
const suffixMatch = findMatchingSuffixInCwd(resolvedPath2, cwd);
|
|
@@ -6930,7 +7041,9 @@ function toDisplayPath(cwd, resolvedPath2, requestedPath) {
|
|
|
6930
7041
|
}
|
|
6931
7042
|
function fileNotFoundError(cwd, resolvedPath2, requestedPath) {
|
|
6932
7043
|
const displayPath = toDisplayPath(cwd, resolvedPath2, requestedPath);
|
|
6933
|
-
|
|
7044
|
+
const hints = collectFileNotFoundHints(resolvedPath2, cwd);
|
|
7045
|
+
const suffix = hints.length > 0 ? ` ${hints.join(" ")}` : "";
|
|
7046
|
+
return new Error(`no such file or directory '${displayPath}'.${suffix}`);
|
|
6934
7047
|
}
|
|
6935
7048
|
function rethrowIfFileNotFound(error, cwd, resolvedPath2, requestedPath) {
|
|
6936
7049
|
if (isEnoentError(error)) {
|
|
@@ -6961,16 +7074,18 @@ function resolveReadPath(filePath, cwd) {
|
|
|
6961
7074
|
}
|
|
6962
7075
|
return resolved;
|
|
6963
7076
|
}
|
|
6964
|
-
var UNICODE_SPACES, NARROW_NO_BREAK_SPACE;
|
|
7077
|
+
var UNICODE_SPACES, NARROW_NO_BREAK_SPACE, BASENAME_SEARCH_LIMIT;
|
|
6965
7078
|
var init_path_utils = __esm({
|
|
6966
7079
|
"src/tools/path-utils.ts"() {
|
|
6967
7080
|
"use strict";
|
|
6968
7081
|
UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
6969
7082
|
NARROW_NO_BREAK_SPACE = "\u202F";
|
|
7083
|
+
BASENAME_SEARCH_LIMIT = 3;
|
|
6970
7084
|
}
|
|
6971
7085
|
});
|
|
6972
7086
|
|
|
6973
7087
|
// src/tools/bash-command-guard.ts
|
|
7088
|
+
import { isAbsolute as isAbsolute2 } from "node:path";
|
|
6974
7089
|
function commandBinary(token) {
|
|
6975
7090
|
if (!token) return "";
|
|
6976
7091
|
const parts = token.split("/");
|
|
@@ -7150,7 +7265,7 @@ function shouldRedirectReadSegment(tokens) {
|
|
|
7150
7265
|
}
|
|
7151
7266
|
return isSedPrintRange(tokens);
|
|
7152
7267
|
}
|
|
7153
|
-
function
|
|
7268
|
+
function getBashDedicatedToolRedirectMessage(command) {
|
|
7154
7269
|
const segments = splitShellSegments(command);
|
|
7155
7270
|
for (let index = 0; index < segments.length; index++) {
|
|
7156
7271
|
const tokens = getCommandTokens(segments[index]);
|
|
@@ -7158,15 +7273,16 @@ function validateBashDedicatedToolRedirect(command) {
|
|
|
7158
7273
|
continue;
|
|
7159
7274
|
}
|
|
7160
7275
|
if (shouldRedirectGrepSegment(tokens, index === 0)) {
|
|
7161
|
-
|
|
7276
|
+
return redirectError("grep", "Shell grep/rg is not allowed for codebase search.").message;
|
|
7162
7277
|
}
|
|
7163
7278
|
if (shouldRedirectFindSegment(tokens)) {
|
|
7164
|
-
|
|
7279
|
+
return redirectError("find", "Shell find is not allowed for file discovery.").message;
|
|
7165
7280
|
}
|
|
7166
7281
|
if (shouldRedirectReadSegment(tokens)) {
|
|
7167
|
-
|
|
7282
|
+
return redirectError("read", "Shell cat/head/tail/sed is not allowed for reading files.").message;
|
|
7168
7283
|
}
|
|
7169
7284
|
}
|
|
7285
|
+
return void 0;
|
|
7170
7286
|
}
|
|
7171
7287
|
function validateBashFilesystemSearch(command, cwd) {
|
|
7172
7288
|
for (const segment of splitShellSegments(command)) {
|
|
@@ -7192,6 +7308,109 @@ function validateBashFilesystemSearch(command, cwd) {
|
|
|
7192
7308
|
}
|
|
7193
7309
|
}
|
|
7194
7310
|
}
|
|
7311
|
+
function unquoteRedirectTarget(target) {
|
|
7312
|
+
if (target.startsWith('"') && target.endsWith('"') || target.startsWith("'") && target.endsWith("'")) {
|
|
7313
|
+
return target.slice(1, -1);
|
|
7314
|
+
}
|
|
7315
|
+
return target;
|
|
7316
|
+
}
|
|
7317
|
+
function extractShellRedirectTargets(command) {
|
|
7318
|
+
const targets = [];
|
|
7319
|
+
let quote = null;
|
|
7320
|
+
let escaped = false;
|
|
7321
|
+
for (let index = 0; index < command.length; index++) {
|
|
7322
|
+
const char = command[index];
|
|
7323
|
+
if (escaped) {
|
|
7324
|
+
escaped = false;
|
|
7325
|
+
continue;
|
|
7326
|
+
}
|
|
7327
|
+
if (char === "\\" && quote !== "'") {
|
|
7328
|
+
escaped = true;
|
|
7329
|
+
continue;
|
|
7330
|
+
}
|
|
7331
|
+
if (quote) {
|
|
7332
|
+
if (char === quote) {
|
|
7333
|
+
quote = null;
|
|
7334
|
+
}
|
|
7335
|
+
continue;
|
|
7336
|
+
}
|
|
7337
|
+
if (char === "'" || char === '"') {
|
|
7338
|
+
quote = char;
|
|
7339
|
+
continue;
|
|
7340
|
+
}
|
|
7341
|
+
const rest = command.slice(index);
|
|
7342
|
+
const operatorMatch = rest.match(/^(\d>>|\d>|>>|>|&>>|&>)(?![&\d])/);
|
|
7343
|
+
if (!operatorMatch) {
|
|
7344
|
+
continue;
|
|
7345
|
+
}
|
|
7346
|
+
index += operatorMatch[0].length;
|
|
7347
|
+
while (index < command.length && /\s/.test(command[index])) {
|
|
7348
|
+
index++;
|
|
7349
|
+
}
|
|
7350
|
+
if (index >= command.length) {
|
|
7351
|
+
continue;
|
|
7352
|
+
}
|
|
7353
|
+
if (command[index] === "'" || command[index] === '"') {
|
|
7354
|
+
const quoteChar = command[index];
|
|
7355
|
+
index++;
|
|
7356
|
+
let target2 = "";
|
|
7357
|
+
while (index < command.length && command[index] !== quoteChar) {
|
|
7358
|
+
target2 += command[index];
|
|
7359
|
+
index++;
|
|
7360
|
+
}
|
|
7361
|
+
if (target2) {
|
|
7362
|
+
targets.push(target2);
|
|
7363
|
+
}
|
|
7364
|
+
continue;
|
|
7365
|
+
}
|
|
7366
|
+
let target = "";
|
|
7367
|
+
while (index < command.length && !/[\s;|&<>]/.test(command[index])) {
|
|
7368
|
+
target += command[index];
|
|
7369
|
+
index++;
|
|
7370
|
+
}
|
|
7371
|
+
if (target && !target.startsWith("&")) {
|
|
7372
|
+
targets.push(target);
|
|
7373
|
+
}
|
|
7374
|
+
index--;
|
|
7375
|
+
}
|
|
7376
|
+
return targets;
|
|
7377
|
+
}
|
|
7378
|
+
function isIgnorableRedirectTarget(target) {
|
|
7379
|
+
const cleaned = unquoteRedirectTarget(target);
|
|
7380
|
+
return IGNORABLE_REDIRECT_TARGETS.has(cleaned);
|
|
7381
|
+
}
|
|
7382
|
+
function isRedirectTargetOutsideRepo(target, repositoryRoot) {
|
|
7383
|
+
const cleaned = unquoteRedirectTarget(target);
|
|
7384
|
+
const expanded = expandPath(cleaned);
|
|
7385
|
+
if (expanded === "/dev/null" || expanded === "/dev/null2") {
|
|
7386
|
+
return false;
|
|
7387
|
+
}
|
|
7388
|
+
if (expanded === "/tmp" || expanded.startsWith("/tmp/")) {
|
|
7389
|
+
return true;
|
|
7390
|
+
}
|
|
7391
|
+
if (expanded === "~" || expanded.startsWith("~/")) {
|
|
7392
|
+
return true;
|
|
7393
|
+
}
|
|
7394
|
+
const resolved = isAbsolute2(expanded) ? expanded : resolveToCwd(cleaned, repositoryRoot);
|
|
7395
|
+
try {
|
|
7396
|
+
validatePathWithinCwd(resolved, repositoryRoot);
|
|
7397
|
+
return false;
|
|
7398
|
+
} catch {
|
|
7399
|
+
return true;
|
|
7400
|
+
}
|
|
7401
|
+
}
|
|
7402
|
+
function getBashOutsideRepoRedirectMessage(command, repositoryRoot) {
|
|
7403
|
+
for (const target of extractShellRedirectTargets(command)) {
|
|
7404
|
+
if (isIgnorableRedirectTarget(target)) {
|
|
7405
|
+
continue;
|
|
7406
|
+
}
|
|
7407
|
+
if (isRedirectTargetOutsideRepo(target, repositoryRoot)) {
|
|
7408
|
+
const display = unquoteRedirectTarget(target);
|
|
7409
|
+
return `Redirecting command output outside the project (${display}) is not allowed when sandbox is enabled. The grep and read tools cannot access paths outside the repo. Use a repo-relative log file instead (e.g. .tarsk-test.log), then grep or read that path.`;
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
7412
|
+
return void 0;
|
|
7413
|
+
}
|
|
7195
7414
|
function isSearchShapedBashCommand(command) {
|
|
7196
7415
|
for (const segment of splitShellSegments(command)) {
|
|
7197
7416
|
const tokens = getCommandTokens(segment);
|
|
@@ -7205,7 +7424,7 @@ function isSearchShapedBashCommand(command) {
|
|
|
7205
7424
|
}
|
|
7206
7425
|
return false;
|
|
7207
7426
|
}
|
|
7208
|
-
var BROAD_SEARCH_ROOTS, FIND_OPTIONS_WITH_ARG, GREP_BINARIES, RECURSIVE_GREP_FLAGS, GREP_FLAGS_WITH_ARG, HEAD_TAIL_FLAGS_WITH_ARG;
|
|
7427
|
+
var BROAD_SEARCH_ROOTS, FIND_OPTIONS_WITH_ARG, GREP_BINARIES, RECURSIVE_GREP_FLAGS, GREP_FLAGS_WITH_ARG, HEAD_TAIL_FLAGS_WITH_ARG, IGNORABLE_REDIRECT_TARGETS;
|
|
7209
7428
|
var init_bash_command_guard = __esm({
|
|
7210
7429
|
"src/tools/bash-command-guard.ts"() {
|
|
7211
7430
|
"use strict";
|
|
@@ -7250,6 +7469,7 @@ var init_bash_command_guard = __esm({
|
|
|
7250
7469
|
"--exclude-dir"
|
|
7251
7470
|
]);
|
|
7252
7471
|
HEAD_TAIL_FLAGS_WITH_ARG = /* @__PURE__ */ new Set(["-n", "-c", "--lines", "--bytes"]);
|
|
7472
|
+
IGNORABLE_REDIRECT_TARGETS = /* @__PURE__ */ new Set(["/dev/null", "/dev/null2", "NUL", "&1", "1"]);
|
|
7253
7473
|
}
|
|
7254
7474
|
});
|
|
7255
7475
|
|
|
@@ -7788,6 +8008,115 @@ var init_log = __esm({
|
|
|
7788
8008
|
}
|
|
7789
8009
|
});
|
|
7790
8010
|
|
|
8011
|
+
// src/tools/compress/test-runner.ts
|
|
8012
|
+
function stripAnsi(text) {
|
|
8013
|
+
return text.replace(ANSI_ESCAPE_RE, "");
|
|
8014
|
+
}
|
|
8015
|
+
function looksLikeTestRunnerOutput(text) {
|
|
8016
|
+
const plain = stripAnsi(text);
|
|
8017
|
+
return TEST_FILES_LINE_RE.test(plain) || TESTS_SUMMARY_LINE_RE.test(plain);
|
|
8018
|
+
}
|
|
8019
|
+
function isSummaryOrTimingLine(line) {
|
|
8020
|
+
const plain = stripAnsi(line);
|
|
8021
|
+
return TEST_FILES_LINE_RE.test(plain) || TESTS_SUMMARY_LINE_RE.test(plain) || TIMING_LINE_RE.test(plain);
|
|
8022
|
+
}
|
|
8023
|
+
function isFailureHeader(line) {
|
|
8024
|
+
return FAIL_HEADER_RE.test(stripAnsi(line));
|
|
8025
|
+
}
|
|
8026
|
+
function isWorkerNoise(line) {
|
|
8027
|
+
const plain = stripAnsi(line);
|
|
8028
|
+
return WORKER_NOISE_RE.test(plain) || /^\s*at processTicksAndRejections/.test(plain);
|
|
8029
|
+
}
|
|
8030
|
+
function shouldKeepFailureLine(line) {
|
|
8031
|
+
const plain = stripAnsi(line);
|
|
8032
|
+
if (!plain.trim()) {
|
|
8033
|
+
return false;
|
|
8034
|
+
}
|
|
8035
|
+
if (isWorkerNoise(line)) {
|
|
8036
|
+
return false;
|
|
8037
|
+
}
|
|
8038
|
+
if (/node_modules[/\\]/.test(plain) && !FAILURE_DETAIL_RE.test(plain)) {
|
|
8039
|
+
return false;
|
|
8040
|
+
}
|
|
8041
|
+
return true;
|
|
8042
|
+
}
|
|
8043
|
+
function extractFailureBlocks(lines) {
|
|
8044
|
+
const blocks = [];
|
|
8045
|
+
let blockLines = [];
|
|
8046
|
+
let inBlock = false;
|
|
8047
|
+
function flushBlock() {
|
|
8048
|
+
if (blockLines.length === 0) {
|
|
8049
|
+
return;
|
|
8050
|
+
}
|
|
8051
|
+
blocks.push(blockLines.join("\n"));
|
|
8052
|
+
blockLines = [];
|
|
8053
|
+
inBlock = false;
|
|
8054
|
+
}
|
|
8055
|
+
for (const line of lines) {
|
|
8056
|
+
if (isFailureHeader(line)) {
|
|
8057
|
+
flushBlock();
|
|
8058
|
+
inBlock = true;
|
|
8059
|
+
blockLines = [stripAnsi(line)];
|
|
8060
|
+
continue;
|
|
8061
|
+
}
|
|
8062
|
+
if (!inBlock) {
|
|
8063
|
+
continue;
|
|
8064
|
+
}
|
|
8065
|
+
if (blockLines.length >= MAX_FAILURE_BLOCK_LINES) {
|
|
8066
|
+
continue;
|
|
8067
|
+
}
|
|
8068
|
+
if (line.trim() === "" && blockLines.length > 2) {
|
|
8069
|
+
flushBlock();
|
|
8070
|
+
continue;
|
|
8071
|
+
}
|
|
8072
|
+
if (shouldKeepFailureLine(line)) {
|
|
8073
|
+
blockLines.push(stripAnsi(line));
|
|
8074
|
+
}
|
|
8075
|
+
}
|
|
8076
|
+
flushBlock();
|
|
8077
|
+
return blocks;
|
|
8078
|
+
}
|
|
8079
|
+
function summarizeTestRunnerOutput(text) {
|
|
8080
|
+
if (!text || !looksLikeTestRunnerOutput(text)) {
|
|
8081
|
+
return { text, applied: false };
|
|
8082
|
+
}
|
|
8083
|
+
const lines = text.split("\n");
|
|
8084
|
+
const summaryLines = [];
|
|
8085
|
+
for (const line of lines) {
|
|
8086
|
+
if (isSummaryOrTimingLine(line)) {
|
|
8087
|
+
summaryLines.push(stripAnsi(line).trimEnd());
|
|
8088
|
+
}
|
|
8089
|
+
}
|
|
8090
|
+
const failureBlocks = extractFailureBlocks(lines);
|
|
8091
|
+
const parts = [TEST_RUNNER_BANNER];
|
|
8092
|
+
if (summaryLines.length > 0) {
|
|
8093
|
+
parts.push(summaryLines.join("\n"));
|
|
8094
|
+
}
|
|
8095
|
+
if (failureBlocks.length > 0) {
|
|
8096
|
+
parts.push(failureBlocks.join("\n\n"));
|
|
8097
|
+
}
|
|
8098
|
+
const compressed = parts.join("\n");
|
|
8099
|
+
if (compressed === text || Buffer.byteLength(compressed) >= Buffer.byteLength(text)) {
|
|
8100
|
+
return { text, applied: false };
|
|
8101
|
+
}
|
|
8102
|
+
return { text: compressed, applied: true };
|
|
8103
|
+
}
|
|
8104
|
+
var TEST_RUNNER_BANNER, ANSI_ESCAPE_RE, TEST_FILES_LINE_RE, TESTS_SUMMARY_LINE_RE, TIMING_LINE_RE, FAIL_HEADER_RE, FAILURE_DETAIL_RE, WORKER_NOISE_RE, MAX_FAILURE_BLOCK_LINES;
|
|
8105
|
+
var init_test_runner = __esm({
|
|
8106
|
+
"src/tools/compress/test-runner.ts"() {
|
|
8107
|
+
"use strict";
|
|
8108
|
+
TEST_RUNNER_BANNER = "[Test output summarized; passing tests and worker teardown omitted]";
|
|
8109
|
+
ANSI_ESCAPE_RE = /\u001b\[[0-9;]*m/g;
|
|
8110
|
+
TEST_FILES_LINE_RE = /\bTest Files\b/;
|
|
8111
|
+
TESTS_SUMMARY_LINE_RE = /^\s*Tests\s+\d+/;
|
|
8112
|
+
TIMING_LINE_RE = /^\s*(Start at|Duration)\b/;
|
|
8113
|
+
FAIL_HEADER_RE = /^\s*FAIL\s+/;
|
|
8114
|
+
FAILURE_DETAIL_RE = /AssertionError|Expected|Received|\berror TS\d+\b|Cannot find name|✗|×|❯/;
|
|
8115
|
+
WORKER_NOISE_RE = /^\[vitest-pool\]:|^Emitted 'error' event|^Error: kill EPERM|^node:events:/;
|
|
8116
|
+
MAX_FAILURE_BLOCK_LINES = 14;
|
|
8117
|
+
}
|
|
8118
|
+
});
|
|
8119
|
+
|
|
7791
8120
|
// src/tools/compress/bash.ts
|
|
7792
8121
|
function looksLikePathLine(line) {
|
|
7793
8122
|
if (!line) {
|
|
@@ -7903,6 +8232,11 @@ function compressBashOutput(text) {
|
|
|
7903
8232
|
if (!text) {
|
|
7904
8233
|
return text;
|
|
7905
8234
|
}
|
|
8235
|
+
const testSummary = summarizeTestRunnerOutput(text);
|
|
8236
|
+
if (testSummary.applied) {
|
|
8237
|
+
logCompressOutput("bash.test-runner", text, testSummary.text);
|
|
8238
|
+
return testSummary.text;
|
|
8239
|
+
}
|
|
7906
8240
|
const lines = text.split("\n");
|
|
7907
8241
|
const attempts = [
|
|
7908
8242
|
tryCompressPurePathList,
|
|
@@ -7925,6 +8259,7 @@ var init_bash = __esm({
|
|
|
7925
8259
|
init_find();
|
|
7926
8260
|
init_grep();
|
|
7927
8261
|
init_log();
|
|
8262
|
+
init_test_runner();
|
|
7928
8263
|
GIT_COMMIT_HEADER = /^[0-9a-f]{7,40} /;
|
|
7929
8264
|
}
|
|
7930
8265
|
});
|
|
@@ -8120,7 +8455,29 @@ function createBashTool(repositoryRoot, options) {
|
|
|
8120
8455
|
const resolvedCommand = commandPrefix ? `${commandPrefix}
|
|
8121
8456
|
${command}` : command;
|
|
8122
8457
|
tarskDebugLog(`[ai] bash-start: ${resolvedCommand}`);
|
|
8123
|
-
|
|
8458
|
+
if (sandboxConfig) {
|
|
8459
|
+
const outsideRepoRedirect = getBashOutsideRepoRedirectMessage(
|
|
8460
|
+
resolvedCommand,
|
|
8461
|
+
repositoryRoot
|
|
8462
|
+
);
|
|
8463
|
+
if (outsideRepoRedirect) {
|
|
8464
|
+
log12.debug("Redirecting bash away from out-of-repo output path", {
|
|
8465
|
+
command: resolvedCommand
|
|
8466
|
+
});
|
|
8467
|
+
return {
|
|
8468
|
+
content: [{ type: "text", text: outsideRepoRedirect }],
|
|
8469
|
+
details: void 0
|
|
8470
|
+
};
|
|
8471
|
+
}
|
|
8472
|
+
}
|
|
8473
|
+
const dedicatedRedirect = getBashDedicatedToolRedirectMessage(resolvedCommand);
|
|
8474
|
+
if (dedicatedRedirect) {
|
|
8475
|
+
log12.debug("Redirecting bash to dedicated tool", { command: resolvedCommand });
|
|
8476
|
+
return {
|
|
8477
|
+
content: [{ type: "text", text: dedicatedRedirect }],
|
|
8478
|
+
details: void 0
|
|
8479
|
+
};
|
|
8480
|
+
}
|
|
8124
8481
|
validateBashFilesystemSearch(resolvedCommand, repositoryRoot);
|
|
8125
8482
|
if (shellPermissionGate) {
|
|
8126
8483
|
await shellPermissionGate.ensureAllowed(toolCallId, resolvedCommand, "bash", signal);
|
|
@@ -8526,16 +8883,86 @@ var init_tool_helpers = __esm({
|
|
|
8526
8883
|
import { Type as Type2 } from "@sinclair/typebox";
|
|
8527
8884
|
import { constants as constants2 } from "fs";
|
|
8528
8885
|
import { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
|
|
8886
|
+
function resolveReplacements(input) {
|
|
8887
|
+
if (input.replacements && input.replacements.length > 0) {
|
|
8888
|
+
return input.replacements.map((replacement) => ({
|
|
8889
|
+
oldText: replacement.oldText,
|
|
8890
|
+
newText: replacement.newText,
|
|
8891
|
+
replaceAll: replacement.replaceAll ?? input.replaceAll ?? false
|
|
8892
|
+
}));
|
|
8893
|
+
}
|
|
8894
|
+
if (input.oldText !== void 0 && input.newText !== void 0) {
|
|
8895
|
+
return [
|
|
8896
|
+
{
|
|
8897
|
+
oldText: input.oldText,
|
|
8898
|
+
newText: input.newText,
|
|
8899
|
+
replaceAll: input.replaceAll ?? false
|
|
8900
|
+
}
|
|
8901
|
+
];
|
|
8902
|
+
}
|
|
8903
|
+
throw new Error(
|
|
8904
|
+
"Provide either oldText+newText or a non-empty replacements array of { oldText, newText } hunks."
|
|
8905
|
+
);
|
|
8906
|
+
}
|
|
8907
|
+
function applySingleReplacement(content, normalizedOldText, normalizedNewText) {
|
|
8908
|
+
const matchResult = fuzzyFindText(content, normalizedOldText);
|
|
8909
|
+
if (!matchResult.found) {
|
|
8910
|
+
return { content, replaced: false };
|
|
8911
|
+
}
|
|
8912
|
+
return {
|
|
8913
|
+
content: content.substring(0, matchResult.index) + normalizedNewText + content.substring(matchResult.index + matchResult.matchLength),
|
|
8914
|
+
replaced: true
|
|
8915
|
+
};
|
|
8916
|
+
}
|
|
8917
|
+
function applyReplacement(content, path8, oldText, newText, replaceAll) {
|
|
8918
|
+
const normalizedOldText = normalizeToLF(oldText);
|
|
8919
|
+
const normalizedNewText = normalizeToLF(newText);
|
|
8920
|
+
if (!replaceAll) {
|
|
8921
|
+
const matchResult = fuzzyFindText(content, normalizedOldText);
|
|
8922
|
+
if (!matchResult.found) {
|
|
8923
|
+
throw new Error(
|
|
8924
|
+
`Could not find the exact text in ${path8}. The old text must match exactly including all whitespace and newlines.`
|
|
8925
|
+
);
|
|
8926
|
+
}
|
|
8927
|
+
const fuzzyContent = normalizeForFuzzyMatch(content);
|
|
8928
|
+
const fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);
|
|
8929
|
+
const occurrences = fuzzyContent.split(fuzzyOldText).length - 1;
|
|
8930
|
+
if (occurrences > 1) {
|
|
8931
|
+
throw new Error(
|
|
8932
|
+
`Found ${occurrences} occurrences of the text in ${path8}. The text must be unique. Please provide more context to make it unique, or set replaceAll: true to replace every occurrence.`
|
|
8933
|
+
);
|
|
8934
|
+
}
|
|
8935
|
+
return content.substring(0, matchResult.index) + normalizedNewText + content.substring(matchResult.index + matchResult.matchLength);
|
|
8936
|
+
}
|
|
8937
|
+
let result = content;
|
|
8938
|
+
let replacedCount = 0;
|
|
8939
|
+
while (true) {
|
|
8940
|
+
const next = applySingleReplacement(result, normalizedOldText, normalizedNewText);
|
|
8941
|
+
if (!next.replaced) {
|
|
8942
|
+
break;
|
|
8943
|
+
}
|
|
8944
|
+
result = next.content;
|
|
8945
|
+
replacedCount++;
|
|
8946
|
+
}
|
|
8947
|
+
if (replacedCount === 0) {
|
|
8948
|
+
throw new Error(
|
|
8949
|
+
`Could not find the exact text in ${path8}. The old text must match exactly including all whitespace and newlines.`
|
|
8950
|
+
);
|
|
8951
|
+
}
|
|
8952
|
+
return result;
|
|
8953
|
+
}
|
|
8529
8954
|
function createEditTool(cwd, options) {
|
|
8530
8955
|
const ops = options?.operations ?? defaultEditOperations;
|
|
8531
8956
|
return {
|
|
8532
8957
|
name: "edit",
|
|
8533
8958
|
label: "edit",
|
|
8534
|
-
description: "Edit a file by replacing exact text
|
|
8959
|
+
description: "Edit a file by replacing exact text (oldText must match uniquely unless replaceAll is true). Prefer fewer larger hunks over many tiny serial edits. Use replacements to apply multiple hunks in one call; set replaceAll on a hunk (or top-level) for mechanical refactors with repeated oldText. Call edit in parallel across independent files.",
|
|
8535
8960
|
parameters: editSchema,
|
|
8536
|
-
execute: async (_toolCallId,
|
|
8961
|
+
execute: async (_toolCallId, input, signal) => {
|
|
8962
|
+
const { path: path8 } = input;
|
|
8537
8963
|
const absolutePath = resolveToCwd(path8, cwd);
|
|
8538
8964
|
validatePathWithinCwd(absolutePath, cwd);
|
|
8965
|
+
const hunks = resolveReplacements(input);
|
|
8539
8966
|
return withAbortSignal(signal, async (isAborted) => {
|
|
8540
8967
|
try {
|
|
8541
8968
|
await ops.access(absolutePath);
|
|
@@ -8548,26 +8975,18 @@ function createEditTool(cwd, options) {
|
|
|
8548
8975
|
if (isAborted()) return { content: [], details: void 0 };
|
|
8549
8976
|
const { bom, text: content } = stripBom(rawContent);
|
|
8550
8977
|
const originalEnding = detectLineEnding(content);
|
|
8551
|
-
const
|
|
8552
|
-
|
|
8553
|
-
const
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8978
|
+
const baseContent = normalizeToLF(content);
|
|
8979
|
+
let newContent = baseContent;
|
|
8980
|
+
for (const hunk of hunks) {
|
|
8981
|
+
newContent = applyReplacement(
|
|
8982
|
+
newContent,
|
|
8983
|
+
path8,
|
|
8984
|
+
hunk.oldText,
|
|
8985
|
+
hunk.newText,
|
|
8986
|
+
hunk.replaceAll
|
|
8558
8987
|
);
|
|
8988
|
+
if (isAborted()) return { content: [], details: void 0 };
|
|
8559
8989
|
}
|
|
8560
|
-
const fuzzyContent = normalizeForFuzzyMatch(normalizedContent);
|
|
8561
|
-
const fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);
|
|
8562
|
-
const occurrences = fuzzyContent.split(fuzzyOldText).length - 1;
|
|
8563
|
-
if (occurrences > 1) {
|
|
8564
|
-
throw new Error(
|
|
8565
|
-
`Found ${occurrences} occurrences of the text in ${path8}. The text must be unique. Please provide more context to make it unique.`
|
|
8566
|
-
);
|
|
8567
|
-
}
|
|
8568
|
-
if (isAborted()) return { content: [], details: void 0 };
|
|
8569
|
-
const baseContent = normalizedContent;
|
|
8570
|
-
const newContent = baseContent.substring(0, matchResult.index) + normalizedNewText + baseContent.substring(matchResult.index + matchResult.matchLength);
|
|
8571
8990
|
if (baseContent === newContent) {
|
|
8572
8991
|
throw new Error(
|
|
8573
8992
|
`No changes made to ${path8}. The replacement produced identical content.`
|
|
@@ -8577,25 +8996,65 @@ function createEditTool(cwd, options) {
|
|
|
8577
8996
|
await ops.writeFile(absolutePath, finalContent);
|
|
8578
8997
|
if (isAborted()) return { content: [], details: void 0 };
|
|
8579
8998
|
const diffResult = generateDiffString(baseContent, newContent);
|
|
8999
|
+
const hunkLabel = hunks.length === 1 ? "text" : `${hunks.length} hunks`;
|
|
8580
9000
|
return {
|
|
8581
|
-
content: [
|
|
8582
|
-
|
|
9001
|
+
content: [
|
|
9002
|
+
{
|
|
9003
|
+
type: "text",
|
|
9004
|
+
text: `Successfully replaced ${hunkLabel} in ${path8}.`
|
|
9005
|
+
}
|
|
9006
|
+
],
|
|
9007
|
+
details: {
|
|
9008
|
+
diff: diffResult.diff,
|
|
9009
|
+
firstChangedLine: diffResult.firstChangedLine,
|
|
9010
|
+
replacementCount: hunks.length
|
|
9011
|
+
}
|
|
8583
9012
|
};
|
|
8584
9013
|
});
|
|
8585
9014
|
}
|
|
8586
9015
|
};
|
|
8587
9016
|
}
|
|
8588
|
-
var editSchema, defaultEditOperations, editTool;
|
|
9017
|
+
var replacementSchema, editSchema, defaultEditOperations, editTool;
|
|
8589
9018
|
var init_edit = __esm({
|
|
8590
9019
|
"src/tools/edit.ts"() {
|
|
8591
9020
|
"use strict";
|
|
8592
9021
|
init_edit_diff();
|
|
8593
9022
|
init_path_utils();
|
|
8594
9023
|
init_tool_helpers();
|
|
9024
|
+
replacementSchema = Type2.Object({
|
|
9025
|
+
oldText: Type2.String({
|
|
9026
|
+
description: "Exact text to find and replace (must match exactly; unique unless replaceAll is true)"
|
|
9027
|
+
}),
|
|
9028
|
+
newText: Type2.String({ description: "Replacement text" }),
|
|
9029
|
+
replaceAll: Type2.Optional(
|
|
9030
|
+
Type2.Boolean({
|
|
9031
|
+
description: "When true, replace every occurrence of oldText in the file."
|
|
9032
|
+
})
|
|
9033
|
+
)
|
|
9034
|
+
});
|
|
8595
9035
|
editSchema = Type2.Object({
|
|
8596
9036
|
path: Type2.String({ description: "Path to the file to edit (relative or absolute)" }),
|
|
8597
|
-
oldText: Type2.
|
|
8598
|
-
|
|
9037
|
+
oldText: Type2.Optional(
|
|
9038
|
+
Type2.String({
|
|
9039
|
+
description: "Exact text to find and replace (must match exactly). Required unless replacements is set."
|
|
9040
|
+
})
|
|
9041
|
+
),
|
|
9042
|
+
newText: Type2.Optional(
|
|
9043
|
+
Type2.String({
|
|
9044
|
+
description: "New text to replace oldText with. Required unless replacements is set."
|
|
9045
|
+
})
|
|
9046
|
+
),
|
|
9047
|
+
replaceAll: Type2.Optional(
|
|
9048
|
+
Type2.Boolean({
|
|
9049
|
+
description: "When true with oldText/newText, replace every occurrence. Per-hunk replaceAll in replacements overrides this."
|
|
9050
|
+
})
|
|
9051
|
+
),
|
|
9052
|
+
replacements: Type2.Optional(
|
|
9053
|
+
Type2.Array(replacementSchema, {
|
|
9054
|
+
minItems: 1,
|
|
9055
|
+
description: "Multiple hunks to apply in one call (applied in order). Prefer this over serial single-hunk edits on the same file."
|
|
9056
|
+
})
|
|
9057
|
+
)
|
|
8599
9058
|
});
|
|
8600
9059
|
defaultEditOperations = {
|
|
8601
9060
|
readFile: (path8) => fsReadFile(path8),
|
|
@@ -8611,13 +9070,13 @@ import { Type as Type3 } from "@sinclair/typebox";
|
|
|
8611
9070
|
import { existsSync as existsSync7 } from "fs";
|
|
8612
9071
|
import os2 from "node:os";
|
|
8613
9072
|
import path2 from "path";
|
|
8614
|
-
import { globSync } from "glob";
|
|
9073
|
+
import { globSync as globSync2 } from "glob";
|
|
8615
9074
|
function createFindTool(cwd, options) {
|
|
8616
9075
|
const customOps = options?.operations;
|
|
8617
9076
|
return {
|
|
8618
9077
|
name: "find",
|
|
8619
9078
|
label: "find",
|
|
8620
|
-
description: `Search for files by glob pattern. Returns matching file paths relative to the search directory. When many results share directories, output is grouped as "dir/: file1, file2" lines (one path per file, "dir/name") to save space; bare filenames are root-level files. Output truncated to ${DEFAULT_LIMIT} results or ${DEFAULT_MAX_BYTES / 1024}KB.`,
|
|
9079
|
+
description: `Search for files by glob pattern. Returns matching file paths relative to the search directory. When many results share directories, output is grouped as "dir/: file1, file2" lines (one path per file, "dir/name") to save space; bare filenames are root-level files. Brace expansion like dir/{a,b}.tsx only matches that directory level; use **/name.tsx or dir/**/*.tsx for nested files. Output truncated to ${DEFAULT_LIMIT} results or ${DEFAULT_MAX_BYTES / 1024}KB.`,
|
|
8621
9080
|
parameters: findSchema,
|
|
8622
9081
|
execute: async (_toolCallId, { pattern, path: searchDir, limit }, signal) => {
|
|
8623
9082
|
return withAbortSignal(signal, async (isAborted) => {
|
|
@@ -8727,7 +9186,7 @@ var init_find2 = __esm({
|
|
|
8727
9186
|
glob: async (pattern, searchCwd, { ignore, limit }) => {
|
|
8728
9187
|
const results = [];
|
|
8729
9188
|
try {
|
|
8730
|
-
const found =
|
|
9189
|
+
const found = globSync2(pattern, {
|
|
8731
9190
|
cwd: searchCwd,
|
|
8732
9191
|
dot: true,
|
|
8733
9192
|
ignore: ["**/node_modules/**", "**/.git/**", ...ignore],
|
|
@@ -9155,6 +9614,16 @@ function toRipgrepSearchPath(absoluteSearchPath, cwd) {
|
|
|
9155
9614
|
}
|
|
9156
9615
|
return relative8.replace(/\\/g, "/");
|
|
9157
9616
|
}
|
|
9617
|
+
function formatRipgrepFailure(stderr, code) {
|
|
9618
|
+
const trimmed = stderr.trim();
|
|
9619
|
+
const base = trimmed || (code === 2 ? "ripgrep exited with code 2" : `ripgrep exited with code ${code}`);
|
|
9620
|
+
if (REGEX_PARSE_ERROR_RE.test(trimmed)) {
|
|
9621
|
+
return new Error(`${base}
|
|
9622
|
+
|
|
9623
|
+
${REGEX_PARSE_HINT}`);
|
|
9624
|
+
}
|
|
9625
|
+
return new Error(base);
|
|
9626
|
+
}
|
|
9158
9627
|
async function runRipgrep(args2, searchPath, cwd) {
|
|
9159
9628
|
const rgSearchPath = toRipgrepSearchPath(searchPath, cwd);
|
|
9160
9629
|
const { code, stdout, stderr } = await ripgrep([...args2, rgSearchPath], {
|
|
@@ -9162,10 +9631,10 @@ async function runRipgrep(args2, searchPath, cwd) {
|
|
|
9162
9631
|
preopens: { ".": cwd }
|
|
9163
9632
|
});
|
|
9164
9633
|
if (code === 2) {
|
|
9165
|
-
throw
|
|
9634
|
+
throw formatRipgrepFailure(stderr, code);
|
|
9166
9635
|
}
|
|
9167
9636
|
if (code !== 0 && code !== 1) {
|
|
9168
|
-
throw
|
|
9637
|
+
throw formatRipgrepFailure(stderr, code);
|
|
9169
9638
|
}
|
|
9170
9639
|
return parseRipgrepOutput(stdout);
|
|
9171
9640
|
}
|
|
@@ -9188,13 +9657,25 @@ function sortFilesByMtime(files) {
|
|
|
9188
9657
|
return timeComparison;
|
|
9189
9658
|
}).map(([filePath]) => filePath);
|
|
9190
9659
|
}
|
|
9191
|
-
function
|
|
9660
|
+
function looksLikePathScopedGlob(glob3) {
|
|
9661
|
+
if (!glob3) {
|
|
9662
|
+
return false;
|
|
9663
|
+
}
|
|
9664
|
+
return glob3.includes("/") || glob3.includes("\\");
|
|
9665
|
+
}
|
|
9666
|
+
function emptyMatchText(input) {
|
|
9667
|
+
if (looksLikePathScopedGlob(input.glob)) {
|
|
9668
|
+
return 'No matches found. Tip: prefer path for file/directory scope (e.g. path="app/components") and extension globs like "*.tsx" or "**/*.ts"; path-prefixed globs can miss matches depending on the search root.';
|
|
9669
|
+
}
|
|
9670
|
+
return "No matches found";
|
|
9671
|
+
}
|
|
9672
|
+
function formatContentResult(cwd, results, headLimit, offset, emptyText) {
|
|
9192
9673
|
const { items: limitedResults, appliedLimit } = applyHeadLimit(results, headLimit, offset);
|
|
9193
9674
|
const finalLines = limitedResults.map((line) => relativizeContentLine(cwd, line));
|
|
9194
9675
|
const limitInfo = formatLimitInfo(appliedLimit, offset > 0 ? offset : void 0);
|
|
9195
9676
|
let text;
|
|
9196
9677
|
if (finalLines.length === 0) {
|
|
9197
|
-
text =
|
|
9678
|
+
text = emptyText;
|
|
9198
9679
|
} else {
|
|
9199
9680
|
const flat = finalLines.join("\n");
|
|
9200
9681
|
const compressed = compressContent(finalLines);
|
|
@@ -9215,7 +9696,7 @@ ${compressed.text}` : compressed.text;
|
|
|
9215
9696
|
};
|
|
9216
9697
|
return { text, details };
|
|
9217
9698
|
}
|
|
9218
|
-
function formatCountResult(cwd, results, headLimit, offset) {
|
|
9699
|
+
function formatCountResult(cwd, results, headLimit, offset, emptyText) {
|
|
9219
9700
|
const { items: limitedResults, appliedLimit } = applyHeadLimit(results, headLimit, offset);
|
|
9220
9701
|
const finalCountLines = limitedResults.map((line) => relativizeCountLine(cwd, line));
|
|
9221
9702
|
const limitInfo = formatLimitInfo(appliedLimit, offset > 0 ? offset : void 0);
|
|
@@ -9233,7 +9714,7 @@ function formatCountResult(cwd, results, headLimit, offset) {
|
|
|
9233
9714
|
}
|
|
9234
9715
|
let text;
|
|
9235
9716
|
if (finalCountLines.length === 0) {
|
|
9236
|
-
text =
|
|
9717
|
+
text = emptyText;
|
|
9237
9718
|
} else {
|
|
9238
9719
|
const flat = finalCountLines.join("\n");
|
|
9239
9720
|
const compressed = compressCount(finalCountLines);
|
|
@@ -9258,14 +9739,14 @@ Found ${totalMatches} total ${occurrenceLabel} across ${fileCount} ${fileLabel}.
|
|
|
9258
9739
|
};
|
|
9259
9740
|
return { text, details };
|
|
9260
9741
|
}
|
|
9261
|
-
function formatFilesResult(cwd, results, headLimit, offset) {
|
|
9742
|
+
function formatFilesResult(cwd, results, headLimit, offset, emptyText) {
|
|
9262
9743
|
const sortedMatches = sortFilesByMtime(results);
|
|
9263
9744
|
const { items: finalMatches, appliedLimit } = applyHeadLimit(sortedMatches, headLimit, offset);
|
|
9264
9745
|
const relativeMatches = finalMatches.map((filePath) => toRelativePath(cwd, filePath));
|
|
9265
9746
|
const limitInfo = formatLimitInfo(appliedLimit, offset > 0 ? offset : void 0);
|
|
9266
9747
|
let text;
|
|
9267
9748
|
if (relativeMatches.length === 0) {
|
|
9268
|
-
text = "No files found";
|
|
9749
|
+
text = emptyText === "No matches found" ? "No files found" : emptyText.replace("No matches found", "No files found");
|
|
9269
9750
|
} else {
|
|
9270
9751
|
const fileLabel = relativeMatches.length === 1 ? "file" : "files";
|
|
9271
9752
|
const flat = relativeMatches.join("\n");
|
|
@@ -9306,13 +9787,14 @@ function createRipGrepTool(cwd) {
|
|
|
9306
9787
|
const offset = input.offset ?? 0;
|
|
9307
9788
|
const args2 = buildRipgrepArgs({ ...input, output_mode: outputMode });
|
|
9308
9789
|
const results = await runRipgrep(args2, searchPath, cwd);
|
|
9790
|
+
const emptyText = emptyMatchText(input);
|
|
9309
9791
|
let formatted;
|
|
9310
9792
|
if (outputMode === "content") {
|
|
9311
|
-
formatted = formatContentResult(cwd, results, headLimit, offset);
|
|
9793
|
+
formatted = formatContentResult(cwd, results, headLimit, offset, emptyText);
|
|
9312
9794
|
} else if (outputMode === "count") {
|
|
9313
|
-
formatted = formatCountResult(cwd, results, headLimit, offset);
|
|
9795
|
+
formatted = formatCountResult(cwd, results, headLimit, offset, emptyText);
|
|
9314
9796
|
} else {
|
|
9315
|
-
formatted = formatFilesResult(cwd, results, headLimit, offset);
|
|
9797
|
+
formatted = formatFilesResult(cwd, results, headLimit, offset, emptyText);
|
|
9316
9798
|
}
|
|
9317
9799
|
const truncation = truncateHead(formatted.text);
|
|
9318
9800
|
let output = truncation.content;
|
|
@@ -9351,7 +9833,7 @@ function createRipGrepTool(cwd) {
|
|
|
9351
9833
|
}
|
|
9352
9834
|
};
|
|
9353
9835
|
}
|
|
9354
|
-
var VCS_DIRECTORIES_TO_EXCLUDE, DEFAULT_HEAD_LIMIT, ripGrepSchema, log15, PATHLESS_CONTENT_LINE2, ripGrepTool;
|
|
9836
|
+
var VCS_DIRECTORIES_TO_EXCLUDE, DEFAULT_HEAD_LIMIT, ripGrepSchema, log15, PATHLESS_CONTENT_LINE2, REGEX_PARSE_ERROR_RE, REGEX_PARSE_HINT, ripGrepTool;
|
|
9355
9837
|
var init_rip_grep = __esm({
|
|
9356
9838
|
"src/tools/rip-grep.ts"() {
|
|
9357
9839
|
"use strict";
|
|
@@ -9438,6 +9920,8 @@ var init_rip_grep = __esm({
|
|
|
9438
9920
|
});
|
|
9439
9921
|
log15 = createLogger("tools.rip-grep");
|
|
9440
9922
|
PATHLESS_CONTENT_LINE2 = /^\d+[:-]/;
|
|
9923
|
+
REGEX_PARSE_ERROR_RE = /regex parse error/i;
|
|
9924
|
+
REGEX_PARSE_HINT = "Tip: grep patterns use ripgrep regex syntax. Escape special characters ($, {, }, (, ), [, ], *, +, ?, |, \\) with a backslash, or search for a simpler literal substring.";
|
|
9441
9925
|
ripGrepTool = createRipGrepTool(process.cwd());
|
|
9442
9926
|
}
|
|
9443
9927
|
});
|
|
@@ -10780,11 +11264,49 @@ var init_ralph_progress = __esm({
|
|
|
10780
11264
|
|
|
10781
11265
|
// src/tools/todo.ts
|
|
10782
11266
|
import { Type as Type13 } from "@sinclair/typebox";
|
|
11267
|
+
function truncateTodoDescription(description, maxLength = 48) {
|
|
11268
|
+
const oneLine = description.replace(/\s+/g, " ").trim();
|
|
11269
|
+
if (oneLine.length <= maxLength) {
|
|
11270
|
+
return oneLine;
|
|
11271
|
+
}
|
|
11272
|
+
return `${oneLine.slice(0, maxLength - 1)}\u2026`;
|
|
11273
|
+
}
|
|
11274
|
+
function formatUpdatedTodosAck(items) {
|
|
11275
|
+
if (items.length === 1) {
|
|
11276
|
+
const item = items[0];
|
|
11277
|
+
return `Updated 1 todo: "${truncateTodoDescription(item.description)}" \u2192 ${item.status}`;
|
|
11278
|
+
}
|
|
11279
|
+
const parts = items.map(
|
|
11280
|
+
(item) => `"${truncateTodoDescription(item.description)}" \u2192 ${item.status}`
|
|
11281
|
+
);
|
|
11282
|
+
return `Updated ${items.length} todos: ${parts.join("; ")}`;
|
|
11283
|
+
}
|
|
11284
|
+
function formatDeletedTodosAck(items) {
|
|
11285
|
+
if (items.length === 1) {
|
|
11286
|
+
return `Deleted 1 todo: "${truncateTodoDescription(items[0].description)}"`;
|
|
11287
|
+
}
|
|
11288
|
+
const parts = items.map((item) => `"${truncateTodoDescription(item.description)}"`);
|
|
11289
|
+
return `Deleted ${items.length} todos: ${parts.join("; ")}`;
|
|
11290
|
+
}
|
|
11291
|
+
async function fetchTodosByIds(db, threadId, ids) {
|
|
11292
|
+
const rows = /* @__PURE__ */ new Map();
|
|
11293
|
+
for (const id of ids) {
|
|
11294
|
+
const result = await db.execute({
|
|
11295
|
+
sql: `SELECT id, description, status FROM todos WHERE id = ? AND threadId = ?`,
|
|
11296
|
+
args: [id, threadId]
|
|
11297
|
+
});
|
|
11298
|
+
const row = result.rows[0];
|
|
11299
|
+
if (row) {
|
|
11300
|
+
rows.set(id, row);
|
|
11301
|
+
}
|
|
11302
|
+
}
|
|
11303
|
+
return rows;
|
|
11304
|
+
}
|
|
10783
11305
|
function createTodoTool(threadId, threadPath) {
|
|
10784
11306
|
return {
|
|
10785
11307
|
name: "todo",
|
|
10786
11308
|
label: "todo",
|
|
10787
|
-
description: "Manage todo items to break down the current task into 2 more items. Use 'add' to create items when planning work, 'update' to change an item's status as you progress (pending \u2192 working \u2192 done), 'list' to see all current items, and 'delete' to remove one. Marking an item done does not end the conversation \u2014 continue working on remaining items.
|
|
11309
|
+
description: "Manage todo items to break down the current task into 2 more items. Use 'add' to create items when planning work, 'update' to change an item's status as you progress (pending \u2192 working \u2192 done), 'list' to see all current items, and 'delete' to remove one. Marking an item done does not end the conversation \u2014 continue working on remaining items. Use 'list' when you need the full checklist; 'update' and 'delete' return a short confirmation only.",
|
|
10788
11310
|
parameters: todoSchema,
|
|
10789
11311
|
execute: async (_toolCallId, { action, descriptions, ids, status, assignedTo, passes, learnings }) => {
|
|
10790
11312
|
const db = await getDatabase();
|
|
@@ -10818,16 +11340,22 @@ function createTodoTool(threadId, threadPath) {
|
|
|
10818
11340
|
details: void 0
|
|
10819
11341
|
};
|
|
10820
11342
|
}
|
|
10821
|
-
const
|
|
11343
|
+
const existingById = await fetchTodosByIds(db, threadId, ids);
|
|
11344
|
+
const notFound = ids.filter((id) => !existingById.has(id));
|
|
11345
|
+
if (notFound.length > 0) {
|
|
11346
|
+
return {
|
|
11347
|
+
content: [
|
|
11348
|
+
{
|
|
11349
|
+
type: "text",
|
|
11350
|
+
text: `Error: todos not found: ${notFound.join(", ")}`
|
|
11351
|
+
}
|
|
11352
|
+
],
|
|
11353
|
+
details: void 0
|
|
11354
|
+
};
|
|
11355
|
+
}
|
|
11356
|
+
const updatedItems = [];
|
|
10822
11357
|
for (const id of ids) {
|
|
10823
|
-
const existing =
|
|
10824
|
-
sql: `SELECT id FROM todos WHERE id = ? AND threadId = ?`,
|
|
10825
|
-
args: [id, threadId]
|
|
10826
|
-
});
|
|
10827
|
-
if (existing.rows.length === 0) {
|
|
10828
|
-
notFound.push(id);
|
|
10829
|
-
continue;
|
|
10830
|
-
}
|
|
11358
|
+
const existing = existingById.get(id);
|
|
10831
11359
|
const updates = [];
|
|
10832
11360
|
const args2 = [];
|
|
10833
11361
|
if (status) {
|
|
@@ -10849,24 +11377,16 @@ function createTodoTool(threadId, threadPath) {
|
|
|
10849
11377
|
sql: `UPDATE todos SET ${updates.join(", ")} WHERE id = ? AND threadId = ?`,
|
|
10850
11378
|
args: args2
|
|
10851
11379
|
});
|
|
11380
|
+
updatedItems.push({
|
|
11381
|
+
description: existing.description,
|
|
11382
|
+
status: status ?? existing.status
|
|
11383
|
+
});
|
|
10852
11384
|
}
|
|
10853
|
-
if (notFound.length > 0) {
|
|
10854
|
-
return {
|
|
10855
|
-
content: [
|
|
10856
|
-
{
|
|
10857
|
-
type: "text",
|
|
10858
|
-
text: `Error: todos not found: ${notFound.join(", ")}`
|
|
10859
|
-
}
|
|
10860
|
-
],
|
|
10861
|
-
details: void 0
|
|
10862
|
-
};
|
|
10863
|
-
}
|
|
10864
|
-
const todosAfterUpdate = await fetchAllTodos(db, threadId);
|
|
10865
11385
|
return {
|
|
10866
11386
|
content: [
|
|
10867
11387
|
{
|
|
10868
11388
|
type: "text",
|
|
10869
|
-
text:
|
|
11389
|
+
text: formatUpdatedTodosAck(updatedItems)
|
|
10870
11390
|
}
|
|
10871
11391
|
],
|
|
10872
11392
|
details: void 0
|
|
@@ -10891,18 +11411,19 @@ function createTodoTool(threadId, threadPath) {
|
|
|
10891
11411
|
details: void 0
|
|
10892
11412
|
};
|
|
10893
11413
|
}
|
|
11414
|
+
const existingById = await fetchTodosByIds(db, threadId, ids);
|
|
11415
|
+
const deletedItems = ids.map((id) => existingById.get(id)).filter((row) => row !== void 0).map((row) => ({ description: row.description }));
|
|
10894
11416
|
for (const id of ids) {
|
|
10895
11417
|
await db.execute({
|
|
10896
11418
|
sql: `DELETE FROM todos WHERE id = ? AND threadId = ?`,
|
|
10897
11419
|
args: [id, threadId]
|
|
10898
11420
|
});
|
|
10899
11421
|
}
|
|
10900
|
-
const todosAfterDelete = await fetchAllTodos(db, threadId);
|
|
10901
11422
|
return {
|
|
10902
11423
|
content: [
|
|
10903
11424
|
{
|
|
10904
11425
|
type: "text",
|
|
10905
|
-
text:
|
|
11426
|
+
text: formatDeletedTodosAck(deletedItems)
|
|
10906
11427
|
}
|
|
10907
11428
|
],
|
|
10908
11429
|
details: void 0
|
|
@@ -11665,7 +12186,7 @@ var init_spend_record = __esm({
|
|
|
11665
12186
|
|
|
11666
12187
|
// src/features/git/git.utils.ts
|
|
11667
12188
|
import { existsSync as existsSync11, readFileSync as readFileSync4, statSync as statSync6 } from "fs";
|
|
11668
|
-
import { isAbsolute as
|
|
12189
|
+
import { isAbsolute as isAbsolute3, normalize as normalize2, resolve as resolve2, join as join10 } from "path";
|
|
11669
12190
|
async function resolveModelAndKey(provider, modelId, metadataManager) {
|
|
11670
12191
|
const providerConfig = await resolveProviderConfig(provider);
|
|
11671
12192
|
if (!providerConfig) return null;
|
|
@@ -11812,7 +12333,7 @@ Generate only the commit message, nothing else:`;
|
|
|
11812
12333
|
}
|
|
11813
12334
|
}
|
|
11814
12335
|
function resolveThreadPath(repoPath) {
|
|
11815
|
-
const base =
|
|
12336
|
+
const base = isAbsolute3(repoPath) ? repoPath : resolve2(getDataDir(), repoPath);
|
|
11816
12337
|
return normalize2(base);
|
|
11817
12338
|
}
|
|
11818
12339
|
async function getGitRoot(cwd) {
|
|
@@ -17675,7 +18196,7 @@ function createGetObservationTool(threadId, options) {
|
|
|
17675
18196
|
return {
|
|
17676
18197
|
name: "get_observation",
|
|
17677
18198
|
label: "get_observation",
|
|
17678
|
-
description: `Retrieve exact stored tool output by observation ID. Output is limited to ${OBSERVATION_RETRIEVAL_MAX_LINES} lines or ${formatSize(OBSERVATION_RETRIEVAL_MAX_BYTES)}. Use offset and limit to page through large observations. IDs are scoped to the current thread.`,
|
|
18199
|
+
description: `Retrieve exact stored tool output by observation ID. Output is limited to ${OBSERVATION_RETRIEVAL_MAX_LINES} lines or ${formatSize(OBSERVATION_RETRIEVAL_MAX_BYTES)}. Use offset and limit to page through large observations; offset counts lines within the stored output (see the preview's lines=N attribute), not line numbers shown inside grep results. IDs are scoped to the current thread.`,
|
|
17679
18200
|
parameters: getObservationSchema,
|
|
17680
18201
|
execute: async (_toolCallId, input) => {
|
|
17681
18202
|
if (!isObservationId(input.id)) {
|
|
@@ -17692,7 +18213,7 @@ function createGetObservationTool(threadId, options) {
|
|
|
17692
18213
|
const startIndex = offset - 1;
|
|
17693
18214
|
if (startIndex >= lines.length) {
|
|
17694
18215
|
throw new Error(
|
|
17695
|
-
`Offset ${offset} is beyond end of observation (${stored.ref.lineCount} lines total)
|
|
18216
|
+
`Offset ${offset} is beyond end of observation (${stored.ref.lineCount} lines total). Observation offset is 1-indexed within the stored tool output, not a source-file line number. Use offset=1 to read from the start.`
|
|
17696
18217
|
);
|
|
17697
18218
|
}
|
|
17698
18219
|
const selected = lines.slice(startIndex, startIndex + limit).join("\n");
|
|
@@ -17743,7 +18264,7 @@ var init_get_observation = __esm({
|
|
|
17743
18264
|
}),
|
|
17744
18265
|
offset: Type23.Optional(
|
|
17745
18266
|
Type23.Integer({
|
|
17746
|
-
description: "Line number to start reading
|
|
18267
|
+
description: "Line number to start reading within the stored tool output (1-indexed). Not a source-file line number from grep/read content.",
|
|
17747
18268
|
minimum: 1
|
|
17748
18269
|
})
|
|
17749
18270
|
),
|
|
@@ -20618,6 +21139,13 @@ function usesTailPreview(toolName) {
|
|
|
20618
21139
|
const normalized = toolName.toLowerCase();
|
|
20619
21140
|
return normalized === "bash" || normalized.includes("shell") || normalized.includes("terminal");
|
|
20620
21141
|
}
|
|
21142
|
+
function observationRetrievalHint(toolName, ref, firstLine, previewEndLine, nextOffset) {
|
|
21143
|
+
const base = `[Full output stored as ${ref.id} (${formatSize(ref.byteLength)}, ${ref.lineCount} lines). Preview shows lines ${firstLine}-${previewEndLine}. Use get_observation with id="${ref.id}", offset=${nextOffset}, and limit=200 to retrieve exact output.`;
|
|
21144
|
+
if (toolName.toLowerCase() === "grep") {
|
|
21145
|
+
return `${base} Line numbers in grep output (e.g. :275:) are source-file lines; get_observation offset is relative to this stored output (lines=${ref.lineCount}).`;
|
|
21146
|
+
}
|
|
21147
|
+
return base;
|
|
21148
|
+
}
|
|
20621
21149
|
function buildObservationPreview(fullContent, toolName, ref) {
|
|
20622
21150
|
const options = {
|
|
20623
21151
|
maxBytes: OBSERVATION_PREVIEW_MAX_BYTES - PREVIEW_METADATA_RESERVE_BYTES,
|
|
@@ -20626,13 +21154,14 @@ function buildObservationPreview(fullContent, toolName, ref) {
|
|
|
20626
21154
|
const tail = usesTailPreview(toolName);
|
|
20627
21155
|
const truncation = tail ? truncateTail(fullContent, options) : truncateHead(fullContent, options);
|
|
20628
21156
|
const firstLine = tail ? Math.max(1, ref.lineCount - truncation.outputLines + 1) : 1;
|
|
21157
|
+
const previewEndLine = firstLine + Math.max(0, truncation.outputLines - 1);
|
|
20629
21158
|
const nextOffset = tail ? 1 : truncation.outputLines + 1;
|
|
20630
21159
|
const preview = [
|
|
20631
21160
|
`<observation_preview id="${ref.id}" tool="${toolName}" bytes="${ref.byteLength}" lines="${ref.lineCount}">`,
|
|
20632
21161
|
truncation.content,
|
|
20633
21162
|
"</observation_preview>",
|
|
20634
21163
|
"",
|
|
20635
|
-
|
|
21164
|
+
observationRetrievalHint(toolName, ref, firstLine, previewEndLine, nextOffset)
|
|
20636
21165
|
].join("\n");
|
|
20637
21166
|
return {
|
|
20638
21167
|
content: preview,
|
|
@@ -20758,6 +21287,94 @@ function estimateMessageTokens(message) {
|
|
|
20758
21287
|
}
|
|
20759
21288
|
return 0;
|
|
20760
21289
|
}
|
|
21290
|
+
function countAssistantTurns(messages) {
|
|
21291
|
+
let count = 0;
|
|
21292
|
+
for (const message of messages) {
|
|
21293
|
+
if (typeof message === "object" && message !== null && "role" in message && message.role === "assistant") {
|
|
21294
|
+
count += 1;
|
|
21295
|
+
}
|
|
21296
|
+
}
|
|
21297
|
+
return count;
|
|
21298
|
+
}
|
|
21299
|
+
function isUserMessage(message) {
|
|
21300
|
+
return typeof message === "object" && message !== null && "role" in message && message.role === "user";
|
|
21301
|
+
}
|
|
21302
|
+
function countUserMessages(messages) {
|
|
21303
|
+
let count = 0;
|
|
21304
|
+
for (const message of messages) {
|
|
21305
|
+
if (isUserMessage(message)) {
|
|
21306
|
+
count += 1;
|
|
21307
|
+
}
|
|
21308
|
+
}
|
|
21309
|
+
return count;
|
|
21310
|
+
}
|
|
21311
|
+
function findLastUserMessageIndex(messages) {
|
|
21312
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
21313
|
+
if (isUserMessage(messages[i])) {
|
|
21314
|
+
return i;
|
|
21315
|
+
}
|
|
21316
|
+
}
|
|
21317
|
+
return -1;
|
|
21318
|
+
}
|
|
21319
|
+
function isCompactedToolResultText(text) {
|
|
21320
|
+
return text.startsWith(MICROCOMPACT_MARKER_PREFIX) || text.startsWith(TODO_DUPLICATE_OMITTED_PREFIX);
|
|
21321
|
+
}
|
|
21322
|
+
async function digestToolResultMessage(original, text, options, memo) {
|
|
21323
|
+
const memoized = memo.get(original.toolCallId);
|
|
21324
|
+
if (memoized) {
|
|
21325
|
+
return { compacted: memoized, observationIdsReused: 0, observationIdsCreated: 0 };
|
|
21326
|
+
}
|
|
21327
|
+
let observation = findObservationRef(original);
|
|
21328
|
+
let observationIdsReused = 0;
|
|
21329
|
+
let observationIdsCreated = 0;
|
|
21330
|
+
if (observation) {
|
|
21331
|
+
observationIdsReused = 1;
|
|
21332
|
+
} else {
|
|
21333
|
+
const created = await maybeExternalizeForDigest(original, text, options);
|
|
21334
|
+
if (created) {
|
|
21335
|
+
observation = created;
|
|
21336
|
+
observationIdsCreated = 1;
|
|
21337
|
+
}
|
|
21338
|
+
}
|
|
21339
|
+
const digest = observation ? buildObservationDigest(original.toolName, observation, original.isError) : buildHeuristicDigest(original.toolName, text, original.isError);
|
|
21340
|
+
const compacted = replaceToolResultContent(original, digest, observation);
|
|
21341
|
+
memo.set(original.toolCallId, compacted);
|
|
21342
|
+
return { compacted, observationIdsReused, observationIdsCreated };
|
|
21343
|
+
}
|
|
21344
|
+
function deduplicateIdenticalTodoResults(messages) {
|
|
21345
|
+
const lastIndexByContent = /* @__PURE__ */ new Map();
|
|
21346
|
+
for (let i = 0; i < messages.length; i++) {
|
|
21347
|
+
const message = messages[i];
|
|
21348
|
+
if (!isToolResultMessage(message) || message.toolName !== "todo") {
|
|
21349
|
+
continue;
|
|
21350
|
+
}
|
|
21351
|
+
const text = extractTextContent(message.content);
|
|
21352
|
+
if (text.length > 0) {
|
|
21353
|
+
lastIndexByContent.set(text, i);
|
|
21354
|
+
}
|
|
21355
|
+
}
|
|
21356
|
+
const next = messages.slice();
|
|
21357
|
+
let changed = false;
|
|
21358
|
+
for (let i = 0; i < next.length; i++) {
|
|
21359
|
+
const message = next[i];
|
|
21360
|
+
if (!isToolResultMessage(message) || message.toolName !== "todo") {
|
|
21361
|
+
continue;
|
|
21362
|
+
}
|
|
21363
|
+
const text = extractTextContent(message.content);
|
|
21364
|
+
const keepIndex = lastIndexByContent.get(text);
|
|
21365
|
+
if (keepIndex === void 0 || keepIndex === i) {
|
|
21366
|
+
continue;
|
|
21367
|
+
}
|
|
21368
|
+
const keptId = messages[keepIndex].toolCallId;
|
|
21369
|
+
next[i] = replaceToolResultContent(
|
|
21370
|
+
message,
|
|
21371
|
+
`${TODO_DUPLICATE_OMITTED_PREFIX}${keptId}]`,
|
|
21372
|
+
void 0
|
|
21373
|
+
);
|
|
21374
|
+
changed = true;
|
|
21375
|
+
}
|
|
21376
|
+
return changed ? next : messages;
|
|
21377
|
+
}
|
|
20761
21378
|
function findObservationRef(message) {
|
|
20762
21379
|
if (isObservationRef(message.details?.observation)) {
|
|
20763
21380
|
return message.details.observation;
|
|
@@ -20887,6 +21504,9 @@ async function microcompactMessages(messages, options = {}) {
|
|
|
20887
21504
|
const triggerMargin = options.triggerMargin ?? MICROCOMPACT_TRIGGER_MARGIN;
|
|
20888
21505
|
const minSaveTokens = options.minSaveTokens ?? MICROCOMPACT_MIN_SAVE_TOKENS;
|
|
20889
21506
|
const digestMinChars = options.digestMinChars ?? MICROCOMPACT_DIGEST_MIN_CHARS;
|
|
21507
|
+
const proactiveTurnThreshold = options.proactiveTurnThreshold ?? MICROCOMPACT_PROACTIVE_TURN_THRESHOLD;
|
|
21508
|
+
const proactivePayloadTokens = options.proactivePayloadTokens ?? MICROCOMPACT_PROACTIVE_PAYLOAD_TOKENS;
|
|
21509
|
+
const proactiveMinSaveTokens = options.proactiveMinSaveTokens ?? MICROCOMPACT_PROACTIVE_MIN_SAVE_TOKENS;
|
|
20890
21510
|
const effectiveWindow = computeEffectiveContextWindow(
|
|
20891
21511
|
options.contextWindow,
|
|
20892
21512
|
options.maxOutputTokens
|
|
@@ -20894,12 +21514,16 @@ async function microcompactMessages(messages, options = {}) {
|
|
|
20894
21514
|
const messageTokens = messages.reduce((sum, message) => sum + estimateMessageTokens(message), 0);
|
|
20895
21515
|
const fixedOverheadTokens = typeof options.fixedOverheadTokens === "number" && options.fixedOverheadTokens > 0 ? options.fixedOverheadTokens : 0;
|
|
20896
21516
|
const tokensBefore = messageTokens + fixedOverheadTokens;
|
|
20897
|
-
|
|
21517
|
+
const agentTurnCount = countAssistantTurns(messages);
|
|
21518
|
+
const proactiveTrigger = agentTurnCount >= proactiveTurnThreshold || tokensBefore >= proactivePayloadTokens;
|
|
21519
|
+
const nearWindowCeiling = tokensBefore >= effectiveWindow - triggerMargin;
|
|
21520
|
+
if (!nearWindowCeiling && !proactiveTrigger) {
|
|
20898
21521
|
return {
|
|
20899
21522
|
messages,
|
|
20900
21523
|
stats: { ...emptyStats, tokensBefore, tokensAfter: tokensBefore }
|
|
20901
21524
|
};
|
|
20902
21525
|
}
|
|
21526
|
+
const effectiveMinSaveTokens = proactiveTrigger && !nearWindowCeiling ? proactiveMinSaveTokens : minSaveTokens;
|
|
20903
21527
|
const eligibleIndexes = [];
|
|
20904
21528
|
for (let i = 0; i < messages.length; i++) {
|
|
20905
21529
|
const message = messages[i];
|
|
@@ -20943,7 +21567,7 @@ async function microcompactMessages(messages, options = {}) {
|
|
|
20943
21567
|
for (const index of clearCandidates) {
|
|
20944
21568
|
clearableTokens += estimateMessageTokens(messages[index]);
|
|
20945
21569
|
}
|
|
20946
|
-
if (clearableTokens <
|
|
21570
|
+
if (clearableTokens < effectiveMinSaveTokens || clearCandidates.length === 0) {
|
|
20947
21571
|
return {
|
|
20948
21572
|
messages,
|
|
20949
21573
|
stats: {
|
|
@@ -20961,26 +21585,14 @@ async function microcompactMessages(messages, options = {}) {
|
|
|
20961
21585
|
let clearedCount = 0;
|
|
20962
21586
|
for (const index of clearCandidates) {
|
|
20963
21587
|
const original = messages[index];
|
|
20964
|
-
const memoized = memo.get(original.toolCallId);
|
|
20965
|
-
if (memoized) {
|
|
20966
|
-
next[index] = memoized;
|
|
20967
|
-
clearedCount += 1;
|
|
20968
|
-
continue;
|
|
20969
|
-
}
|
|
20970
21588
|
const text = extractTextContent(original.content);
|
|
20971
|
-
|
|
20972
|
-
|
|
20973
|
-
observationIdsReused
|
|
20974
|
-
|
|
20975
|
-
|
|
20976
|
-
|
|
20977
|
-
|
|
20978
|
-
observationIdsCreated += 1;
|
|
20979
|
-
}
|
|
20980
|
-
}
|
|
20981
|
-
const digest = observation ? buildObservationDigest(original.toolName, observation, original.isError) : buildHeuristicDigest(original.toolName, text, original.isError);
|
|
20982
|
-
const compacted = replaceToolResultContent(original, digest, observation);
|
|
20983
|
-
memo.set(original.toolCallId, compacted);
|
|
21589
|
+
const {
|
|
21590
|
+
compacted,
|
|
21591
|
+
observationIdsReused: reused,
|
|
21592
|
+
observationIdsCreated: created
|
|
21593
|
+
} = await digestToolResultMessage(original, text, options, memo);
|
|
21594
|
+
observationIdsReused += reused;
|
|
21595
|
+
observationIdsCreated += created;
|
|
20984
21596
|
next[index] = compacted;
|
|
20985
21597
|
clearedCount += 1;
|
|
20986
21598
|
}
|
|
@@ -21002,19 +21614,98 @@ async function microcompactMessages(messages, options = {}) {
|
|
|
21002
21614
|
}
|
|
21003
21615
|
return { messages: next, stats };
|
|
21004
21616
|
}
|
|
21617
|
+
async function prunePriorTurnToolResults(messages, options = {}) {
|
|
21618
|
+
const emptyStats = {
|
|
21619
|
+
eligibleCount: 0,
|
|
21620
|
+
keptCount: 0,
|
|
21621
|
+
clearedCount: 0,
|
|
21622
|
+
tokensBefore: 0,
|
|
21623
|
+
tokensAfter: 0,
|
|
21624
|
+
tokensSaved: 0,
|
|
21625
|
+
observationIdsReused: 0,
|
|
21626
|
+
observationIdsCreated: 0,
|
|
21627
|
+
applied: false
|
|
21628
|
+
};
|
|
21629
|
+
if (!areMicrocompactEnabled()) {
|
|
21630
|
+
return { messages, stats: emptyStats };
|
|
21631
|
+
}
|
|
21632
|
+
if (countUserMessages(messages) < 2) {
|
|
21633
|
+
return { messages, stats: emptyStats };
|
|
21634
|
+
}
|
|
21635
|
+
const lastUserIndex = findLastUserMessageIndex(messages);
|
|
21636
|
+
if (lastUserIndex <= 0) {
|
|
21637
|
+
return { messages, stats: emptyStats };
|
|
21638
|
+
}
|
|
21639
|
+
const memo = options.memo ?? /* @__PURE__ */ new Map();
|
|
21640
|
+
const digestMinChars = options.digestMinChars ?? MICROCOMPACT_DIGEST_MIN_CHARS;
|
|
21641
|
+
const next = messages.slice();
|
|
21642
|
+
let observationIdsReused = 0;
|
|
21643
|
+
let observationIdsCreated = 0;
|
|
21644
|
+
let clearedCount = 0;
|
|
21645
|
+
let eligibleCount = 0;
|
|
21646
|
+
for (let index = 0; index < lastUserIndex; index++) {
|
|
21647
|
+
const message = messages[index];
|
|
21648
|
+
if (!isToolResultMessage(message) || message.isError) {
|
|
21649
|
+
continue;
|
|
21650
|
+
}
|
|
21651
|
+
if (!isMicrocompactEligibleTool(message.toolName)) {
|
|
21652
|
+
continue;
|
|
21653
|
+
}
|
|
21654
|
+
const text = extractTextContent(message.content);
|
|
21655
|
+
if (text.length < digestMinChars || isCompactedToolResultText(text)) {
|
|
21656
|
+
continue;
|
|
21657
|
+
}
|
|
21658
|
+
eligibleCount += 1;
|
|
21659
|
+
const {
|
|
21660
|
+
compacted,
|
|
21661
|
+
observationIdsReused: reused,
|
|
21662
|
+
observationIdsCreated: created
|
|
21663
|
+
} = await digestToolResultMessage(message, text, options, memo);
|
|
21664
|
+
observationIdsReused += reused;
|
|
21665
|
+
observationIdsCreated += created;
|
|
21666
|
+
next[index] = compacted;
|
|
21667
|
+
clearedCount += 1;
|
|
21668
|
+
}
|
|
21669
|
+
if (clearedCount === 0) {
|
|
21670
|
+
return { messages, stats: emptyStats };
|
|
21671
|
+
}
|
|
21672
|
+
const messagesTokensBefore = messages.reduce(
|
|
21673
|
+
(sum, message) => sum + estimateMessageTokens(message),
|
|
21674
|
+
0
|
|
21675
|
+
);
|
|
21676
|
+
const messagesTokensAfter = next.reduce(
|
|
21677
|
+
(sum, message) => sum + estimateMessageTokens(message),
|
|
21678
|
+
0
|
|
21679
|
+
);
|
|
21680
|
+
const stats = {
|
|
21681
|
+
eligibleCount,
|
|
21682
|
+
keptCount: 0,
|
|
21683
|
+
clearedCount,
|
|
21684
|
+
tokensBefore: messagesTokensBefore,
|
|
21685
|
+
tokensAfter: messagesTokensAfter,
|
|
21686
|
+
tokensSaved: Math.max(0, messagesTokensBefore - messagesTokensAfter),
|
|
21687
|
+
observationIdsReused,
|
|
21688
|
+
observationIdsCreated,
|
|
21689
|
+
applied: true
|
|
21690
|
+
};
|
|
21691
|
+
log49.info("Applied prior-turn tool-result prune", stats);
|
|
21692
|
+
return { messages: next, stats };
|
|
21693
|
+
}
|
|
21005
21694
|
function createMicrocompactTransformContext(options) {
|
|
21006
21695
|
const memo = options.memo ?? /* @__PURE__ */ new Map();
|
|
21007
21696
|
return async function transformContext(messages) {
|
|
21008
21697
|
try {
|
|
21009
|
-
const
|
|
21698
|
+
const deduped = deduplicateIdenticalTodoResults(messages);
|
|
21699
|
+
const pruned = await prunePriorTurnToolResults(deduped, { ...options, memo });
|
|
21700
|
+
const result = await microcompactMessages(pruned.messages, { ...options, memo });
|
|
21010
21701
|
return result.messages;
|
|
21011
21702
|
} catch (error) {
|
|
21012
21703
|
log49.warn("Micro-compaction failed; sending original context", { error });
|
|
21013
|
-
return messages;
|
|
21704
|
+
return deduplicateIdenticalTodoResults(messages);
|
|
21014
21705
|
}
|
|
21015
21706
|
};
|
|
21016
21707
|
}
|
|
21017
|
-
var log49, OBSERVATION_ID_IN_TEXT;
|
|
21708
|
+
var log49, OBSERVATION_ID_IN_TEXT, TODO_DUPLICATE_OMITTED_PREFIX;
|
|
21018
21709
|
var init_microcompact2 = __esm({
|
|
21019
21710
|
"src/features/microcompact/microcompact.ts"() {
|
|
21020
21711
|
"use strict";
|
|
@@ -21024,6 +21715,7 @@ var init_microcompact2 = __esm({
|
|
|
21024
21715
|
init_microcompact_config();
|
|
21025
21716
|
log49 = createLogger("microcompact");
|
|
21026
21717
|
OBSERVATION_ID_IN_TEXT = /obs_[a-f0-9]{64}/;
|
|
21718
|
+
TODO_DUPLICATE_OMITTED_PREFIX = "[Duplicate todo result omitted; identical to toolCallId=";
|
|
21027
21719
|
}
|
|
21028
21720
|
});
|
|
21029
21721
|
|
|
@@ -21833,7 +22525,7 @@ import {
|
|
|
21833
22525
|
InMemorySessionRepo as InMemorySessionRepo2
|
|
21834
22526
|
} from "@earendil-works/pi-agent-core";
|
|
21835
22527
|
import { homedir as homedir6 } from "node:os";
|
|
21836
|
-
import { resolve as resolve4, isAbsolute as
|
|
22528
|
+
import { resolve as resolve4, isAbsolute as isAbsolute4 } from "path";
|
|
21837
22529
|
function getAssistantHistoryContent(exchange, isMostRecent) {
|
|
21838
22530
|
return resolveAssistantHistoryContent(exchange, isMostRecent);
|
|
21839
22531
|
}
|
|
@@ -22023,7 +22715,7 @@ var init_agent_executor = __esm({
|
|
|
22023
22715
|
if (!model) {
|
|
22024
22716
|
throw new Error("No model specified in execution context");
|
|
22025
22717
|
}
|
|
22026
|
-
const cwd =
|
|
22718
|
+
const cwd = isAbsolute4(context.threadPath) ? context.threadPath : resolve4(getDataDir(), context.threadPath);
|
|
22027
22719
|
log52.info("Execution context", {
|
|
22028
22720
|
model,
|
|
22029
22721
|
cwd,
|
|
@@ -22241,6 +22933,11 @@ var init_agent_executor = __esm({
|
|
|
22241
22933
|
});
|
|
22242
22934
|
if (microcompactTransform) {
|
|
22243
22935
|
harness.on("context", createHarnessContextHandler(microcompactTransform));
|
|
22936
|
+
} else {
|
|
22937
|
+
harness.on(
|
|
22938
|
+
"context",
|
|
22939
|
+
createHarnessContextHandler((messages) => deduplicateIdenticalTodoResults(messages))
|
|
22940
|
+
);
|
|
22244
22941
|
}
|
|
22245
22942
|
harness.on("before_provider_payload", (event) => {
|
|
22246
22943
|
recordPromptCacheFingerprint(
|
|
@@ -24967,8 +25664,23 @@ async function resolveSeatbeltSandboxEnabled(db) {
|
|
|
24967
25664
|
}
|
|
24968
25665
|
return true;
|
|
24969
25666
|
}
|
|
24970
|
-
function
|
|
25667
|
+
function isLoggingEnabled(rawLogging, rawFileLogging, rawBetterStackLogging) {
|
|
25668
|
+
if (rawLogging !== null && rawLogging !== void 0) {
|
|
25669
|
+
return rawLogging === true;
|
|
25670
|
+
}
|
|
25671
|
+
return rawFileLogging === true || rawBetterStackLogging === true;
|
|
25672
|
+
}
|
|
25673
|
+
async function applyLoggingEnabled(db, enabled) {
|
|
25674
|
+
await setState(db, KEY_LOGGING, enabled);
|
|
25675
|
+
await setState(db, KEY_FILE_LOGGING, enabled);
|
|
25676
|
+
await setState(db, KEY_BETTERSTACK_LOGGING, enabled);
|
|
25677
|
+
setFileLoggingEnabledByUser(enabled);
|
|
25678
|
+
setBetterStackLoggingEnabledByUser(enabled);
|
|
25679
|
+
syncRuntimeLogLevel(isCliDebugMode());
|
|
25680
|
+
}
|
|
25681
|
+
function buildAccountSettingsResponse(rawMaximumTurnCount, rawUseToolsThroughCode, rawVoiceAiProcessing, rawLogging, rawFileLogging, rawBetterStackLogging, rawShowDailySpend, rawSeatbeltSandboxEnabled, serverBindMode) {
|
|
24971
25682
|
const betterStackEnvConfigured = isBetterStackEnvConfigured();
|
|
25683
|
+
const loggingEnabled = isLoggingEnabled(rawLogging, rawFileLogging, rawBetterStackLogging);
|
|
24972
25684
|
const seatbeltHealth = checkSeatbeltSandboxHealth();
|
|
24973
25685
|
const seatbeltPreferenceEnabled = isSeatbeltSandboxPreferenceEnabled(rawSeatbeltSandboxEnabled);
|
|
24974
25686
|
const seatbeltSandboxEnabled = seatbeltPreferenceEnabled && seatbeltHealth.healthy;
|
|
@@ -24981,11 +25693,12 @@ function buildAccountSettingsResponse(rawMaximumTurnCount, rawUseToolsThroughCod
|
|
|
24981
25693
|
seatbeltSandboxSupported: seatbeltHealth.supported,
|
|
24982
25694
|
seatbeltSandboxHealthy: seatbeltHealth.healthy,
|
|
24983
25695
|
seatbeltSandboxMessage: seatbeltHealth.message,
|
|
24984
|
-
betterStackLoggingEnabled:
|
|
25696
|
+
betterStackLoggingEnabled: loggingEnabled,
|
|
24985
25697
|
betterStackEnvConfigured,
|
|
24986
25698
|
betterStackIngestingHost: betterStackEnvConfigured ? getBetterStackIngestingHost() : null,
|
|
24987
|
-
fileLoggingEnabled:
|
|
25699
|
+
fileLoggingEnabled: loggingEnabled,
|
|
24988
25700
|
fileLoggingPath: getFileLoggingPath(),
|
|
25701
|
+
loggingEnabled,
|
|
24989
25702
|
serverBindMode,
|
|
24990
25703
|
networkAddresses: serverBindMode === "network" ? getLocalNetworkAddresses() : []
|
|
24991
25704
|
};
|
|
@@ -25015,10 +25728,16 @@ async function getServerBindModeSetting() {
|
|
|
25015
25728
|
}
|
|
25016
25729
|
async function initializeLoggingSettings() {
|
|
25017
25730
|
const db = await getDatabase();
|
|
25731
|
+
const rawLogging = await getState(db, KEY_LOGGING);
|
|
25018
25732
|
const rawBetterStackLogging = await getState(db, KEY_BETTERSTACK_LOGGING);
|
|
25019
25733
|
const rawFileLogging = await getState(db, KEY_FILE_LOGGING);
|
|
25020
|
-
|
|
25021
|
-
|
|
25734
|
+
const loggingEnabled = isLoggingEnabled(rawLogging, rawFileLogging, rawBetterStackLogging);
|
|
25735
|
+
if (loggingEnabled && rawLogging !== true) {
|
|
25736
|
+
await applyLoggingEnabled(db, true);
|
|
25737
|
+
return;
|
|
25738
|
+
}
|
|
25739
|
+
setBetterStackLoggingEnabledByUser(loggingEnabled);
|
|
25740
|
+
setFileLoggingEnabledByUser(loggingEnabled);
|
|
25022
25741
|
syncRuntimeLogLevel(isCliDebugMode());
|
|
25023
25742
|
}
|
|
25024
25743
|
async function getAccountSettings(c) {
|
|
@@ -25028,6 +25747,7 @@ async function getAccountSettings(c) {
|
|
|
25028
25747
|
const rawMaximumTurnCount = await getState(db, KEY_MAXIMUM_TURN_COUNT);
|
|
25029
25748
|
const rawBetterStackLogging = await getState(db, KEY_BETTERSTACK_LOGGING);
|
|
25030
25749
|
const rawFileLogging = await getState(db, KEY_FILE_LOGGING);
|
|
25750
|
+
const rawLogging = await getState(db, KEY_LOGGING);
|
|
25031
25751
|
const rawShowDailySpend = await getState(db, KEY_SHOW_DAILY_SPEND);
|
|
25032
25752
|
let rawSeatbeltSandboxEnabled = await getState(db, KEY_SEATBELT_SANDBOX_ENABLED);
|
|
25033
25753
|
const serverBindMode = await getServerBindModeSetting();
|
|
@@ -25040,8 +25760,9 @@ async function getAccountSettings(c) {
|
|
|
25040
25760
|
rawMaximumTurnCount,
|
|
25041
25761
|
rawUseToolsThroughCode,
|
|
25042
25762
|
rawVoiceAiProcessing,
|
|
25043
|
-
|
|
25763
|
+
rawLogging,
|
|
25044
25764
|
rawFileLogging,
|
|
25765
|
+
rawBetterStackLogging,
|
|
25045
25766
|
rawShowDailySpend,
|
|
25046
25767
|
rawSeatbeltSandboxEnabled,
|
|
25047
25768
|
serverBindMode
|
|
@@ -25078,15 +25799,9 @@ async function updateAccountSettings(c) {
|
|
|
25078
25799
|
}
|
|
25079
25800
|
await setState(db, KEY_SEATBELT_SANDBOX_ENABLED, body.seatbeltSandboxEnabled);
|
|
25080
25801
|
}
|
|
25081
|
-
|
|
25082
|
-
|
|
25083
|
-
|
|
25084
|
-
syncRuntimeLogLevel(isCliDebugMode());
|
|
25085
|
-
}
|
|
25086
|
-
if (typeof body.fileLoggingEnabled === "boolean") {
|
|
25087
|
-
await setState(db, KEY_FILE_LOGGING, body.fileLoggingEnabled);
|
|
25088
|
-
setFileLoggingEnabledByUser(body.fileLoggingEnabled);
|
|
25089
|
-
syncRuntimeLogLevel(isCliDebugMode());
|
|
25802
|
+
const loggingEnabledUpdate = typeof body.loggingEnabled === "boolean" ? body.loggingEnabled : typeof body.fileLoggingEnabled === "boolean" ? body.fileLoggingEnabled : typeof body.betterStackLoggingEnabled === "boolean" ? body.betterStackLoggingEnabled : void 0;
|
|
25803
|
+
if (typeof loggingEnabledUpdate === "boolean") {
|
|
25804
|
+
await applyLoggingEnabled(db, loggingEnabledUpdate);
|
|
25090
25805
|
}
|
|
25091
25806
|
if (body.maximumTurnCount !== void 0) {
|
|
25092
25807
|
await setState(db, KEY_MAXIMUM_TURN_COUNT, normalizeMaximumTurnCount(body.maximumTurnCount));
|
|
@@ -25099,6 +25814,7 @@ async function updateAccountSettings(c) {
|
|
|
25099
25814
|
const rawMaximumTurnCount = await getState(db, KEY_MAXIMUM_TURN_COUNT);
|
|
25100
25815
|
const rawBetterStackLogging = await getState(db, KEY_BETTERSTACK_LOGGING);
|
|
25101
25816
|
const rawFileLogging = await getState(db, KEY_FILE_LOGGING);
|
|
25817
|
+
const rawLogging = await getState(db, KEY_LOGGING);
|
|
25102
25818
|
const rawShowDailySpend = await getState(db, KEY_SHOW_DAILY_SPEND);
|
|
25103
25819
|
const rawSeatbeltSandboxEnabled = await getState(db, KEY_SEATBELT_SANDBOX_ENABLED);
|
|
25104
25820
|
const serverBindMode = readServerBindMode();
|
|
@@ -25107,15 +25823,16 @@ async function updateAccountSettings(c) {
|
|
|
25107
25823
|
rawMaximumTurnCount,
|
|
25108
25824
|
rawUseToolsThroughCode,
|
|
25109
25825
|
rawVoiceAiProcessing,
|
|
25110
|
-
|
|
25826
|
+
rawLogging,
|
|
25111
25827
|
rawFileLogging,
|
|
25828
|
+
rawBetterStackLogging,
|
|
25112
25829
|
rawShowDailySpend,
|
|
25113
25830
|
rawSeatbeltSandboxEnabled,
|
|
25114
25831
|
serverBindMode
|
|
25115
25832
|
)
|
|
25116
25833
|
);
|
|
25117
25834
|
}
|
|
25118
|
-
var DEFAULT_MAXIMUM_TURN_COUNT, KEY_MAXIMUM_TURN_COUNT, KEY_USE_TOOLS_THROUGH_CODE, KEY_VOICE_AI_PROCESSING, KEY_BETTERSTACK_LOGGING, KEY_FILE_LOGGING, KEY_SHOW_DAILY_SPEND, KEY_SEATBELT_SANDBOX_ENABLED, LEGACY_KEY_SERVER_BIND_MODE;
|
|
25835
|
+
var DEFAULT_MAXIMUM_TURN_COUNT, KEY_MAXIMUM_TURN_COUNT, KEY_USE_TOOLS_THROUGH_CODE, KEY_VOICE_AI_PROCESSING, KEY_BETTERSTACK_LOGGING, KEY_FILE_LOGGING, KEY_LOGGING, KEY_SHOW_DAILY_SPEND, KEY_SEATBELT_SANDBOX_ENABLED, LEGACY_KEY_SERVER_BIND_MODE;
|
|
25119
25836
|
var init_account_settings_route = __esm({
|
|
25120
25837
|
"src/features/account/account-settings.route.ts"() {
|
|
25121
25838
|
"use strict";
|
|
@@ -25134,6 +25851,7 @@ var init_account_settings_route = __esm({
|
|
|
25134
25851
|
KEY_VOICE_AI_PROCESSING = "VoiceAiProcessing";
|
|
25135
25852
|
KEY_BETTERSTACK_LOGGING = "BetterStackLogging";
|
|
25136
25853
|
KEY_FILE_LOGGING = "FileLogging";
|
|
25854
|
+
KEY_LOGGING = "Logging";
|
|
25137
25855
|
KEY_SHOW_DAILY_SPEND = "ShowDailySpend";
|
|
25138
25856
|
KEY_SEATBELT_SANDBOX_ENABLED = "SeatbeltSandboxEnabled";
|
|
25139
25857
|
LEGACY_KEY_SERVER_BIND_MODE = "ServerBindMode";
|
|
@@ -30421,7 +31139,7 @@ var init_ai_files_route_helpers = __esm({
|
|
|
30421
31139
|
|
|
30422
31140
|
// src/features/projects/projects-ai-files.route.ts
|
|
30423
31141
|
import { readdir as readdir6 } from "fs/promises";
|
|
30424
|
-
import { join as join28, extname as extname3, basename } from "path";
|
|
31142
|
+
import { join as join28, extname as extname3, basename as basename2 } from "path";
|
|
30425
31143
|
import { existsSync as existsSync20 } from "fs";
|
|
30426
31144
|
async function buildFullTree(dirPath, relativeDirPath) {
|
|
30427
31145
|
const nodes = [];
|
|
@@ -30474,7 +31192,7 @@ async function buildMarkdownFilteredTree(dirPath, relativeDirPath) {
|
|
|
30474
31192
|
} else if (entry.isFile() && MARKDOWN_EXTS.has(extname3(entry.name))) {
|
|
30475
31193
|
nodes.push({
|
|
30476
31194
|
id: entryRelPath,
|
|
30477
|
-
name:
|
|
31195
|
+
name: basename2(entry.name, extname3(entry.name)),
|
|
30478
31196
|
type: "file",
|
|
30479
31197
|
path: entryRelPath
|
|
30480
31198
|
});
|
|
@@ -30533,7 +31251,7 @@ async function buildAIFileTree(projectPath) {
|
|
|
30533
31251
|
if (entry.isFile() && MARKDOWN_EXTS.has(extname3(entry.name))) {
|
|
30534
31252
|
nodes.push({
|
|
30535
31253
|
id: entryRelPath,
|
|
30536
|
-
name:
|
|
31254
|
+
name: basename2(entry.name, extname3(entry.name)),
|
|
30537
31255
|
type: "file",
|
|
30538
31256
|
path: entryRelPath
|
|
30539
31257
|
});
|
|
@@ -32344,7 +33062,7 @@ var init_random_words = __esm({
|
|
|
32344
33062
|
|
|
32345
33063
|
// src/features/projects/projects.creator.ts
|
|
32346
33064
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
32347
|
-
import { basename as
|
|
33065
|
+
import { basename as basename3, join as join30, relative as relative4, resolve as resolvePath2 } from "path";
|
|
32348
33066
|
import { realpathSync as realpathSync2 } from "fs";
|
|
32349
33067
|
import { access as access4, mkdir as mkdir5, rm as rm3, stat as stat4, readdir as readdir8 } from "fs/promises";
|
|
32350
33068
|
async function loadUtils() {
|
|
@@ -32844,7 +33562,7 @@ var init_projects_creator = __esm({
|
|
|
32844
33562
|
try {
|
|
32845
33563
|
const existingNames = existingProjects.map((p) => p.name);
|
|
32846
33564
|
const projectName = this.ensureUniqueProjectName(
|
|
32847
|
-
|
|
33565
|
+
basename3(normalizedPath) || "project",
|
|
32848
33566
|
existingNames
|
|
32849
33567
|
);
|
|
32850
33568
|
yield {
|
|
@@ -34611,7 +35329,7 @@ var init_scaffold_routes = __esm({
|
|
|
34611
35329
|
|
|
34612
35330
|
// src/features/slash-commands/slash-commands.manager.ts
|
|
34613
35331
|
import { readdir as readdir9, readFile as readFile13, mkdir as mkdir6, writeFile as writeFile6, unlink as unlink4 } from "fs/promises";
|
|
34614
|
-
import { join as join33, basename as
|
|
35332
|
+
import { join as join33, basename as basename4, extname as extname4, relative as relative5 } from "path";
|
|
34615
35333
|
import { existsSync as existsSync22 } from "fs";
|
|
34616
35334
|
import { homedir as homedir9 } from "os";
|
|
34617
35335
|
function slugify(filename) {
|
|
@@ -34739,7 +35457,7 @@ var init_slash_commands_manager = __esm({
|
|
|
34739
35457
|
const filePath = join33(dir, file);
|
|
34740
35458
|
const fileContent = await readFile13(filePath, "utf-8");
|
|
34741
35459
|
const { metadata, content } = parseFrontmatter2(fileContent);
|
|
34742
|
-
const nameWithoutExt =
|
|
35460
|
+
const nameWithoutExt = basename4(file, extname4(file));
|
|
34743
35461
|
const commandName = slugify(nameWithoutExt);
|
|
34744
35462
|
if (!commandName) continue;
|
|
34745
35463
|
commands.push({
|
|
@@ -35440,7 +36158,7 @@ var init_rules_routes = __esm({
|
|
|
35440
36158
|
});
|
|
35441
36159
|
|
|
35442
36160
|
// src/features/threads/thread-folder-name.ts
|
|
35443
|
-
import { basename as
|
|
36161
|
+
import { basename as basename5 } from "path";
|
|
35444
36162
|
function normalizeThreadFolderName(input) {
|
|
35445
36163
|
return input.trim().toLowerCase().replace(/[\\/]+/g, "-").replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 120);
|
|
35446
36164
|
}
|
|
@@ -35470,7 +36188,7 @@ function deriveRepoSlugFromGitUrl(gitUrl) {
|
|
|
35470
36188
|
if (segments.length === 1) {
|
|
35471
36189
|
return normalizeThreadFolderName(segments[0]);
|
|
35472
36190
|
}
|
|
35473
|
-
return normalizeThreadFolderName(
|
|
36191
|
+
return normalizeThreadFolderName(basename5(trimmed));
|
|
35474
36192
|
}
|
|
35475
36193
|
var init_thread_folder_name = __esm({
|
|
35476
36194
|
"src/features/threads/thread-folder-name.ts"() {
|
|
@@ -36214,7 +36932,7 @@ var init_project_scripts_database = __esm({
|
|
|
36214
36932
|
// src/core/project-inspector.ts
|
|
36215
36933
|
import { readFile as readFile14, readdir as readdir10 } from "fs/promises";
|
|
36216
36934
|
import { existsSync as existsSync23 } from "fs";
|
|
36217
|
-
import { join as join35, basename as
|
|
36935
|
+
import { join as join35, basename as basename6, relative as relative6 } from "path";
|
|
36218
36936
|
import { glob as glob2 } from "glob";
|
|
36219
36937
|
async function inspectProject(projectPath, projectId) {
|
|
36220
36938
|
const packageManager = await detectPackageManager2(projectPath);
|
|
@@ -36309,7 +37027,7 @@ async function collectWorkspacesFromGlobs(projectPath, patterns, options = {}) {
|
|
|
36309
37027
|
for (const folder of folders) {
|
|
36310
37028
|
const absPath = join35(projectPath, folder);
|
|
36311
37029
|
if (!existsSync23(join35(absPath, "package.json"))) continue;
|
|
36312
|
-
let name =
|
|
37030
|
+
let name = basename6(folder);
|
|
36313
37031
|
if (options.preferPackageName) {
|
|
36314
37032
|
const wsPkg = await readPackageJson(absPath);
|
|
36315
37033
|
name = wsPkg?.name ?? name;
|
|
@@ -36752,7 +37470,7 @@ var init_threads_files_route = __esm({
|
|
|
36752
37470
|
|
|
36753
37471
|
// src/features/threads/threads-explorer.route.ts
|
|
36754
37472
|
import { readdir as readdir11, readFile as readFile15, rename as rename3, writeFile as writeFile7, mkdir as mkdir8, access as access5, open as open2, stat as stat6, rm as rm6 } from "fs/promises";
|
|
36755
|
-
import { isAbsolute as
|
|
37473
|
+
import { isAbsolute as isAbsolute5, join as join36, resolve as resolve5, basename as basename7 } from "path";
|
|
36756
37474
|
import { spawn as spawn2 } from "child_process";
|
|
36757
37475
|
import openFilePath from "open";
|
|
36758
37476
|
async function loadUtils2() {
|
|
@@ -37068,7 +37786,7 @@ async function handlePrintMarkdownExplorerFile(c, threadManager) {
|
|
|
37068
37786
|
const exportDir = join36(thread.path, ".tarsk", "exports");
|
|
37069
37787
|
await mkdir8(exportDir, { recursive: true });
|
|
37070
37788
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-");
|
|
37071
|
-
const safeBaseName =
|
|
37789
|
+
const safeBaseName = basename7(filePath, ".md").replace(/[^a-zA-Z0-9._-]+/g, "-") || "document";
|
|
37072
37790
|
const outputPath = join36(exportDir, `${safeBaseName}-${timestamp}.pdf`);
|
|
37073
37791
|
await runMarkdownToPdf(absPath, outputPath);
|
|
37074
37792
|
await openExportPath(outputPath);
|
|
@@ -37131,7 +37849,7 @@ async function handleCreateExplorerFile(c, threadManager) {
|
|
|
37131
37849
|
}
|
|
37132
37850
|
}
|
|
37133
37851
|
function resolveExplorerMediaAbsolutePath(threadPath, filePath) {
|
|
37134
|
-
if (
|
|
37852
|
+
if (isAbsolute5(filePath)) {
|
|
37135
37853
|
const normalized = resolve5(filePath);
|
|
37136
37854
|
const dataDir = resolve5(getDataDir());
|
|
37137
37855
|
if ((normalized === dataDir || normalized.startsWith(`${dataDir}/`)) && getExplorerFileMediaType(normalized)) {
|