tarsk 0.5.43 → 0.5.45
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 +1821 -378
- package/dist/public/assets/{account-view-xKotpUyx.js → account-view-CQetjhCu.js} +1 -1
- package/dist/public/assets/api-BiOks4gS.js +1 -0
- package/dist/public/assets/{browser-tab-DxigYzoT.js → browser-tab-CqyQXekj.js} +1 -1
- package/dist/public/assets/commit-dialog-CF1xviwe.js +1 -0
- package/dist/public/assets/context-menu-DcqiR-vK.js +1 -0
- package/dist/public/assets/create-repo-dialog-zVm7eDO3.js +1 -0
- package/dist/public/assets/dialogs-config-Cmwn8AP3.js +51 -0
- package/dist/public/assets/diff-view-CKaqAShe.js +3 -0
- package/dist/public/assets/explorer-tab-view-CDb7lUWX.js +2 -0
- package/dist/public/assets/explorer-tree-C-9BuD8n.js +1 -0
- package/dist/public/assets/{explorer-view-DIM08sdy.js → explorer-view-txWDc-Tg.js} +1 -1
- package/dist/public/assets/git-history-dialog-WU95COhj.js +1 -0
- package/dist/public/assets/git-ops-button-eOAENpwR.js +2 -0
- package/dist/public/assets/history-view-DTJug8Lv.js +9 -0
- package/dist/public/assets/index-CaswO76A.js +89 -0
- package/dist/public/assets/index-WIITae9I.css +1 -0
- package/dist/public/assets/mcp-server-card-BKJl2_RK.js +1 -0
- package/dist/public/assets/merged-pr-dialog-rSHpRiME.js +1 -0
- package/dist/public/assets/model-star-rating-DpF6_FVG.js +1 -0
- package/dist/public/assets/onboarding-ZCm02KNK.js +1 -0
- package/dist/public/assets/project-settings-view-COigmUzh.js +1 -0
- package/dist/public/assets/providers-list-view-BWQJtM6g.js +1 -0
- package/dist/public/assets/pull-request-dialog-DQVVlS8M.js +1 -0
- package/dist/public/assets/pull-with-changes-dialog-DtMvqvd8.js +1 -0
- package/dist/public/assets/push-before-pr-dialog-Bi6MHZiO.js +1 -0
- package/dist/public/assets/radio-group-s_ij8p3t.js +1 -0
- package/dist/public/assets/react-vendor-DMQDEfby.js +16 -0
- package/dist/public/assets/settings-general-view-Cgl5v7l-.js +1 -0
- package/dist/public/assets/settings-instructions-view-DrIwhrWJ.js +1 -0
- package/dist/public/assets/settings-list-B6nYThKU.js +1 -0
- package/dist/public/assets/settings-mcp-servers-view-QPaXg_C8.js +5 -0
- package/dist/public/assets/{settings-models-skeleton-DPnYbg69.js → settings-models-skeleton-D44p69YI.js} +1 -1
- package/dist/public/assets/settings-models-view--0zYzSYj.js +1 -0
- package/dist/public/assets/settings-rules-view-DtTJGnIp.js +8 -0
- package/dist/public/assets/settings-skills-view-CHT2EsBm.js +3 -0
- package/dist/public/assets/settings-slash-commands-view-RamAmlbD.js +1 -0
- package/dist/public/assets/settings-subagents-view-BY1e9nax.js +2 -0
- package/dist/public/assets/settings-system-prompt-view-DNR6lOSK.js +1 -0
- package/dist/public/assets/settings-view-XnpSVwUl.js +2 -0
- package/dist/public/assets/skeleton-_77ZLAk1.js +1 -0
- package/dist/public/assets/slug-utils-C0Ke4-ko.js +1 -0
- package/dist/public/assets/{terminal-panel-DTOx74_o.js → terminal-panel-Cp5-H4GS.js} +1 -1
- package/dist/public/assets/{ui-components-Jc6oi6bz.js → ui-components-BMQhWki4.js} +1 -1
- package/dist/public/assets/{use-deferred-search-B7EdyRbt.js → use-deferred-search-D0WCS0gz.js} +1 -1
- package/dist/public/assets/{utils-tgi5ym_d.js → utils-CkwFiI9G.js} +1 -1
- package/dist/public/assets/vosk-speech-CgILeNJR.js +2 -0
- package/dist/public/assets/{web-C3vJZ_3_.js → web-CdlbRaqq.js} +1 -1
- package/dist/public/assets/{web-CUAWBWPy.js → web-zT0ibfrw.js} +1 -1
- package/dist/public/index.html +8 -8
- package/package.json +1 -1
- package/dist/public/assets/api-D6uLdHBQ.js +0 -1
- package/dist/public/assets/commit-dialog-CLQM9ah3.js +0 -1
- package/dist/public/assets/context-menu-rC7iWcty.js +0 -1
- package/dist/public/assets/create-repo-dialog-C6k5wZPW.js +0 -1
- package/dist/public/assets/dialogs-config-CjKh5Rl2.js +0 -51
- package/dist/public/assets/diff-view-DWDWI5nl.js +0 -3
- package/dist/public/assets/explorer-tab-view-B0kT8Hl6.js +0 -2
- package/dist/public/assets/explorer-tree-BC4fBpxi.js +0 -1
- package/dist/public/assets/git-history-dialog-CuxOTngT.js +0 -1
- package/dist/public/assets/git-ops-button-C04zFAnF.js +0 -2
- package/dist/public/assets/history-view-ar7GLZ-R.js +0 -9
- package/dist/public/assets/index--HY4BbcM.js +0 -90
- package/dist/public/assets/index-DKOXV50p.css +0 -1
- package/dist/public/assets/mcp-server-card-Cy4RU2_Q.js +0 -1
- package/dist/public/assets/merged-pr-dialog-Bo07VouF.js +0 -1
- package/dist/public/assets/model-star-rating-BmkpdXfr.js +0 -1
- package/dist/public/assets/onboarding-ClZrOxX7.js +0 -1
- package/dist/public/assets/project-settings-view-Dm9pQAp_.js +0 -1
- package/dist/public/assets/providers-list-view-D5gHsjl_.js +0 -1
- package/dist/public/assets/pull-request-dialog-8AYlOUNX.js +0 -1
- package/dist/public/assets/pull-with-changes-dialog-CSa5OE-d.js +0 -1
- package/dist/public/assets/push-before-pr-dialog-D5W_xsqv.js +0 -1
- package/dist/public/assets/radio-group-CbatNaj1.js +0 -1
- package/dist/public/assets/react-vendor-DwQYi7es.js +0 -16
- package/dist/public/assets/settings-general-view-BP5ULy9A.js +0 -1
- package/dist/public/assets/settings-instructions-view-DMAjbi6E.js +0 -1
- package/dist/public/assets/settings-list-B8hiBkBz.js +0 -1
- package/dist/public/assets/settings-mcp-servers-view-OimQz-Rd.js +0 -5
- package/dist/public/assets/settings-models-view-Fq3WtdKG.js +0 -1
- package/dist/public/assets/settings-rules-view-DBk7DzV2.js +0 -8
- package/dist/public/assets/settings-skills-view-CmOw-WMM.js +0 -2
- package/dist/public/assets/settings-slash-commands-view-FsrF5FkK.js +0 -1
- package/dist/public/assets/settings-subagents-view-D98Nxoly.js +0 -2
- package/dist/public/assets/settings-system-prompt-view-B6Hy9ZyK.js +0 -1
- package/dist/public/assets/settings-view-J-rjoRcU.js +0 -2
- package/dist/public/assets/skeleton-BHhGML7J.js +0 -1
- package/dist/public/assets/slug-utils-DyRUJ1NS.js +0 -1
- package/dist/public/assets/whisper-wasm-CWcbC1MB.js +0 -2
- package/dist/public/wasm/libmain-CWYJvMY5.js +0 -3318
- package/dist/public/wasm/libmain-D9-QM3iM.mjs +0 -3301
package/dist/index.js
CHANGED
|
@@ -60,11 +60,127 @@ var init_programs = __esm({
|
|
|
60
60
|
}
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
// ../shared/dist/model-reasoning.js
|
|
64
|
+
function isReasoningEffortLevel(value) {
|
|
65
|
+
return REASONING_EFFORT_LEVEL_SET.has(value);
|
|
66
|
+
}
|
|
67
|
+
function isModelReasoningSetting(value) {
|
|
68
|
+
return value === REASONING_EFFORT_OFF || isReasoningEffortLevel(value);
|
|
69
|
+
}
|
|
70
|
+
function normalizeModelSettings(settings) {
|
|
71
|
+
if (!settings) {
|
|
72
|
+
return { reasoningEffort: REASONING_EFFORT_OFF };
|
|
73
|
+
}
|
|
74
|
+
if (settings.reasoningEffort !== void 0) {
|
|
75
|
+
return { reasoningEffort: settings.reasoningEffort };
|
|
76
|
+
}
|
|
77
|
+
if (settings.reasoning !== void 0) {
|
|
78
|
+
return {
|
|
79
|
+
reasoningEffort: settings.reasoning ? "medium" : REASONING_EFFORT_OFF
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return { reasoningEffort: REASONING_EFFORT_OFF };
|
|
83
|
+
}
|
|
84
|
+
function isReasoningEnabled(settings) {
|
|
85
|
+
return normalizeModelSettings(settings).reasoningEffort !== REASONING_EFFORT_OFF;
|
|
86
|
+
}
|
|
87
|
+
function getThinkingLevelFromSettings(settings) {
|
|
88
|
+
return normalizeModelSettings(settings).reasoningEffort ?? REASONING_EFFORT_OFF;
|
|
89
|
+
}
|
|
90
|
+
function clampReasoningEffort(effort, supportedEfforts) {
|
|
91
|
+
if (effort === REASONING_EFFORT_OFF || supportedEfforts.length === 0) {
|
|
92
|
+
return REASONING_EFFORT_OFF;
|
|
93
|
+
}
|
|
94
|
+
if (supportedEfforts.includes(effort)) {
|
|
95
|
+
return effort;
|
|
96
|
+
}
|
|
97
|
+
return supportedEfforts.includes("medium") ? "medium" : supportedEfforts[0] ?? REASONING_EFFORT_OFF;
|
|
98
|
+
}
|
|
99
|
+
var REASONING_EFFORT_OFF, REASONING_EFFORT_LEVELS, REASONING_EFFORT_LEVEL_SET;
|
|
100
|
+
var init_model_reasoning = __esm({
|
|
101
|
+
"../shared/dist/model-reasoning.js"() {
|
|
102
|
+
"use strict";
|
|
103
|
+
REASONING_EFFORT_OFF = "off";
|
|
104
|
+
REASONING_EFFORT_LEVELS = ["minimal", "low", "medium", "high", "xhigh"];
|
|
105
|
+
REASONING_EFFORT_LEVEL_SET = new Set(REASONING_EFFORT_LEVELS);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
63
109
|
// ../shared/dist/models.js
|
|
64
110
|
var init_models = __esm({
|
|
65
111
|
"../shared/dist/models.js"() {
|
|
66
112
|
"use strict";
|
|
67
113
|
init_programs();
|
|
114
|
+
init_model_reasoning();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// ../shared/dist/model-aliases.js
|
|
119
|
+
function isModelAliasName(value) {
|
|
120
|
+
return MODEL_ALIAS_NAME_SET.has(value);
|
|
121
|
+
}
|
|
122
|
+
function normalizeProjectModelAliases(value) {
|
|
123
|
+
if (value == null) {
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
127
|
+
throw new Error("modelAliases must be an object");
|
|
128
|
+
}
|
|
129
|
+
const normalized = {};
|
|
130
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
131
|
+
if (!isModelAliasName(key)) {
|
|
132
|
+
throw new Error(`Invalid model alias: ${key}`);
|
|
133
|
+
}
|
|
134
|
+
if (entry == null) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (typeof entry !== "object" || Array.isArray(entry)) {
|
|
138
|
+
throw new Error(`Invalid model alias target for '${key}'`);
|
|
139
|
+
}
|
|
140
|
+
const model = typeof entry.model === "string" ? entry.model.trim() : "";
|
|
141
|
+
const provider = typeof entry.provider === "string" ? entry.provider.trim() : "";
|
|
142
|
+
if (!model || !provider) {
|
|
143
|
+
throw new Error(`Model alias '${key}' requires both model and provider`);
|
|
144
|
+
}
|
|
145
|
+
normalized[key] = { model, provider };
|
|
146
|
+
}
|
|
147
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
148
|
+
}
|
|
149
|
+
function resolveModelAlias(model, aliases) {
|
|
150
|
+
if (!isModelAliasName(model)) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return aliases?.[model] ?? null;
|
|
154
|
+
}
|
|
155
|
+
function getEnabledModelIdsForProvider(provider, enabledModelsByProvider) {
|
|
156
|
+
const providerSlug = provider.toLowerCase();
|
|
157
|
+
if (enabledModelsByProvider[providerSlug]) {
|
|
158
|
+
return enabledModelsByProvider[providerSlug];
|
|
159
|
+
}
|
|
160
|
+
const matchedEntry = Object.entries(enabledModelsByProvider).find(([key]) => key.toLowerCase() === providerSlug);
|
|
161
|
+
return matchedEntry?.[1] ?? [];
|
|
162
|
+
}
|
|
163
|
+
function isModelAliasTargetEnabled(target, enabledModelsByProvider) {
|
|
164
|
+
const enabledModelIds = getEnabledModelIdsForProvider(target.provider, enabledModelsByProvider);
|
|
165
|
+
return enabledModelIds.includes(target.model);
|
|
166
|
+
}
|
|
167
|
+
function validateProjectModelAliasesAgainstEnabled(aliases, enabledModelsByProvider) {
|
|
168
|
+
for (const aliasName of MODEL_ALIAS_NAMES) {
|
|
169
|
+
const target = aliases[aliasName];
|
|
170
|
+
if (!target) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (!isModelAliasTargetEnabled(target, enabledModelsByProvider)) {
|
|
174
|
+
throw new Error(`Model alias '${aliasName}' must reference an enabled model. '${target.provider}/${target.model}' is not enabled.`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
var MODEL_ALIAS_NAMES, MODEL_ALIAS_NAME_SET;
|
|
179
|
+
var init_model_aliases = __esm({
|
|
180
|
+
"../shared/dist/model-aliases.js"() {
|
|
181
|
+
"use strict";
|
|
182
|
+
MODEL_ALIAS_NAMES = ["fast", "local", "cheap", "smart"];
|
|
183
|
+
MODEL_ALIAS_NAME_SET = new Set(MODEL_ALIAS_NAMES);
|
|
68
184
|
}
|
|
69
185
|
});
|
|
70
186
|
|
|
@@ -695,13 +811,14 @@ function resolveLocalProviderApiUrl(envValues) {
|
|
|
695
811
|
function isLocalProvider(providerId) {
|
|
696
812
|
return providerId.toLowerCase() === LOCAL_PROVIDER_ID;
|
|
697
813
|
}
|
|
698
|
-
var LOCAL_PROVIDER_ID, LOCAL_PROVIDER_NAME, LOCAL_PROVIDER_DEFAULT_API_URL, LOCAL_API_URL_ENV, LOCAL_API_KEY_ENV;
|
|
814
|
+
var LOCAL_PROVIDER_ID, LOCAL_PROVIDER_NAME, LOCAL_PROVIDER_DEFAULT_API_URL, LOCAL_MODELS_FETCH_TIMEOUT_MS, LOCAL_API_URL_ENV, LOCAL_API_KEY_ENV;
|
|
699
815
|
var init_providers = __esm({
|
|
700
816
|
"../shared/dist/providers.js"() {
|
|
701
817
|
"use strict";
|
|
702
818
|
LOCAL_PROVIDER_ID = "local";
|
|
703
819
|
LOCAL_PROVIDER_NAME = "Local";
|
|
704
820
|
LOCAL_PROVIDER_DEFAULT_API_URL = "http://127.0.0.1:8000/v1";
|
|
821
|
+
LOCAL_MODELS_FETCH_TIMEOUT_MS = 5e3;
|
|
705
822
|
LOCAL_API_URL_ENV = "LOCAL_API_URL";
|
|
706
823
|
LOCAL_API_KEY_ENV = "LOCAL_API_KEY";
|
|
707
824
|
}
|
|
@@ -867,11 +984,58 @@ var init_tmp_images = __esm({
|
|
|
867
984
|
}
|
|
868
985
|
});
|
|
869
986
|
|
|
987
|
+
// ../shared/dist/git-status-dot.js
|
|
988
|
+
function computeGitStatusDot(status) {
|
|
989
|
+
if (status.hasChanges)
|
|
990
|
+
return "changes";
|
|
991
|
+
if (status.hasUnpushedCommits && status.prExists)
|
|
992
|
+
return "update-pr";
|
|
993
|
+
if (status.prExists && status.prState === "OPEN" && !status.hasUnpushedCommits) {
|
|
994
|
+
return "open-pr";
|
|
995
|
+
}
|
|
996
|
+
return false;
|
|
997
|
+
}
|
|
998
|
+
var init_git_status_dot = __esm({
|
|
999
|
+
"../shared/dist/git-status-dot.js"() {
|
|
1000
|
+
"use strict";
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
// ../shared/dist/skill-sources.js
|
|
1005
|
+
function isCustomSkillSourceId(sourceId) {
|
|
1006
|
+
return sourceId.startsWith(CUSTOM_SKILL_SOURCE_ID_PREFIX);
|
|
1007
|
+
}
|
|
1008
|
+
function slugifySkillSourceTitle(title) {
|
|
1009
|
+
return title.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "source";
|
|
1010
|
+
}
|
|
1011
|
+
function createCustomSkillSourceId(title, existingIds) {
|
|
1012
|
+
const slug = slugifySkillSourceTitle(title);
|
|
1013
|
+
const base = `${CUSTOM_SKILL_SOURCE_ID_PREFIX}${slug}`;
|
|
1014
|
+
const taken = new Set(existingIds);
|
|
1015
|
+
if (!taken.has(base)) {
|
|
1016
|
+
return base;
|
|
1017
|
+
}
|
|
1018
|
+
let suffix = 2;
|
|
1019
|
+
while (taken.has(`${base}-${suffix}`)) {
|
|
1020
|
+
suffix += 1;
|
|
1021
|
+
}
|
|
1022
|
+
return `${base}-${suffix}`;
|
|
1023
|
+
}
|
|
1024
|
+
var CUSTOM_SKILL_SOURCE_ID_PREFIX;
|
|
1025
|
+
var init_skill_sources = __esm({
|
|
1026
|
+
"../shared/dist/skill-sources.js"() {
|
|
1027
|
+
"use strict";
|
|
1028
|
+
CUSTOM_SKILL_SOURCE_ID_PREFIX = "custom-";
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
|
|
870
1032
|
// ../shared/dist/index.js
|
|
871
1033
|
var init_dist = __esm({
|
|
872
1034
|
"../shared/dist/index.js"() {
|
|
873
1035
|
"use strict";
|
|
874
1036
|
init_models();
|
|
1037
|
+
init_model_aliases();
|
|
1038
|
+
init_model_reasoning();
|
|
875
1039
|
init_commit_methods();
|
|
876
1040
|
init_package_managers();
|
|
877
1041
|
init_programs();
|
|
@@ -889,6 +1053,8 @@ var init_dist = __esm({
|
|
|
889
1053
|
init_project_env_vars();
|
|
890
1054
|
init_browser_tool();
|
|
891
1055
|
init_tmp_images();
|
|
1056
|
+
init_git_status_dot();
|
|
1057
|
+
init_skill_sources();
|
|
892
1058
|
}
|
|
893
1059
|
});
|
|
894
1060
|
|
|
@@ -1966,6 +2132,14 @@ async function runMigrations(db) {
|
|
|
1966
2132
|
tarskDebugLog("[db] Running migration: Adding allowedFetchDomains column to projects");
|
|
1967
2133
|
await db.execute(`ALTER TABLE projects ADD COLUMN allowedFetchDomains TEXT`);
|
|
1968
2134
|
}
|
|
2135
|
+
const modelAliasesProjectsInfo = await db.execute(`PRAGMA table_info(projects)`);
|
|
2136
|
+
const hasModelAliases = modelAliasesProjectsInfo.rows.some(
|
|
2137
|
+
(col) => col.name === "modelAliases"
|
|
2138
|
+
);
|
|
2139
|
+
if (!hasModelAliases) {
|
|
2140
|
+
tarskDebugLog("[db] Running migration: Adding modelAliases column to projects");
|
|
2141
|
+
await db.execute(`ALTER TABLE projects ADD COLUMN modelAliases TEXT`);
|
|
2142
|
+
}
|
|
1969
2143
|
const projectScriptsExists = await db.execute(
|
|
1970
2144
|
`SELECT name FROM sqlite_master WHERE type='table' AND name='project_scripts'`
|
|
1971
2145
|
);
|
|
@@ -3496,9 +3670,9 @@ function createSimpleGrepTool(cwd, options) {
|
|
|
3496
3670
|
const effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT2);
|
|
3497
3671
|
const formatPath = (filePath) => {
|
|
3498
3672
|
if (isDirectory) {
|
|
3499
|
-
const
|
|
3500
|
-
if (
|
|
3501
|
-
return
|
|
3673
|
+
const relative9 = path2.relative(searchPath, filePath);
|
|
3674
|
+
if (relative9 && !relative9.startsWith("..")) {
|
|
3675
|
+
return relative9.replace(/\\/g, "/");
|
|
3502
3676
|
}
|
|
3503
3677
|
}
|
|
3504
3678
|
return path2.basename(filePath);
|
|
@@ -3838,9 +4012,9 @@ function parseRipgrepOutput(stdout) {
|
|
|
3838
4012
|
return stdout.trim().split("\n").map((line) => line.replace(/\r$/, "")).filter(Boolean);
|
|
3839
4013
|
}
|
|
3840
4014
|
function toRelativePath(cwd, filePath) {
|
|
3841
|
-
const
|
|
3842
|
-
if (
|
|
3843
|
-
return
|
|
4015
|
+
const relative9 = path3.relative(cwd, filePath);
|
|
4016
|
+
if (relative9 && !relative9.startsWith("..") && !path3.isAbsolute(relative9)) {
|
|
4017
|
+
return relative9.replace(/\\/g, "/");
|
|
3844
4018
|
}
|
|
3845
4019
|
return filePath.replace(/\\/g, "/");
|
|
3846
4020
|
}
|
|
@@ -6282,6 +6456,7 @@ async function getCatalogProvider(providerId) {
|
|
|
6282
6456
|
}
|
|
6283
6457
|
|
|
6284
6458
|
// src/features/models/local-models.ts
|
|
6459
|
+
init_dist();
|
|
6285
6460
|
function normalizeModelsBaseUrl(apiUrl) {
|
|
6286
6461
|
const trimmed = apiUrl.trim().replace(/\/+$/, "");
|
|
6287
6462
|
return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
@@ -6296,9 +6471,15 @@ async function fetchLocalModels(apiUrl, apiKey) {
|
|
|
6296
6471
|
try {
|
|
6297
6472
|
response = await fetch(`${baseUrl}/models`, {
|
|
6298
6473
|
method: "GET",
|
|
6299
|
-
headers
|
|
6474
|
+
headers,
|
|
6475
|
+
signal: AbortSignal.timeout(LOCAL_MODELS_FETCH_TIMEOUT_MS)
|
|
6300
6476
|
});
|
|
6301
6477
|
} catch (error) {
|
|
6478
|
+
if (error instanceof Error && error.name === "TimeoutError") {
|
|
6479
|
+
throw new Error(
|
|
6480
|
+
`Local API at ${baseUrl}/models did not respond within ${LOCAL_MODELS_FETCH_TIMEOUT_MS / 1e3} seconds`
|
|
6481
|
+
);
|
|
6482
|
+
}
|
|
6302
6483
|
const detail = error instanceof Error ? error.message : "Unknown error";
|
|
6303
6484
|
throw new Error(`Could not reach local API at ${baseUrl}/models: ${detail}`);
|
|
6304
6485
|
}
|
|
@@ -6327,6 +6508,119 @@ async function fetchLocalModels(apiUrl, apiKey) {
|
|
|
6327
6508
|
}));
|
|
6328
6509
|
}
|
|
6329
6510
|
|
|
6511
|
+
// src/agent/agent.model-resolver.ts
|
|
6512
|
+
init_dist();
|
|
6513
|
+
import {
|
|
6514
|
+
getModel,
|
|
6515
|
+
getSupportedThinkingLevels
|
|
6516
|
+
} from "@earendil-works/pi-ai";
|
|
6517
|
+
var DEFAULT_HEADERS = {
|
|
6518
|
+
"HTTP-Referer": "https://tarsk.io",
|
|
6519
|
+
"X-Title": "Tarsk.io"
|
|
6520
|
+
};
|
|
6521
|
+
var PROVIDER_NAME_TO_PI = {
|
|
6522
|
+
anthropic: "anthropic",
|
|
6523
|
+
openai: "openai",
|
|
6524
|
+
google: "google",
|
|
6525
|
+
groq: "groq",
|
|
6526
|
+
cerebras: "cerebras",
|
|
6527
|
+
xai: "xai",
|
|
6528
|
+
openrouter: "openrouter",
|
|
6529
|
+
"github copilot": "github-copilot",
|
|
6530
|
+
minimax: "minimax",
|
|
6531
|
+
"kimi coding plan": "kimi-coding",
|
|
6532
|
+
"hugging face": "huggingface",
|
|
6533
|
+
codex: "openai-codex",
|
|
6534
|
+
mistral: "mistral"
|
|
6535
|
+
};
|
|
6536
|
+
function determineApiType(providerName, apiUrl) {
|
|
6537
|
+
const slug = providerName.toLowerCase();
|
|
6538
|
+
if (isLocalProvider(slug)) {
|
|
6539
|
+
return "openai-completions";
|
|
6540
|
+
}
|
|
6541
|
+
if (slug === "azure" || apiUrl.includes("openai.azure.com")) {
|
|
6542
|
+
return "azure-openai-responses";
|
|
6543
|
+
}
|
|
6544
|
+
if (apiUrl.includes("/anthropic/")) return "anthropic-messages";
|
|
6545
|
+
if (apiUrl.includes("api.anthropic.com")) return "anthropic-messages";
|
|
6546
|
+
return "openai-completions";
|
|
6547
|
+
}
|
|
6548
|
+
var PI_REGISTRY_PROVIDERS_BY_API = {
|
|
6549
|
+
"azure-openai-responses": "azure-openai-responses"
|
|
6550
|
+
};
|
|
6551
|
+
function getLocalModelResolveOptions(providerName, settings) {
|
|
6552
|
+
if (!isLocalProvider(providerName)) {
|
|
6553
|
+
return void 0;
|
|
6554
|
+
}
|
|
6555
|
+
const normalized = normalizeModelSettings(settings);
|
|
6556
|
+
return {
|
|
6557
|
+
reasoning: isReasoningEnabled(normalized)
|
|
6558
|
+
};
|
|
6559
|
+
}
|
|
6560
|
+
function getSupportedReasoningEfforts(providerName, modelId, providerConfig) {
|
|
6561
|
+
const resolved = resolveModel(providerName, modelId, providerConfig, { reasoning: true });
|
|
6562
|
+
return getSupportedThinkingLevels(resolved).filter(
|
|
6563
|
+
(level) => level !== "off"
|
|
6564
|
+
);
|
|
6565
|
+
}
|
|
6566
|
+
function mergeModelHeaders(model) {
|
|
6567
|
+
return { ...model, headers: { ...model.headers, ...DEFAULT_HEADERS } };
|
|
6568
|
+
}
|
|
6569
|
+
function tryGetRegistryModel(provider, modelId) {
|
|
6570
|
+
const model = getModel(provider, modelId);
|
|
6571
|
+
return model ?? void 0;
|
|
6572
|
+
}
|
|
6573
|
+
function applyReasoningOption(model, options) {
|
|
6574
|
+
if (options?.reasoning === void 0) {
|
|
6575
|
+
return model;
|
|
6576
|
+
}
|
|
6577
|
+
return { ...model, reasoning: options.reasoning };
|
|
6578
|
+
}
|
|
6579
|
+
function resolveModel(providerName, modelId, providerConfig, options) {
|
|
6580
|
+
const piProvider = PROVIDER_NAME_TO_PI[providerName.toLowerCase()];
|
|
6581
|
+
if (piProvider) {
|
|
6582
|
+
const model = tryGetRegistryModel(piProvider, modelId);
|
|
6583
|
+
if (model) {
|
|
6584
|
+
return applyReasoningOption(mergeModelHeaders(model), options);
|
|
6585
|
+
}
|
|
6586
|
+
}
|
|
6587
|
+
const baseUrl = providerConfig.api;
|
|
6588
|
+
if (!baseUrl) {
|
|
6589
|
+
throw new Error(`No API URL configured for provider: ${providerName}`);
|
|
6590
|
+
}
|
|
6591
|
+
const apiType = determineApiType(providerName, baseUrl);
|
|
6592
|
+
const registryProvider = PI_REGISTRY_PROVIDERS_BY_API[apiType];
|
|
6593
|
+
if (registryProvider) {
|
|
6594
|
+
const model = tryGetRegistryModel(registryProvider, modelId);
|
|
6595
|
+
if (model) {
|
|
6596
|
+
return applyReasoningOption(
|
|
6597
|
+
mergeModelHeaders({
|
|
6598
|
+
...model,
|
|
6599
|
+
provider: providerName.toLowerCase(),
|
|
6600
|
+
baseUrl
|
|
6601
|
+
}),
|
|
6602
|
+
options
|
|
6603
|
+
);
|
|
6604
|
+
}
|
|
6605
|
+
}
|
|
6606
|
+
return applyReasoningOption(
|
|
6607
|
+
{
|
|
6608
|
+
id: modelId,
|
|
6609
|
+
name: modelId,
|
|
6610
|
+
api: apiType,
|
|
6611
|
+
provider: providerName.toLowerCase(),
|
|
6612
|
+
baseUrl,
|
|
6613
|
+
reasoning: false,
|
|
6614
|
+
input: ["text"],
|
|
6615
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
6616
|
+
contextWindow: 128e3,
|
|
6617
|
+
maxTokens: 16384,
|
|
6618
|
+
headers: DEFAULT_HEADERS
|
|
6619
|
+
},
|
|
6620
|
+
options
|
|
6621
|
+
);
|
|
6622
|
+
}
|
|
6623
|
+
|
|
6330
6624
|
// src/features/models/models.manager.ts
|
|
6331
6625
|
var ModelManager = class {
|
|
6332
6626
|
metadataManager;
|
|
@@ -6341,10 +6635,40 @@ var ModelManager = class {
|
|
|
6341
6635
|
* Get available models for a provider.
|
|
6342
6636
|
* Uses the remote catalog as the source for model lists, names, and pricing.
|
|
6343
6637
|
*/
|
|
6638
|
+
async applyModelSettings(models, providerId) {
|
|
6639
|
+
const settingsByModel = await this.metadataManager.getProviderModelSettings(providerId);
|
|
6640
|
+
if (!isLocalProvider(providerId)) {
|
|
6641
|
+
return models.map((model) => ({
|
|
6642
|
+
...model,
|
|
6643
|
+
settings: normalizeModelSettings(settingsByModel[model.id])
|
|
6644
|
+
}));
|
|
6645
|
+
}
|
|
6646
|
+
const envValues = await this.metadataManager.getProviderEnvValues();
|
|
6647
|
+
const apiUrl = resolveLocalProviderApiUrl({
|
|
6648
|
+
...envValues,
|
|
6649
|
+
[LOCAL_API_URL_ENV]: envValues[LOCAL_API_URL_ENV] ?? process.env[LOCAL_API_URL_ENV]
|
|
6650
|
+
});
|
|
6651
|
+
return models.map((model) => {
|
|
6652
|
+
const supportedReasoningEfforts = getSupportedReasoningEfforts(providerId, model.id, {
|
|
6653
|
+
api: apiUrl || LOCAL_PROVIDER_DEFAULT_API_URL
|
|
6654
|
+
});
|
|
6655
|
+
const normalized = normalizeModelSettings(settingsByModel[model.id]);
|
|
6656
|
+
const reasoningEffort = clampReasoningEffort(
|
|
6657
|
+
normalized.reasoningEffort ?? REASONING_EFFORT_OFF,
|
|
6658
|
+
supportedReasoningEfforts
|
|
6659
|
+
);
|
|
6660
|
+
return {
|
|
6661
|
+
...model,
|
|
6662
|
+
supportedReasoningEfforts,
|
|
6663
|
+
settings: { reasoningEffort }
|
|
6664
|
+
};
|
|
6665
|
+
});
|
|
6666
|
+
}
|
|
6344
6667
|
async getAvailableModels(provider) {
|
|
6345
6668
|
const providerId = provider.toLowerCase();
|
|
6346
6669
|
if (isLocalProvider(providerId)) {
|
|
6347
|
-
|
|
6670
|
+
const models = await this.getLocalAvailableModels();
|
|
6671
|
+
return this.applyModelSettings(models, providerId);
|
|
6348
6672
|
}
|
|
6349
6673
|
const catalogProvider = await getCatalogProvider(providerId);
|
|
6350
6674
|
if (!catalogProvider) {
|
|
@@ -6406,6 +6730,9 @@ var ModelManager = class {
|
|
|
6406
6730
|
return [];
|
|
6407
6731
|
}
|
|
6408
6732
|
}
|
|
6733
|
+
async getModelSettings(provider, modelId) {
|
|
6734
|
+
return this.metadataManager.getModelSettings(provider, modelId);
|
|
6735
|
+
}
|
|
6409
6736
|
/**
|
|
6410
6737
|
* Get a specific model by ID from a provider
|
|
6411
6738
|
* @param provider - Provider name
|
|
@@ -6454,17 +6781,24 @@ var ModelManager = class {
|
|
|
6454
6781
|
}
|
|
6455
6782
|
async getAllEnabledImageModels() {
|
|
6456
6783
|
const enabledByProvider = await this.metadataManager.getAllEnabledImageModels();
|
|
6457
|
-
const
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6784
|
+
const providers = Object.keys(enabledByProvider);
|
|
6785
|
+
const entries = await Promise.all(
|
|
6786
|
+
providers.map(async (provider) => {
|
|
6787
|
+
try {
|
|
6788
|
+
const availableModels = await this.getAvailableModels(provider);
|
|
6789
|
+
const enabledIds = enabledByProvider[provider];
|
|
6790
|
+
const models = availableModels.filter((model) => enabledIds.includes(model.id));
|
|
6791
|
+
return [provider, models];
|
|
6792
|
+
} catch (error) {
|
|
6793
|
+
console.error(`Failed to get enabled image models for ${provider}:`, error);
|
|
6794
|
+
return [provider, []];
|
|
6465
6795
|
}
|
|
6466
|
-
}
|
|
6467
|
-
|
|
6796
|
+
})
|
|
6797
|
+
);
|
|
6798
|
+
const result = {};
|
|
6799
|
+
for (const [provider, models] of entries) {
|
|
6800
|
+
if (models.length > 0) {
|
|
6801
|
+
result[provider] = models;
|
|
6468
6802
|
}
|
|
6469
6803
|
}
|
|
6470
6804
|
return result;
|
|
@@ -6475,15 +6809,22 @@ var ModelManager = class {
|
|
|
6475
6809
|
*/
|
|
6476
6810
|
async getAllEnabledModels() {
|
|
6477
6811
|
const enabledByProvider = await this.metadataManager.getAllEnabledModels();
|
|
6478
|
-
const
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6812
|
+
const providers = Object.keys(enabledByProvider);
|
|
6813
|
+
const entries = await Promise.all(
|
|
6814
|
+
providers.map(async (provider) => {
|
|
6815
|
+
try {
|
|
6816
|
+
const models = await this.getEnabledModels(provider);
|
|
6817
|
+
return [provider, models];
|
|
6818
|
+
} catch (error) {
|
|
6819
|
+
console.error(`Failed to get enabled models for ${provider}:`, error);
|
|
6820
|
+
return [provider, []];
|
|
6484
6821
|
}
|
|
6485
|
-
}
|
|
6486
|
-
|
|
6822
|
+
})
|
|
6823
|
+
);
|
|
6824
|
+
const result = {};
|
|
6825
|
+
for (const [provider, models] of entries) {
|
|
6826
|
+
if (models.length > 0) {
|
|
6827
|
+
result[provider] = models;
|
|
6487
6828
|
}
|
|
6488
6829
|
}
|
|
6489
6830
|
return result;
|
|
@@ -8284,65 +8625,6 @@ function getDataDir() {
|
|
|
8284
8625
|
return DATA_DIR;
|
|
8285
8626
|
}
|
|
8286
8627
|
|
|
8287
|
-
// src/agent/agent.model-resolver.ts
|
|
8288
|
-
import { getModel } from "@earendil-works/pi-ai";
|
|
8289
|
-
var DEFAULT_HEADERS = {
|
|
8290
|
-
"HTTP-Referer": "https://tarsk.io",
|
|
8291
|
-
"X-Title": "Tarsk.io"
|
|
8292
|
-
};
|
|
8293
|
-
var PROVIDER_NAME_TO_PI = {
|
|
8294
|
-
anthropic: "anthropic",
|
|
8295
|
-
openai: "openai",
|
|
8296
|
-
google: "google",
|
|
8297
|
-
groq: "groq",
|
|
8298
|
-
cerebras: "cerebras",
|
|
8299
|
-
xai: "xai",
|
|
8300
|
-
openrouter: "openrouter",
|
|
8301
|
-
"github copilot": "github-copilot",
|
|
8302
|
-
minimax: "minimax",
|
|
8303
|
-
"kimi coding plan": "kimi-coding",
|
|
8304
|
-
"hugging face": "huggingface",
|
|
8305
|
-
codex: "openai-codex",
|
|
8306
|
-
mistral: "mistral"
|
|
8307
|
-
};
|
|
8308
|
-
function determineApiType(providerName, apiUrl) {
|
|
8309
|
-
const slug = providerName.toLowerCase();
|
|
8310
|
-
if (slug === "azure" || apiUrl.includes("openai.azure.com")) {
|
|
8311
|
-
return "azure-openai-responses";
|
|
8312
|
-
}
|
|
8313
|
-
if (apiUrl.includes("/anthropic/")) return "anthropic-messages";
|
|
8314
|
-
if (apiUrl.includes("api.anthropic.com")) return "anthropic-messages";
|
|
8315
|
-
return "openai-completions";
|
|
8316
|
-
}
|
|
8317
|
-
function resolveModel(providerName, modelId, providerConfig) {
|
|
8318
|
-
const piProvider = PROVIDER_NAME_TO_PI[providerName.toLowerCase()];
|
|
8319
|
-
if (piProvider) {
|
|
8320
|
-
try {
|
|
8321
|
-
const model = getModel(piProvider, modelId);
|
|
8322
|
-
return { ...model, headers: { ...model.headers, ...DEFAULT_HEADERS } };
|
|
8323
|
-
} catch {
|
|
8324
|
-
}
|
|
8325
|
-
}
|
|
8326
|
-
const baseUrl = providerConfig.api;
|
|
8327
|
-
if (!baseUrl) {
|
|
8328
|
-
throw new Error(`No API URL configured for provider: ${providerName}`);
|
|
8329
|
-
}
|
|
8330
|
-
const apiType = determineApiType(providerName, baseUrl);
|
|
8331
|
-
return {
|
|
8332
|
-
id: modelId,
|
|
8333
|
-
name: modelId,
|
|
8334
|
-
api: apiType,
|
|
8335
|
-
provider: providerName.toLowerCase(),
|
|
8336
|
-
baseUrl,
|
|
8337
|
-
reasoning: true,
|
|
8338
|
-
input: ["text"],
|
|
8339
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
8340
|
-
contextWindow: 128e3,
|
|
8341
|
-
maxTokens: 16384,
|
|
8342
|
-
headers: DEFAULT_HEADERS
|
|
8343
|
-
};
|
|
8344
|
-
}
|
|
8345
|
-
|
|
8346
8628
|
// src/features/git/git.utils.ts
|
|
8347
8629
|
init_dist();
|
|
8348
8630
|
import { completeSimple } from "@earendil-works/pi-ai";
|
|
@@ -9080,53 +9362,128 @@ function stageAllChanges(gitRoot) {
|
|
|
9080
9362
|
proc.on("error", reject);
|
|
9081
9363
|
});
|
|
9082
9364
|
}
|
|
9083
|
-
function
|
|
9084
|
-
return new Promise((resolve8, reject) => {
|
|
9085
|
-
const proc = spawnProcess("git", ["commit", "-m", message], { cwd: gitRoot });
|
|
9086
|
-
proc.on("close", (code) => {
|
|
9087
|
-
if (code === 0) resolve8();
|
|
9088
|
-
else reject(new Error("Failed to commit changes"));
|
|
9089
|
-
});
|
|
9090
|
-
proc.on("error", reject);
|
|
9091
|
-
});
|
|
9092
|
-
}
|
|
9093
|
-
function pushToOrigin(gitRoot, currentBranch) {
|
|
9365
|
+
function getCommitCount(gitRoot) {
|
|
9094
9366
|
return new Promise((resolve8, reject) => {
|
|
9095
|
-
|
|
9096
|
-
|
|
9367
|
+
const proc = spawnProcess("git", ["rev-list", "--count", "HEAD"], { cwd: gitRoot });
|
|
9368
|
+
let out = "";
|
|
9097
9369
|
let err = "";
|
|
9098
|
-
let _out = "";
|
|
9099
9370
|
if (proc.stdout) {
|
|
9100
9371
|
proc.stdout.on("data", (d) => {
|
|
9101
|
-
|
|
9102
|
-
console.log(`[git-push] stdout: ${d.toString().trim()}`);
|
|
9372
|
+
out += d.toString();
|
|
9103
9373
|
});
|
|
9104
9374
|
}
|
|
9105
9375
|
if (proc.stderr) {
|
|
9106
9376
|
proc.stderr.on("data", (d) => {
|
|
9107
9377
|
err += d.toString();
|
|
9108
|
-
console.log(`[git-push] stderr: ${d.toString().trim()}`);
|
|
9109
9378
|
});
|
|
9110
9379
|
}
|
|
9111
9380
|
proc.on("close", (code) => {
|
|
9112
|
-
console.log(`[git-push] git push exited with code: ${code}`);
|
|
9113
9381
|
if (code === 0) {
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
}
|
|
9117
|
-
|
|
9118
|
-
|
|
9382
|
+
resolve8(parseInt(out.trim(), 10) || 0);
|
|
9383
|
+
return;
|
|
9384
|
+
}
|
|
9385
|
+
if (/unknown revision|ambiguous argument|does not have any commits yet|bad revision|needed a single revision/i.test(
|
|
9386
|
+
err
|
|
9387
|
+
)) {
|
|
9388
|
+
resolve8(0);
|
|
9389
|
+
return;
|
|
9119
9390
|
}
|
|
9391
|
+
reject(new Error(err.trim() || "Failed to count commits"));
|
|
9120
9392
|
});
|
|
9121
|
-
proc.on("error",
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9393
|
+
proc.on("error", reject);
|
|
9394
|
+
});
|
|
9395
|
+
}
|
|
9396
|
+
function checkBranchExists(gitRoot, branchName) {
|
|
9397
|
+
return new Promise((resolve8) => {
|
|
9398
|
+
const proc = spawnProcess(
|
|
9399
|
+
"git",
|
|
9400
|
+
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
9401
|
+
{
|
|
9402
|
+
cwd: gitRoot
|
|
9403
|
+
}
|
|
9404
|
+
);
|
|
9405
|
+
proc.on("close", (code) => {
|
|
9406
|
+
resolve8(code === 0);
|
|
9407
|
+
});
|
|
9408
|
+
proc.on("error", () => {
|
|
9409
|
+
resolve8(false);
|
|
9126
9410
|
});
|
|
9127
9411
|
});
|
|
9128
9412
|
}
|
|
9129
|
-
function
|
|
9413
|
+
function renameCurrentBranch(gitRoot, newBranchName) {
|
|
9414
|
+
return new Promise((resolve8, reject) => {
|
|
9415
|
+
const proc = spawnProcess("git", ["branch", "-m", newBranchName], {
|
|
9416
|
+
cwd: gitRoot
|
|
9417
|
+
});
|
|
9418
|
+
let err = "";
|
|
9419
|
+
if (proc.stderr) {
|
|
9420
|
+
proc.stderr.on("data", (d) => {
|
|
9421
|
+
err += d.toString();
|
|
9422
|
+
});
|
|
9423
|
+
}
|
|
9424
|
+
proc.on("close", (code) => {
|
|
9425
|
+
if (code === 0) {
|
|
9426
|
+
resolve8();
|
|
9427
|
+
} else {
|
|
9428
|
+
reject(new Error(err || `Failed to rename branch to "${newBranchName}"`));
|
|
9429
|
+
}
|
|
9430
|
+
});
|
|
9431
|
+
proc.on("error", reject);
|
|
9432
|
+
});
|
|
9433
|
+
}
|
|
9434
|
+
function commitChanges(gitRoot, message) {
|
|
9435
|
+
return new Promise((resolve8, reject) => {
|
|
9436
|
+
const proc = spawnProcess("git", ["commit", "-m", message], { cwd: gitRoot });
|
|
9437
|
+
let err = "";
|
|
9438
|
+
if (proc.stderr) {
|
|
9439
|
+
proc.stderr.on("data", (d) => {
|
|
9440
|
+
err += d.toString();
|
|
9441
|
+
});
|
|
9442
|
+
}
|
|
9443
|
+
proc.on("close", (code) => {
|
|
9444
|
+
if (code === 0) resolve8({ createdCommit: true });
|
|
9445
|
+
else reject(new Error(err.trim() || "Failed to commit changes"));
|
|
9446
|
+
});
|
|
9447
|
+
proc.on("error", reject);
|
|
9448
|
+
});
|
|
9449
|
+
}
|
|
9450
|
+
function pushToOrigin(gitRoot, currentBranch) {
|
|
9451
|
+
return new Promise((resolve8, reject) => {
|
|
9452
|
+
console.log(`[git-push] Executing: git push -u origin ${currentBranch}`);
|
|
9453
|
+
const proc = spawnProcess("git", ["push", "-u", "origin", currentBranch], { cwd: gitRoot });
|
|
9454
|
+
let err = "";
|
|
9455
|
+
let _out = "";
|
|
9456
|
+
if (proc.stdout) {
|
|
9457
|
+
proc.stdout.on("data", (d) => {
|
|
9458
|
+
_out += d.toString();
|
|
9459
|
+
console.log(`[git-push] stdout: ${d.toString().trim()}`);
|
|
9460
|
+
});
|
|
9461
|
+
}
|
|
9462
|
+
if (proc.stderr) {
|
|
9463
|
+
proc.stderr.on("data", (d) => {
|
|
9464
|
+
err += d.toString();
|
|
9465
|
+
console.log(`[git-push] stderr: ${d.toString().trim()}`);
|
|
9466
|
+
});
|
|
9467
|
+
}
|
|
9468
|
+
proc.on("close", (code) => {
|
|
9469
|
+
console.log(`[git-push] git push exited with code: ${code}`);
|
|
9470
|
+
if (code === 0) {
|
|
9471
|
+
console.log(`[git-push] \u2713 Push completed successfully`);
|
|
9472
|
+
resolve8();
|
|
9473
|
+
} else {
|
|
9474
|
+
console.log(`[git-push] \u2717 Push failed with error: ${err || "Unknown error"}`);
|
|
9475
|
+
reject(new Error(err || "Failed to push changes"));
|
|
9476
|
+
}
|
|
9477
|
+
});
|
|
9478
|
+
proc.on("error", (error) => {
|
|
9479
|
+
console.log(
|
|
9480
|
+
`[git-push] \u2717 Process error: ${error instanceof Error ? error.message : String(error)}`
|
|
9481
|
+
);
|
|
9482
|
+
reject(error);
|
|
9483
|
+
});
|
|
9484
|
+
});
|
|
9485
|
+
}
|
|
9486
|
+
function fetchFromOrigin(gitRoot) {
|
|
9130
9487
|
return new Promise((resolve8, reject) => {
|
|
9131
9488
|
const proc = spawnProcess("git", ["fetch", "origin"], { cwd: gitRoot });
|
|
9132
9489
|
let err = "";
|
|
@@ -10785,10 +11142,26 @@ async function* loadCodingToolsWithMcpStatus(cwd, options) {
|
|
|
10785
11142
|
}
|
|
10786
11143
|
|
|
10787
11144
|
// src/tools/agent-tool.ts
|
|
11145
|
+
init_dist();
|
|
10788
11146
|
import { Type as Type21 } from "@sinclair/typebox";
|
|
10789
11147
|
|
|
11148
|
+
// src/features/projects/project-model-aliases.ts
|
|
11149
|
+
init_dist();
|
|
11150
|
+
async function validateProjectModelAliasesForSave(metadataManager, aliases) {
|
|
11151
|
+
if (!aliases) {
|
|
11152
|
+
return;
|
|
11153
|
+
}
|
|
11154
|
+
const enabledModelsByProvider = await metadataManager.getAllEnabledModels();
|
|
11155
|
+
validateProjectModelAliasesAgainstEnabled(aliases, enabledModelsByProvider);
|
|
11156
|
+
}
|
|
11157
|
+
async function isProjectModelAliasTargetEnabled(metadataManager, target) {
|
|
11158
|
+
const enabledModelsByProvider = await metadataManager.getAllEnabledModels();
|
|
11159
|
+
return isModelAliasTargetEnabled(target, enabledModelsByProvider);
|
|
11160
|
+
}
|
|
11161
|
+
|
|
10790
11162
|
// src/agent/agent.subagent-executor.ts
|
|
10791
11163
|
import { Agent } from "@earendil-works/pi-agent-core";
|
|
11164
|
+
init_dist();
|
|
10792
11165
|
|
|
10793
11166
|
// src/agent/agent.event-transformer.ts
|
|
10794
11167
|
init_dist();
|
|
@@ -11049,7 +11422,14 @@ async function executeSubagent(options) {
|
|
|
11049
11422
|
`No API key found for subagent provider: ${providerName}. Set ${providerConfig.apiKeyEnv ?? "an API key"} in environment variables or configuration.`
|
|
11050
11423
|
);
|
|
11051
11424
|
}
|
|
11052
|
-
const
|
|
11425
|
+
const localModelSettings = isLocalProvider(providerName) ? await metadataManager.getModelSettings(providerName, model) : {};
|
|
11426
|
+
const thinkingLevel = getThinkingLevelFromSettings(localModelSettings);
|
|
11427
|
+
const resolvedModel = resolveModel(
|
|
11428
|
+
providerName,
|
|
11429
|
+
model,
|
|
11430
|
+
providerConfig,
|
|
11431
|
+
getLocalModelResolveOptions(providerName, localModelSettings)
|
|
11432
|
+
);
|
|
11053
11433
|
console.log(
|
|
11054
11434
|
`[subagent] Starting with model=${resolvedModel.id}, tools=${tools.map((t) => t.name).join(",")}`
|
|
11055
11435
|
);
|
|
@@ -11057,7 +11437,8 @@ async function executeSubagent(options) {
|
|
|
11057
11437
|
initialState: {
|
|
11058
11438
|
systemPrompt,
|
|
11059
11439
|
model: resolvedModel,
|
|
11060
|
-
tools
|
|
11440
|
+
tools,
|
|
11441
|
+
thinkingLevel
|
|
11061
11442
|
},
|
|
11062
11443
|
streamFn: cachedStreamFn,
|
|
11063
11444
|
getApiKey: async () => apiKey
|
|
@@ -11156,6 +11537,31 @@ function filterTools(baseTools, allowedToolNames) {
|
|
|
11156
11537
|
);
|
|
11157
11538
|
return baseTools.filter((t) => allowed.has(t.name));
|
|
11158
11539
|
}
|
|
11540
|
+
function resolveSubagentModelSelection(agentDef, parentModel, parentProvider) {
|
|
11541
|
+
if (agentDef?.model) {
|
|
11542
|
+
return {
|
|
11543
|
+
model: agentDef.model,
|
|
11544
|
+
provider: agentDef.provider ?? parentProvider
|
|
11545
|
+
};
|
|
11546
|
+
}
|
|
11547
|
+
return {
|
|
11548
|
+
model: parentModel,
|
|
11549
|
+
provider: parentProvider
|
|
11550
|
+
};
|
|
11551
|
+
}
|
|
11552
|
+
async function resolveSubagentModelSelectionWithAliases(agentDef, parentModel, parentProvider, modelAliases, metadataManager) {
|
|
11553
|
+
if (agentDef?.model && isModelAliasName(agentDef.model)) {
|
|
11554
|
+
const aliasTarget = resolveModelAlias(agentDef.model, modelAliases);
|
|
11555
|
+
if (aliasTarget && await isProjectModelAliasTargetEnabled(metadataManager, aliasTarget)) {
|
|
11556
|
+
return aliasTarget;
|
|
11557
|
+
}
|
|
11558
|
+
return {
|
|
11559
|
+
model: parentModel,
|
|
11560
|
+
provider: parentProvider
|
|
11561
|
+
};
|
|
11562
|
+
}
|
|
11563
|
+
return resolveSubagentModelSelection(agentDef, parentModel, parentProvider);
|
|
11564
|
+
}
|
|
11159
11565
|
function createAgentTool(options) {
|
|
11160
11566
|
const {
|
|
11161
11567
|
agents,
|
|
@@ -11165,7 +11571,8 @@ function createAgentTool(options) {
|
|
|
11165
11571
|
parentProvider,
|
|
11166
11572
|
defaultSystemPrompt,
|
|
11167
11573
|
onEvent,
|
|
11168
|
-
signal
|
|
11574
|
+
signal,
|
|
11575
|
+
modelAliases
|
|
11169
11576
|
} = options;
|
|
11170
11577
|
return {
|
|
11171
11578
|
name: "agent",
|
|
@@ -11188,8 +11595,13 @@ function createAgentTool(options) {
|
|
|
11188
11595
|
}
|
|
11189
11596
|
const label = description ?? agentName ?? "subagent";
|
|
11190
11597
|
const systemPrompt = agentDef?.systemPrompt ?? defaultSystemPrompt;
|
|
11191
|
-
const model =
|
|
11192
|
-
|
|
11598
|
+
const { model, provider } = await resolveSubagentModelSelectionWithAliases(
|
|
11599
|
+
agentDef,
|
|
11600
|
+
parentModel,
|
|
11601
|
+
parentProvider,
|
|
11602
|
+
modelAliases,
|
|
11603
|
+
metadataManager
|
|
11604
|
+
);
|
|
11193
11605
|
const subTools = filterTools(baseTools, agentDef?.tools);
|
|
11194
11606
|
console.log(
|
|
11195
11607
|
`[agent-tool] Spawning subagent '${label}' with model=${model}, tools=${subTools.map((t) => t.name).join(",")}`
|
|
@@ -12475,7 +12887,14 @@ var PiExecutorImpl = class {
|
|
|
12475
12887
|
);
|
|
12476
12888
|
}
|
|
12477
12889
|
console.log(`[ai] API key source for ${providerName}: ${apiKeySource}`);
|
|
12478
|
-
const
|
|
12890
|
+
const localModelSettings = isLocalProvider(providerName) ? await this.metadataManager.getModelSettings(providerName, model) : {};
|
|
12891
|
+
const thinkingLevel = getThinkingLevelFromSettings(localModelSettings);
|
|
12892
|
+
const resolvedModel = resolveModel(
|
|
12893
|
+
providerName,
|
|
12894
|
+
model,
|
|
12895
|
+
providerConfig,
|
|
12896
|
+
getLocalModelResolveOptions(providerName, localModelSettings)
|
|
12897
|
+
);
|
|
12479
12898
|
console.log("[ai] Resolved model:", {
|
|
12480
12899
|
id: resolvedModel.id,
|
|
12481
12900
|
api: resolvedModel.api,
|
|
@@ -12554,7 +12973,8 @@ var PiExecutorImpl = class {
|
|
|
12554
12973
|
parentProvider: providerName,
|
|
12555
12974
|
defaultSystemPrompt: systemPrompt,
|
|
12556
12975
|
onEvent: (event) => eventQueue.push(event),
|
|
12557
|
-
signal
|
|
12976
|
+
signal,
|
|
12977
|
+
modelAliases: context.modelAliases
|
|
12558
12978
|
});
|
|
12559
12979
|
tools.push(agentToolInstance);
|
|
12560
12980
|
}
|
|
@@ -12564,7 +12984,8 @@ var PiExecutorImpl = class {
|
|
|
12564
12984
|
systemPrompt,
|
|
12565
12985
|
model: resolvedModel,
|
|
12566
12986
|
tools,
|
|
12567
|
-
messages: historyMessages
|
|
12987
|
+
messages: historyMessages,
|
|
12988
|
+
thinkingLevel
|
|
12568
12989
|
},
|
|
12569
12990
|
streamFn: cachedStreamFn,
|
|
12570
12991
|
getApiKey: async () => apiKey,
|
|
@@ -14401,8 +14822,8 @@ async function insertProject(db, project) {
|
|
|
14401
14822
|
try {
|
|
14402
14823
|
await db.execute(
|
|
14403
14824
|
`
|
|
14404
|
-
INSERT INTO projects (id, name, gitUrl, path, createdAt, openWith, commands, setupScript, validationScript, runCommand, commitMethod, planPrompt, testPrompt, reviewPrompt, customSystemPrompt, useCustomSystemPrompt, allowAllCommands, allowedCommandPatterns, allowedFetchDomains)
|
|
14405
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
14825
|
+
INSERT INTO projects (id, name, gitUrl, path, createdAt, openWith, commands, setupScript, validationScript, runCommand, commitMethod, planPrompt, testPrompt, reviewPrompt, customSystemPrompt, useCustomSystemPrompt, allowAllCommands, allowedCommandPatterns, allowedFetchDomains, modelAliases)
|
|
14826
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
14406
14827
|
`,
|
|
14407
14828
|
[
|
|
14408
14829
|
project.id,
|
|
@@ -14423,7 +14844,8 @@ async function insertProject(db, project) {
|
|
|
14423
14844
|
project.useCustomSystemPrompt ? 1 : 0,
|
|
14424
14845
|
project.allowAllCommands === false ? 0 : 1,
|
|
14425
14846
|
project.allowedCommandPatterns ? JSON.stringify(project.allowedCommandPatterns) : null,
|
|
14426
|
-
project.allowedFetchDomains ? JSON.stringify(project.allowedFetchDomains) : null
|
|
14847
|
+
project.allowedFetchDomains ? JSON.stringify(project.allowedFetchDomains) : null,
|
|
14848
|
+
project.modelAliases ? JSON.stringify(project.modelAliases) : null
|
|
14427
14849
|
]
|
|
14428
14850
|
);
|
|
14429
14851
|
} catch (error) {
|
|
@@ -14507,6 +14929,10 @@ async function updateProject(db, id, updates) {
|
|
|
14507
14929
|
updates.allowedFetchDomains.length > 0 ? JSON.stringify(updates.allowedFetchDomains) : null
|
|
14508
14930
|
);
|
|
14509
14931
|
}
|
|
14932
|
+
if (updates.modelAliases !== void 0) {
|
|
14933
|
+
fields.push("modelAliases = ?");
|
|
14934
|
+
values.push(updates.modelAliases ? JSON.stringify(updates.modelAliases) : null);
|
|
14935
|
+
}
|
|
14510
14936
|
if (fields.length === 0) {
|
|
14511
14937
|
return;
|
|
14512
14938
|
}
|
|
@@ -14614,7 +15040,8 @@ function deserializeProjectWithThreads(row, threadIds) {
|
|
|
14614
15040
|
useCustomSystemPrompt: Boolean(row.useCustomSystemPrompt),
|
|
14615
15041
|
allowAllCommands: row.allowAllCommands === null ? true : Boolean(row.allowAllCommands),
|
|
14616
15042
|
allowedCommandPatterns: row.allowedCommandPatterns ? JSON.parse(row.allowedCommandPatterns) : void 0,
|
|
14617
|
-
allowedFetchDomains: row.allowedFetchDomains ? JSON.parse(row.allowedFetchDomains) : void 0
|
|
15043
|
+
allowedFetchDomains: row.allowedFetchDomains ? JSON.parse(row.allowedFetchDomains) : void 0,
|
|
15044
|
+
modelAliases: row.modelAliases ? JSON.parse(row.modelAliases) : void 0
|
|
14618
15045
|
};
|
|
14619
15046
|
}
|
|
14620
15047
|
async function deserializeProject(db, row) {
|
|
@@ -14661,7 +15088,8 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
|
|
|
14661
15088
|
attachments,
|
|
14662
15089
|
planMode,
|
|
14663
15090
|
ralphMode,
|
|
14664
|
-
checkpointRef
|
|
15091
|
+
checkpointRef,
|
|
15092
|
+
conversationId: requestedConversationId
|
|
14665
15093
|
} = body;
|
|
14666
15094
|
let model = baseModel;
|
|
14667
15095
|
if (provider && typeof provider === "string") {
|
|
@@ -14758,7 +15186,7 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
|
|
|
14758
15186
|
}
|
|
14759
15187
|
return streamAsyncGenerator(c, replayGenerator());
|
|
14760
15188
|
}
|
|
14761
|
-
let conversationId = thread.currentConversationId;
|
|
15189
|
+
let conversationId = typeof requestedConversationId === "string" && requestedConversationId.length > 0 ? requestedConversationId : thread.currentConversationId;
|
|
14762
15190
|
if (!conversationId) {
|
|
14763
15191
|
conversationId = randomUUID8();
|
|
14764
15192
|
const db2 = await getDatabase();
|
|
@@ -14809,7 +15237,8 @@ async function postChatMessage(c, threadManager, agentExecutor, conversationMana
|
|
|
14809
15237
|
await updateProject(dbInstance2, thread.projectId, {
|
|
14810
15238
|
allowedFetchDomains: domains
|
|
14811
15239
|
});
|
|
14812
|
-
}
|
|
15240
|
+
},
|
|
15241
|
+
modelAliases: project?.modelAliases
|
|
14813
15242
|
};
|
|
14814
15243
|
console.log("[ChatRoute] Execution context:", {
|
|
14815
15244
|
threadId,
|
|
@@ -16916,7 +17345,8 @@ async function computeContextBreakdown(threadId, conversationId, conversationMan
|
|
|
16916
17345
|
parentProvider: thread.modelProvider ?? "",
|
|
16917
17346
|
defaultSystemPrompt: promptSections.systemPrompt,
|
|
16918
17347
|
onEvent: () => {
|
|
16919
|
-
}
|
|
17348
|
+
},
|
|
17349
|
+
modelAliases: project?.modelAliases
|
|
16920
17350
|
});
|
|
16921
17351
|
tools.push(agentToolInstance);
|
|
16922
17352
|
}
|
|
@@ -17328,6 +17758,7 @@ var MetadataManager = class {
|
|
|
17328
17758
|
await setState(this.db, "selectedThreadId", state.selectedThreadId);
|
|
17329
17759
|
await setState(this.db, "enabledModels", state.enabledModels);
|
|
17330
17760
|
await setState(this.db, "enabledImageModels", state.enabledImageModels);
|
|
17761
|
+
await setState(this.db, "modelSettings", state.modelSettings);
|
|
17331
17762
|
await setState(this.db, "providerEnvValues", state.providerEnvValues);
|
|
17332
17763
|
await setState(this.db, "onboardingCompleted", state.onboardingCompleted);
|
|
17333
17764
|
const encryptedKeys = {};
|
|
@@ -17365,6 +17796,7 @@ var MetadataManager = class {
|
|
|
17365
17796
|
const enabledImageModels = mergeEnabledModelsByProviderSlug(rawEnabledImageModels);
|
|
17366
17797
|
const needsEnabledModelsMigration = enabledModelsNeedMigration(rawEnabledModels, enabledModels) || enabledModelsNeedMigration(rawEnabledImageModels, enabledImageModels);
|
|
17367
17798
|
const providerEnvValues = allState.providerEnvValues || {};
|
|
17799
|
+
const modelSettings = allState.modelSettings || {};
|
|
17368
17800
|
const encryptedKeys = allState.providerKeys || {};
|
|
17369
17801
|
const providerKeys = {};
|
|
17370
17802
|
let needsMigration = false;
|
|
@@ -17406,7 +17838,8 @@ var MetadataManager = class {
|
|
|
17406
17838
|
providerKeys,
|
|
17407
17839
|
providerEnvValues,
|
|
17408
17840
|
enabledModels,
|
|
17409
|
-
enabledImageModels
|
|
17841
|
+
enabledImageModels,
|
|
17842
|
+
modelSettings
|
|
17410
17843
|
};
|
|
17411
17844
|
} catch (error) {
|
|
17412
17845
|
throw new Error(`Failed to load state: ${String(error)}`);
|
|
@@ -17597,6 +18030,34 @@ var MetadataManager = class {
|
|
|
17597
18030
|
}
|
|
17598
18031
|
await this.saveState(state);
|
|
17599
18032
|
}
|
|
18033
|
+
async getModelSettings(provider, modelId) {
|
|
18034
|
+
const providerSlug = normalizeProviderSlug(provider);
|
|
18035
|
+
const state = await this.loadState();
|
|
18036
|
+
return state.modelSettings[providerSlug]?.[modelId] ?? {};
|
|
18037
|
+
}
|
|
18038
|
+
async getProviderModelSettings(provider) {
|
|
18039
|
+
const providerSlug = normalizeProviderSlug(provider);
|
|
18040
|
+
const state = await this.loadState();
|
|
18041
|
+
return state.modelSettings[providerSlug] ?? {};
|
|
18042
|
+
}
|
|
18043
|
+
async setModelSettings(provider, modelId, settings) {
|
|
18044
|
+
const providerSlug = normalizeProviderSlug(provider);
|
|
18045
|
+
const state = await this.loadState();
|
|
18046
|
+
if (!state.modelSettings[providerSlug]) {
|
|
18047
|
+
state.modelSettings[providerSlug] = {};
|
|
18048
|
+
}
|
|
18049
|
+
const nextSettings = { ...state.modelSettings[providerSlug][modelId], ...settings };
|
|
18050
|
+
const hasSettings = Object.values(nextSettings).some((value) => value !== void 0);
|
|
18051
|
+
if (hasSettings) {
|
|
18052
|
+
state.modelSettings[providerSlug][modelId] = nextSettings;
|
|
18053
|
+
} else {
|
|
18054
|
+
delete state.modelSettings[providerSlug][modelId];
|
|
18055
|
+
if (Object.keys(state.modelSettings[providerSlug]).length === 0) {
|
|
18056
|
+
delete state.modelSettings[providerSlug];
|
|
18057
|
+
}
|
|
18058
|
+
}
|
|
18059
|
+
await this.saveState(state);
|
|
18060
|
+
}
|
|
17600
18061
|
// ── Image model methods ─────────────────────────────────────────────────────
|
|
17601
18062
|
async enableImageModel(provider, modelId) {
|
|
17602
18063
|
const providerSlug = normalizeProviderSlug(provider);
|
|
@@ -17917,6 +18378,38 @@ async function handleGetModelInfo(c, modelManager) {
|
|
|
17917
18378
|
}
|
|
17918
18379
|
}
|
|
17919
18380
|
|
|
18381
|
+
// src/features/models/models-model-settings.route.ts
|
|
18382
|
+
init_dist();
|
|
18383
|
+
async function handleModelSettings(c, metadataManager, modelManager) {
|
|
18384
|
+
try {
|
|
18385
|
+
const provider = c.req.param("provider");
|
|
18386
|
+
const modelId = c.req.param("modelId");
|
|
18387
|
+
if (!provider || !modelId) {
|
|
18388
|
+
return c.json({ error: "provider and modelId are required" }, 400);
|
|
18389
|
+
}
|
|
18390
|
+
if (!isLocalProvider(provider)) {
|
|
18391
|
+
return c.json({ error: "Model settings are only supported for the local provider" }, 400);
|
|
18392
|
+
}
|
|
18393
|
+
const body = await c.req.json();
|
|
18394
|
+
if (body.reasoning !== void 0 && typeof body.reasoning !== "boolean") {
|
|
18395
|
+
return c.json({ error: "reasoning must be a boolean" }, 400);
|
|
18396
|
+
}
|
|
18397
|
+
if (body.reasoningEffort !== void 0 && (typeof body.reasoningEffort !== "string" || !isModelReasoningSetting(body.reasoningEffort))) {
|
|
18398
|
+
return c.json({ error: "reasoningEffort must be a supported reasoning level" }, 400);
|
|
18399
|
+
}
|
|
18400
|
+
const model = await modelManager.getModel(provider, modelId);
|
|
18401
|
+
if (!model) {
|
|
18402
|
+
return c.json({ error: `Model "${modelId}" not found in provider "${provider}"` }, 404);
|
|
18403
|
+
}
|
|
18404
|
+
await metadataManager.setModelSettings(provider, modelId, body);
|
|
18405
|
+
const settings = await metadataManager.getModelSettings(provider, modelId);
|
|
18406
|
+
return c.json({ provider, modelId, settings });
|
|
18407
|
+
} catch (error) {
|
|
18408
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
18409
|
+
return c.json({ error: message }, 500);
|
|
18410
|
+
}
|
|
18411
|
+
}
|
|
18412
|
+
|
|
17920
18413
|
// src/features/models/models.routes.ts
|
|
17921
18414
|
function createModelRoutes(metadataManager) {
|
|
17922
18415
|
const router = new Hono6();
|
|
@@ -17939,6 +18432,9 @@ function createModelRoutes(metadataManager) {
|
|
|
17939
18432
|
router.post("/:provider/:modelId/disable", async (c) => {
|
|
17940
18433
|
return handleModelDisable(c, modelManager);
|
|
17941
18434
|
});
|
|
18435
|
+
router.post("/:provider/:modelId/settings", async (c) => {
|
|
18436
|
+
return handleModelSettings(c, metadataManager, modelManager);
|
|
18437
|
+
});
|
|
17942
18438
|
router.post("/:provider/refresh", async (c) => {
|
|
17943
18439
|
return handleProviderRefresh(c, modelManager);
|
|
17944
18440
|
});
|
|
@@ -18116,12 +18612,7 @@ async function handleCreateFolderProject(c, projectManager) {
|
|
|
18116
18612
|
}
|
|
18117
18613
|
|
|
18118
18614
|
// src/features/projects/projects-list.route.ts
|
|
18119
|
-
|
|
18120
|
-
if (status.hasChanges) return true;
|
|
18121
|
-
if (status.commitsAheadOfDefault && !status.prExists) return true;
|
|
18122
|
-
if (status.status === "Behind" || status.status === "Diverged") return true;
|
|
18123
|
-
return false;
|
|
18124
|
-
}
|
|
18615
|
+
init_dist();
|
|
18125
18616
|
async function handleListProjects(c, projectManager, threadManager, db) {
|
|
18126
18617
|
try {
|
|
18127
18618
|
const projects = await projectManager.listProjects();
|
|
@@ -18158,6 +18649,7 @@ async function handleListProjects(c, projectManager, threadManager, db) {
|
|
|
18158
18649
|
useCustomSystemPrompt: project.useCustomSystemPrompt ?? false,
|
|
18159
18650
|
allowAllCommands: project.allowAllCommands !== false,
|
|
18160
18651
|
allowedCommandPatterns: project.allowedCommandPatterns ?? [],
|
|
18652
|
+
modelAliases: project.modelAliases,
|
|
18161
18653
|
threads: threads.map((thread) => {
|
|
18162
18654
|
const cached = statusCache.get(thread.id);
|
|
18163
18655
|
const gitStatusDot = cached ? computeGitStatusDot(cached) : false;
|
|
@@ -18423,7 +18915,7 @@ async function handleOpenProject(c, projectManager) {
|
|
|
18423
18915
|
|
|
18424
18916
|
// src/features/projects/projects-update.route.ts
|
|
18425
18917
|
init_dist();
|
|
18426
|
-
async function handleUpdateProject(c, projectManager) {
|
|
18918
|
+
async function handleUpdateProject(c, projectManager, metadataManager) {
|
|
18427
18919
|
try {
|
|
18428
18920
|
const projectId = c.req.param("id");
|
|
18429
18921
|
const body = await c.req.json();
|
|
@@ -18441,7 +18933,8 @@ async function handleUpdateProject(c, projectManager) {
|
|
|
18441
18933
|
customSystemPrompt,
|
|
18442
18934
|
useCustomSystemPrompt,
|
|
18443
18935
|
allowAllCommands,
|
|
18444
|
-
allowedCommandPatterns
|
|
18936
|
+
allowedCommandPatterns,
|
|
18937
|
+
modelAliases
|
|
18445
18938
|
} = body;
|
|
18446
18939
|
if (!projectId) {
|
|
18447
18940
|
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Project ID is required", 400);
|
|
@@ -18505,6 +18998,17 @@ async function handleUpdateProject(c, projectManager) {
|
|
|
18505
18998
|
allowedCommandPatterns
|
|
18506
18999
|
});
|
|
18507
19000
|
}
|
|
19001
|
+
if (modelAliases !== void 0) {
|
|
19002
|
+
let normalizedAliases;
|
|
19003
|
+
try {
|
|
19004
|
+
normalizedAliases = normalizeProjectModelAliases(modelAliases);
|
|
19005
|
+
await validateProjectModelAliasesForSave(metadataManager, normalizedAliases);
|
|
19006
|
+
} catch (error) {
|
|
19007
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19008
|
+
return errorResponse(c, ErrorCodes.INVALID_REQUEST, message, 400);
|
|
19009
|
+
}
|
|
19010
|
+
await projectManager.updateModelAliases(projectId, normalizedAliases);
|
|
19011
|
+
}
|
|
18508
19012
|
if (name !== void 0) {
|
|
18509
19013
|
if (!name?.trim()) {
|
|
18510
19014
|
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Project name is required", 400);
|
|
@@ -19360,7 +19864,7 @@ async function handleSyncProjectEnvVars(c, projectManager) {
|
|
|
19360
19864
|
}
|
|
19361
19865
|
|
|
19362
19866
|
// src/features/projects/projects.routes.ts
|
|
19363
|
-
function createProjectRoutes(projectManager, threadManager) {
|
|
19867
|
+
function createProjectRoutes(projectManager, threadManager, metadataManager) {
|
|
19364
19868
|
const router = new Hono8();
|
|
19365
19869
|
router.post("/", async (c) => {
|
|
19366
19870
|
return handleCreateProject(c, projectManager);
|
|
@@ -19386,7 +19890,7 @@ function createProjectRoutes(projectManager, threadManager) {
|
|
|
19386
19890
|
return handleOpenProject(c, projectManager);
|
|
19387
19891
|
});
|
|
19388
19892
|
router.put("/:id", async (c) => {
|
|
19389
|
-
return handleUpdateProject(c, projectManager);
|
|
19893
|
+
return handleUpdateProject(c, projectManager, metadataManager);
|
|
19390
19894
|
});
|
|
19391
19895
|
router.post("/:id/commands", async (c) => {
|
|
19392
19896
|
return handleSaveCommand(c, projectManager);
|
|
@@ -19449,7 +19953,8 @@ import { rm as rm4 } from "fs/promises";
|
|
|
19449
19953
|
// src/agent/agent.process-manager.ts
|
|
19450
19954
|
init_utils();
|
|
19451
19955
|
import { EventEmitter } from "events";
|
|
19452
|
-
|
|
19956
|
+
|
|
19957
|
+
// src/core/dev-server-url-detector.ts
|
|
19453
19958
|
var URL_PATTERNS = [
|
|
19454
19959
|
// http(s)://localhost:PORT
|
|
19455
19960
|
/https?:\/\/localhost[:/](\d+)/i,
|
|
@@ -19462,6 +19967,24 @@ var URL_PATTERNS = [
|
|
|
19462
19967
|
// Local address patterns like http://192.168...
|
|
19463
19968
|
/https?:\/\/(192\.168\.\d+\.\d+):(\d+)/i
|
|
19464
19969
|
];
|
|
19970
|
+
function detectDevServerUrl(text) {
|
|
19971
|
+
for (const pattern of URL_PATTERNS) {
|
|
19972
|
+
const match = text.match(pattern);
|
|
19973
|
+
if (match) {
|
|
19974
|
+
if (match[1] && !match[0].includes("://")) {
|
|
19975
|
+
if (match[0].startsWith("127.0.0.1:")) {
|
|
19976
|
+
return `http://${match[0]}`;
|
|
19977
|
+
}
|
|
19978
|
+
return `http://localhost:${match[1]}`;
|
|
19979
|
+
}
|
|
19980
|
+
return match[0];
|
|
19981
|
+
}
|
|
19982
|
+
}
|
|
19983
|
+
return void 0;
|
|
19984
|
+
}
|
|
19985
|
+
|
|
19986
|
+
// src/agent/agent.process-manager.ts
|
|
19987
|
+
init_tarsk_debug();
|
|
19465
19988
|
var ProcessManager = class extends EventEmitter {
|
|
19466
19989
|
processes = /* @__PURE__ */ new Map();
|
|
19467
19990
|
detectedUrls = /* @__PURE__ */ new Map();
|
|
@@ -19537,7 +20060,7 @@ var ProcessManager = class extends EventEmitter {
|
|
|
19537
20060
|
const text = data.toString();
|
|
19538
20061
|
urlBuffer += text;
|
|
19539
20062
|
if (!urlDetected) {
|
|
19540
|
-
const detectedUrl =
|
|
20063
|
+
const detectedUrl = detectDevServerUrl(urlBuffer);
|
|
19541
20064
|
if (detectedUrl) {
|
|
19542
20065
|
urlDetected = true;
|
|
19543
20066
|
this.detectedUrls.set(projectId, detectedUrl);
|
|
@@ -19551,7 +20074,7 @@ var ProcessManager = class extends EventEmitter {
|
|
|
19551
20074
|
const text = data.toString();
|
|
19552
20075
|
urlBuffer += text;
|
|
19553
20076
|
if (!urlDetected) {
|
|
19554
|
-
const detectedUrl =
|
|
20077
|
+
const detectedUrl = detectDevServerUrl(urlBuffer);
|
|
19555
20078
|
if (detectedUrl) {
|
|
19556
20079
|
urlDetected = true;
|
|
19557
20080
|
this.detectedUrls.set(projectId, detectedUrl);
|
|
@@ -19642,23 +20165,6 @@ var ProcessManager = class extends EventEmitter {
|
|
|
19642
20165
|
getDetectedUrl(projectId) {
|
|
19643
20166
|
return this.detectedUrls.get(projectId);
|
|
19644
20167
|
}
|
|
19645
|
-
/**
|
|
19646
|
-
* Detect URL from text output
|
|
19647
|
-
* @param text - The text to search for URLs
|
|
19648
|
-
* @returns The first detected URL or undefined
|
|
19649
|
-
*/
|
|
19650
|
-
detectUrl(text) {
|
|
19651
|
-
for (const pattern of URL_PATTERNS) {
|
|
19652
|
-
const match = text.match(pattern);
|
|
19653
|
-
if (match) {
|
|
19654
|
-
if (match[1] && !match[0].includes("://")) {
|
|
19655
|
-
return `http://localhost:${match[1]}`;
|
|
19656
|
-
}
|
|
19657
|
-
return match[0];
|
|
19658
|
-
}
|
|
19659
|
-
}
|
|
19660
|
-
return void 0;
|
|
19661
|
-
}
|
|
19662
20168
|
};
|
|
19663
20169
|
|
|
19664
20170
|
// src/features/projects/projects.creator.ts
|
|
@@ -20829,10 +21335,10 @@ async function loadUtils() {
|
|
|
20829
21335
|
}
|
|
20830
21336
|
}
|
|
20831
21337
|
var ProjectCreator = class {
|
|
20832
|
-
constructor(rootFolder, metadataManager,
|
|
21338
|
+
constructor(rootFolder, metadataManager, gitManager2, processingStateManager, runCommandCallbacks) {
|
|
20833
21339
|
this.rootFolder = rootFolder;
|
|
20834
21340
|
this.metadataManager = metadataManager;
|
|
20835
|
-
this.gitManager =
|
|
21341
|
+
this.gitManager = gitManager2;
|
|
20836
21342
|
this.processingStateManager = processingStateManager;
|
|
20837
21343
|
this.runCommandCallbacks = runCommandCallbacks;
|
|
20838
21344
|
}
|
|
@@ -21526,7 +22032,7 @@ var ProjectManagerImpl = class {
|
|
|
21526
22032
|
* - 1.1 - WHEN a user provides a git URL to create a new Project, THE CLI SHALL clone the repository into a folder under the Root_Folder
|
|
21527
22033
|
* - 1.4 - THE CLI SHALL determine the storage path for each Project folder
|
|
21528
22034
|
*/
|
|
21529
|
-
constructor(rootFolder, metadataManager,
|
|
22035
|
+
constructor(rootFolder, metadataManager, gitManager2, processingStateManager) {
|
|
21530
22036
|
this.rootFolder = rootFolder;
|
|
21531
22037
|
this.metadataManager = metadataManager;
|
|
21532
22038
|
this.processManager = new ProcessManager();
|
|
@@ -21535,7 +22041,7 @@ var ProjectManagerImpl = class {
|
|
|
21535
22041
|
this.projectCreator = new ProjectCreator(
|
|
21536
22042
|
rootFolder,
|
|
21537
22043
|
metadataManager,
|
|
21538
|
-
|
|
22044
|
+
gitManager2,
|
|
21539
22045
|
processingStateManager,
|
|
21540
22046
|
{
|
|
21541
22047
|
suggestRunCommand: (id) => this.suggestRunCommand(id),
|
|
@@ -21915,6 +22421,15 @@ ___CWD___%s
|
|
|
21915
22421
|
}
|
|
21916
22422
|
await this.metadataManager.saveProjects(projects);
|
|
21917
22423
|
}
|
|
22424
|
+
async updateModelAliases(projectId, modelAliases) {
|
|
22425
|
+
const projects = await this.metadataManager.loadProjects();
|
|
22426
|
+
const project = projects.find((p) => p.id === projectId);
|
|
22427
|
+
if (!project) {
|
|
22428
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
22429
|
+
}
|
|
22430
|
+
project.modelAliases = modelAliases;
|
|
22431
|
+
await this.metadataManager.saveProjects(projects);
|
|
22432
|
+
}
|
|
21918
22433
|
/**
|
|
21919
22434
|
* Starts running a dev server process for a project
|
|
21920
22435
|
* @param projectId - The project ID
|
|
@@ -23326,9 +23841,9 @@ var ThreadManagerImpl = class {
|
|
|
23326
23841
|
* - 2.1 - WHEN a user creates a new Thread for a Project, THE CLI SHALL create a new clone of the Project's git repository
|
|
23327
23842
|
* - 2.4 - THE System SHALL maintain a title for each Thread
|
|
23328
23843
|
*/
|
|
23329
|
-
constructor(metadataManager,
|
|
23844
|
+
constructor(metadataManager, gitManager2, processingStateManager, rootFolder = "") {
|
|
23330
23845
|
this.metadataManager = metadataManager;
|
|
23331
|
-
this.gitManager =
|
|
23846
|
+
this.gitManager = gitManager2;
|
|
23332
23847
|
this.processingStateManager = processingStateManager;
|
|
23333
23848
|
this.rootFolder = rootFolder;
|
|
23334
23849
|
}
|
|
@@ -23807,7 +24322,7 @@ function validateThreadName(name) {
|
|
|
23807
24322
|
}
|
|
23808
24323
|
|
|
23809
24324
|
// src/features/threads/threads-create.route.ts
|
|
23810
|
-
async function handleCreateThread(c, threadManager,
|
|
24325
|
+
async function handleCreateThread(c, threadManager, gitManager2) {
|
|
23811
24326
|
try {
|
|
23812
24327
|
const body = await c.req.json();
|
|
23813
24328
|
const { projectId, title } = body;
|
|
@@ -23833,11 +24348,11 @@ async function handleCreateThread(c, threadManager, gitManager) {
|
|
|
23833
24348
|
return errorResponse(c, ErrorCodes.INVALID_REQUEST, nameError, 400);
|
|
23834
24349
|
}
|
|
23835
24350
|
const existingThreads = await threadManager.listThreads(projectId);
|
|
23836
|
-
const sanitizedNewName =
|
|
23837
|
-
const existingSanitized = existingThreads.map((t) =>
|
|
24351
|
+
const sanitizedNewName = gitManager2.sanitizeBranchName(title);
|
|
24352
|
+
const existingSanitized = existingThreads.map((t) => gitManager2.sanitizeBranchName(t.title));
|
|
23838
24353
|
if (existingSanitized.includes(sanitizedNewName)) {
|
|
23839
24354
|
let fallbackTitle = generateRandomThreadName();
|
|
23840
|
-
while (existingSanitized.includes(
|
|
24355
|
+
while (existingSanitized.includes(gitManager2.sanitizeBranchName(fallbackTitle))) {
|
|
23841
24356
|
fallbackTitle = generateRandomThreadName();
|
|
23842
24357
|
}
|
|
23843
24358
|
return streamAsyncGenerator(c, threadManager.createThread(projectId, fallbackTitle));
|
|
@@ -23856,12 +24371,7 @@ async function handleCreateThread(c, threadManager, gitManager) {
|
|
|
23856
24371
|
}
|
|
23857
24372
|
|
|
23858
24373
|
// src/features/threads/threads-list.route.ts
|
|
23859
|
-
|
|
23860
|
-
if (status.hasChanges) return true;
|
|
23861
|
-
if (status.commitsAheadOfDefault && !status.prExists) return true;
|
|
23862
|
-
if (status.status === "Behind" || status.status === "Diverged") return true;
|
|
23863
|
-
return false;
|
|
23864
|
-
}
|
|
24374
|
+
init_dist();
|
|
23865
24375
|
async function handleListThreads(c, threadManager, db) {
|
|
23866
24376
|
try {
|
|
23867
24377
|
const projectId = c.req.query("projectId");
|
|
@@ -23883,7 +24393,7 @@ async function handleListThreads(c, threadManager, db) {
|
|
|
23883
24393
|
);
|
|
23884
24394
|
const threadsWithStatus = threads.map((thread) => {
|
|
23885
24395
|
const cached = statusCache.get(thread.id);
|
|
23886
|
-
const gitStatusDot = cached ?
|
|
24396
|
+
const gitStatusDot = cached ? computeGitStatusDot(cached) : false;
|
|
23887
24397
|
return { ...thread, gitStatusDot };
|
|
23888
24398
|
});
|
|
23889
24399
|
return c.json(threadsWithStatus);
|
|
@@ -24441,9 +24951,11 @@ async function handleGetThreadMessages(c, threadManager, conversationManager) {
|
|
|
24441
24951
|
if (!thread) {
|
|
24442
24952
|
return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
|
|
24443
24953
|
}
|
|
24444
|
-
const
|
|
24954
|
+
const requestedConversationId = c.req.query("conversationId");
|
|
24955
|
+
const targetConversationId = typeof requestedConversationId === "string" && requestedConversationId.length > 0 ? requestedConversationId : thread.currentConversationId;
|
|
24956
|
+
const history = targetConversationId ? await conversationManager.getConversationHistoryByConversationId(
|
|
24445
24957
|
threadId,
|
|
24446
|
-
|
|
24958
|
+
targetConversationId
|
|
24447
24959
|
) : await conversationManager.getConversationHistory(threadId);
|
|
24448
24960
|
if (!history || history.messages.length === 0) {
|
|
24449
24961
|
return c.json([]);
|
|
@@ -25117,20 +25629,43 @@ async function handleFixComments(c, threadManager) {
|
|
|
25117
25629
|
|
|
25118
25630
|
// src/features/threads/threads-ai-files.route.ts
|
|
25119
25631
|
init_dist();
|
|
25120
|
-
import { readFile as
|
|
25121
|
-
import { join as
|
|
25632
|
+
import { readFile as readFile17, writeFile as writeFile8, mkdir as mkdir9, access as access5, rm as rm8, stat as stat6 } from "fs/promises";
|
|
25633
|
+
import { join as join34 } from "path";
|
|
25122
25634
|
import { existsSync as existsSync19 } from "fs";
|
|
25123
25635
|
|
|
25124
25636
|
// src/features/git/git-download-folder.ts
|
|
25125
25637
|
import { mkdir as mkdir8, writeFile as writeFile7 } from "fs/promises";
|
|
25126
25638
|
import { join as join32, dirname as dirname6 } from "path";
|
|
25127
|
-
|
|
25128
|
-
|
|
25639
|
+
|
|
25640
|
+
// src/features/git/github-folder.ts
|
|
25641
|
+
function parseGithubRepoUrl(repoUrl) {
|
|
25129
25642
|
const match = repoUrl.replace(/\.git$/, "").match(/github\.com\/([^/]+)\/([^/]+)/);
|
|
25130
25643
|
if (!match) {
|
|
25131
25644
|
throw new Error(`Invalid GitHub URL: ${repoUrl}`);
|
|
25132
25645
|
}
|
|
25133
25646
|
const [, owner, repo] = match;
|
|
25647
|
+
return { owner, repo };
|
|
25648
|
+
}
|
|
25649
|
+
function normalizeFolderPrefix(srcPath) {
|
|
25650
|
+
const trimmed = srcPath.replace(/^\/|\/$/g, "");
|
|
25651
|
+
return trimmed.length > 0 ? `${trimmed}/` : "";
|
|
25652
|
+
}
|
|
25653
|
+
function filterFolderBlobPaths(tree, srcPath, excludeRelativePrefixes = []) {
|
|
25654
|
+
const prefix = normalizeFolderPrefix(srcPath);
|
|
25655
|
+
return tree.filter((entry) => {
|
|
25656
|
+
if (entry.type !== "blob") {
|
|
25657
|
+
return false;
|
|
25658
|
+
}
|
|
25659
|
+
if (prefix.length > 0) {
|
|
25660
|
+
if (!entry.path.startsWith(prefix)) {
|
|
25661
|
+
return false;
|
|
25662
|
+
}
|
|
25663
|
+
}
|
|
25664
|
+
const relativePath = prefix.length > 0 ? entry.path.slice(prefix.length) : entry.path;
|
|
25665
|
+
return !excludeRelativePrefixes.some((skipPrefix) => relativePath.startsWith(skipPrefix));
|
|
25666
|
+
}).map((entry) => prefix.length > 0 ? entry.path.slice(prefix.length) : entry.path).sort((a, b) => a.localeCompare(b));
|
|
25667
|
+
}
|
|
25668
|
+
function buildGithubHeaders(token) {
|
|
25134
25669
|
const headers = {
|
|
25135
25670
|
Accept: "application/vnd.github+json",
|
|
25136
25671
|
"X-GitHub-Api-Version": "2022-11-28"
|
|
@@ -25138,42 +25673,63 @@ async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
|
|
|
25138
25673
|
if (token) {
|
|
25139
25674
|
headers["Authorization"] = `Bearer ${token}`;
|
|
25140
25675
|
}
|
|
25141
|
-
|
|
25676
|
+
return headers;
|
|
25677
|
+
}
|
|
25678
|
+
async function fetchGithubTree(owner, repo, refParam, token) {
|
|
25142
25679
|
const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${refParam}?recursive=1`;
|
|
25143
|
-
const treeRes = await fetch(treeUrl, { headers });
|
|
25680
|
+
const treeRes = await fetch(treeUrl, { headers: buildGithubHeaders(token) });
|
|
25144
25681
|
if (!treeRes.ok) {
|
|
25145
25682
|
throw new Error(`GitHub API error ${treeRes.status}: ${await treeRes.text()}`);
|
|
25146
25683
|
}
|
|
25147
|
-
|
|
25684
|
+
return await treeRes.json();
|
|
25685
|
+
}
|
|
25686
|
+
async function listGithubFolderFiles(repoUrl, srcPath, options = {}) {
|
|
25687
|
+
const { token, ref, excludeRelativePrefixes = [] } = options;
|
|
25688
|
+
const { owner, repo } = parseGithubRepoUrl(repoUrl);
|
|
25689
|
+
const refParam = ref ?? "HEAD";
|
|
25690
|
+
const tree = await fetchGithubTree(owner, repo, refParam, token);
|
|
25148
25691
|
if (tree.truncated) {
|
|
25149
25692
|
console.warn(
|
|
25150
25693
|
"Warning: the repository tree was truncated by GitHub. Large repos may be incomplete."
|
|
25151
25694
|
);
|
|
25152
25695
|
}
|
|
25153
|
-
const
|
|
25154
|
-
const files = tree.tree.filter((entry) => {
|
|
25155
|
-
if (entry.type !== "blob" || !entry.path.startsWith(prefix)) {
|
|
25156
|
-
return false;
|
|
25157
|
-
}
|
|
25158
|
-
const relativePath = entry.path.slice(prefix.length);
|
|
25159
|
-
return !excludeRelativePrefixes.some((skipPrefix) => relativePath.startsWith(skipPrefix));
|
|
25160
|
-
});
|
|
25696
|
+
const files = filterFolderBlobPaths(tree.tree, srcPath, excludeRelativePrefixes);
|
|
25161
25697
|
if (files.length === 0) {
|
|
25162
25698
|
throw new Error(`No files found at path "${srcPath}" in ${owner}/${repo}@${refParam}`);
|
|
25163
25699
|
}
|
|
25700
|
+
return { files, ref: refParam };
|
|
25701
|
+
}
|
|
25702
|
+
async function fetchGithubFolderFile(repoUrl, srcPath, relativePath, options = {}) {
|
|
25703
|
+
const { token, ref } = options;
|
|
25704
|
+
const { owner, repo } = parseGithubRepoUrl(repoUrl);
|
|
25705
|
+
const refParam = ref ?? "HEAD";
|
|
25706
|
+
const folderPrefix = normalizeFolderPrefix(srcPath);
|
|
25707
|
+
const fullPath = folderPrefix.length > 0 ? `${folderPrefix}${relativePath}` : relativePath;
|
|
25708
|
+
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${refParam}/${fullPath}`;
|
|
25709
|
+
const fileRes = await fetch(rawUrl, { headers: buildGithubHeaders(token) });
|
|
25710
|
+
if (!fileRes.ok) {
|
|
25711
|
+
throw new Error(`Failed to download ${fullPath}: HTTP ${fileRes.status}`);
|
|
25712
|
+
}
|
|
25713
|
+
let content = Buffer.from(await fileRes.arrayBuffer()).toString("utf8");
|
|
25714
|
+
content = content.replace(/Claude/gi, "Tarsk");
|
|
25715
|
+
return content;
|
|
25716
|
+
}
|
|
25717
|
+
|
|
25718
|
+
// src/features/git/git-download-folder.ts
|
|
25719
|
+
async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
|
|
25720
|
+
const { ref, excludeRelativePrefixes = [] } = options;
|
|
25721
|
+
const { files, ref: refParam } = await listGithubFolderFiles(repoUrl, srcPath, options);
|
|
25722
|
+
const { owner, repo } = parseGithubRepoUrl(repoUrl);
|
|
25164
25723
|
console.log(`Downloading ${files.length} file(s) from ${owner}/${repo}/${srcPath} \u2192 ${destPath}`);
|
|
25165
25724
|
await Promise.all(
|
|
25166
|
-
files.map(async (
|
|
25167
|
-
const relativePath = file.path.slice(prefix.length);
|
|
25725
|
+
files.map(async (relativePath) => {
|
|
25168
25726
|
const localPath = join32(destPath, relativePath);
|
|
25169
25727
|
await mkdir8(dirname6(localPath), { recursive: true });
|
|
25170
|
-
const
|
|
25171
|
-
|
|
25172
|
-
|
|
25173
|
-
|
|
25174
|
-
}
|
|
25175
|
-
let content = Buffer.from(await fileRes.arrayBuffer()).toString("utf8");
|
|
25176
|
-
content = content.replace(/Claude/gi, "Tarsk");
|
|
25728
|
+
const content = await fetchGithubFolderFile(repoUrl, srcPath, relativePath, {
|
|
25729
|
+
...options,
|
|
25730
|
+
ref: ref ?? refParam,
|
|
25731
|
+
excludeRelativePrefixes
|
|
25732
|
+
});
|
|
25177
25733
|
await writeFile7(localPath, content, "utf8");
|
|
25178
25734
|
console.log(` \u2713 ${relativePath}`);
|
|
25179
25735
|
})
|
|
@@ -25181,46 +25737,473 @@ async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
|
|
|
25181
25737
|
console.log("Done.");
|
|
25182
25738
|
}
|
|
25183
25739
|
|
|
25184
|
-
// src/features/
|
|
25185
|
-
|
|
25186
|
-
|
|
25187
|
-
|
|
25188
|
-
|
|
25189
|
-
|
|
25190
|
-
|
|
25191
|
-
|
|
25192
|
-
|
|
25193
|
-
|
|
25194
|
-
|
|
25195
|
-
|
|
25196
|
-
|
|
25197
|
-
|
|
25198
|
-
|
|
25199
|
-
|
|
25200
|
-
}
|
|
25201
|
-
|
|
25202
|
-
|
|
25203
|
-
|
|
25204
|
-
|
|
25205
|
-
|
|
25206
|
-
|
|
25740
|
+
// src/features/skills/skills.catalog-cache.ts
|
|
25741
|
+
import { cp, mkdtemp, readdir as readdir12, readFile as readFile16, rm as rm7 } from "fs/promises";
|
|
25742
|
+
import { join as join33, relative as relative8, sep } from "path";
|
|
25743
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
25744
|
+
|
|
25745
|
+
// src/features/git/git-clone-folder.ts
|
|
25746
|
+
init_utils();
|
|
25747
|
+
function normalizeGitCloneUrl(repoUrl) {
|
|
25748
|
+
const trimmed = repoUrl.trim().replace(/\/$/, "");
|
|
25749
|
+
return trimmed.endsWith(".git") ? trimmed : `${trimmed}.git`;
|
|
25750
|
+
}
|
|
25751
|
+
function runGit2(cwd, args2) {
|
|
25752
|
+
const result = spawnSyncProcess("git", args2, {
|
|
25753
|
+
cwd,
|
|
25754
|
+
encoding: "utf-8",
|
|
25755
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
25756
|
+
});
|
|
25757
|
+
return {
|
|
25758
|
+
ok: result.status === 0,
|
|
25759
|
+
stderr: (typeof result.stderr === "string" ? result.stderr : result.stderr?.toString() || "").trim()
|
|
25760
|
+
};
|
|
25761
|
+
}
|
|
25762
|
+
async function cloneGithubRepoFolder(destPath, repoUrl, folderPath, options = {}) {
|
|
25763
|
+
const ref = options.ref?.trim();
|
|
25764
|
+
const cloneUrl = normalizeGitCloneUrl(repoUrl);
|
|
25765
|
+
const trimmedFolderPath = folderPath.replace(/^\/|\/$/g, "");
|
|
25766
|
+
const useSparseCheckout = trimmedFolderPath.length > 0;
|
|
25767
|
+
const cloneArgs = [
|
|
25768
|
+
"clone",
|
|
25769
|
+
"--depth",
|
|
25770
|
+
"1",
|
|
25771
|
+
...ref ? ["--branch", ref, "--single-branch"] : [],
|
|
25772
|
+
...useSparseCheckout ? ["--filter=blob:none", "--sparse"] : [],
|
|
25773
|
+
cloneUrl,
|
|
25774
|
+
destPath
|
|
25775
|
+
];
|
|
25776
|
+
const cloneResult = runGit2(process.cwd(), cloneArgs);
|
|
25777
|
+
if (!cloneResult.ok) {
|
|
25778
|
+
const refLabel = ref ?? "default branch";
|
|
25779
|
+
throw new Error(`Failed to clone ${repoUrl}@${refLabel}: ${cloneResult.stderr}`);
|
|
25780
|
+
}
|
|
25781
|
+
if (!useSparseCheckout) {
|
|
25782
|
+
return;
|
|
25783
|
+
}
|
|
25784
|
+
const sparseResult = runGit2(destPath, ["sparse-checkout", "set", trimmedFolderPath]);
|
|
25785
|
+
if (!sparseResult.ok) {
|
|
25786
|
+
throw new Error(
|
|
25787
|
+
`Failed to sparse-checkout "${trimmedFolderPath}" from ${repoUrl}: ${sparseResult.stderr}`
|
|
25207
25788
|
);
|
|
25208
25789
|
}
|
|
25209
25790
|
}
|
|
25210
|
-
|
|
25211
|
-
|
|
25212
|
-
|
|
25213
|
-
|
|
25214
|
-
|
|
25215
|
-
|
|
25216
|
-
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
|
|
25220
|
-
|
|
25221
|
-
|
|
25222
|
-
|
|
25223
|
-
|
|
25791
|
+
|
|
25792
|
+
// src/features/git/github-tree-url.ts
|
|
25793
|
+
function parseGithubTreeUrl(url) {
|
|
25794
|
+
const trimmed = url.trim().replace(/\/$/, "");
|
|
25795
|
+
const treeMatch = trimmed.match(
|
|
25796
|
+
/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)(?:\/(.*))?$/
|
|
25797
|
+
);
|
|
25798
|
+
if (treeMatch) {
|
|
25799
|
+
const [, owner, repoWithSuffix, ref, folderPath = ""] = treeMatch;
|
|
25800
|
+
const repo = repoWithSuffix.replace(/\.git$/, "");
|
|
25801
|
+
return {
|
|
25802
|
+
owner,
|
|
25803
|
+
repo,
|
|
25804
|
+
repoUrl: `https://github.com/${owner}/${repo}`,
|
|
25805
|
+
ref,
|
|
25806
|
+
folderPath: folderPath.replace(/^\/|\/$/g, "")
|
|
25807
|
+
};
|
|
25808
|
+
}
|
|
25809
|
+
const repoMatch = trimmed.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
25810
|
+
if (repoMatch) {
|
|
25811
|
+
const [, owner, repoWithSuffix] = repoMatch;
|
|
25812
|
+
const repo = repoWithSuffix.replace(/\.git$/, "");
|
|
25813
|
+
return {
|
|
25814
|
+
owner,
|
|
25815
|
+
repo,
|
|
25816
|
+
repoUrl: `https://github.com/${owner}/${repo}`,
|
|
25817
|
+
ref: "",
|
|
25818
|
+
folderPath: ""
|
|
25819
|
+
};
|
|
25820
|
+
}
|
|
25821
|
+
throw new Error(`Invalid GitHub tree URL: ${url}`);
|
|
25822
|
+
}
|
|
25823
|
+
|
|
25824
|
+
// src/features/skills/skills.catalog-cache.ts
|
|
25825
|
+
init_dist();
|
|
25826
|
+
|
|
25827
|
+
// src/features/skills/skills.custom-sources.ts
|
|
25828
|
+
init_dist();
|
|
25829
|
+
init_database();
|
|
25830
|
+
init_database_state();
|
|
25831
|
+
var STATE_KEY = "customSkillSources";
|
|
25832
|
+
function isValidSkillSource(value) {
|
|
25833
|
+
return typeof value === "object" && value !== null && typeof value.id === "string" && typeof value.title === "string" && typeof value.url === "string";
|
|
25834
|
+
}
|
|
25835
|
+
function parseCustomSkillSources(raw) {
|
|
25836
|
+
if (!Array.isArray(raw)) {
|
|
25837
|
+
return [];
|
|
25838
|
+
}
|
|
25839
|
+
return raw.filter(isValidSkillSource).filter((source) => isCustomSkillSourceId(source.id)).sort((a, b) => a.title.localeCompare(b.title));
|
|
25840
|
+
}
|
|
25841
|
+
async function getCustomSkillSources() {
|
|
25842
|
+
const db = await getDatabase();
|
|
25843
|
+
const raw = await getState(db, STATE_KEY);
|
|
25844
|
+
return parseCustomSkillSources(raw);
|
|
25845
|
+
}
|
|
25846
|
+
async function getCustomSkillSourceById(sourceId) {
|
|
25847
|
+
const sources = await getCustomSkillSources();
|
|
25848
|
+
return sources.find((source) => source.id === sourceId);
|
|
25849
|
+
}
|
|
25850
|
+
async function saveCustomSkillSource(source) {
|
|
25851
|
+
if (!isCustomSkillSourceId(source.id)) {
|
|
25852
|
+
throw new Error("Custom skill sources must use the custom- id prefix");
|
|
25853
|
+
}
|
|
25854
|
+
const db = await getDatabase();
|
|
25855
|
+
const existing = await getCustomSkillSources();
|
|
25856
|
+
const next = [...existing.filter((entry) => entry.id !== source.id), source].sort(
|
|
25857
|
+
(a, b) => a.title.localeCompare(b.title)
|
|
25858
|
+
);
|
|
25859
|
+
await setState(db, STATE_KEY, next);
|
|
25860
|
+
return source;
|
|
25861
|
+
}
|
|
25862
|
+
async function importCustomSkillSources(sources) {
|
|
25863
|
+
const valid = sources.filter(isValidSkillSource).filter((source) => isCustomSkillSourceId(source.id));
|
|
25864
|
+
if (valid.length === 0) {
|
|
25865
|
+
return getCustomSkillSources();
|
|
25866
|
+
}
|
|
25867
|
+
const db = await getDatabase();
|
|
25868
|
+
const existing = await getCustomSkillSources();
|
|
25869
|
+
const byId = new Map(existing.map((source) => [source.id, source]));
|
|
25870
|
+
for (const source of valid) {
|
|
25871
|
+
byId.set(source.id, source);
|
|
25872
|
+
}
|
|
25873
|
+
const next = [...byId.values()].sort((a, b) => a.title.localeCompare(b.title));
|
|
25874
|
+
await setState(db, STATE_KEY, next);
|
|
25875
|
+
return next;
|
|
25876
|
+
}
|
|
25877
|
+
async function deleteCustomSkillSource(sourceId) {
|
|
25878
|
+
if (!isCustomSkillSourceId(sourceId)) {
|
|
25879
|
+
throw new Error("Only custom skill sources can be deleted");
|
|
25880
|
+
}
|
|
25881
|
+
const existing = await getCustomSkillSourceById(sourceId);
|
|
25882
|
+
if (!existing) {
|
|
25883
|
+
throw new Error("Unknown custom skill source");
|
|
25884
|
+
}
|
|
25885
|
+
const db = await getDatabase();
|
|
25886
|
+
const sources = await getCustomSkillSources();
|
|
25887
|
+
const next = sources.filter((entry) => entry.id !== sourceId);
|
|
25888
|
+
await setState(db, STATE_KEY, next);
|
|
25889
|
+
await clearCatalogClone(sourceId);
|
|
25890
|
+
}
|
|
25891
|
+
|
|
25892
|
+
// src/features/skills/skills.sources.json
|
|
25893
|
+
var skills_sources_default = [
|
|
25894
|
+
{
|
|
25895
|
+
id: "openai-curated",
|
|
25896
|
+
title: "OpenAI Curated",
|
|
25897
|
+
url: "https://github.com/openai/skills/tree/main/skills/.curated"
|
|
25898
|
+
},
|
|
25899
|
+
{
|
|
25900
|
+
id: "anthropic",
|
|
25901
|
+
title: "Anthropic",
|
|
25902
|
+
url: "https://github.com/anthropics/skills/tree/main/skills"
|
|
25903
|
+
},
|
|
25904
|
+
{
|
|
25905
|
+
id: "matt-pocock-engineering",
|
|
25906
|
+
title: "Engineering - Matt Pocock",
|
|
25907
|
+
url: "https://github.com/mattpocock/skills/tree/main/skills/engineering"
|
|
25908
|
+
},
|
|
25909
|
+
{
|
|
25910
|
+
id: "matt-pocock-productivity",
|
|
25911
|
+
title: "Productivity -Matt Pocock",
|
|
25912
|
+
url: "https://github.com/mattpocock/skills/tree/main/skills/productivity"
|
|
25913
|
+
},
|
|
25914
|
+
{
|
|
25915
|
+
id: "cloudflare",
|
|
25916
|
+
title: "Cloudflare",
|
|
25917
|
+
url: "https://github.com/cloudflare/skills/tree/main/skills"
|
|
25918
|
+
},
|
|
25919
|
+
{
|
|
25920
|
+
id: "vercel",
|
|
25921
|
+
title: "Vercel",
|
|
25922
|
+
url: "https://github.com/vercel-labs/agent-skills/tree/main/skills"
|
|
25923
|
+
},
|
|
25924
|
+
{
|
|
25925
|
+
id: "microsoft",
|
|
25926
|
+
title: "Microsoft",
|
|
25927
|
+
url: "https://github.com/MicrosoftDocs/Agent-Skills/tree/main/skills"
|
|
25928
|
+
},
|
|
25929
|
+
{
|
|
25930
|
+
id: "sentry",
|
|
25931
|
+
title: "Sentry",
|
|
25932
|
+
url: "https://github.com/getsentry/skills/tree/main/skills"
|
|
25933
|
+
},
|
|
25934
|
+
{
|
|
25935
|
+
id: "firebase",
|
|
25936
|
+
title: "Firebase",
|
|
25937
|
+
url: "https://github.com/firebase/skills/tree/main/skills"
|
|
25938
|
+
},
|
|
25939
|
+
{
|
|
25940
|
+
id: "flutter",
|
|
25941
|
+
title: "Flutter",
|
|
25942
|
+
url: "https://github.com/flutter/skills/tree/main/skills"
|
|
25943
|
+
},
|
|
25944
|
+
{
|
|
25945
|
+
id: "angular",
|
|
25946
|
+
title: "Angular",
|
|
25947
|
+
url: "https://github.com/angular/skills/tree/main"
|
|
25948
|
+
},
|
|
25949
|
+
{
|
|
25950
|
+
id: "firecrawl",
|
|
25951
|
+
title: "Firecrawl",
|
|
25952
|
+
url: "https://github.com/firecrawl/skills/tree/main/skills"
|
|
25953
|
+
},
|
|
25954
|
+
{
|
|
25955
|
+
id: "redis",
|
|
25956
|
+
title: "Redis",
|
|
25957
|
+
url: "https://github.com/redis/agent-skills/tree/main/skills"
|
|
25958
|
+
},
|
|
25959
|
+
{
|
|
25960
|
+
id: "corey-haines-marketing",
|
|
25961
|
+
title: "Marketing - Corey Haines",
|
|
25962
|
+
url: "https://github.com/coreyhaines31/marketingskills/tree/main/skills"
|
|
25963
|
+
},
|
|
25964
|
+
{
|
|
25965
|
+
id: "dean-peters-product-management",
|
|
25966
|
+
title: "Product Management - Dean Peters",
|
|
25967
|
+
url: "https://github.com/deanpeters/Product-Manager-Skills/tree/main/skills"
|
|
25968
|
+
},
|
|
25969
|
+
{
|
|
25970
|
+
id: "venice-ai",
|
|
25971
|
+
title: "AI - Venice",
|
|
25972
|
+
url: "https://github.com/veniceai/skills/tree/main/skills"
|
|
25973
|
+
},
|
|
25974
|
+
{
|
|
25975
|
+
id: "superpowers",
|
|
25976
|
+
title: "Development - Superpowers",
|
|
25977
|
+
url: "https://github.com/obra/superpowers/tree/main/skills"
|
|
25978
|
+
},
|
|
25979
|
+
{
|
|
25980
|
+
id: "cypress",
|
|
25981
|
+
title: "Testing - Cypress",
|
|
25982
|
+
url: "https://github.com/cypress-io/ai-toolkit/tree/main/skills"
|
|
25983
|
+
},
|
|
25984
|
+
{
|
|
25985
|
+
id: "resend",
|
|
25986
|
+
title: "Email - Resend",
|
|
25987
|
+
url: "https://github.com/resend/resend-skills/tree/main/skills"
|
|
25988
|
+
},
|
|
25989
|
+
{
|
|
25990
|
+
id: "n8n",
|
|
25991
|
+
title: "Automation - n8n",
|
|
25992
|
+
url: "https://github.com/czlonkowski/n8n-skills/tree/main/skills"
|
|
25993
|
+
}
|
|
25994
|
+
];
|
|
25995
|
+
|
|
25996
|
+
// src/features/skills/skills.catalog-cache.ts
|
|
25997
|
+
var catalogCloneCache = /* @__PURE__ */ new Map();
|
|
25998
|
+
var inFlightRefreshes = /* @__PURE__ */ new Map();
|
|
25999
|
+
function getBundledSkillSourceById(sourceId) {
|
|
26000
|
+
return skills_sources_default.find((source) => source.id === sourceId);
|
|
26001
|
+
}
|
|
26002
|
+
async function resolveSkillSource(sourceId) {
|
|
26003
|
+
const bundled = getBundledSkillSourceById(sourceId);
|
|
26004
|
+
if (bundled) {
|
|
26005
|
+
return bundled;
|
|
26006
|
+
}
|
|
26007
|
+
const custom = await getCustomSkillSourceById(sourceId);
|
|
26008
|
+
if (custom) {
|
|
26009
|
+
return custom;
|
|
26010
|
+
}
|
|
26011
|
+
throw new Error(`Unknown skill source: ${sourceId}`);
|
|
26012
|
+
}
|
|
26013
|
+
function normalizeSkillContent(content) {
|
|
26014
|
+
return content.replace(/Claude/gi, "Tarsk");
|
|
26015
|
+
}
|
|
26016
|
+
async function listFilesRecursive(dir, rootDir = dir) {
|
|
26017
|
+
const entries = await readdir12(dir, { withFileTypes: true });
|
|
26018
|
+
const files = [];
|
|
26019
|
+
for (const entry of entries) {
|
|
26020
|
+
const fullPath = join33(dir, entry.name);
|
|
26021
|
+
if (entry.isDirectory()) {
|
|
26022
|
+
files.push(...await listFilesRecursive(fullPath, rootDir));
|
|
26023
|
+
continue;
|
|
26024
|
+
}
|
|
26025
|
+
if (entry.isFile()) {
|
|
26026
|
+
files.push(relative8(rootDir, fullPath));
|
|
26027
|
+
}
|
|
26028
|
+
}
|
|
26029
|
+
return files.sort((a, b) => a.localeCompare(b));
|
|
26030
|
+
}
|
|
26031
|
+
function resolveSkillRelativePath(skillDir, relativePath) {
|
|
26032
|
+
const resolved = join33(skillDir, relativePath);
|
|
26033
|
+
const normalizedSkillDir = skillDir.endsWith(sep) ? skillDir : `${skillDir}${sep}`;
|
|
26034
|
+
if (resolved === skillDir || resolved.startsWith(normalizedSkillDir)) {
|
|
26035
|
+
return resolved;
|
|
26036
|
+
}
|
|
26037
|
+
return null;
|
|
26038
|
+
}
|
|
26039
|
+
async function ensureCatalogClone(sourceId) {
|
|
26040
|
+
const cached = catalogCloneCache.get(sourceId);
|
|
26041
|
+
if (cached) {
|
|
26042
|
+
return cached;
|
|
26043
|
+
}
|
|
26044
|
+
return refreshCatalogClone(sourceId);
|
|
26045
|
+
}
|
|
26046
|
+
async function getSkillDirEnsured(sourceId, skillId) {
|
|
26047
|
+
const entry = await ensureCatalogClone(sourceId);
|
|
26048
|
+
return join33(entry.scanDir, skillId);
|
|
26049
|
+
}
|
|
26050
|
+
async function cloneSourceToCache(sourceId, trustedUrl) {
|
|
26051
|
+
let url;
|
|
26052
|
+
if (trustedUrl !== void 0) {
|
|
26053
|
+
if (!isCustomSkillSourceId(sourceId)) {
|
|
26054
|
+
throw new Error(`Cannot override URL for skill source: ${sourceId}`);
|
|
26055
|
+
}
|
|
26056
|
+
url = trustedUrl;
|
|
26057
|
+
} else {
|
|
26058
|
+
url = (await resolveSkillSource(sourceId)).url;
|
|
26059
|
+
}
|
|
26060
|
+
const parsed = parseGithubTreeUrl(url);
|
|
26061
|
+
const cloneDir = await mkdtemp(join33(tmpdir3(), "tarsk-skills-catalog-clone-"));
|
|
26062
|
+
await cloneGithubRepoFolder(cloneDir, parsed.repoUrl, parsed.folderPath, {
|
|
26063
|
+
ref: parsed.ref
|
|
26064
|
+
});
|
|
26065
|
+
const scanDir = parsed.folderPath ? join33(cloneDir, parsed.folderPath) : cloneDir;
|
|
26066
|
+
const entry = {
|
|
26067
|
+
sourceId,
|
|
26068
|
+
cloneDir,
|
|
26069
|
+
scanDir,
|
|
26070
|
+
parsed,
|
|
26071
|
+
fetchedAt: Date.now()
|
|
26072
|
+
};
|
|
26073
|
+
const previous = catalogCloneCache.get(sourceId);
|
|
26074
|
+
catalogCloneCache.set(sourceId, entry);
|
|
26075
|
+
if (previous) {
|
|
26076
|
+
await rm7(previous.cloneDir, { recursive: true, force: true }).catch(() => {
|
|
26077
|
+
});
|
|
26078
|
+
}
|
|
26079
|
+
return entry;
|
|
26080
|
+
}
|
|
26081
|
+
async function refreshCatalogClone(sourceId, trustedUrl) {
|
|
26082
|
+
const existing = inFlightRefreshes.get(sourceId);
|
|
26083
|
+
if (existing) {
|
|
26084
|
+
return existing;
|
|
26085
|
+
}
|
|
26086
|
+
const refreshPromise = cloneSourceToCache(sourceId, trustedUrl).finally(() => {
|
|
26087
|
+
inFlightRefreshes.delete(sourceId);
|
|
26088
|
+
});
|
|
26089
|
+
inFlightRefreshes.set(sourceId, refreshPromise);
|
|
26090
|
+
return refreshPromise;
|
|
26091
|
+
}
|
|
26092
|
+
function normalizeGithubRepoUrl(repoUrl) {
|
|
26093
|
+
return repoUrl.trim().replace(/\/$/, "").replace(/\.git$/, "");
|
|
26094
|
+
}
|
|
26095
|
+
function resolveCatalogSkillFromGithubFolder(gitHubRepo, gitHubFolder) {
|
|
26096
|
+
const normalizedRepo = normalizeGithubRepoUrl(gitHubRepo);
|
|
26097
|
+
const normalizedFolder = gitHubFolder.replace(/^\/|\/$/g, "");
|
|
26098
|
+
for (const source of skills_sources_default) {
|
|
26099
|
+
const parsed = parseGithubTreeUrl(source.url);
|
|
26100
|
+
if (normalizeGithubRepoUrl(parsed.repoUrl) !== normalizedRepo) {
|
|
26101
|
+
continue;
|
|
26102
|
+
}
|
|
26103
|
+
const basePath = parsed.folderPath.replace(/^\/|\/$/g, "");
|
|
26104
|
+
if (basePath.length > 0) {
|
|
26105
|
+
const prefix = `${basePath}/`;
|
|
26106
|
+
if (!normalizedFolder.startsWith(prefix)) {
|
|
26107
|
+
continue;
|
|
26108
|
+
}
|
|
26109
|
+
const skillId2 = normalizedFolder.slice(prefix.length);
|
|
26110
|
+
if (!skillId2 || skillId2.includes("/")) {
|
|
26111
|
+
continue;
|
|
26112
|
+
}
|
|
26113
|
+
return { sourceId: source.id, skillId: skillId2, ref: parsed.ref };
|
|
26114
|
+
}
|
|
26115
|
+
const skillId = normalizedFolder.split("/")[0];
|
|
26116
|
+
if (!skillId) {
|
|
26117
|
+
continue;
|
|
26118
|
+
}
|
|
26119
|
+
return { sourceId: source.id, skillId, ref: parsed.ref };
|
|
26120
|
+
}
|
|
26121
|
+
return null;
|
|
26122
|
+
}
|
|
26123
|
+
async function resolveCatalogSkillFromGithubFolderEnsured(gitHubRepo, gitHubFolder) {
|
|
26124
|
+
const resolved = resolveCatalogSkillFromGithubFolder(gitHubRepo, gitHubFolder);
|
|
26125
|
+
if (!resolved) {
|
|
26126
|
+
throw new Error(`No catalog source found for ${gitHubRepo}/${gitHubFolder}`);
|
|
26127
|
+
}
|
|
26128
|
+
await ensureCatalogClone(resolved.sourceId);
|
|
26129
|
+
return resolved;
|
|
26130
|
+
}
|
|
26131
|
+
async function listCachedCatalogSkillFilesByGithubFolder(gitHubRepo, gitHubFolder) {
|
|
26132
|
+
const resolved = await resolveCatalogSkillFromGithubFolderEnsured(gitHubRepo, gitHubFolder);
|
|
26133
|
+
const files = await listCachedCatalogSkillFiles(resolved.sourceId, resolved.skillId);
|
|
26134
|
+
return { files, ref: resolved.ref };
|
|
26135
|
+
}
|
|
26136
|
+
async function readCachedCatalogSkillFileByGithubFolder(gitHubRepo, gitHubFolder, relativePath) {
|
|
26137
|
+
const resolved = await resolveCatalogSkillFromGithubFolderEnsured(gitHubRepo, gitHubFolder);
|
|
26138
|
+
return readCachedCatalogSkillFile(resolved.sourceId, resolved.skillId, relativePath);
|
|
26139
|
+
}
|
|
26140
|
+
async function listCachedCatalogSkillFiles(sourceId, skillId) {
|
|
26141
|
+
const skillDir = await getSkillDirEnsured(sourceId, skillId);
|
|
26142
|
+
return listFilesRecursive(skillDir);
|
|
26143
|
+
}
|
|
26144
|
+
async function readCachedCatalogSkillFile(sourceId, skillId, relativePath) {
|
|
26145
|
+
const skillDir = await getSkillDirEnsured(sourceId, skillId);
|
|
26146
|
+
const absPath = resolveSkillRelativePath(skillDir, relativePath);
|
|
26147
|
+
if (!absPath) {
|
|
26148
|
+
throw new Error(`Invalid file path: ${relativePath}`);
|
|
26149
|
+
}
|
|
26150
|
+
const content = await readFile16(absPath, "utf8");
|
|
26151
|
+
return normalizeSkillContent(content);
|
|
26152
|
+
}
|
|
26153
|
+
async function copyCachedCatalogSkillToDirectory(sourceId, skillId, destPath) {
|
|
26154
|
+
const skillDir = await getSkillDirEnsured(sourceId, skillId);
|
|
26155
|
+
await cp(skillDir, destPath, { recursive: true });
|
|
26156
|
+
}
|
|
26157
|
+
async function clearCatalogClone(sourceId) {
|
|
26158
|
+
const entry = catalogCloneCache.get(sourceId);
|
|
26159
|
+
if (!entry) {
|
|
26160
|
+
return;
|
|
26161
|
+
}
|
|
26162
|
+
catalogCloneCache.delete(sourceId);
|
|
26163
|
+
await rm7(entry.cloneDir, { recursive: true, force: true }).catch(() => {
|
|
26164
|
+
});
|
|
26165
|
+
}
|
|
26166
|
+
|
|
26167
|
+
// src/features/threads/threads-ai-files.route.ts
|
|
26168
|
+
async function handleGetThreadAIFiles(c, threadManager) {
|
|
26169
|
+
try {
|
|
26170
|
+
const threadId = c.req.param("id");
|
|
26171
|
+
if (!threadId) {
|
|
26172
|
+
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Thread ID is required", 400);
|
|
26173
|
+
}
|
|
26174
|
+
if (!threadId) {
|
|
26175
|
+
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Thread ID is required", 400);
|
|
26176
|
+
}
|
|
26177
|
+
const thread = await threadManager.getThread(threadId);
|
|
26178
|
+
if (!thread) {
|
|
26179
|
+
return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
|
|
26180
|
+
}
|
|
26181
|
+
const nodes = await buildAIFileTree(thread.path);
|
|
26182
|
+
return c.json({ nodes });
|
|
26183
|
+
} catch (error) {
|
|
26184
|
+
return errorResponse(
|
|
26185
|
+
c,
|
|
26186
|
+
ErrorCodes.INTERNAL_ERROR,
|
|
26187
|
+
"Failed to list AI files",
|
|
26188
|
+
500,
|
|
26189
|
+
error instanceof Error ? error.message : String(error)
|
|
26190
|
+
);
|
|
26191
|
+
}
|
|
26192
|
+
}
|
|
26193
|
+
async function handleGetThreadAIFile(c, threadManager) {
|
|
26194
|
+
try {
|
|
26195
|
+
const threadId = c.req.param("id");
|
|
26196
|
+
if (!threadId) {
|
|
26197
|
+
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Thread ID is required", 400);
|
|
26198
|
+
}
|
|
26199
|
+
const filePath = c.req.query("path");
|
|
26200
|
+
if (!filePath) {
|
|
26201
|
+
return c.json(
|
|
26202
|
+
{ error: { code: "BAD_REQUEST", message: "path query parameter is required" } },
|
|
26203
|
+
400
|
|
26204
|
+
);
|
|
26205
|
+
}
|
|
26206
|
+
const thread = await threadManager.getThread(threadId);
|
|
25224
26207
|
if (!thread) {
|
|
25225
26208
|
return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
|
|
25226
26209
|
}
|
|
@@ -25283,7 +26266,7 @@ Links to important documentation, tools, or references.
|
|
|
25283
26266
|
400
|
|
25284
26267
|
);
|
|
25285
26268
|
}
|
|
25286
|
-
const content = await
|
|
26269
|
+
const content = await readFile17(absPath, "utf-8");
|
|
25287
26270
|
return c.json({ content, path: filePath });
|
|
25288
26271
|
} catch (error) {
|
|
25289
26272
|
return errorResponse(
|
|
@@ -25322,7 +26305,7 @@ async function handleSaveThreadAIFile(c, threadManager) {
|
|
|
25322
26305
|
if (!absPath) {
|
|
25323
26306
|
return c.json({ error: { code: "BAD_REQUEST", message: "Invalid file path" } }, 400);
|
|
25324
26307
|
}
|
|
25325
|
-
const parentDir =
|
|
26308
|
+
const parentDir = join34(absPath, "..");
|
|
25326
26309
|
await mkdir9(parentDir, { recursive: true });
|
|
25327
26310
|
await writeFile8(absPath, content, "utf-8");
|
|
25328
26311
|
return c.json({ success: true, path: filePath });
|
|
@@ -25365,7 +26348,7 @@ async function handleDeleteThreadAIFile(c, threadManager) {
|
|
|
25365
26348
|
404
|
|
25366
26349
|
);
|
|
25367
26350
|
}
|
|
25368
|
-
await
|
|
26351
|
+
await rm8(absPath, { recursive: true, force: true });
|
|
25369
26352
|
return c.json({ success: true, path: filePath });
|
|
25370
26353
|
} catch (error) {
|
|
25371
26354
|
return errorResponse(
|
|
@@ -25411,10 +26394,10 @@ async function handleCreateThreadAgent(c, threadManager) {
|
|
|
25411
26394
|
if (!thread) {
|
|
25412
26395
|
return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
|
|
25413
26396
|
}
|
|
25414
|
-
const agentRelPath =
|
|
25415
|
-
const agentAbsPath =
|
|
25416
|
-
const agentFileRelPath =
|
|
25417
|
-
const agentFileAbsPath =
|
|
26397
|
+
const agentRelPath = join34(".agents", "agents", name);
|
|
26398
|
+
const agentAbsPath = join34(thread.path, agentRelPath);
|
|
26399
|
+
const agentFileRelPath = join34(agentRelPath, "AGENT.md");
|
|
26400
|
+
const agentFileAbsPath = join34(agentAbsPath, "AGENT.md");
|
|
25418
26401
|
if (existsSync19(agentAbsPath)) {
|
|
25419
26402
|
return c.json(
|
|
25420
26403
|
{ error: { code: "CONFLICT", message: `Agent '${name}' already exists` } },
|
|
@@ -25481,10 +26464,10 @@ async function handleCreateThreadSkill(c, threadManager) {
|
|
|
25481
26464
|
if (!thread) {
|
|
25482
26465
|
return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
|
|
25483
26466
|
}
|
|
25484
|
-
const skillRelPath =
|
|
25485
|
-
const skillAbsPath =
|
|
25486
|
-
const skillFileRelPath =
|
|
25487
|
-
const skillFileAbsPath =
|
|
26467
|
+
const skillRelPath = join34(".agents", "skills", name);
|
|
26468
|
+
const skillAbsPath = join34(thread.path, skillRelPath);
|
|
26469
|
+
const skillFileRelPath = join34(skillRelPath, "SKILL.md");
|
|
26470
|
+
const skillFileAbsPath = join34(skillAbsPath, "SKILL.md");
|
|
25488
26471
|
if (existsSync19(skillAbsPath)) {
|
|
25489
26472
|
return c.json(
|
|
25490
26473
|
{ error: { code: "CONFLICT", message: `Skill '${name}' already exists` } },
|
|
@@ -25527,7 +26510,7 @@ async function handleInstallGithubFolder(c, threadManager) {
|
|
|
25527
26510
|
} catch {
|
|
25528
26511
|
return c.json({ error: { code: "BAD_REQUEST", message: "Invalid JSON body" } }, 400);
|
|
25529
26512
|
}
|
|
25530
|
-
const { gitHubRepo, gitHubFolder, destPath } = body;
|
|
26513
|
+
const { gitHubRepo, gitHubFolder, destPath, gitHubRef } = body;
|
|
25531
26514
|
if (!gitHubRepo || !gitHubFolder || !destPath) {
|
|
25532
26515
|
return c.json(
|
|
25533
26516
|
{
|
|
@@ -25547,7 +26530,9 @@ async function handleInstallGithubFolder(c, threadManager) {
|
|
|
25547
26530
|
if (!absDestPath) {
|
|
25548
26531
|
return c.json({ error: { code: "BAD_REQUEST", message: "Invalid destPath" } }, 400);
|
|
25549
26532
|
}
|
|
25550
|
-
await downloadGithubFolder(gitHubRepo, gitHubFolder, absDestPath
|
|
26533
|
+
await downloadGithubFolder(gitHubRepo, gitHubFolder, absDestPath, {
|
|
26534
|
+
ref: gitHubRef
|
|
26535
|
+
});
|
|
25551
26536
|
return c.json({ success: true, path: destPath });
|
|
25552
26537
|
} catch (error) {
|
|
25553
26538
|
return errorResponse(
|
|
@@ -25559,6 +26544,50 @@ async function handleInstallGithubFolder(c, threadManager) {
|
|
|
25559
26544
|
);
|
|
25560
26545
|
}
|
|
25561
26546
|
}
|
|
26547
|
+
async function handleInstallCatalogSkill(c, threadManager) {
|
|
26548
|
+
try {
|
|
26549
|
+
const threadId = c.req.param("id");
|
|
26550
|
+
if (!threadId) {
|
|
26551
|
+
return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Thread ID is required", 400);
|
|
26552
|
+
}
|
|
26553
|
+
let body;
|
|
26554
|
+
try {
|
|
26555
|
+
body = await c.req.json();
|
|
26556
|
+
} catch {
|
|
26557
|
+
return c.json({ error: { code: "BAD_REQUEST", message: "Invalid JSON body" } }, 400);
|
|
26558
|
+
}
|
|
26559
|
+
const { sourceId, skillId, destPath } = body;
|
|
26560
|
+
if (!sourceId || !skillId || !destPath) {
|
|
26561
|
+
return c.json(
|
|
26562
|
+
{
|
|
26563
|
+
error: {
|
|
26564
|
+
code: "BAD_REQUEST",
|
|
26565
|
+
message: "sourceId, skillId, and destPath are required"
|
|
26566
|
+
}
|
|
26567
|
+
},
|
|
26568
|
+
400
|
|
26569
|
+
);
|
|
26570
|
+
}
|
|
26571
|
+
const thread = await threadManager.getThread(threadId);
|
|
26572
|
+
if (!thread) {
|
|
26573
|
+
return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
|
|
26574
|
+
}
|
|
26575
|
+
const absDestPath = validateFilePath(thread.path, destPath);
|
|
26576
|
+
if (!absDestPath) {
|
|
26577
|
+
return c.json({ error: { code: "BAD_REQUEST", message: "Invalid destPath" } }, 400);
|
|
26578
|
+
}
|
|
26579
|
+
await copyCachedCatalogSkillToDirectory(sourceId, skillId, absDestPath);
|
|
26580
|
+
return c.json({ success: true, path: destPath });
|
|
26581
|
+
} catch (error) {
|
|
26582
|
+
return errorResponse(
|
|
26583
|
+
c,
|
|
26584
|
+
ErrorCodes.INTERNAL_ERROR,
|
|
26585
|
+
"Failed to install catalog skill",
|
|
26586
|
+
500,
|
|
26587
|
+
error instanceof Error ? error.message : String(error)
|
|
26588
|
+
);
|
|
26589
|
+
}
|
|
26590
|
+
}
|
|
25562
26591
|
|
|
25563
26592
|
// src/features/threads/threads-project-scripts.route.ts
|
|
25564
26593
|
init_database();
|
|
@@ -25568,17 +26597,22 @@ init_utils();
|
|
|
25568
26597
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
25569
26598
|
var ScriptProcessManager = class {
|
|
25570
26599
|
processes = /* @__PURE__ */ new Map();
|
|
26600
|
+
threadIds = /* @__PURE__ */ new Map();
|
|
25571
26601
|
/**
|
|
25572
26602
|
* Run a script and stream its output
|
|
25573
26603
|
* @param key - Unique key for this script (e.g. "projectId:scriptName")
|
|
25574
26604
|
* @param command - The shell command to run
|
|
25575
26605
|
* @param cwd - Working directory for the command
|
|
26606
|
+
* @param threadId - Optional thread ID for dev server URL caching
|
|
25576
26607
|
* @yields Output chunks from stdout/stderr
|
|
25577
26608
|
*/
|
|
25578
|
-
async *runScript(key, command, cwd) {
|
|
26609
|
+
async *runScript(key, command, cwd, threadId) {
|
|
25579
26610
|
if (this.processes.has(key)) {
|
|
25580
26611
|
this.stopScript(key);
|
|
25581
26612
|
}
|
|
26613
|
+
if (threadId) {
|
|
26614
|
+
this.threadIds.set(key, threadId);
|
|
26615
|
+
}
|
|
25582
26616
|
const { shell, args: shellArgs } = getShellConfig();
|
|
25583
26617
|
const child = spawnProcess(shell, [...shellArgs, command], {
|
|
25584
26618
|
cwd,
|
|
@@ -25594,16 +26628,39 @@ var ScriptProcessManager = class {
|
|
|
25594
26628
|
const decoder = new TextDecoder();
|
|
25595
26629
|
const outputBuffer = [];
|
|
25596
26630
|
let isDone = false;
|
|
26631
|
+
let urlDetected = false;
|
|
26632
|
+
let urlBuffer = "";
|
|
26633
|
+
function appendOutput(text, isError) {
|
|
26634
|
+
urlBuffer += text;
|
|
26635
|
+
if (!urlDetected) {
|
|
26636
|
+
const detectedUrl = detectDevServerUrl(urlBuffer);
|
|
26637
|
+
if (detectedUrl) {
|
|
26638
|
+
urlDetected = true;
|
|
26639
|
+
if (threadId) {
|
|
26640
|
+
devServerCache.setUrl(threadId, detectedUrl);
|
|
26641
|
+
}
|
|
26642
|
+
outputBuffer.push({ type: "url", url: detectedUrl });
|
|
26643
|
+
}
|
|
26644
|
+
}
|
|
26645
|
+
if (isError) {
|
|
26646
|
+
outputBuffer.push({ type: "error", message: text });
|
|
26647
|
+
} else {
|
|
26648
|
+
outputBuffer.push(text);
|
|
26649
|
+
}
|
|
26650
|
+
}
|
|
25597
26651
|
child.stdout?.on("data", (data) => {
|
|
25598
|
-
|
|
26652
|
+
appendOutput(decoder.decode(data), false);
|
|
25599
26653
|
});
|
|
25600
26654
|
child.stderr?.on("data", (data) => {
|
|
25601
|
-
|
|
26655
|
+
appendOutput(decoder.decode(data), true);
|
|
25602
26656
|
});
|
|
25603
26657
|
child.on("close", (code) => {
|
|
25604
26658
|
if (code !== 0 && code !== null) {
|
|
25605
26659
|
outputBuffer.push({ type: "error", message: `Process exited with code ${code}` });
|
|
25606
26660
|
}
|
|
26661
|
+
if (threadId && urlDetected) {
|
|
26662
|
+
devServerCache.clearUrl(threadId);
|
|
26663
|
+
}
|
|
25607
26664
|
isDone = true;
|
|
25608
26665
|
});
|
|
25609
26666
|
child.on("error", (err) => {
|
|
@@ -25620,6 +26677,7 @@ var ScriptProcessManager = class {
|
|
|
25620
26677
|
}
|
|
25621
26678
|
} finally {
|
|
25622
26679
|
this.processes.delete(key);
|
|
26680
|
+
this.threadIds.delete(key);
|
|
25623
26681
|
}
|
|
25624
26682
|
}
|
|
25625
26683
|
/**
|
|
@@ -25630,8 +26688,13 @@ var ScriptProcessManager = class {
|
|
|
25630
26688
|
stopScript(key) {
|
|
25631
26689
|
const entry = this.processes.get(key);
|
|
25632
26690
|
if (!entry) return false;
|
|
26691
|
+
const threadId = this.threadIds.get(key);
|
|
25633
26692
|
killProcessTree2(entry.pid, entry.process);
|
|
25634
26693
|
this.processes.delete(key);
|
|
26694
|
+
this.threadIds.delete(key);
|
|
26695
|
+
if (threadId) {
|
|
26696
|
+
devServerCache.clearUrl(threadId);
|
|
26697
|
+
}
|
|
25635
26698
|
return true;
|
|
25636
26699
|
}
|
|
25637
26700
|
/**
|
|
@@ -25706,7 +26769,10 @@ async function handleRunProjectScript(c, threadManager) {
|
|
|
25706
26769
|
return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
|
|
25707
26770
|
}
|
|
25708
26771
|
const key = `${thread.projectId}:${scriptName}`;
|
|
25709
|
-
return streamAsyncGenerator(
|
|
26772
|
+
return streamAsyncGenerator(
|
|
26773
|
+
c,
|
|
26774
|
+
scriptProcessManager.runScript(key, command, thread.path, threadId)
|
|
26775
|
+
);
|
|
25710
26776
|
} catch (error) {
|
|
25711
26777
|
return errorResponse(
|
|
25712
26778
|
c,
|
|
@@ -25748,10 +26814,10 @@ async function handleStopProjectScript(c, threadManager) {
|
|
|
25748
26814
|
}
|
|
25749
26815
|
|
|
25750
26816
|
// src/features/threads/threads.routes.ts
|
|
25751
|
-
function createThreadRoutes(threadManager,
|
|
26817
|
+
function createThreadRoutes(threadManager, gitManager2, conversationManager) {
|
|
25752
26818
|
const router = new Hono12();
|
|
25753
26819
|
router.post("/", async (c) => {
|
|
25754
|
-
return handleCreateThread(c, threadManager,
|
|
26820
|
+
return handleCreateThread(c, threadManager, gitManager2);
|
|
25755
26821
|
});
|
|
25756
26822
|
router.get("/", async (c) => {
|
|
25757
26823
|
const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
@@ -25836,6 +26902,9 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
|
|
|
25836
26902
|
router.post("/:id/ai-files/github-folder", async (c) => {
|
|
25837
26903
|
return handleInstallGithubFolder(c, threadManager);
|
|
25838
26904
|
});
|
|
26905
|
+
router.post("/:id/ai-files/catalog-skill", async (c) => {
|
|
26906
|
+
return handleInstallCatalogSkill(c, threadManager);
|
|
26907
|
+
});
|
|
25839
26908
|
return router;
|
|
25840
26909
|
}
|
|
25841
26910
|
|
|
@@ -26476,9 +27545,63 @@ async function gitGenerateCommitMessageHandler(c, metadataManager) {
|
|
|
26476
27545
|
}
|
|
26477
27546
|
|
|
26478
27547
|
// src/features/git/git-commit.route.ts
|
|
27548
|
+
var gitManager = new GitManagerImpl();
|
|
27549
|
+
async function getUniqueBranchName(baseBranchName, gitRoot, existingBranchNames) {
|
|
27550
|
+
async function isBranchNameTaken(branchName) {
|
|
27551
|
+
if (existingBranchNames.has(branchName)) {
|
|
27552
|
+
return true;
|
|
27553
|
+
}
|
|
27554
|
+
return checkBranchExists(gitRoot, branchName);
|
|
27555
|
+
}
|
|
27556
|
+
if (!await isBranchNameTaken(baseBranchName)) {
|
|
27557
|
+
return baseBranchName;
|
|
27558
|
+
}
|
|
27559
|
+
let counter = 2;
|
|
27560
|
+
while (await isBranchNameTaken(`${baseBranchName}-${counter}`)) {
|
|
27561
|
+
counter++;
|
|
27562
|
+
}
|
|
27563
|
+
return `${baseBranchName}-${counter}`;
|
|
27564
|
+
}
|
|
27565
|
+
async function maybeRenameInitialTarskBranch(gitRoot, threadId, commitMessage, metadataManager) {
|
|
27566
|
+
const [commitCountBeforeCommit, currentBranch, threads] = await Promise.all([
|
|
27567
|
+
getCommitCount(gitRoot),
|
|
27568
|
+
getCurrentBranch(gitRoot),
|
|
27569
|
+
metadataManager.loadThreads()
|
|
27570
|
+
]);
|
|
27571
|
+
if (commitCountBeforeCommit !== 0) {
|
|
27572
|
+
return;
|
|
27573
|
+
}
|
|
27574
|
+
const thread = threads.find((candidate) => candidate.id === threadId);
|
|
27575
|
+
if (!thread) {
|
|
27576
|
+
return;
|
|
27577
|
+
}
|
|
27578
|
+
if (currentBranch !== thread.currentBranch) {
|
|
27579
|
+
return;
|
|
27580
|
+
}
|
|
27581
|
+
const expectedTarskBranchName = gitManager.sanitizeBranchName(thread.title);
|
|
27582
|
+
if (currentBranch !== expectedTarskBranchName) {
|
|
27583
|
+
return;
|
|
27584
|
+
}
|
|
27585
|
+
const requestedBranchName = gitManager.sanitizeBranchName(commitMessage);
|
|
27586
|
+
if (!requestedBranchName || requestedBranchName === currentBranch) {
|
|
27587
|
+
return;
|
|
27588
|
+
}
|
|
27589
|
+
const existingBranchNames = new Set(threads.map((candidate) => candidate.currentBranch));
|
|
27590
|
+
existingBranchNames.delete(currentBranch);
|
|
27591
|
+
const newBranchName = await getUniqueBranchName(
|
|
27592
|
+
requestedBranchName,
|
|
27593
|
+
gitRoot,
|
|
27594
|
+
existingBranchNames
|
|
27595
|
+
);
|
|
27596
|
+
await renameCurrentBranch(gitRoot, newBranchName);
|
|
27597
|
+
await metadataManager.updateThreadFields(threadId, { currentBranch: newBranchName });
|
|
27598
|
+
}
|
|
26479
27599
|
async function gitCommitHandler(c, metadataManager) {
|
|
26480
27600
|
try {
|
|
26481
27601
|
const threadId = c.req.param("threadId");
|
|
27602
|
+
if (!threadId) {
|
|
27603
|
+
return c.json({ error: "Thread ID is required" }, 400);
|
|
27604
|
+
}
|
|
26482
27605
|
const body = await c.req.json();
|
|
26483
27606
|
const { message } = body;
|
|
26484
27607
|
if (!message) {
|
|
@@ -26500,6 +27623,7 @@ async function gitCommitHandler(c, metadataManager) {
|
|
|
26500
27623
|
return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
|
|
26501
27624
|
}
|
|
26502
27625
|
await stageAllChanges(gitRoot);
|
|
27626
|
+
await maybeRenameInitialTarskBranch(gitRoot, threadId, message, metadataManager);
|
|
26503
27627
|
await commitChanges(gitRoot, message);
|
|
26504
27628
|
return c.json({ success: true });
|
|
26505
27629
|
} catch (error) {
|
|
@@ -26803,6 +27927,9 @@ async function gitLogHandler(c, metadataManager) {
|
|
|
26803
27927
|
}
|
|
26804
27928
|
|
|
26805
27929
|
// src/features/git/git-create-pr.route.ts
|
|
27930
|
+
function normalizeThreadTitle(title) {
|
|
27931
|
+
return (title ?? "").trim().replace(/\s+/g, " ");
|
|
27932
|
+
}
|
|
26806
27933
|
async function gitCreatePrHandler(c, metadataManager) {
|
|
26807
27934
|
try {
|
|
26808
27935
|
const threadId = c.req.param("threadId");
|
|
@@ -26812,6 +27939,9 @@ async function gitCreatePrHandler(c, metadataManager) {
|
|
|
26812
27939
|
console.log(`[git-create-pr] PR title: "${title}"`);
|
|
26813
27940
|
console.log(`[git-create-pr] PR description length: ${description?.length ?? 0} chars`);
|
|
26814
27941
|
const thread = await metadataManager.loadThreads().then((threads) => threads.find((t) => t.id === threadId));
|
|
27942
|
+
if (!threadId) {
|
|
27943
|
+
return c.json({ error: "Thread ID is required" }, 400);
|
|
27944
|
+
}
|
|
26815
27945
|
if (!thread) {
|
|
26816
27946
|
console.log(`[git-create-pr] Thread not found: ${threadId}`);
|
|
26817
27947
|
return c.json({ error: "Thread not found" }, 404);
|
|
@@ -26834,10 +27964,17 @@ async function gitCreatePrHandler(c, metadataManager) {
|
|
|
26834
27964
|
}
|
|
26835
27965
|
const currentBranch = await getCurrentBranch(gitRoot);
|
|
26836
27966
|
console.log(`[git-create-pr] Current branch: ${currentBranch}`);
|
|
27967
|
+
const prTitle = title ?? currentBranch;
|
|
26837
27968
|
console.log(`[git-create-pr] Creating PR with GitHub CLI...`);
|
|
26838
|
-
const prUrl = await createPullRequest(gitRoot,
|
|
27969
|
+
const prUrl = await createPullRequest(gitRoot, prTitle, description ?? "");
|
|
26839
27970
|
console.log(`[git-create-pr] \u2713 PR created successfully: ${prUrl}`);
|
|
26840
|
-
|
|
27971
|
+
const normalizedPrTitle = normalizeThreadTitle(prTitle);
|
|
27972
|
+
if (normalizedPrTitle !== thread.title) {
|
|
27973
|
+
const threadUpdates = { title: normalizedPrTitle };
|
|
27974
|
+
await metadataManager.updateThreadFields(threadId, threadUpdates);
|
|
27975
|
+
console.log(`[git-create-pr] \u2713 Updated thread title to PR title: ${normalizedPrTitle}`);
|
|
27976
|
+
}
|
|
27977
|
+
return c.json({ success: true, prUrl, title: normalizedPrTitle });
|
|
26841
27978
|
} catch (error) {
|
|
26842
27979
|
const message = error instanceof Error ? error.message : "Failed to create PR";
|
|
26843
27980
|
console.log(`[git-create-pr] \u2717 PR creation failed: ${message}`);
|
|
@@ -27163,7 +28300,7 @@ function sanitizeBranchName(name) {
|
|
|
27163
28300
|
}
|
|
27164
28301
|
return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9\-_/.]/g, "").replace(/^[/.]+|[/.]+$/g, "").replace(/-+/g, "-") || "branch";
|
|
27165
28302
|
}
|
|
27166
|
-
function
|
|
28303
|
+
function checkBranchExists2(gitRoot, branchName) {
|
|
27167
28304
|
return new Promise((resolve8) => {
|
|
27168
28305
|
const proc = spawnProcess(
|
|
27169
28306
|
"git",
|
|
@@ -27227,12 +28364,12 @@ async function gitCreateRepoHandler(c, metadataManager) {
|
|
|
27227
28364
|
}
|
|
27228
28365
|
const repoUrl = await createGitHubRepo(gitRoot, repoName, description, isPrivate);
|
|
27229
28366
|
let branchName = sanitizeBranchName(generateRandomThreadName());
|
|
27230
|
-
let branchExists = await
|
|
28367
|
+
let branchExists = await checkBranchExists2(gitRoot, branchName);
|
|
27231
28368
|
let counter = 2;
|
|
27232
28369
|
const baseBranchName = branchName;
|
|
27233
28370
|
while (branchExists) {
|
|
27234
28371
|
branchName = `${baseBranchName}-${counter}`;
|
|
27235
|
-
branchExists = await
|
|
28372
|
+
branchExists = await checkBranchExists2(gitRoot, branchName);
|
|
27236
28373
|
counter++;
|
|
27237
28374
|
}
|
|
27238
28375
|
await createAndCheckoutBranch(gitRoot, branchName);
|
|
@@ -27285,7 +28422,7 @@ function sanitizeBranchName2(name) {
|
|
|
27285
28422
|
}
|
|
27286
28423
|
return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9\-_/.]/g, "").replace(/^[/.]+|[/.]+$/g, "").replace(/-+/g, "-") || "branch";
|
|
27287
28424
|
}
|
|
27288
|
-
function
|
|
28425
|
+
function checkBranchExists3(gitRoot, branchName) {
|
|
27289
28426
|
return new Promise((resolve8) => {
|
|
27290
28427
|
const proc = spawnProcess(
|
|
27291
28428
|
"git",
|
|
@@ -27348,7 +28485,7 @@ async function gitCreateBranchHandler(c, metadataManager) {
|
|
|
27348
28485
|
if (!branchName) {
|
|
27349
28486
|
return c.json({ error: "Invalid branch name" }, 400);
|
|
27350
28487
|
}
|
|
27351
|
-
const branchExists = await
|
|
28488
|
+
const branchExists = await checkBranchExists3(gitRoot, branchName);
|
|
27352
28489
|
if (body.branchName) {
|
|
27353
28490
|
if (branchExists) {
|
|
27354
28491
|
return c.json({ error: `Branch "${branchName}" already exists` }, 409);
|
|
@@ -27359,7 +28496,7 @@ async function gitCreateBranchHandler(c, metadataManager) {
|
|
|
27359
28496
|
let exists = branchExists;
|
|
27360
28497
|
while (exists) {
|
|
27361
28498
|
branchName = `${baseBranchName}-${counter}`;
|
|
27362
|
-
exists = await
|
|
28499
|
+
exists = await checkBranchExists3(gitRoot, branchName);
|
|
27363
28500
|
counter++;
|
|
27364
28501
|
}
|
|
27365
28502
|
}
|
|
@@ -27731,7 +28868,7 @@ async function gitCheckoutBranchHandler(c, metadataManager) {
|
|
|
27731
28868
|
import { unlinkSync } from "fs";
|
|
27732
28869
|
import { resolve as resolve6 } from "path";
|
|
27733
28870
|
init_utils();
|
|
27734
|
-
function
|
|
28871
|
+
function runGit3(args2, cwd) {
|
|
27735
28872
|
return new Promise((resolve8, reject) => {
|
|
27736
28873
|
const proc = spawnProcess("git", args2, { cwd });
|
|
27737
28874
|
let out = "";
|
|
@@ -27767,7 +28904,7 @@ async function gitRevertFileHandler(c, metadataManager) {
|
|
|
27767
28904
|
} catch {
|
|
27768
28905
|
return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
|
|
27769
28906
|
}
|
|
27770
|
-
const statusOutput = await
|
|
28907
|
+
const statusOutput = await runGit3(["status", "--porcelain", "--", filePath], gitRoot).catch(
|
|
27771
28908
|
() => ""
|
|
27772
28909
|
);
|
|
27773
28910
|
const statusCode = statusOutput.trim().substring(0, 2);
|
|
@@ -27777,13 +28914,13 @@ async function gitRevertFileHandler(c, metadataManager) {
|
|
|
27777
28914
|
} catch {
|
|
27778
28915
|
}
|
|
27779
28916
|
} else if (statusCode === "A ") {
|
|
27780
|
-
await
|
|
28917
|
+
await runGit3(["rm", "--cached", "--", filePath], gitRoot);
|
|
27781
28918
|
try {
|
|
27782
28919
|
unlinkSync(resolve6(gitRoot, filePath));
|
|
27783
28920
|
} catch {
|
|
27784
28921
|
}
|
|
27785
28922
|
} else {
|
|
27786
|
-
await
|
|
28923
|
+
await runGit3(["checkout", "HEAD", "--", filePath], gitRoot);
|
|
27787
28924
|
}
|
|
27788
28925
|
return c.json({ success: true });
|
|
27789
28926
|
} catch (error) {
|
|
@@ -27974,51 +29111,12 @@ async function gitCheckpointRestoreHandler(c, metadataManager) {
|
|
|
27974
29111
|
}
|
|
27975
29112
|
|
|
27976
29113
|
// src/features/git/git-rename-branch.route.ts
|
|
27977
|
-
init_utils();
|
|
27978
29114
|
function sanitizeBranchName3(name) {
|
|
27979
29115
|
if (!name || typeof name !== "string") {
|
|
27980
29116
|
return "";
|
|
27981
29117
|
}
|
|
27982
29118
|
return name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9\-_/.]/g, "").replace(/^[/.]+|[/.]+$/g, "").replace(/-+/g, "-") || "";
|
|
27983
29119
|
}
|
|
27984
|
-
function checkBranchExists3(gitRoot, branchName) {
|
|
27985
|
-
return new Promise((resolve8) => {
|
|
27986
|
-
const proc = spawnProcess(
|
|
27987
|
-
"git",
|
|
27988
|
-
["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
|
27989
|
-
{
|
|
27990
|
-
cwd: gitRoot
|
|
27991
|
-
}
|
|
27992
|
-
);
|
|
27993
|
-
proc.on("close", (code) => {
|
|
27994
|
-
resolve8(code === 0);
|
|
27995
|
-
});
|
|
27996
|
-
proc.on("error", () => {
|
|
27997
|
-
resolve8(false);
|
|
27998
|
-
});
|
|
27999
|
-
});
|
|
28000
|
-
}
|
|
28001
|
-
function renameBranch(gitRoot, newBranchName) {
|
|
28002
|
-
return new Promise((resolve8, reject) => {
|
|
28003
|
-
const proc = spawnProcess("git", ["branch", "-m", newBranchName], {
|
|
28004
|
-
cwd: gitRoot
|
|
28005
|
-
});
|
|
28006
|
-
let err = "";
|
|
28007
|
-
if (proc.stderr) {
|
|
28008
|
-
proc.stderr.on("data", (d) => {
|
|
28009
|
-
err += d.toString();
|
|
28010
|
-
});
|
|
28011
|
-
}
|
|
28012
|
-
proc.on("close", (code) => {
|
|
28013
|
-
if (code === 0) {
|
|
28014
|
-
resolve8();
|
|
28015
|
-
} else {
|
|
28016
|
-
reject(new Error(err || `Failed to rename branch to "${newBranchName}"`));
|
|
28017
|
-
}
|
|
28018
|
-
});
|
|
28019
|
-
proc.on("error", reject);
|
|
28020
|
-
});
|
|
28021
|
-
}
|
|
28022
29120
|
async function gitRenameBranchHandler(c, metadataManager) {
|
|
28023
29121
|
try {
|
|
28024
29122
|
const threadId = c.req.param("threadId");
|
|
@@ -28046,11 +29144,11 @@ async function gitRenameBranchHandler(c, metadataManager) {
|
|
|
28046
29144
|
} catch {
|
|
28047
29145
|
return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
|
|
28048
29146
|
}
|
|
28049
|
-
const branchExists = await
|
|
29147
|
+
const branchExists = await checkBranchExists(gitRoot, newBranchName);
|
|
28050
29148
|
if (branchExists) {
|
|
28051
29149
|
return c.json({ error: `Branch "${newBranchName}" already exists` }, 409);
|
|
28052
29150
|
}
|
|
28053
|
-
await
|
|
29151
|
+
await renameCurrentBranch(gitRoot, newBranchName);
|
|
28054
29152
|
const threads = await metadataManager.loadThreads();
|
|
28055
29153
|
const threadIndex = threads.findIndex((t) => t.id === threadId);
|
|
28056
29154
|
if (threadIndex !== -1) {
|
|
@@ -28292,8 +29390,8 @@ function createUpdateRoutes(updater) {
|
|
|
28292
29390
|
|
|
28293
29391
|
// src/features/mcp/mcp.routes.ts
|
|
28294
29392
|
import { Hono as Hono15 } from "hono";
|
|
28295
|
-
import { readFile as
|
|
28296
|
-
import { join as
|
|
29393
|
+
import { readFile as readFile18, writeFile as writeFile9, mkdir as mkdir11, access as access6 } from "fs/promises";
|
|
29394
|
+
import { join as join35, dirname as dirname8 } from "path";
|
|
28297
29395
|
|
|
28298
29396
|
// src/features/mcp/mcp.popular.json
|
|
28299
29397
|
var mcp_popular_default = [
|
|
@@ -28415,10 +29513,10 @@ var mcp_popular_default = [
|
|
|
28415
29513
|
var MCP_CONFIG_PATHS2 = [".agents/mcp.json", "mcp.json"];
|
|
28416
29514
|
async function readMCPConfig(projectPath) {
|
|
28417
29515
|
for (const configPath of MCP_CONFIG_PATHS2) {
|
|
28418
|
-
const fullPath =
|
|
29516
|
+
const fullPath = join35(projectPath, configPath);
|
|
28419
29517
|
try {
|
|
28420
29518
|
await access6(fullPath);
|
|
28421
|
-
const content = await
|
|
29519
|
+
const content = await readFile18(fullPath, "utf-8");
|
|
28422
29520
|
const config = JSON.parse(content);
|
|
28423
29521
|
return { config, filePath: fullPath };
|
|
28424
29522
|
} catch {
|
|
@@ -28429,7 +29527,7 @@ async function readMCPConfig(projectPath) {
|
|
|
28429
29527
|
}
|
|
28430
29528
|
async function writeMCPConfig(projectPath, config) {
|
|
28431
29529
|
const existing = await readMCPConfig(projectPath);
|
|
28432
|
-
const targetPath = existing?.filePath ??
|
|
29530
|
+
const targetPath = existing?.filePath ?? join35(projectPath, ".agents/mcp.json");
|
|
28433
29531
|
await mkdir11(dirname8(targetPath), { recursive: true });
|
|
28434
29532
|
await writeFile9(targetPath, JSON.stringify(config, null, 2), "utf-8");
|
|
28435
29533
|
}
|
|
@@ -28632,11 +29730,328 @@ var skills_popular_default = [
|
|
|
28632
29730
|
}
|
|
28633
29731
|
];
|
|
28634
29732
|
|
|
29733
|
+
// src/features/skills/skills.catalog.ts
|
|
29734
|
+
init_dist();
|
|
29735
|
+
import { readdir as readdir13, readFile as readFile19 } from "fs/promises";
|
|
29736
|
+
import { join as join36 } from "path";
|
|
29737
|
+
function getSkillSources() {
|
|
29738
|
+
const sources = skills_sources_default;
|
|
29739
|
+
return [...sources].sort((a, b) => a.title.localeCompare(b.title));
|
|
29740
|
+
}
|
|
29741
|
+
async function getAllSkillSources() {
|
|
29742
|
+
const bundled = getSkillSources();
|
|
29743
|
+
const custom = await getCustomSkillSources();
|
|
29744
|
+
const bundledIds = new Set(bundled.map((source) => source.id));
|
|
29745
|
+
const uniqueCustom = custom.filter((source) => !bundledIds.has(source.id));
|
|
29746
|
+
return [...bundled, ...uniqueCustom].sort((a, b) => a.title.localeCompare(b.title));
|
|
29747
|
+
}
|
|
29748
|
+
async function scanDownloadedSkillCatalog(tmpDir, parsed, sourceId) {
|
|
29749
|
+
const entries = await readdir13(tmpDir, { withFileTypes: true });
|
|
29750
|
+
const catalogItems = [];
|
|
29751
|
+
for (const entry of entries) {
|
|
29752
|
+
if (!entry.isDirectory()) {
|
|
29753
|
+
continue;
|
|
29754
|
+
}
|
|
29755
|
+
const skillMdPath = join36(tmpDir, entry.name, "SKILL.md");
|
|
29756
|
+
let skillMdContent;
|
|
29757
|
+
try {
|
|
29758
|
+
skillMdContent = await readFile19(skillMdPath, "utf8");
|
|
29759
|
+
} catch {
|
|
29760
|
+
continue;
|
|
29761
|
+
}
|
|
29762
|
+
const { metadata } = parseSkillFrontmatter(skillMdContent);
|
|
29763
|
+
const folderPath = parsed.folderPath ? `${parsed.folderPath}/${entry.name}` : entry.name;
|
|
29764
|
+
catalogItems.push({
|
|
29765
|
+
id: entry.name,
|
|
29766
|
+
name: metadata.name ?? entry.name,
|
|
29767
|
+
description: metadata.description ?? "",
|
|
29768
|
+
sourceId,
|
|
29769
|
+
gitHubRepo: parsed.repoUrl,
|
|
29770
|
+
gitHubFolder: folderPath,
|
|
29771
|
+
gitHubRef: parsed.ref
|
|
29772
|
+
});
|
|
29773
|
+
}
|
|
29774
|
+
catalogItems.sort((a, b) => a.name.localeCompare(b.name));
|
|
29775
|
+
return catalogItems;
|
|
29776
|
+
}
|
|
29777
|
+
async function fetchSkillCatalogFromSource(sourceId) {
|
|
29778
|
+
const entry = await refreshCatalogClone(sourceId);
|
|
29779
|
+
return scanDownloadedSkillCatalog(entry.scanDir, entry.parsed, sourceId);
|
|
29780
|
+
}
|
|
29781
|
+
async function verifySkillSource(title, url) {
|
|
29782
|
+
const trimmedTitle = title.trim();
|
|
29783
|
+
const trimmedUrl = url.trim();
|
|
29784
|
+
if (!trimmedTitle) {
|
|
29785
|
+
throw new Error("Title is required");
|
|
29786
|
+
}
|
|
29787
|
+
if (!trimmedUrl) {
|
|
29788
|
+
throw new Error("URL is required");
|
|
29789
|
+
}
|
|
29790
|
+
parseGithubTreeUrl(trimmedUrl);
|
|
29791
|
+
const bundledIds = skills_sources_default.map((source2) => source2.id);
|
|
29792
|
+
const customIds = (await getCustomSkillSources()).map((source2) => source2.id);
|
|
29793
|
+
const sourceId = createCustomSkillSourceId(trimmedTitle, [...bundledIds, ...customIds]);
|
|
29794
|
+
const entry = await refreshCatalogClone(sourceId, trimmedUrl);
|
|
29795
|
+
const skills = await scanDownloadedSkillCatalog(entry.scanDir, entry.parsed, sourceId);
|
|
29796
|
+
if (skills.length === 0) {
|
|
29797
|
+
await clearCatalogClone(sourceId);
|
|
29798
|
+
throw new Error("No skills found at this URL");
|
|
29799
|
+
}
|
|
29800
|
+
const source = {
|
|
29801
|
+
id: sourceId,
|
|
29802
|
+
title: trimmedTitle,
|
|
29803
|
+
url: trimmedUrl
|
|
29804
|
+
};
|
|
29805
|
+
await saveCustomSkillSource(source);
|
|
29806
|
+
return {
|
|
29807
|
+
source,
|
|
29808
|
+
skillCount: skills.length
|
|
29809
|
+
};
|
|
29810
|
+
}
|
|
29811
|
+
async function updateCustomSkillSource(sourceId, title, url) {
|
|
29812
|
+
if (!isCustomSkillSourceId(sourceId)) {
|
|
29813
|
+
throw new Error("Only custom skill sources can be updated");
|
|
29814
|
+
}
|
|
29815
|
+
const existing = await getCustomSkillSourceById(sourceId);
|
|
29816
|
+
if (!existing) {
|
|
29817
|
+
throw new Error("Unknown custom skill source");
|
|
29818
|
+
}
|
|
29819
|
+
const trimmedTitle = title.trim();
|
|
29820
|
+
const trimmedUrl = url.trim();
|
|
29821
|
+
if (!trimmedTitle) {
|
|
29822
|
+
throw new Error("Title is required");
|
|
29823
|
+
}
|
|
29824
|
+
if (!trimmedUrl) {
|
|
29825
|
+
throw new Error("URL is required");
|
|
29826
|
+
}
|
|
29827
|
+
parseGithubTreeUrl(trimmedUrl);
|
|
29828
|
+
const entry = await refreshCatalogClone(sourceId, trimmedUrl);
|
|
29829
|
+
const skills = await scanDownloadedSkillCatalog(entry.scanDir, entry.parsed, sourceId);
|
|
29830
|
+
if (skills.length === 0) {
|
|
29831
|
+
await clearCatalogClone(sourceId);
|
|
29832
|
+
throw new Error("No skills found at this URL");
|
|
29833
|
+
}
|
|
29834
|
+
const source = {
|
|
29835
|
+
id: sourceId,
|
|
29836
|
+
title: trimmedTitle,
|
|
29837
|
+
url: trimmedUrl
|
|
29838
|
+
};
|
|
29839
|
+
await saveCustomSkillSource(source);
|
|
29840
|
+
return {
|
|
29841
|
+
source,
|
|
29842
|
+
skillCount: skills.length
|
|
29843
|
+
};
|
|
29844
|
+
}
|
|
29845
|
+
|
|
28635
29846
|
// src/features/skills/skills.routes.ts
|
|
28636
29847
|
function createSkillRoutes() {
|
|
28637
29848
|
const router = new Hono16();
|
|
28638
29849
|
router.get("/popular", (c) => {
|
|
28639
|
-
|
|
29850
|
+
const skills = [...skills_popular_default].sort((a, b) => a.name.localeCompare(b.name));
|
|
29851
|
+
return c.json({ skills });
|
|
29852
|
+
});
|
|
29853
|
+
router.get("/sources", async (c) => {
|
|
29854
|
+
return c.json({ sources: await getAllSkillSources() });
|
|
29855
|
+
});
|
|
29856
|
+
router.get("/catalog", async (c) => {
|
|
29857
|
+
const sourceId = c.req.query("sourceId");
|
|
29858
|
+
if (!sourceId) {
|
|
29859
|
+
return c.json({ error: { code: "BAD_REQUEST", message: "sourceId is required" } }, 400);
|
|
29860
|
+
}
|
|
29861
|
+
try {
|
|
29862
|
+
const skills = await fetchSkillCatalogFromSource(sourceId);
|
|
29863
|
+
return c.json({ skills });
|
|
29864
|
+
} catch (error) {
|
|
29865
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29866
|
+
if (message.startsWith("Unknown skill source:") || message.startsWith("Invalid GitHub tree URL:")) {
|
|
29867
|
+
return c.json({ error: { code: "NOT_FOUND", message } }, 404);
|
|
29868
|
+
}
|
|
29869
|
+
return c.json({ error: { code: "INTERNAL_ERROR", message } }, 500);
|
|
29870
|
+
}
|
|
29871
|
+
});
|
|
29872
|
+
router.post("/sources/verify", async (c) => {
|
|
29873
|
+
let body;
|
|
29874
|
+
try {
|
|
29875
|
+
body = await c.req.json();
|
|
29876
|
+
} catch {
|
|
29877
|
+
return c.json({ error: { code: "BAD_REQUEST", message: "Invalid JSON body" } }, 400);
|
|
29878
|
+
}
|
|
29879
|
+
const title = body.title?.trim() ?? "";
|
|
29880
|
+
const url = body.url?.trim() ?? "";
|
|
29881
|
+
if (!title || !url) {
|
|
29882
|
+
return c.json({ error: { code: "BAD_REQUEST", message: "title and url are required" } }, 400);
|
|
29883
|
+
}
|
|
29884
|
+
try {
|
|
29885
|
+
const result = await verifySkillSource(title, url);
|
|
29886
|
+
return c.json(result);
|
|
29887
|
+
} catch (error) {
|
|
29888
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29889
|
+
if (message === "Title is required" || message === "URL is required" || message === "No skills found at this URL" || message.startsWith("Invalid GitHub tree URL:")) {
|
|
29890
|
+
return c.json({ error: { code: "BAD_REQUEST", message } }, 400);
|
|
29891
|
+
}
|
|
29892
|
+
return c.json({ error: { code: "INTERNAL_ERROR", message } }, 500);
|
|
29893
|
+
}
|
|
29894
|
+
});
|
|
29895
|
+
router.put("/sources/:sourceId", async (c) => {
|
|
29896
|
+
const sourceId = c.req.param("sourceId");
|
|
29897
|
+
let body;
|
|
29898
|
+
try {
|
|
29899
|
+
body = await c.req.json();
|
|
29900
|
+
} catch {
|
|
29901
|
+
return c.json({ error: { code: "BAD_REQUEST", message: "Invalid JSON body" } }, 400);
|
|
29902
|
+
}
|
|
29903
|
+
const title = body.title?.trim() ?? "";
|
|
29904
|
+
const url = body.url?.trim() ?? "";
|
|
29905
|
+
if (!title || !url) {
|
|
29906
|
+
return c.json({ error: { code: "BAD_REQUEST", message: "title and url are required" } }, 400);
|
|
29907
|
+
}
|
|
29908
|
+
try {
|
|
29909
|
+
const result = await updateCustomSkillSource(sourceId, title, url);
|
|
29910
|
+
return c.json(result);
|
|
29911
|
+
} catch (error) {
|
|
29912
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29913
|
+
if (message === "Unknown custom skill source") {
|
|
29914
|
+
return c.json({ error: { code: "NOT_FOUND", message } }, 404);
|
|
29915
|
+
}
|
|
29916
|
+
if (message === "Only custom skill sources can be updated" || message === "Title is required" || message === "URL is required" || message === "No skills found at this URL" || message.startsWith("Invalid GitHub tree URL:")) {
|
|
29917
|
+
return c.json({ error: { code: "BAD_REQUEST", message } }, 400);
|
|
29918
|
+
}
|
|
29919
|
+
return c.json({ error: { code: "INTERNAL_ERROR", message } }, 500);
|
|
29920
|
+
}
|
|
29921
|
+
});
|
|
29922
|
+
router.delete("/sources/:sourceId", async (c) => {
|
|
29923
|
+
const sourceId = c.req.param("sourceId");
|
|
29924
|
+
try {
|
|
29925
|
+
await deleteCustomSkillSource(sourceId);
|
|
29926
|
+
return c.json({ success: true });
|
|
29927
|
+
} catch (error) {
|
|
29928
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29929
|
+
if (message === "Unknown custom skill source") {
|
|
29930
|
+
return c.json({ error: { code: "NOT_FOUND", message } }, 404);
|
|
29931
|
+
}
|
|
29932
|
+
if (message === "Only custom skill sources can be deleted") {
|
|
29933
|
+
return c.json({ error: { code: "BAD_REQUEST", message } }, 400);
|
|
29934
|
+
}
|
|
29935
|
+
return c.json({ error: { code: "INTERNAL_ERROR", message } }, 500);
|
|
29936
|
+
}
|
|
29937
|
+
});
|
|
29938
|
+
router.post("/sources/import", async (c) => {
|
|
29939
|
+
let body;
|
|
29940
|
+
try {
|
|
29941
|
+
body = await c.req.json();
|
|
29942
|
+
} catch {
|
|
29943
|
+
return c.json({ error: { code: "BAD_REQUEST", message: "Invalid JSON body" } }, 400);
|
|
29944
|
+
}
|
|
29945
|
+
if (!Array.isArray(body.sources)) {
|
|
29946
|
+
return c.json({ error: { code: "BAD_REQUEST", message: "sources array is required" } }, 400);
|
|
29947
|
+
}
|
|
29948
|
+
try {
|
|
29949
|
+
const sources = await importCustomSkillSources(
|
|
29950
|
+
body.sources.filter(
|
|
29951
|
+
(source) => typeof source?.id === "string" && typeof source?.title === "string" && typeof source?.url === "string"
|
|
29952
|
+
)
|
|
29953
|
+
);
|
|
29954
|
+
return c.json({ sources });
|
|
29955
|
+
} catch (error) {
|
|
29956
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29957
|
+
return c.json({ error: { code: "INTERNAL_ERROR", message } }, 500);
|
|
29958
|
+
}
|
|
29959
|
+
});
|
|
29960
|
+
router.get("/catalog/files", async (c) => {
|
|
29961
|
+
const sourceId = c.req.query("sourceId");
|
|
29962
|
+
const skillId = c.req.query("skillId");
|
|
29963
|
+
if (!sourceId || !skillId) {
|
|
29964
|
+
return c.json(
|
|
29965
|
+
{ error: { code: "BAD_REQUEST", message: "sourceId and skillId are required" } },
|
|
29966
|
+
400
|
|
29967
|
+
);
|
|
29968
|
+
}
|
|
29969
|
+
try {
|
|
29970
|
+
const files = await listCachedCatalogSkillFiles(sourceId, skillId);
|
|
29971
|
+
return c.json({ files });
|
|
29972
|
+
} catch (error) {
|
|
29973
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29974
|
+
if (message.startsWith("Unknown skill source:")) {
|
|
29975
|
+
return c.json({ error: { code: "NOT_FOUND", message } }, 404);
|
|
29976
|
+
}
|
|
29977
|
+
return c.json({ error: { code: "INTERNAL_ERROR", message } }, 500);
|
|
29978
|
+
}
|
|
29979
|
+
});
|
|
29980
|
+
router.get("/catalog/file", async (c) => {
|
|
29981
|
+
const sourceId = c.req.query("sourceId");
|
|
29982
|
+
const skillId = c.req.query("skillId");
|
|
29983
|
+
const relativePath = c.req.query("relativePath");
|
|
29984
|
+
if (!sourceId || !skillId || !relativePath) {
|
|
29985
|
+
return c.json(
|
|
29986
|
+
{
|
|
29987
|
+
error: {
|
|
29988
|
+
code: "BAD_REQUEST",
|
|
29989
|
+
message: "sourceId, skillId, and relativePath are required"
|
|
29990
|
+
}
|
|
29991
|
+
},
|
|
29992
|
+
400
|
|
29993
|
+
);
|
|
29994
|
+
}
|
|
29995
|
+
try {
|
|
29996
|
+
const content = await readCachedCatalogSkillFile(sourceId, skillId, relativePath);
|
|
29997
|
+
return c.json({ content, relativePath });
|
|
29998
|
+
} catch (error) {
|
|
29999
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
30000
|
+
if (message.startsWith("Unknown skill source:") || message.startsWith("Invalid file path:")) {
|
|
30001
|
+
return c.json({ error: { code: "NOT_FOUND", message } }, 404);
|
|
30002
|
+
}
|
|
30003
|
+
return c.json({ error: { code: "INTERNAL_ERROR", message } }, 500);
|
|
30004
|
+
}
|
|
30005
|
+
});
|
|
30006
|
+
router.get("/github-folder/files", async (c) => {
|
|
30007
|
+
const gitHubRepo = c.req.query("gitHubRepo");
|
|
30008
|
+
const gitHubFolder = c.req.query("gitHubFolder");
|
|
30009
|
+
if (!gitHubRepo || !gitHubFolder) {
|
|
30010
|
+
return c.json(
|
|
30011
|
+
{ error: { code: "BAD_REQUEST", message: "gitHubRepo and gitHubFolder are required" } },
|
|
30012
|
+
400
|
|
30013
|
+
);
|
|
30014
|
+
}
|
|
30015
|
+
try {
|
|
30016
|
+
const result = await listCachedCatalogSkillFilesByGithubFolder(gitHubRepo, gitHubFolder);
|
|
30017
|
+
return c.json(result);
|
|
30018
|
+
} catch (error) {
|
|
30019
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
30020
|
+
if (message.startsWith("No catalog source found")) {
|
|
30021
|
+
return c.json({ error: { code: "NOT_FOUND", message } }, 404);
|
|
30022
|
+
}
|
|
30023
|
+
return c.json({ error: { code: "INTERNAL_ERROR", message } }, 500);
|
|
30024
|
+
}
|
|
30025
|
+
});
|
|
30026
|
+
router.get("/github-folder/file", async (c) => {
|
|
30027
|
+
const gitHubRepo = c.req.query("gitHubRepo");
|
|
30028
|
+
const gitHubFolder = c.req.query("gitHubFolder");
|
|
30029
|
+
const relativePath = c.req.query("relativePath");
|
|
30030
|
+
if (!gitHubRepo || !gitHubFolder || !relativePath) {
|
|
30031
|
+
return c.json(
|
|
30032
|
+
{
|
|
30033
|
+
error: {
|
|
30034
|
+
code: "BAD_REQUEST",
|
|
30035
|
+
message: "gitHubRepo, gitHubFolder, and relativePath are required"
|
|
30036
|
+
}
|
|
30037
|
+
},
|
|
30038
|
+
400
|
|
30039
|
+
);
|
|
30040
|
+
}
|
|
30041
|
+
try {
|
|
30042
|
+
const content = await readCachedCatalogSkillFileByGithubFolder(
|
|
30043
|
+
gitHubRepo,
|
|
30044
|
+
gitHubFolder,
|
|
30045
|
+
relativePath
|
|
30046
|
+
);
|
|
30047
|
+
return c.json({ content, relativePath });
|
|
30048
|
+
} catch (error) {
|
|
30049
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
30050
|
+
if (message.startsWith("No catalog source found") || message.startsWith("Invalid file path:")) {
|
|
30051
|
+
return c.json({ error: { code: "NOT_FOUND", message } }, 404);
|
|
30052
|
+
}
|
|
30053
|
+
return c.json({ error: { code: "INTERNAL_ERROR", message } }, 500);
|
|
30054
|
+
}
|
|
28640
30055
|
});
|
|
28641
30056
|
return router;
|
|
28642
30057
|
}
|
|
@@ -28846,7 +30261,7 @@ function getLocalNetworkAddresses() {
|
|
|
28846
30261
|
|
|
28847
30262
|
// src/core/server-bind-mode-store.ts
|
|
28848
30263
|
import { existsSync as existsSync25, mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync } from "fs";
|
|
28849
|
-
import { join as
|
|
30264
|
+
import { join as join37 } from "path";
|
|
28850
30265
|
|
|
28851
30266
|
// src/core/server-bind-mode.ts
|
|
28852
30267
|
var DEFAULT_SERVER_BIND_MODE = "local";
|
|
@@ -28858,7 +30273,7 @@ function getServerBindHostname(mode) {
|
|
|
28858
30273
|
}
|
|
28859
30274
|
|
|
28860
30275
|
// src/core/server-bind-mode-store.ts
|
|
28861
|
-
var SERVER_BIND_MODE_FILE =
|
|
30276
|
+
var SERVER_BIND_MODE_FILE = join37(DATA_DIR, "server-bind-mode.json");
|
|
28862
30277
|
function hasServerBindModeFile() {
|
|
28863
30278
|
return existsSync25(SERVER_BIND_MODE_FILE);
|
|
28864
30279
|
}
|
|
@@ -29212,7 +30627,7 @@ async function clipboardWriteImage(c) {
|
|
|
29212
30627
|
|
|
29213
30628
|
// src/features/image/image-save.route.ts
|
|
29214
30629
|
init_dist();
|
|
29215
|
-
import { join as
|
|
30630
|
+
import { join as join38 } from "path";
|
|
29216
30631
|
var Utils5 = null;
|
|
29217
30632
|
var utilsLoaded5 = false;
|
|
29218
30633
|
async function loadUtils5() {
|
|
@@ -29250,7 +30665,7 @@ async function saveImage(c) {
|
|
|
29250
30665
|
return c.json({ error: "imageUrl or imageData is required" }, 400);
|
|
29251
30666
|
}
|
|
29252
30667
|
const name = filename ?? `generated-image-${Date.now()}.png`;
|
|
29253
|
-
const savePath =
|
|
30668
|
+
const savePath = join38(Utils5.paths.downloads, name);
|
|
29254
30669
|
await Bun.write(savePath, data);
|
|
29255
30670
|
return c.json({ success: true, path: savePath });
|
|
29256
30671
|
} catch (error) {
|
|
@@ -29431,23 +30846,12 @@ function createBrowserRoutes() {
|
|
|
29431
30846
|
|
|
29432
30847
|
// src/features/voice-model/voice-model.routes.ts
|
|
29433
30848
|
import { Hono as Hono24 } from "hono";
|
|
29434
|
-
var
|
|
29435
|
-
default: "https://install.tarsk.io/voice-models/ggml-tiny.en.bin",
|
|
29436
|
-
tiny: "https://install.tarsk.io/voice-models/ggml-tiny.en-q5_1.bin"
|
|
29437
|
-
};
|
|
29438
|
-
function getVoiceModelUrl(model) {
|
|
29439
|
-
if (model === "tiny") {
|
|
29440
|
-
return VOICE_MODEL_URLS.tiny;
|
|
29441
|
-
}
|
|
29442
|
-
return VOICE_MODEL_URLS.default;
|
|
29443
|
-
}
|
|
30849
|
+
var VOICE_MODEL_URL = "https://install.tarsk.io/voice-models/vosk-model-small-en-us-0.15.tar.gz";
|
|
29444
30850
|
function createVoiceModelRoutes() {
|
|
29445
30851
|
const router = new Hono24();
|
|
29446
30852
|
router.get("/download", async (c) => {
|
|
29447
|
-
const selectedModel = c.req.query("model");
|
|
29448
|
-
const modelUrl = getVoiceModelUrl(selectedModel);
|
|
29449
30853
|
try {
|
|
29450
|
-
const response = await fetch(
|
|
30854
|
+
const response = await fetch(VOICE_MODEL_URL);
|
|
29451
30855
|
if (!response.ok) {
|
|
29452
30856
|
return errorResponse(
|
|
29453
30857
|
c,
|
|
@@ -29458,8 +30862,7 @@ function createVoiceModelRoutes() {
|
|
|
29458
30862
|
}
|
|
29459
30863
|
const contentLength = response.headers.get("content-length");
|
|
29460
30864
|
const headers = {
|
|
29461
|
-
"Content-Type": "application/
|
|
29462
|
-
"X-Tarsk-Voice-Model": selectedModel === "tiny" ? "tiny" : "default"
|
|
30865
|
+
"Content-Type": "application/gzip"
|
|
29463
30866
|
};
|
|
29464
30867
|
if (contentLength) {
|
|
29465
30868
|
headers["Content-Length"] = contentLength;
|
|
@@ -29675,7 +31078,7 @@ async function startTarskServer(options) {
|
|
|
29675
31078
|
}
|
|
29676
31079
|
async function startTarskServerInternal(options) {
|
|
29677
31080
|
const { isDebug: isDebug2, publicDir: publicDirOverride } = options;
|
|
29678
|
-
const
|
|
31081
|
+
const initialPort = isDebug2 ? 462 : process.env.PORT ? parseInt(process.env.PORT) : 641;
|
|
29679
31082
|
const app = new Hono28();
|
|
29680
31083
|
app.use("/*", cors());
|
|
29681
31084
|
app.use("/*", async (c, next) => {
|
|
@@ -29687,7 +31090,9 @@ async function startTarskServerInternal(options) {
|
|
|
29687
31090
|
if (c.req.path.startsWith("/api/")) {
|
|
29688
31091
|
const method = c.req.method;
|
|
29689
31092
|
const reqPath = c.req.path;
|
|
29690
|
-
const
|
|
31093
|
+
const hostHeader = c.req.header("host") ?? `localhost:${initialPort}`;
|
|
31094
|
+
const requestUrl = new URL(c.req.url);
|
|
31095
|
+
const fullUrl = `${requestUrl.protocol}//${hostHeader}${reqPath}`;
|
|
29691
31096
|
const isThreadMessages = reqPath.startsWith("/api/threads/") && reqPath.endsWith("/messages");
|
|
29692
31097
|
if (!fullUrl.includes("processing") && !reqPath.startsWith("/api/logs") && !isThreadMessages) {
|
|
29693
31098
|
console.log(`${method} ${fullUrl}`);
|
|
@@ -29697,17 +31102,17 @@ async function startTarskServerInternal(options) {
|
|
|
29697
31102
|
});
|
|
29698
31103
|
const dataDir = getDataDir();
|
|
29699
31104
|
const metadataManager = new MetadataManager(dataDir);
|
|
29700
|
-
const
|
|
31105
|
+
const gitManager2 = new GitManagerImpl();
|
|
29701
31106
|
const processingStateManager = new ProcessingStateManagerImpl();
|
|
29702
31107
|
const projectManager = new ProjectManagerImpl(
|
|
29703
31108
|
dataDir,
|
|
29704
31109
|
metadataManager,
|
|
29705
|
-
|
|
31110
|
+
gitManager2,
|
|
29706
31111
|
processingStateManager
|
|
29707
31112
|
);
|
|
29708
31113
|
const threadManager = new ThreadManagerImpl(
|
|
29709
31114
|
metadataManager,
|
|
29710
|
-
|
|
31115
|
+
gitManager2,
|
|
29711
31116
|
processingStateManager,
|
|
29712
31117
|
dataDir
|
|
29713
31118
|
);
|
|
@@ -29720,7 +31125,8 @@ async function startTarskServerInternal(options) {
|
|
|
29720
31125
|
return c.json({
|
|
29721
31126
|
status: "ok",
|
|
29722
31127
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
29723
|
-
service: "project-threads-manager-cli"
|
|
31128
|
+
service: "project-threads-manager-cli",
|
|
31129
|
+
isDevMode: port !== initialPort
|
|
29724
31130
|
});
|
|
29725
31131
|
});
|
|
29726
31132
|
app.get("/api/programs", (c) => {
|
|
@@ -29733,10 +31139,10 @@ async function startTarskServerInternal(options) {
|
|
|
29733
31139
|
processingThreadIds: processingStateManager.getProcessingThreadIds()
|
|
29734
31140
|
});
|
|
29735
31141
|
});
|
|
29736
|
-
app.route("/api/projects", createProjectRoutes(projectManager, threadManager));
|
|
31142
|
+
app.route("/api/projects", createProjectRoutes(projectManager, threadManager, metadataManager));
|
|
29737
31143
|
app.route("/api/projects", createRunRoutes(projectManager));
|
|
29738
31144
|
app.route("/api/projects", createProjectTodosRoutes(metadataManager));
|
|
29739
|
-
app.route("/api/threads", createThreadRoutes(threadManager,
|
|
31145
|
+
app.route("/api/threads", createThreadRoutes(threadManager, gitManager2, conversationManager));
|
|
29740
31146
|
app.route("/api/threads", createUserTaskRoutes());
|
|
29741
31147
|
app.route(
|
|
29742
31148
|
"/api/chat",
|
|
@@ -29786,16 +31192,37 @@ async function startTarskServerInternal(options) {
|
|
|
29786
31192
|
`No static frontend assets found. Expected one of: ${prodPublicDir}, ${devCopiedPublicDir}, ${appDistDir}. Build the app first with \`cd ../app && bun run build\`.`
|
|
29787
31193
|
);
|
|
29788
31194
|
}
|
|
31195
|
+
function isStaticAssetPath(requestPath) {
|
|
31196
|
+
if (requestPath.startsWith("/assets/")) {
|
|
31197
|
+
return true;
|
|
31198
|
+
}
|
|
31199
|
+
const lastSegment = requestPath.split("/").pop() ?? "";
|
|
31200
|
+
return lastSegment.includes(".") && !lastSegment.endsWith(".html");
|
|
31201
|
+
}
|
|
31202
|
+
function setNoCacheIndexHeaders(c) {
|
|
31203
|
+
c.header("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
31204
|
+
}
|
|
29789
31205
|
app.use("/*", async (c, next) => {
|
|
29790
31206
|
if (c.req.path.startsWith("/api/")) {
|
|
29791
31207
|
return next();
|
|
29792
31208
|
}
|
|
29793
|
-
return serveStatic({
|
|
31209
|
+
return serveStatic({
|
|
31210
|
+
root: resolvedPublicDir,
|
|
31211
|
+
onFound: (_path, context) => {
|
|
31212
|
+
if (context.req.path === "/" || context.req.path === "/index.html") {
|
|
31213
|
+
setNoCacheIndexHeaders(context);
|
|
31214
|
+
}
|
|
31215
|
+
}
|
|
31216
|
+
})(c, next);
|
|
29794
31217
|
});
|
|
29795
31218
|
app.get("*", async (c, next) => {
|
|
29796
31219
|
if (c.req.path.startsWith("/api/")) {
|
|
29797
31220
|
return next();
|
|
29798
31221
|
}
|
|
31222
|
+
if (isStaticAssetPath(c.req.path)) {
|
|
31223
|
+
return next();
|
|
31224
|
+
}
|
|
31225
|
+
setNoCacheIndexHeaders(c);
|
|
29799
31226
|
return serveStatic({
|
|
29800
31227
|
path: path5.join(resolvedPublicDir, "index.html")
|
|
29801
31228
|
})(c, next);
|
|
@@ -29812,16 +31239,22 @@ async function startTarskServerInternal(options) {
|
|
|
29812
31239
|
});
|
|
29813
31240
|
const serverBindMode = readServerBindMode();
|
|
29814
31241
|
const hostname2 = getServerBindHostname(serverBindMode);
|
|
29815
|
-
const
|
|
29816
|
-
const bound = await listenForTarskServer({
|
|
31242
|
+
const port = await listenForTarskServer({
|
|
29817
31243
|
fetch: app.fetch,
|
|
29818
31244
|
hostname: hostname2,
|
|
29819
|
-
|
|
31245
|
+
onPortConflict: (candidatePort) => {
|
|
31246
|
+
process.stdout.write(
|
|
31247
|
+
`Port ${candidatePort} is already in use, trying ${candidatePort + 1}.
|
|
31248
|
+
`
|
|
31249
|
+
);
|
|
31250
|
+
},
|
|
31251
|
+
onListening: (listeningPort) => {
|
|
31252
|
+
const url = `http://localhost:${listeningPort}`;
|
|
29820
31253
|
process.stdout.write(`Tarsk started on ${url}
|
|
29821
31254
|
`);
|
|
29822
31255
|
if (serverBindMode === "network") {
|
|
29823
31256
|
for (const address of getLocalNetworkAddresses()) {
|
|
29824
|
-
process.stdout.write(` Network: http://${address}:${
|
|
31257
|
+
process.stdout.write(` Network: http://${address}:${listeningPort}
|
|
29825
31258
|
`);
|
|
29826
31259
|
}
|
|
29827
31260
|
}
|
|
@@ -29830,38 +31263,48 @@ async function startTarskServerInternal(options) {
|
|
|
29830
31263
|
});
|
|
29831
31264
|
}
|
|
29832
31265
|
},
|
|
29833
|
-
port
|
|
31266
|
+
port: initialPort
|
|
29834
31267
|
});
|
|
29835
|
-
|
|
29836
|
-
|
|
29837
|
-
|
|
29838
|
-
|
|
31268
|
+
return {
|
|
31269
|
+
url: `http://localhost:${port}`,
|
|
31270
|
+
port,
|
|
31271
|
+
bound: true,
|
|
31272
|
+
isDevMode: port !== initialPort
|
|
31273
|
+
};
|
|
29839
31274
|
}
|
|
29840
31275
|
async function listenForTarskServer(options) {
|
|
29841
|
-
|
|
29842
|
-
|
|
29843
|
-
|
|
29844
|
-
|
|
29845
|
-
|
|
29846
|
-
|
|
29847
|
-
|
|
29848
|
-
|
|
29849
|
-
|
|
29850
|
-
|
|
31276
|
+
let port = options.port;
|
|
31277
|
+
while (true) {
|
|
31278
|
+
const server = createAdaptorServer({
|
|
31279
|
+
fetch: options.fetch,
|
|
31280
|
+
hostname: options.hostname,
|
|
31281
|
+
port
|
|
31282
|
+
});
|
|
31283
|
+
const result = await new Promise((resolve8, reject) => {
|
|
31284
|
+
server.once("error", (error) => {
|
|
31285
|
+
if (error.code === "EADDRINUSE") {
|
|
31286
|
+
resolve8("in-use");
|
|
31287
|
+
return;
|
|
31288
|
+
}
|
|
31289
|
+
startPromise = null;
|
|
31290
|
+
reject(error);
|
|
31291
|
+
});
|
|
31292
|
+
function onListening() {
|
|
31293
|
+
resolve8("listening");
|
|
31294
|
+
}
|
|
31295
|
+
if (options.hostname) {
|
|
31296
|
+
server.listen(port, options.hostname, onListening);
|
|
31297
|
+
} else {
|
|
31298
|
+
server.listen(port, onListening);
|
|
29851
31299
|
}
|
|
29852
|
-
startPromise = null;
|
|
29853
|
-
reject(error);
|
|
29854
31300
|
});
|
|
29855
|
-
|
|
29856
|
-
options.onListening();
|
|
29857
|
-
|
|
31301
|
+
if (result === "listening") {
|
|
31302
|
+
options.onListening(port);
|
|
31303
|
+
return port;
|
|
29858
31304
|
}
|
|
29859
|
-
|
|
29860
|
-
|
|
29861
|
-
|
|
29862
|
-
server.listen(options.port, onListening);
|
|
29863
|
-
}
|
|
29864
|
-
});
|
|
31305
|
+
options.onPortConflict(port);
|
|
31306
|
+
port += 1;
|
|
31307
|
+
}
|
|
29865
31308
|
}
|
|
29866
31309
|
|
|
29867
31310
|
// src/index.ts
|