neko-harness 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/dist/client/assets/index-Cdi87SA6.css +2 -0
- package/dist/client/assets/{index-zaSSWfyH.js → index-DYhp2f2g.js} +58 -58
- package/dist/client/index.html +2 -2
- package/dist/gateway.js +646 -127
- package/dist/neko.js +1375 -438
- package/package.json +1 -1
- package/dist/client/assets/index-Bwk2sNhZ.css +0 -2
package/dist/neko.js
CHANGED
|
@@ -80,7 +80,7 @@ function updateAction(input) {
|
|
|
80
80
|
return "install";
|
|
81
81
|
return "none";
|
|
82
82
|
}
|
|
83
|
-
var RUNNING_VERSION = "0.4.
|
|
83
|
+
var RUNNING_VERSION = "0.4.7", APPLICATION_PROTOCOL = 8, ApplicationProtocolError;
|
|
84
84
|
var init_application_version = __esm(() => {
|
|
85
85
|
ApplicationProtocolError = class ApplicationProtocolError extends Error {
|
|
86
86
|
constructor() {
|
|
@@ -90,8 +90,35 @@ var init_application_version = __esm(() => {
|
|
|
90
90
|
};
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
+
// packages/core/src/rename-with-retry.ts
|
|
94
|
+
import { rename } from "fs/promises";
|
|
95
|
+
async function renameWithRetry(from, to, deps = {}) {
|
|
96
|
+
const doRename = deps.rename ?? rename;
|
|
97
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
98
|
+
let delay = FIRST_DELAY_MS;
|
|
99
|
+
let waited = 0;
|
|
100
|
+
for (;; ) {
|
|
101
|
+
try {
|
|
102
|
+
await doRename(from, to);
|
|
103
|
+
return;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
const code = error.code;
|
|
106
|
+
if (typeof code !== "string" || !TRANSIENT.has(code) || waited >= MAX_TOTAL_MS)
|
|
107
|
+
throw error;
|
|
108
|
+
const step = Math.min(delay, MAX_TOTAL_MS - waited);
|
|
109
|
+
await sleep(step);
|
|
110
|
+
waited += step;
|
|
111
|
+
delay *= 2;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
var TRANSIENT, FIRST_DELAY_MS = 10, MAX_TOTAL_MS = 1000;
|
|
116
|
+
var init_rename_with_retry = __esm(() => {
|
|
117
|
+
TRANSIENT = new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
118
|
+
});
|
|
119
|
+
|
|
93
120
|
// packages/core/src/application-updates.ts
|
|
94
|
-
import { mkdir, readFile,
|
|
121
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
95
122
|
import { homedir } from "os";
|
|
96
123
|
import { dirname, join } from "path";
|
|
97
124
|
import { fileURLToPath } from "url";
|
|
@@ -220,7 +247,7 @@ class UpdateChecker {
|
|
|
220
247
|
try {
|
|
221
248
|
await mkdir(dirname(this.#cachePath), { recursive: true });
|
|
222
249
|
await writeFile(temporary, JSON.stringify({ latestVersion: value.version, checkedAt: now }), { mode: 384 });
|
|
223
|
-
await
|
|
250
|
+
await renameWithRetry(temporary, this.#cachePath);
|
|
224
251
|
} catch {} finally {
|
|
225
252
|
await rm(temporary, { force: true }).catch(() => {});
|
|
226
253
|
}
|
|
@@ -238,6 +265,7 @@ class UpdateChecker {
|
|
|
238
265
|
var INSTALL_MANIFEST;
|
|
239
266
|
var init_application_updates = __esm(() => {
|
|
240
267
|
init_application_version();
|
|
268
|
+
init_rename_with_retry();
|
|
241
269
|
INSTALL_MANIFEST = stableVersion(RUNNING_VERSION) ? fileURLToPath(new URL("../package.json", import.meta.url)) : undefined;
|
|
242
270
|
});
|
|
243
271
|
|
|
@@ -665,6 +693,16 @@ function validate(config, source) {
|
|
|
665
693
|
throw new Error(`neko config: ${source}: "skills.marketUrl" must be a string.`);
|
|
666
694
|
}
|
|
667
695
|
}
|
|
696
|
+
const shell = config.shell;
|
|
697
|
+
if (shell !== undefined) {
|
|
698
|
+
if (typeof shell !== "object" || shell === null || Array.isArray(shell)) {
|
|
699
|
+
throw new Error(`neko config: ${source}: "shell" must be an object.`);
|
|
700
|
+
}
|
|
701
|
+
const { path } = shell;
|
|
702
|
+
if (path !== undefined && typeof path !== "string") {
|
|
703
|
+
throw new Error(`neko config: ${source}: "shell.path" must be a string.`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
668
706
|
const webSearch = config.webSearch;
|
|
669
707
|
if (webSearch !== undefined) {
|
|
670
708
|
if (typeof webSearch !== "object" || webSearch === null || Array.isArray(webSearch)) {
|
|
@@ -691,30 +729,85 @@ var init_config = __esm(() => {
|
|
|
691
729
|
});
|
|
692
730
|
|
|
693
731
|
// packages/core/src/credential-providers.ts
|
|
732
|
+
function isModelProvider(id) {
|
|
733
|
+
return MODEL_PROVIDERS.some((item) => item.id === id);
|
|
734
|
+
}
|
|
694
735
|
function credentialProvider(id) {
|
|
695
736
|
const spec = CREDENTIAL_PROVIDERS.find((item) => item.id === id);
|
|
696
737
|
if (!spec)
|
|
697
738
|
throw new Error(`Unknown credential provider: ${id}`);
|
|
698
739
|
return spec;
|
|
699
740
|
}
|
|
700
|
-
|
|
741
|
+
function normalizeEnabledProviders(saved) {
|
|
742
|
+
const wanted = new Set([...LOCKED_PROVIDERS, ...saved ?? DEFAULT_ENABLED_PROVIDERS]);
|
|
743
|
+
return MODEL_PROVIDERS.map((item) => item.id).filter((id) => wanted.has(id));
|
|
744
|
+
}
|
|
745
|
+
var model = (id, name, envVar, signupUrl) => ({ id, kind: "model", name, envVar, signupUrl, keyless: false }), MODEL_PROVIDERS, SEARCH_CREDENTIAL_PROVIDERS, CREDENTIAL_PROVIDERS, DEFAULT_ENABLED_PROVIDERS, LOCKED_PROVIDERS;
|
|
701
746
|
var init_credential_providers = __esm(() => {
|
|
747
|
+
MODEL_PROVIDERS = [
|
|
748
|
+
model("deepseek", "DeepSeek", "DEEPSEEK_API_KEY", "https://platform.deepseek.com/api_keys"),
|
|
749
|
+
model("openrouter", "OpenRouter", "OPENROUTER_API_KEY", "https://openrouter.ai/keys"),
|
|
750
|
+
model("opencode-go", "OpenCode Go", "OPENCODE_API_KEY", "https://opencode.ai/auth"),
|
|
751
|
+
model("opencode", "OpenCode Zen", "OPENCODE_API_KEY", "https://opencode.ai/auth"),
|
|
752
|
+
model("openai", "OpenAI", "OPENAI_API_KEY", "https://platform.openai.com/api-keys"),
|
|
753
|
+
model("google", "Google", "GEMINI_API_KEY", "https://aistudio.google.com/apikey"),
|
|
754
|
+
model("groq", "Groq", "GROQ_API_KEY", "https://console.groq.com/keys"),
|
|
755
|
+
model("mistral", "Mistral", "MISTRAL_API_KEY", "https://console.mistral.ai/api-keys"),
|
|
756
|
+
model("xai", "xAI", "XAI_API_KEY", "https://console.x.ai"),
|
|
757
|
+
model("together", "Together", "TOGETHER_API_KEY", "https://api.together.ai/settings/api-keys"),
|
|
758
|
+
model("fireworks", "Fireworks", "FIREWORKS_API_KEY", "https://fireworks.ai/account/api-keys"),
|
|
759
|
+
model("cerebras", "Cerebras", "CEREBRAS_API_KEY", "https://cloud.cerebras.ai"),
|
|
760
|
+
model("nvidia", "NVIDIA", "NVIDIA_API_KEY", "https://build.nvidia.com/settings/api-keys"),
|
|
761
|
+
model("huggingface", "Hugging Face", "HF_TOKEN", "https://huggingface.co/settings/tokens"),
|
|
762
|
+
model("vercel-ai-gateway", "Vercel AI Gateway", "AI_GATEWAY_API_KEY", "https://vercel.com/ai-gateway"),
|
|
763
|
+
model("moonshotai", "Moonshot AI", "MOONSHOT_API_KEY", "https://platform.moonshot.ai/console/api-keys"),
|
|
764
|
+
model("moonshotai-cn", "Moonshot AI CN", "MOONSHOT_API_KEY", "https://platform.moonshot.cn/console/api-keys"),
|
|
765
|
+
model("minimax", "MiniMax", "MINIMAX_API_KEY", "https://platform.minimax.io"),
|
|
766
|
+
model("minimax-cn", "MiniMax CN", "MINIMAX_CN_API_KEY", "https://platform.minimaxi.com"),
|
|
767
|
+
model("zai", "Z.AI", "ZAI_API_KEY", "https://z.ai/manage-apikey/apikey-list"),
|
|
768
|
+
model("zai-coding-cn", "Z.AI Coding CN", "ZAI_CODING_CN_API_KEY", "https://bigmodel.cn/usercenter/proj-mgmt/apikeys"),
|
|
769
|
+
model("qwen-token-plan", "Qwen Token Plan", "QWEN_TOKEN_PLAN_API_KEY", "https://modelstudio.console.alibabacloud.com"),
|
|
770
|
+
model("qwen-token-plan-cn", "Qwen Token Plan CN", "QWEN_TOKEN_PLAN_CN_API_KEY", "https://bailian.console.aliyun.com"),
|
|
771
|
+
model("qwen-token-plan-individual", "Qwen Token Plan Individual", "QWEN_TOKEN_PLAN_API_KEY", "https://modelstudio.console.alibabacloud.com"),
|
|
772
|
+
model("xiaomi", "Xiaomi MiMo", "XIAOMI_API_KEY", "https://platform.xiaomimimo.com"),
|
|
773
|
+
model("xiaomi-token-plan-cn", "Xiaomi Token Plan CN", "XIAOMI_TOKEN_PLAN_CN_API_KEY", "https://platform.xiaomimimo.com"),
|
|
774
|
+
model("xiaomi-token-plan-ams", "Xiaomi Token Plan AMS", "XIAOMI_TOKEN_PLAN_AMS_API_KEY", "https://platform.xiaomimimo.com"),
|
|
775
|
+
model("xiaomi-token-plan-sgp", "Xiaomi Token Plan SGP", "XIAOMI_TOKEN_PLAN_SGP_API_KEY", "https://platform.xiaomimimo.com"),
|
|
776
|
+
model("baseten", "Baseten", "BASETEN_API_KEY", "https://app.baseten.co/settings/api_keys"),
|
|
777
|
+
model("ant-ling", "Ant Ling", "ANT_LING_API_KEY", "https://ling.tbox.cn"),
|
|
778
|
+
model("kimi-coding", "Kimi For Coding", "KIMI_API_KEY", "https://www.kimi.com/code/console"),
|
|
779
|
+
model("meta", "Meta", "META_API_KEY", "https://llama.developer.meta.com")
|
|
780
|
+
];
|
|
781
|
+
SEARCH_CREDENTIAL_PROVIDERS = [
|
|
782
|
+
{
|
|
783
|
+
id: "tavily",
|
|
784
|
+
kind: "search",
|
|
785
|
+
name: "Tavily",
|
|
786
|
+
envVar: "TAVILY_API_KEY",
|
|
787
|
+
signupUrl: "https://app.tavily.com",
|
|
788
|
+
keyless: true
|
|
789
|
+
}
|
|
790
|
+
];
|
|
702
791
|
CREDENTIAL_PROVIDERS = [
|
|
703
|
-
|
|
704
|
-
|
|
792
|
+
...MODEL_PROVIDERS,
|
|
793
|
+
...SEARCH_CREDENTIAL_PROVIDERS
|
|
705
794
|
];
|
|
795
|
+
DEFAULT_ENABLED_PROVIDERS = ["deepseek"];
|
|
796
|
+
LOCKED_PROVIDERS = ["deepseek"];
|
|
706
797
|
});
|
|
707
798
|
|
|
708
799
|
// packages/core/src/credentials.ts
|
|
709
|
-
import { chmod, mkdir as mkdir3, open, readFile as readFile3,
|
|
800
|
+
import { chmod, mkdir as mkdir3, open, readFile as readFile3, stat as stat2, unlink } from "fs/promises";
|
|
710
801
|
import { homedir as homedir3 } from "os";
|
|
711
802
|
import { dirname as dirname4, join as join4 } from "path";
|
|
712
803
|
import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
|
|
713
804
|
|
|
714
805
|
class FileCredentialStore {
|
|
715
806
|
path;
|
|
716
|
-
|
|
807
|
+
#renameDeps;
|
|
808
|
+
constructor(path = DEFAULT_CREDENTIALS_PATH, renameDeps = {}) {
|
|
717
809
|
this.path = path;
|
|
810
|
+
this.#renameDeps = renameDeps;
|
|
718
811
|
}
|
|
719
812
|
async#read() {
|
|
720
813
|
try {
|
|
@@ -744,6 +837,7 @@ class FileCredentialStore {
|
|
|
744
837
|
await mkdir3(dirname4(this.path), { recursive: true, mode: 448 });
|
|
745
838
|
const lockPath = `${this.path}.lock`;
|
|
746
839
|
let lock;
|
|
840
|
+
let reclaimed = false;
|
|
747
841
|
for (let attempt = 0;; attempt++) {
|
|
748
842
|
try {
|
|
749
843
|
lock = await open(lockPath, "wx", 384);
|
|
@@ -751,6 +845,16 @@ class FileCredentialStore {
|
|
|
751
845
|
} catch (error) {
|
|
752
846
|
if (error.code !== "EEXIST" || attempt >= 100)
|
|
753
847
|
throw new Error("Credential storage is busy or unavailable.");
|
|
848
|
+
if (!reclaimed) {
|
|
849
|
+
const info = await stat2(lockPath).catch(() => {
|
|
850
|
+
return;
|
|
851
|
+
});
|
|
852
|
+
if (info && Date.now() - info.mtimeMs > STALE_LOCK_MS) {
|
|
853
|
+
reclaimed = true;
|
|
854
|
+
await unlink(lockPath).catch(() => {});
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
754
858
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
755
859
|
}
|
|
756
860
|
}
|
|
@@ -772,7 +876,7 @@ class FileCredentialStore {
|
|
|
772
876
|
} finally {
|
|
773
877
|
await file.close();
|
|
774
878
|
}
|
|
775
|
-
await
|
|
879
|
+
await renameWithRetry(temporary, this.path, this.#renameDeps);
|
|
776
880
|
return result;
|
|
777
881
|
} catch (error) {
|
|
778
882
|
if (callbackFailed)
|
|
@@ -798,8 +902,9 @@ class FileCredentialStore {
|
|
|
798
902
|
});
|
|
799
903
|
}
|
|
800
904
|
}
|
|
801
|
-
var DEFAULT_CREDENTIALS_PATH;
|
|
905
|
+
var STALE_LOCK_MS = 30000, DEFAULT_CREDENTIALS_PATH;
|
|
802
906
|
var init_credentials = __esm(() => {
|
|
907
|
+
init_rename_with_retry();
|
|
803
908
|
init_credential_providers();
|
|
804
909
|
DEFAULT_CREDENTIALS_PATH = join4(homedir3(), ".neko", "credentials.json");
|
|
805
910
|
});
|
|
@@ -1521,7 +1626,7 @@ async function acquireSlot(tx, round, cwd) {
|
|
|
1521
1626
|
slots.items.push({ taskId: round.taskId, parentId: round.parentId, cwd, coder: round.role === "coder" });
|
|
1522
1627
|
return true;
|
|
1523
1628
|
}
|
|
1524
|
-
function createSubagentExtension(prepare, discard) {
|
|
1629
|
+
function createSubagentExtension(prepare, discard, guard) {
|
|
1525
1630
|
const finish = async (tx, input, status, report) => {
|
|
1526
1631
|
const round = (await tx.doc(DelegationsDoc, input.parentId)).items.find((r) => r.taskId === input.taskId);
|
|
1527
1632
|
round.status = status;
|
|
@@ -1665,6 +1770,8 @@ function createSubagentExtension(prepare, discard) {
|
|
|
1665
1770
|
if (!args.task.trim() || !args.title.trim() || Buffer.byteLength(args.task) > 32768)
|
|
1666
1771
|
throw new Error("Task/title must be nonempty; task limit is 32 KiB.");
|
|
1667
1772
|
const agent = await api.agent(context);
|
|
1773
|
+
if (agent.model)
|
|
1774
|
+
await guard?.(agent.model);
|
|
1668
1775
|
const round = await api.commit(async (tx) => {
|
|
1669
1776
|
const rounds = await refreshDelegations(tx, api.conversationId);
|
|
1670
1777
|
const receipt = rounds.find((r) => r.taskId === api.taskId);
|
|
@@ -2842,7 +2949,7 @@ var init_agent_file_search = __esm(() => {
|
|
|
2842
2949
|
complete = false;
|
|
2843
2950
|
continue;
|
|
2844
2951
|
}
|
|
2845
|
-
const lines = new TextDecoder("utf-8", { fatal: true }).decode(data).split(/\r?\n/);
|
|
2952
|
+
const lines = new TextDecoder("utf-8", { fatal: true }).decode(data).replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
2846
2953
|
for (let i = 0;i < lines.length; i++) {
|
|
2847
2954
|
ctx.abortSignal?.throwIfAborted();
|
|
2848
2955
|
const value = lines[i];
|
|
@@ -3232,8 +3339,11 @@ class JobManager {
|
|
|
3232
3339
|
outcome = { status: "failed", exitCode: null, reason: "timeout" };
|
|
3233
3340
|
else if (result.error.code === "aborted")
|
|
3234
3341
|
outcome = { status: "stopped", exitCode: null, reason: "aborted" };
|
|
3235
|
-
else
|
|
3342
|
+
else {
|
|
3343
|
+
entry.buffer.append(`${result.error.message}
|
|
3344
|
+
`);
|
|
3236
3345
|
outcome = { status: "failed", exitCode: null, reason: "spawn-error" };
|
|
3346
|
+
}
|
|
3237
3347
|
}
|
|
3238
3348
|
if (this.#closing) {
|
|
3239
3349
|
await new Promise((resolve) => {
|
|
@@ -3641,10 +3751,13 @@ var init_invoke = __esm(() => {
|
|
|
3641
3751
|
|
|
3642
3752
|
// packages/core/src/prompt/prompt.ts
|
|
3643
3753
|
import { defineExtension as defineExtension5, section } from "@earendil-works/pi-durable";
|
|
3644
|
-
var KEYS, PREAMBLE = "You are an expert coding assistant operating inside neko, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.", TOOL_GUIDELINES, GENERIC_RULES,
|
|
3754
|
+
var KEYS, PREAMBLE = "You are an expert coding assistant operating inside neko, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.", TOOL_GUIDELINES, GENERIC_RULES, WINDOWS_SHELL_RULE = "On Windows, commands run in Git Bash (not PowerShell, cmd or WSL); write paths with forward slashes, e.g. D:/code or /d/code.", toolRules = (name, platform) => {
|
|
3755
|
+
const rules = TOOL_GUIDELINES[name] ?? [];
|
|
3756
|
+
return platform === "win32" && (name === "bash" || name === "job") ? [...rules, WINDOWS_SHELL_RULE] : rules;
|
|
3757
|
+
}, buildRules = (toolNames, platform) => {
|
|
3645
3758
|
const rules = [];
|
|
3646
3759
|
const seen = new Set;
|
|
3647
|
-
for (const rule of [...toolNames.flatMap((name) =>
|
|
3760
|
+
for (const rule of [...toolNames.flatMap((name) => toolRules(name, platform)), ...GENERIC_RULES]) {
|
|
3648
3761
|
if (seen.has(rule))
|
|
3649
3762
|
continue;
|
|
3650
3763
|
seen.add(rule);
|
|
@@ -3659,7 +3772,7 @@ ${content}
|
|
|
3659
3772
|
</project_instructions>`)
|
|
3660
3773
|
].join(`
|
|
3661
3774
|
|
|
3662
|
-
`), createNekoPrompt = (loadResources) => {
|
|
3775
|
+
`), createNekoPrompt = (loadResources, platform = process.platform) => {
|
|
3663
3776
|
const built = new WeakMap;
|
|
3664
3777
|
const build = async (input, context) => {
|
|
3665
3778
|
const cwd = input.agent.cwd;
|
|
@@ -3667,7 +3780,7 @@ ${content}
|
|
|
3667
3780
|
const resources = cwd ? await loadResources(input.conversationId, cwd) : { contextFiles: [], skills: [] };
|
|
3668
3781
|
const sections = {
|
|
3669
3782
|
preamble: role ? ROLE_PROMPTS[role] + SUBAGENT_DELIVERY : PREAMBLE,
|
|
3670
|
-
rules: buildRules(input.agent.tools.map((tool) => tool.name)),
|
|
3783
|
+
rules: buildRules(input.agent.tools.map((tool) => tool.name), platform),
|
|
3671
3784
|
project_context: renderProjectContext(resources.contextFiles),
|
|
3672
3785
|
skills: formatSkillsForPrompt(resources.skills) || undefined,
|
|
3673
3786
|
cwd: cwd?.replace(/\\/g, "/")
|
|
@@ -3746,7 +3859,7 @@ var userAgentsFile = (home) => join6(home, ".agents", "AGENTS.md"), userSkillsDi
|
|
|
3746
3859
|
const contextFiles = [];
|
|
3747
3860
|
for (const path of [userAgentsFile(home), workspaceAgentsFile(workspace)]) {
|
|
3748
3861
|
try {
|
|
3749
|
-
const content = await readFile5(path, "utf8");
|
|
3862
|
+
const content = (await readFile5(path, "utf8")).replace(/^\uFEFF/, "");
|
|
3750
3863
|
if (Buffer.byteLength(content, "utf8") > 64 * 1024)
|
|
3751
3864
|
report({
|
|
3752
3865
|
path,
|
|
@@ -4250,7 +4363,7 @@ function mergeUsage(base, extra) {
|
|
|
4250
4363
|
}
|
|
4251
4364
|
return result;
|
|
4252
4365
|
}
|
|
4253
|
-
function namingTask() {
|
|
4366
|
+
function namingTask(canUse) {
|
|
4254
4367
|
let active = 0;
|
|
4255
4368
|
const waits = new Set;
|
|
4256
4369
|
return defineTask3({
|
|
@@ -4287,7 +4400,7 @@ function namingTask() {
|
|
|
4287
4400
|
try {
|
|
4288
4401
|
const saved = await runtime.snapshot(SessionMetadataDoc, runtime.conversationId, context);
|
|
4289
4402
|
const model = saved?.model && runtime.models.getModel(saved.model.provider, saved.model.modelId);
|
|
4290
|
-
if (!saved || !model || saved.attempts >= 2 || !getSupportedThinkingLevels(model).includes("off")) {
|
|
4403
|
+
if (!saved || !model || saved.attempts >= 2 || !getSupportedThinkingLevels(model).includes("off") || !await canUse(model.provider, model.id)) {
|
|
4291
4404
|
await runtime.commit(async (tx) => {
|
|
4292
4405
|
(await tx.doc(SessionMetadataDoc, runtime.conversationId)).naming = "fallback";
|
|
4293
4406
|
return { status: "terminal", outcome: { status: "completed", result: null } };
|
|
@@ -4367,12 +4480,13 @@ class SessionMetadataManager {
|
|
|
4367
4480
|
#known = new Set;
|
|
4368
4481
|
#unsubscribe;
|
|
4369
4482
|
#live = new Map;
|
|
4370
|
-
task
|
|
4483
|
+
task;
|
|
4371
4484
|
harness;
|
|
4372
4485
|
storage;
|
|
4373
4486
|
context;
|
|
4374
4487
|
report;
|
|
4375
|
-
constructor(harness, storage, context, registry, report = console.error) {
|
|
4488
|
+
constructor(harness, storage, context, registry, report = console.error, canUseModel = async () => true) {
|
|
4489
|
+
this.task = namingTask(canUseModel);
|
|
4376
4490
|
this.harness = harness;
|
|
4377
4491
|
this.storage = storage;
|
|
4378
4492
|
this.context = context;
|
|
@@ -5215,11 +5329,153 @@ var init_tavily = __esm(() => {
|
|
|
5215
5329
|
init_http();
|
|
5216
5330
|
});
|
|
5217
5331
|
|
|
5332
|
+
// packages/core/src/windows-shell.ts
|
|
5333
|
+
import { spawnSync } from "child_process";
|
|
5334
|
+
import { statSync as statSync2 } from "fs";
|
|
5335
|
+
import { win32 } from "path";
|
|
5336
|
+
function isWslBashPath(path) {
|
|
5337
|
+
const normalized = path.replace(/\//g, "\\").toLowerCase();
|
|
5338
|
+
return /^[a-z]:\\windows\\(?:system32|sysnative)\\bash\.exe$/.test(normalized) || /\\microsoft\\windowsapps\\(?:[^\\]+\\)*bash\.exe$/.test(normalized);
|
|
5339
|
+
}
|
|
5340
|
+
function envValue(env, name) {
|
|
5341
|
+
const wanted = name.toLowerCase();
|
|
5342
|
+
for (const [key, value] of Object.entries(env))
|
|
5343
|
+
if (key.toLowerCase() === wanted && value)
|
|
5344
|
+
return value;
|
|
5345
|
+
return;
|
|
5346
|
+
}
|
|
5347
|
+
function bashBesideGit(gitExe) {
|
|
5348
|
+
const dir = win32.dirname(gitExe);
|
|
5349
|
+
const parent = win32.dirname(dir);
|
|
5350
|
+
const name = win32.basename(dir).toLowerCase();
|
|
5351
|
+
if (name === "cmd")
|
|
5352
|
+
return win32.join(parent, "bin", "bash.exe");
|
|
5353
|
+
if (name === "bin") {
|
|
5354
|
+
const root = win32.basename(parent).toLowerCase() === "mingw64" ? win32.dirname(parent) : parent;
|
|
5355
|
+
return win32.join(root, "bin", "bash.exe");
|
|
5356
|
+
}
|
|
5357
|
+
return;
|
|
5358
|
+
}
|
|
5359
|
+
function configProblem(path, kind) {
|
|
5360
|
+
if (isWslBashPath(path))
|
|
5361
|
+
return "it is WSL's bash, which is not supported (commands must run in Git Bash)";
|
|
5362
|
+
if (kind === "missing")
|
|
5363
|
+
return "the file does not exist";
|
|
5364
|
+
if (kind === "directory")
|
|
5365
|
+
return "it is a directory, not bash.exe";
|
|
5366
|
+
return;
|
|
5367
|
+
}
|
|
5368
|
+
function resolveWindowsShell(deps, configPath) {
|
|
5369
|
+
if (configPath !== undefined && configPath !== "") {
|
|
5370
|
+
const problem = configProblem(configPath, deps.kind(configPath));
|
|
5371
|
+
if (problem !== undefined) {
|
|
5372
|
+
return {
|
|
5373
|
+
kind: "missing",
|
|
5374
|
+
reason: "invalid_config",
|
|
5375
|
+
detail: `shell.path "${configPath}" is invalid: ${problem}.`,
|
|
5376
|
+
searched: [configPath]
|
|
5377
|
+
};
|
|
5378
|
+
}
|
|
5379
|
+
return { kind: "found", path: configPath, source: "config" };
|
|
5380
|
+
}
|
|
5381
|
+
const searched = [];
|
|
5382
|
+
const check = (path) => {
|
|
5383
|
+
if (!searched.includes(path))
|
|
5384
|
+
searched.push(path);
|
|
5385
|
+
return !isWslBashPath(path) && deps.kind(path) === "file";
|
|
5386
|
+
};
|
|
5387
|
+
const roots = [
|
|
5388
|
+
envValue(deps.env, "ProgramFiles"),
|
|
5389
|
+
envValue(deps.env, "ProgramFiles(x86)"),
|
|
5390
|
+
(() => {
|
|
5391
|
+
const local = envValue(deps.env, "LOCALAPPDATA");
|
|
5392
|
+
return local === undefined ? undefined : win32.join(local, "Programs");
|
|
5393
|
+
})()
|
|
5394
|
+
];
|
|
5395
|
+
for (const root of roots) {
|
|
5396
|
+
if (root === undefined)
|
|
5397
|
+
continue;
|
|
5398
|
+
const candidate = win32.join(root, "Git", "bin", "bash.exe");
|
|
5399
|
+
if (check(candidate))
|
|
5400
|
+
return { kind: "found", path: candidate, source: "auto" };
|
|
5401
|
+
}
|
|
5402
|
+
for (const gitExe of deps.where("git.exe")) {
|
|
5403
|
+
const candidate = bashBesideGit(gitExe);
|
|
5404
|
+
if (candidate !== undefined && check(candidate))
|
|
5405
|
+
return { kind: "found", path: candidate, source: "auto" };
|
|
5406
|
+
}
|
|
5407
|
+
for (const candidate of deps.where("bash.exe")) {
|
|
5408
|
+
if (check(candidate))
|
|
5409
|
+
return { kind: "found", path: candidate, source: "auto" };
|
|
5410
|
+
}
|
|
5411
|
+
return { kind: "missing", reason: "not_found", detail: "No Git Bash was found.", searched };
|
|
5412
|
+
}
|
|
5413
|
+
function summarizeWindowsShell(status) {
|
|
5414
|
+
return status.kind === "found" ? { status: "ready", path: status.path, source: status.source } : { status: "missing", reason: status.reason, detail: status.detail };
|
|
5415
|
+
}
|
|
5416
|
+
function windowsShellMessage(status) {
|
|
5417
|
+
const lines = status.reason === "invalid_config" ? [
|
|
5418
|
+
`Cannot run shell commands: ${status.detail}`,
|
|
5419
|
+
"Fix shell.path in neko.config.json, or remove it to auto-detect Git Bash."
|
|
5420
|
+
] : [
|
|
5421
|
+
"Cannot run shell commands: Git Bash was not found. neko runs commands in Git for Windows' bash, not PowerShell, cmd or WSL.",
|
|
5422
|
+
"Install Git for Windows (https://git-scm.com/download/win), or set shell.path in neko.config.json to its bash.exe."
|
|
5423
|
+
];
|
|
5424
|
+
if (status.searched.length > 0)
|
|
5425
|
+
lines.push("Checked:", ...status.searched.map((path) => ` ${path}`));
|
|
5426
|
+
lines.push("File tools (read, write, edit) still work.");
|
|
5427
|
+
return lines.join(`
|
|
5428
|
+
`);
|
|
5429
|
+
}
|
|
5430
|
+
function systemWindowsShellDeps() {
|
|
5431
|
+
return {
|
|
5432
|
+
env: process.env,
|
|
5433
|
+
kind(path) {
|
|
5434
|
+
try {
|
|
5435
|
+
return statSync2(path).isDirectory() ? "directory" : "file";
|
|
5436
|
+
} catch {
|
|
5437
|
+
return "missing";
|
|
5438
|
+
}
|
|
5439
|
+
},
|
|
5440
|
+
where(program) {
|
|
5441
|
+
const result = spawnSync("where", [program], { encoding: "utf8", timeout: 5000, windowsHide: true });
|
|
5442
|
+
if (result.status !== 0 || typeof result.stdout !== "string")
|
|
5443
|
+
return [];
|
|
5444
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== "");
|
|
5445
|
+
}
|
|
5446
|
+
};
|
|
5447
|
+
}
|
|
5448
|
+
function createWindowsShellResolver(options) {
|
|
5449
|
+
const deps = options?.deps ?? systemWindowsShellDeps();
|
|
5450
|
+
const now = options?.now ?? Date.now;
|
|
5451
|
+
let cached;
|
|
5452
|
+
return {
|
|
5453
|
+
status(call) {
|
|
5454
|
+
if (cached !== undefined && call?.refresh !== true) {
|
|
5455
|
+
if (cached.status.kind === "found") {
|
|
5456
|
+
if (deps.kind(cached.status.path) === "file")
|
|
5457
|
+
return cached.status;
|
|
5458
|
+
} else if (now() - cached.at < WINDOWS_SHELL_RETRY_MS) {
|
|
5459
|
+
return cached.status;
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
5462
|
+
cached = { status: resolveWindowsShell(deps, options?.configPath), at: now() };
|
|
5463
|
+
return cached.status;
|
|
5464
|
+
}
|
|
5465
|
+
};
|
|
5466
|
+
}
|
|
5467
|
+
var WINDOWS_SHELL_RETRY_MS = 30000;
|
|
5468
|
+
var init_windows_shell = () => {};
|
|
5469
|
+
|
|
5218
5470
|
// packages/core/src/workspace-files.ts
|
|
5219
|
-
import { open as open2, realpath as realpath3, stat as
|
|
5471
|
+
import { open as open2, realpath as realpath3, stat as stat3 } from "fs/promises";
|
|
5220
5472
|
import { basename as basename3, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve4, sep as sep2 } from "path";
|
|
5221
5473
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT2, withAbortSignal as withAbortSignal3 } from "@earendil-works/chord/context";
|
|
5222
|
-
import {
|
|
5474
|
+
import {
|
|
5475
|
+
ExecutionError,
|
|
5476
|
+
err,
|
|
5477
|
+
getOrThrow as getOrThrow2
|
|
5478
|
+
} from "@earendil-works/pi-durable/env";
|
|
5223
5479
|
import { Connection, LocalExecutionEnv, localDaemon } from "@earendil-works/pi-env";
|
|
5224
5480
|
function isProtectedPath(path) {
|
|
5225
5481
|
const name = basename3(path).toLowerCase();
|
|
@@ -5257,8 +5513,10 @@ class WorkspaceFiles {
|
|
|
5257
5513
|
#waiting = new Set;
|
|
5258
5514
|
#timer;
|
|
5259
5515
|
limits;
|
|
5260
|
-
|
|
5516
|
+
#shell;
|
|
5517
|
+
constructor(limits = FILE_CACHE_LIMITS, shell) {
|
|
5261
5518
|
this.limits = limits;
|
|
5519
|
+
this.#shell = shell;
|
|
5262
5520
|
this.#timer = setInterval(() => {
|
|
5263
5521
|
for (const [key, item] of this.#cache)
|
|
5264
5522
|
if (!item.waiters && Date.now() - item.used >= this.limits.idleMs)
|
|
@@ -5277,20 +5535,31 @@ class WorkspaceFiles {
|
|
|
5277
5535
|
queued: this.#waiting.size
|
|
5278
5536
|
};
|
|
5279
5537
|
}
|
|
5280
|
-
|
|
5538
|
+
shellStatus(options) {
|
|
5539
|
+
return this.#shell?.status(options);
|
|
5540
|
+
}
|
|
5541
|
+
#open() {
|
|
5281
5542
|
if (this.#closed)
|
|
5282
5543
|
throw new Error("File environment closed");
|
|
5283
5544
|
this.#connection ??= new Connection({ command: [localDaemon()] });
|
|
5284
|
-
return
|
|
5285
|
-
|
|
5545
|
+
return this.#connection;
|
|
5546
|
+
}
|
|
5547
|
+
env(cwd) {
|
|
5548
|
+
const options = {
|
|
5549
|
+
connection: this.#open(),
|
|
5286
5550
|
cwd,
|
|
5287
5551
|
...process.platform === "win32" ? { shellEnv: { PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" } } : {}
|
|
5288
|
-
}
|
|
5552
|
+
};
|
|
5553
|
+
const shell = this.#shell?.status();
|
|
5554
|
+
if (shell === undefined)
|
|
5555
|
+
return new LocalExecutionEnv(options);
|
|
5556
|
+
if (shell.kind === "found")
|
|
5557
|
+
return new LocalExecutionEnv({ ...options, shellPath: shell.path });
|
|
5558
|
+
return new ShellUnavailableEnv(options, windowsShellMessage(shell));
|
|
5289
5559
|
}
|
|
5290
5560
|
async#ready() {
|
|
5291
5561
|
if (!this.#starting) {
|
|
5292
|
-
this
|
|
5293
|
-
const connection = this.#connection;
|
|
5562
|
+
const connection = this.#open();
|
|
5294
5563
|
let timer;
|
|
5295
5564
|
const work = Promise.race([
|
|
5296
5565
|
connection.info().then(() => connection.session()),
|
|
@@ -5367,7 +5636,7 @@ class WorkspaceFiles {
|
|
|
5367
5636
|
{ path: item.root, recursive: true, exclude: { names: [".git"] } }
|
|
5368
5637
|
];
|
|
5369
5638
|
const git = resolve4(item.root, ".git");
|
|
5370
|
-
if (await
|
|
5639
|
+
if (await stat3(git).then(() => true, () => false))
|
|
5371
5640
|
targets.push({ path: git, recursive: true, exclude: { names: ["objects", "logs", "refs"] } });
|
|
5372
5641
|
const result = await env.watch(targets, (change) => {
|
|
5373
5642
|
for (const user of current.users) {
|
|
@@ -5475,7 +5744,7 @@ class WorkspaceFiles {
|
|
|
5475
5744
|
const slash = query.lastIndexOf("/"), prefix = slash < 0 ? "" : query.slice(0, slash + 1), needle = query.slice(slash + 1);
|
|
5476
5745
|
root = await realpath3(root);
|
|
5477
5746
|
const base = await workspaceTarget(root, prefix);
|
|
5478
|
-
if (!(await
|
|
5747
|
+
if (!(await stat3(base)).isDirectory())
|
|
5479
5748
|
throw new Error("Workspace directory is unavailable.");
|
|
5480
5749
|
return this.#withListing(root, base, signal, async (listing) => {
|
|
5481
5750
|
const pattern = new RegExp([...needle].map((c) => c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*"), /[A-Z]/.test(needle) ? "" : "i");
|
|
@@ -5505,7 +5774,7 @@ class WorkspaceFiles {
|
|
|
5505
5774
|
const clean = dir === "" ? "" : relativeQuery(dir).replace(/\/+$/, "");
|
|
5506
5775
|
root = await realpath3(root);
|
|
5507
5776
|
const target = await workspaceTarget(root, clean);
|
|
5508
|
-
if (!(await
|
|
5777
|
+
if (!(await stat3(target)).isDirectory())
|
|
5509
5778
|
throw new Error("Not a directory.");
|
|
5510
5779
|
const prefix = clean === "" ? "" : `${clean}/`;
|
|
5511
5780
|
return this.#withListing(root, root, signal, async (listing) => {
|
|
@@ -5547,7 +5816,7 @@ class WorkspaceFiles {
|
|
|
5547
5816
|
throw new Error("File not found.");
|
|
5548
5817
|
throw error;
|
|
5549
5818
|
}
|
|
5550
|
-
const info = await
|
|
5819
|
+
const info = await stat3(target);
|
|
5551
5820
|
if (!info.isFile())
|
|
5552
5821
|
throw new Error("Not a file.");
|
|
5553
5822
|
const base = { path: clean, size: info.size, mtimeMs: info.mtimeMs };
|
|
@@ -5650,8 +5919,9 @@ function cancellable(work, signal) {
|
|
|
5650
5919
|
abort();
|
|
5651
5920
|
});
|
|
5652
5921
|
}
|
|
5653
|
-
var BROWSE_LIMITS, ENV_TEMPLATE, FILE_CACHE_LIMITS;
|
|
5922
|
+
var BROWSE_LIMITS, ENV_TEMPLATE, FILE_CACHE_LIMITS, ShellUnavailableEnv;
|
|
5654
5923
|
var init_workspace_files = __esm(() => {
|
|
5924
|
+
init_windows_shell();
|
|
5655
5925
|
BROWSE_LIMITS = { children: 1000, readBytes: 512 * 1024 };
|
|
5656
5926
|
ENV_TEMPLATE = /\.(example|sample|template)$/;
|
|
5657
5927
|
FILE_CACHE_LIMITS = {
|
|
@@ -5666,6 +5936,18 @@ var init_workspace_files = __esm(() => {
|
|
|
5666
5936
|
idleMs: 300000,
|
|
5667
5937
|
concurrent: 2
|
|
5668
5938
|
};
|
|
5939
|
+
ShellUnavailableEnv = class ShellUnavailableEnv extends LocalExecutionEnv {
|
|
5940
|
+
#message;
|
|
5941
|
+
constructor(options, message) {
|
|
5942
|
+
super(options);
|
|
5943
|
+
this.#message = message;
|
|
5944
|
+
}
|
|
5945
|
+
async exec(command, options, context) {
|
|
5946
|
+
if (typeof command === "string")
|
|
5947
|
+
return err(new ExecutionError("shell_unavailable", this.#message));
|
|
5948
|
+
return super.exec(command, options, context);
|
|
5949
|
+
}
|
|
5950
|
+
};
|
|
5669
5951
|
});
|
|
5670
5952
|
|
|
5671
5953
|
// packages/core/src/runtime.ts
|
|
@@ -5673,7 +5955,7 @@ import { homedir as homedir5 } from "os";
|
|
|
5673
5955
|
import { join as join7 } from "path";
|
|
5674
5956
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT3 } from "@earendil-works/chord/context";
|
|
5675
5957
|
import { clampThinkingLevel, createModels, getSupportedThinkingLevels as getSupportedThinkingLevels2 } from "@earendil-works/pi-ai/models";
|
|
5676
|
-
import {
|
|
5958
|
+
import { builtinProviders } from "@earendil-works/pi-ai/providers/all";
|
|
5677
5959
|
import {
|
|
5678
5960
|
AssistantEntry as AssistantEntry2,
|
|
5679
5961
|
createRegistry,
|
|
@@ -5692,6 +5974,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
5692
5974
|
const context = options.context ?? BACKGROUND_CONTEXT3;
|
|
5693
5975
|
const defaultCwd = normalizeWorkspace(options.cwd);
|
|
5694
5976
|
const model = options.model ?? DEFAULT_MODEL;
|
|
5977
|
+
const modelOverride = options.modelOverride;
|
|
5695
5978
|
const providers = options.providers ?? [];
|
|
5696
5979
|
const credentials = options.credentials ?? (options.storage === "memory" && !options.credentialsPath ? new InMemoryCredentialStore : new FileCredentialStore(options.credentialsPath));
|
|
5697
5980
|
const runtimeKeys = new Map;
|
|
@@ -5711,16 +5994,47 @@ async function createNekoRuntime(options = {}) {
|
|
|
5711
5994
|
delete: (id) => credentials.delete(id)
|
|
5712
5995
|
} : credentials;
|
|
5713
5996
|
const models = createModels({ credentials: effectiveCredentials });
|
|
5714
|
-
|
|
5997
|
+
for (const provider of builtinProviders())
|
|
5998
|
+
if (isModelProvider(provider.id))
|
|
5999
|
+
models.setProvider(provider);
|
|
5715
6000
|
for (const provider of providers)
|
|
5716
6001
|
models.setProvider(provider);
|
|
6002
|
+
let enabled = new Set(normalizeEnabledProviders(null));
|
|
6003
|
+
const guardProvider = async (ref) => {
|
|
6004
|
+
if (!ref || !isModelProvider(ref.provider))
|
|
6005
|
+
return;
|
|
6006
|
+
const spec = credentialProvider(ref.provider);
|
|
6007
|
+
if (!enabled.has(ref.provider))
|
|
6008
|
+
throw new Error(`PROVIDER_DISABLED: ${spec.name} is not enabled. Enable it in /login or Web settings, or switch model.`);
|
|
6009
|
+
if (!await models.checkAuth(ref.provider))
|
|
6010
|
+
throw new Error(`PROVIDER_KEY_REQUIRED: Configure a ${spec.name} API key in /login or Web settings.`);
|
|
6011
|
+
};
|
|
5717
6012
|
const resolveLevel = (ref, level) => {
|
|
5718
6013
|
const info = models.getModel(ref.provider, ref.modelId);
|
|
5719
6014
|
if (!info)
|
|
5720
6015
|
throw new Error("Unknown model");
|
|
5721
6016
|
return clampThinkingLevel(info, level);
|
|
5722
6017
|
};
|
|
5723
|
-
|
|
6018
|
+
let modelInfoCache;
|
|
6019
|
+
const allModels = () => {
|
|
6020
|
+
modelInfoCache ??= models.getModels().map((model) => ({
|
|
6021
|
+
provider: model.provider,
|
|
6022
|
+
modelId: model.id,
|
|
6023
|
+
name: model.name,
|
|
6024
|
+
contextWindow: model.contextWindow,
|
|
6025
|
+
input: model.input,
|
|
6026
|
+
...model.inputLimits ? { inputLimits: model.inputLimits } : {},
|
|
6027
|
+
reasoning: model.reasoning,
|
|
6028
|
+
supportedThinkingLevels: getSupportedThinkingLevels2(model),
|
|
6029
|
+
thinkingLevelAdjustments: Object.fromEntries(["off", "minimal", "low", "medium", "high", "xhigh", "max"].map((level) => [
|
|
6030
|
+
level,
|
|
6031
|
+
clampThinkingLevel(model, level)
|
|
6032
|
+
]))
|
|
6033
|
+
}));
|
|
6034
|
+
return modelInfoCache;
|
|
6035
|
+
};
|
|
6036
|
+
const shellResolver = options.shellResolver ?? (process.platform === "win32" ? createWindowsShellResolver({ configPath: options.shellPath }) : undefined);
|
|
6037
|
+
const workspaceFiles = new WorkspaceFiles(undefined, shellResolver);
|
|
5724
6038
|
const storage = options.storage === "memory" ? new MemoryStorage : await openNodeSqliteStorage(options.storagePath ?? DEFAULT_STORAGE_PATH);
|
|
5725
6039
|
const registry = createRegistry();
|
|
5726
6040
|
registry.install(createCodingTools({ images: await createNodePhotonImages() }));
|
|
@@ -5746,7 +6060,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
5746
6060
|
registry.install(createNekoPrompt(resourceSnapshots.current));
|
|
5747
6061
|
registry.install(createSubagentExtension(async (id, requestId, cwd) => {
|
|
5748
6062
|
resourceSnapshots.stage(id, requestId, cwd, await loadResources(cwd));
|
|
5749
|
-
}, resourceSnapshots.discard));
|
|
6063
|
+
}, resourceSnapshots.discard, (ref) => guardProvider(ref)));
|
|
5750
6064
|
const jobs = new JobManager;
|
|
5751
6065
|
registry.install(jobs.extension);
|
|
5752
6066
|
registry.install(createTodoExtension());
|
|
@@ -5764,7 +6078,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
5764
6078
|
registry,
|
|
5765
6079
|
settings: {
|
|
5766
6080
|
get compaction() {
|
|
5767
|
-
return compactionPolicy(contextState,
|
|
6081
|
+
return compactionPolicy(contextState, allModels().filter((model) => !isModelProvider(model.provider) || enabled.has(model.provider)).map((model) => model.contextWindow));
|
|
5768
6082
|
}
|
|
5769
6083
|
},
|
|
5770
6084
|
onReport: options.onReport,
|
|
@@ -5806,13 +6120,15 @@ async function createNekoRuntime(options = {}) {
|
|
|
5806
6120
|
throw error;
|
|
5807
6121
|
}
|
|
5808
6122
|
await questions.reconcileAll();
|
|
6123
|
+
const rootModel = modelOverride ?? model;
|
|
5809
6124
|
const conversation = await harness.root(context, {
|
|
5810
6125
|
agent: {
|
|
5811
|
-
model,
|
|
5812
|
-
thinkingLevel: resolveLevel(
|
|
6126
|
+
model: rootModel,
|
|
6127
|
+
thinkingLevel: resolveLevel(rootModel, options.thinkingLevel ?? "high"),
|
|
5813
6128
|
cwd: defaultCwd
|
|
5814
6129
|
}
|
|
5815
6130
|
});
|
|
6131
|
+
enabled = new Set(normalizeEnabledProviders((await harness.snapshot(ProviderSettingsDoc, conversation.id, context))?.enabled));
|
|
5816
6132
|
const savedSearch = (await harness.snapshot(SearchSettingsDoc, conversation.id, context))?.provider;
|
|
5817
6133
|
if (isSearchProvider(savedSearch))
|
|
5818
6134
|
searchProvider = savedSearch;
|
|
@@ -5858,7 +6174,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
5858
6174
|
};
|
|
5859
6175
|
if (existing.length === 0)
|
|
5860
6176
|
await touchConversation(conversation.id);
|
|
5861
|
-
const metadata = new SessionMetadataManager(harness, storage, context, registry, options.onReport);
|
|
6177
|
+
const metadata = new SessionMetadataManager(harness, storage, context, registry, options.onReport, (provider, modelId) => guardProvider({ provider, modelId }).then(() => true, () => false));
|
|
5862
6178
|
if (existing.length === 0)
|
|
5863
6179
|
await metadata.initialize(conversation.id);
|
|
5864
6180
|
for (const record of existing)
|
|
@@ -5919,9 +6235,8 @@ async function createNekoRuntime(options = {}) {
|
|
|
5919
6235
|
data.skill = skill;
|
|
5920
6236
|
});
|
|
5921
6237
|
}
|
|
5922
|
-
if (!duplicate
|
|
5923
|
-
|
|
5924
|
-
}
|
|
6238
|
+
if (!duplicate)
|
|
6239
|
+
await guardProvider(agent.model);
|
|
5925
6240
|
if (!duplicate && agent.model) {
|
|
5926
6241
|
const resolved = models.getModel(agent.model.provider, agent.model.modelId);
|
|
5927
6242
|
if (resolved)
|
|
@@ -6053,8 +6368,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
6053
6368
|
},
|
|
6054
6369
|
async compact(instructions) {
|
|
6055
6370
|
const agent = await target.agent(context);
|
|
6056
|
-
|
|
6057
|
-
throw new Error("DEEPSEEK_KEY_REQUIRED: Configure DeepSeek in /login or Web settings.");
|
|
6371
|
+
await guardProvider(agent.model);
|
|
6058
6372
|
await admit(target.id, async () => {
|
|
6059
6373
|
const live = await harness.snapshot(importLiveDoc, target.id, context);
|
|
6060
6374
|
if (live?.compactions?.length || (await readCompaction(harness, storage, target.id, context))?.phase === "waiting-to-apply")
|
|
@@ -6230,6 +6544,10 @@ async function createNekoRuntime(options = {}) {
|
|
|
6230
6544
|
questions,
|
|
6231
6545
|
closed: harness.closed,
|
|
6232
6546
|
workspaceFiles,
|
|
6547
|
+
shellStatus(call) {
|
|
6548
|
+
const status = workspaceFiles.shellStatus(call);
|
|
6549
|
+
return status === undefined ? undefined : summarizeWindowsShell(status);
|
|
6550
|
+
},
|
|
6233
6551
|
conversation,
|
|
6234
6552
|
root: rootSession,
|
|
6235
6553
|
submit: rootSession.submit,
|
|
@@ -6280,11 +6598,13 @@ async function createNekoRuntime(options = {}) {
|
|
|
6280
6598
|
},
|
|
6281
6599
|
async createConversation(createOptions) {
|
|
6282
6600
|
const workspace = await addWorkspace(createOptions?.cwd ?? defaultCwd);
|
|
6601
|
+
const savedDefault = (await harness.snapshot(DefaultModelDoc, conversation.id, context))?.model ?? null;
|
|
6602
|
+
const effective = createOptions?.model ?? modelOverride ?? savedDefault ?? model;
|
|
6283
6603
|
const created = await harness.createConversation({
|
|
6284
6604
|
ownership: { kind: "ownerless" },
|
|
6285
6605
|
agent: {
|
|
6286
|
-
model:
|
|
6287
|
-
thinkingLevel: resolveLevel(
|
|
6606
|
+
model: effective,
|
|
6607
|
+
thinkingLevel: resolveLevel(effective, createOptions?.thinkingLevel ?? options.thinkingLevel ?? "high"),
|
|
6288
6608
|
cwd: workspace.path
|
|
6289
6609
|
}
|
|
6290
6610
|
}, context);
|
|
@@ -6314,6 +6634,8 @@ async function createNekoRuntime(options = {}) {
|
|
|
6314
6634
|
const targetModel = ref && models.getModel(ref.provider, ref.modelId);
|
|
6315
6635
|
if (!targetModel)
|
|
6316
6636
|
throw new Error("Unknown model");
|
|
6637
|
+
if (change.model && isModelProvider(ref.provider) && !enabled.has(ref.provider))
|
|
6638
|
+
throw new Error(`PROVIDER_DISABLED: ${credentialProvider(ref.provider).name} is not enabled.`);
|
|
6317
6639
|
await session.conversation.configure({ model: ref, thinkingLevel: clampThinkingLevel(targetModel, change.thinkingLevel ?? current.thinkingLevel) }, context);
|
|
6318
6640
|
return true;
|
|
6319
6641
|
});
|
|
@@ -6368,20 +6690,62 @@ async function createNekoRuntime(options = {}) {
|
|
|
6368
6690
|
}
|
|
6369
6691
|
},
|
|
6370
6692
|
listModels() {
|
|
6371
|
-
return
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6693
|
+
return allModels();
|
|
6694
|
+
},
|
|
6695
|
+
async listSelectableModels() {
|
|
6696
|
+
const availability = new Map;
|
|
6697
|
+
const result = [];
|
|
6698
|
+
for (const info of allModels()) {
|
|
6699
|
+
const managed = isModelProvider(info.provider);
|
|
6700
|
+
if (managed && !enabled.has(info.provider))
|
|
6701
|
+
continue;
|
|
6702
|
+
if (!availability.has(info.provider))
|
|
6703
|
+
availability.set(info.provider, !managed || Boolean(await models.checkAuth(info.provider)));
|
|
6704
|
+
result.push({ ...info, available: availability.get(info.provider) ?? false });
|
|
6705
|
+
}
|
|
6706
|
+
return result;
|
|
6707
|
+
},
|
|
6708
|
+
enabledProviders() {
|
|
6709
|
+
return MODEL_PROVIDERS.map((item) => item.id).filter((id) => enabled.has(id));
|
|
6710
|
+
},
|
|
6711
|
+
async setProviderEnabled(provider, value) {
|
|
6712
|
+
if (!isModelProvider(provider))
|
|
6713
|
+
throw new Error("Unknown model provider.");
|
|
6714
|
+
if (typeof value !== "boolean")
|
|
6715
|
+
throw new Error("Enabled must be a boolean.");
|
|
6716
|
+
if (!value && LOCKED_PROVIDERS.includes(provider))
|
|
6717
|
+
throw new Error("DeepSeek is always enabled.");
|
|
6718
|
+
const next = new Set(enabled);
|
|
6719
|
+
if (value)
|
|
6720
|
+
next.add(provider);
|
|
6721
|
+
else
|
|
6722
|
+
next.delete(provider);
|
|
6723
|
+
const list = normalizeEnabledProviders([...next]);
|
|
6724
|
+
await harness.commit(async (tx) => {
|
|
6725
|
+
(await tx.doc(ProviderSettingsDoc, conversation.id)).enabled = list;
|
|
6726
|
+
}, context);
|
|
6727
|
+
enabled = new Set(list);
|
|
6728
|
+
},
|
|
6729
|
+
async defaultModel() {
|
|
6730
|
+
const saved = (await harness.snapshot(DefaultModelDoc, conversation.id, context))?.model ?? null;
|
|
6731
|
+
if (modelOverride)
|
|
6732
|
+
return { effective: modelOverride, source: "cli" };
|
|
6733
|
+
if (saved)
|
|
6734
|
+
return { effective: saved, source: "saved" };
|
|
6735
|
+
if (options.model)
|
|
6736
|
+
return { effective: model, source: "config" };
|
|
6737
|
+
return { effective: model, source: "default" };
|
|
6738
|
+
},
|
|
6739
|
+
async setDefaultModel(ref) {
|
|
6740
|
+
if (ref !== null) {
|
|
6741
|
+
if (!models.getModel(ref.provider, ref.modelId))
|
|
6742
|
+
throw new Error("Unknown model");
|
|
6743
|
+
if (isModelProvider(ref.provider) && !enabled.has(ref.provider))
|
|
6744
|
+
throw new Error(`PROVIDER_DISABLED: ${credentialProvider(ref.provider).name} is not enabled.`);
|
|
6745
|
+
}
|
|
6746
|
+
await harness.commit(async (tx) => {
|
|
6747
|
+
(await tx.doc(DefaultModelDoc, conversation.id)).model = ref;
|
|
6748
|
+
}, context);
|
|
6385
6749
|
},
|
|
6386
6750
|
async getAgent(id) {
|
|
6387
6751
|
const session = await sessionFor(id);
|
|
@@ -6422,7 +6786,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
6422
6786
|
}
|
|
6423
6787
|
};
|
|
6424
6788
|
}
|
|
6425
|
-
var DEFAULT_MODEL, DEFAULT_STORAGE_PATH, neverAborted = () => new AbortController().signal, ArchivedDoc, WorkspacesDoc, SearchSettingsDoc, ContextSettingsDoc, ActivityDoc;
|
|
6789
|
+
var DEFAULT_MODEL, DEFAULT_STORAGE_PATH, neverAborted = () => new AbortController().signal, ArchivedDoc, WorkspacesDoc, SearchSettingsDoc, ProviderSettingsDoc, DefaultModelDoc, ContextSettingsDoc, ActivityDoc;
|
|
6426
6790
|
var init_runtime = __esm(() => {
|
|
6427
6791
|
init_agent_file_search();
|
|
6428
6792
|
init_compaction();
|
|
@@ -6451,6 +6815,7 @@ var init_runtime = __esm(() => {
|
|
|
6451
6815
|
init_deepseek();
|
|
6452
6816
|
init_web_search();
|
|
6453
6817
|
init_tavily();
|
|
6818
|
+
init_windows_shell();
|
|
6454
6819
|
init_workspace();
|
|
6455
6820
|
init_workspace_files();
|
|
6456
6821
|
DEFAULT_MODEL = { provider: "deepseek", modelId: "deepseek-flash" };
|
|
@@ -6479,6 +6844,22 @@ var init_runtime = __esm(() => {
|
|
|
6479
6844
|
fork: "initial",
|
|
6480
6845
|
initial: () => ({ provider: null })
|
|
6481
6846
|
});
|
|
6847
|
+
ProviderSettingsDoc = defineDoc7({
|
|
6848
|
+
kind: "app.neko.provider-settings",
|
|
6849
|
+
version: 1,
|
|
6850
|
+
scope: "conversation",
|
|
6851
|
+
history: "latest",
|
|
6852
|
+
fork: "initial",
|
|
6853
|
+
initial: () => ({ enabled: null })
|
|
6854
|
+
});
|
|
6855
|
+
DefaultModelDoc = defineDoc7({
|
|
6856
|
+
kind: "app.neko.default-model",
|
|
6857
|
+
version: 1,
|
|
6858
|
+
scope: "conversation",
|
|
6859
|
+
history: "latest",
|
|
6860
|
+
fork: "initial",
|
|
6861
|
+
initial: () => ({ model: null })
|
|
6862
|
+
});
|
|
6482
6863
|
ContextSettingsDoc = defineDoc7({
|
|
6483
6864
|
kind: "app.neko.context-settings",
|
|
6484
6865
|
version: 1,
|
|
@@ -6521,6 +6902,7 @@ var init_src = __esm(() => {
|
|
|
6521
6902
|
init_projection();
|
|
6522
6903
|
init_frontmatter();
|
|
6523
6904
|
init_queued();
|
|
6905
|
+
init_rename_with_retry();
|
|
6524
6906
|
init_runtime();
|
|
6525
6907
|
init_search_providers();
|
|
6526
6908
|
init_session_metadata();
|
|
@@ -6529,6 +6911,7 @@ var init_src = __esm(() => {
|
|
|
6529
6911
|
init_todos();
|
|
6530
6912
|
init_tool_approvals();
|
|
6531
6913
|
init_user_questions();
|
|
6914
|
+
init_windows_shell();
|
|
6532
6915
|
init_workspace();
|
|
6533
6916
|
init_workspace_files();
|
|
6534
6917
|
});
|
|
@@ -6654,7 +7037,7 @@ function contract(name, def) {
|
|
|
6654
7037
|
]));
|
|
6655
7038
|
return remote(name, { ...def, host: hostNamed(GATEWAY_HOST), variants });
|
|
6656
7039
|
}
|
|
6657
|
-
var GATEWAY_HOST = "neko", rpc = (args, result) => [args, result], str, num, bool, nil, integer, nullableString, phase, level, thinking,
|
|
7040
|
+
var GATEWAY_HOST = "neko", rpc = (args, result) => [args, result], str, num, bool, nil, integer, nullableString, phase, level, thinking, model2, change, usage, image, skill, call, DelegationSchema, SubagentSummarySchema, jobStatus, JobSummarySchema, JobOutputSchema, QueuedInputSchema, WithdrawResultSchema, SteerResultSchema, TodoItemSchema, TodoListSchema, QuestionOptionSchema, questionStatus, QuestionSummarySchema, WorkspaceEntrySchema, WorkspaceDirSchema, WorkspaceFileSchema, ChangeSummarySchema, ChangedFileSchema, ChangeListSchema, ChangeOpSchema, ChangeDetailSchema, QuestionListSchema, QuestionRecordSchema, QuestionResponseSchema, ToolInteractionSchema, ChatViewSchema, SessionSummarySchema, DirectoryStateSchema, WorkspaceSummarySchema, WorkspaceStateSchema, credentialStatus, CredentialStateSchema, AttachmentSchema, chatInput, NekoSessionDirectory, NekoSessionManagement, resize, inputLimits, modelInfo, NekoModels, imageChunk, NekoSubagent, NekoChat, browse, page, NekoWorkspaces, provider, NekoCredentials, SearchSettingsStateSchema, NekoSearchSettings, contextSource, ContextSettingsStateSchema, nullable = (schema) => Type7.Union([schema, nil]), ContextSettingsPatchSchema, NekoContextSettings, defaultModelSource, DefaultModelStateSchema, NekoDefaultModel, FileCandidatesSchema, NekoWorkspaceFiles, marketSkill, preview, scope, installResult, NekoSkillMarket, ShellStatusSchema, ApplicationInfoSchema, NekoApplicationInfo;
|
|
6658
7041
|
var init_contract = __esm(() => {
|
|
6659
7042
|
init_interactions();
|
|
6660
7043
|
str = Type7.String();
|
|
@@ -6666,8 +7049,8 @@ var init_contract = __esm(() => {
|
|
|
6666
7049
|
phase = Type7.Enum(["pending", "running", "done"]);
|
|
6667
7050
|
level = Type7.Enum(["project", "user", "builtin"]);
|
|
6668
7051
|
thinking = Type7.Enum(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
6669
|
-
|
|
6670
|
-
change = Type7.Object({ model: Type7.Optional(
|
|
7052
|
+
model2 = Type7.Object({ provider: str, modelId: str });
|
|
7053
|
+
change = Type7.Object({ model: Type7.Optional(model2), thinkingLevel: Type7.Optional(thinking) }, { additionalProperties: false });
|
|
6671
7054
|
usage = Type7.Object({ input: num, output: num, cacheRead: num, cacheWrite: num, cost: num });
|
|
6672
7055
|
image = Type7.Object({
|
|
6673
7056
|
name: str,
|
|
@@ -6905,7 +7288,7 @@ var init_contract = __esm(() => {
|
|
|
6905
7288
|
subagents: Type7.Optional(Type7.Array(DelegationSchema)),
|
|
6906
7289
|
taskUsage: Type7.Optional(usage),
|
|
6907
7290
|
model: str,
|
|
6908
|
-
agent: Type7.Object({ model: Type7.Union([
|
|
7291
|
+
agent: Type7.Object({ model: Type7.Union([model2, nil]), thinkingLevel: Type7.Union([thinking, nil]) }),
|
|
6909
7292
|
cwd: str,
|
|
6910
7293
|
busy: bool,
|
|
6911
7294
|
status: nullableString,
|
|
@@ -6990,7 +7373,7 @@ var init_contract = __esm(() => {
|
|
|
6990
7373
|
firstPromptPreview: str,
|
|
6991
7374
|
createdAt: num,
|
|
6992
7375
|
lastMessageAt: Type7.Union([num, nil]),
|
|
6993
|
-
model: Type7.Union([
|
|
7376
|
+
model: Type7.Union([model2, nil]),
|
|
6994
7377
|
status: Type7.Enum(["idle", "running", "error"]),
|
|
6995
7378
|
awaitingAnswer: Type7.Optional(bool),
|
|
6996
7379
|
id: str
|
|
@@ -7001,11 +7384,12 @@ var init_contract = __esm(() => {
|
|
|
7001
7384
|
credentialStatus = Type7.Object({
|
|
7002
7385
|
savedConfigured: bool,
|
|
7003
7386
|
environmentConfigured: bool,
|
|
7004
|
-
source: Type7.Enum(["saved", "environment", "runtime", "keyless", "none"])
|
|
7387
|
+
source: Type7.Enum(["saved", "environment", "runtime", "keyless", "none"]),
|
|
7388
|
+
enabled: Type7.Optional(bool)
|
|
7005
7389
|
});
|
|
7006
7390
|
CredentialStateSchema = Type7.Object({
|
|
7007
7391
|
revision: integer,
|
|
7008
|
-
statuses: Type7.
|
|
7392
|
+
statuses: Type7.Record(str, credentialStatus)
|
|
7009
7393
|
});
|
|
7010
7394
|
AttachmentSchema = Type7.Object({ sessionId: str, attachmentId: str });
|
|
7011
7395
|
chatInput = Type7.Object({ sessionId: str, attachmentId: str }, { additionalProperties: false });
|
|
@@ -7047,6 +7431,7 @@ var init_contract = __esm(() => {
|
|
|
7047
7431
|
provider: str,
|
|
7048
7432
|
modelId: str,
|
|
7049
7433
|
name: str,
|
|
7434
|
+
available: bool,
|
|
7050
7435
|
contextWindow: num,
|
|
7051
7436
|
input: Type7.Array(str),
|
|
7052
7437
|
inputLimits: Type7.Optional(inputLimits),
|
|
@@ -7152,16 +7537,17 @@ var init_contract = __esm(() => {
|
|
|
7152
7537
|
}
|
|
7153
7538
|
}
|
|
7154
7539
|
});
|
|
7155
|
-
provider = Type7.
|
|
7540
|
+
provider = Type7.String({ minLength: 1, maxLength: 64 });
|
|
7156
7541
|
NekoCredentials = contract("neko.credentials", {
|
|
7157
|
-
version:
|
|
7542
|
+
version: 2,
|
|
7158
7543
|
input: Type7.Object({}, { additionalProperties: false }),
|
|
7159
7544
|
variants: {
|
|
7160
7545
|
ready: {
|
|
7161
7546
|
state: { state: CredentialStateSchema },
|
|
7162
7547
|
methods: {
|
|
7163
7548
|
save: rpc(Type7.Object({ provider, key: Type7.String({ minLength: 1, maxLength: 8192 }) }, { additionalProperties: false }), nil),
|
|
7164
|
-
remove: rpc(Type7.Object({ provider }, { additionalProperties: false }), nil)
|
|
7549
|
+
remove: rpc(Type7.Object({ provider }, { additionalProperties: false }), nil),
|
|
7550
|
+
setEnabled: rpc(Type7.Object({ provider, enabled: bool }, { additionalProperties: false }), nil)
|
|
7165
7551
|
}
|
|
7166
7552
|
}
|
|
7167
7553
|
}
|
|
@@ -7237,6 +7623,24 @@ var init_contract = __esm(() => {
|
|
|
7237
7623
|
}
|
|
7238
7624
|
}
|
|
7239
7625
|
});
|
|
7626
|
+
defaultModelSource = Type7.Enum(["cli", "saved", "config", "default"]);
|
|
7627
|
+
DefaultModelStateSchema = Type7.Object({
|
|
7628
|
+
effective: model2,
|
|
7629
|
+
source: defaultModelSource
|
|
7630
|
+
});
|
|
7631
|
+
NekoDefaultModel = contract("neko.default-model", {
|
|
7632
|
+
version: 1,
|
|
7633
|
+
input: Type7.Object({}, { additionalProperties: false }),
|
|
7634
|
+
variants: {
|
|
7635
|
+
ready: {
|
|
7636
|
+
state: { state: DefaultModelStateSchema },
|
|
7637
|
+
methods: {
|
|
7638
|
+
set: rpc(model2, nil),
|
|
7639
|
+
clear: rpc(nil, nil)
|
|
7640
|
+
}
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
});
|
|
7240
7644
|
FileCandidatesSchema = Type7.Object({
|
|
7241
7645
|
entries: Type7.Array(Type7.Object({ name: str, path: str, kind: Type7.Enum(["file", "directory"]) }), { maxItems: 100 }),
|
|
7242
7646
|
limited: bool,
|
|
@@ -7284,6 +7688,14 @@ var init_contract = __esm(() => {
|
|
|
7284
7688
|
}
|
|
7285
7689
|
}
|
|
7286
7690
|
});
|
|
7691
|
+
ShellStatusSchema = Type7.Union([
|
|
7692
|
+
Type7.Object({ status: Type7.Literal("ready"), path: str, source: Type7.Enum(["config", "auto"]) }),
|
|
7693
|
+
Type7.Object({
|
|
7694
|
+
status: Type7.Literal("missing"),
|
|
7695
|
+
reason: Type7.Enum(["not_found", "invalid_config"]),
|
|
7696
|
+
detail: Type7.Optional(str)
|
|
7697
|
+
})
|
|
7698
|
+
]);
|
|
7287
7699
|
ApplicationInfoSchema = Type7.Object({
|
|
7288
7700
|
runningVersion: str,
|
|
7289
7701
|
installedVersion: nullableString,
|
|
@@ -7291,7 +7703,8 @@ var init_contract = __esm(() => {
|
|
|
7291
7703
|
latestVersion: nullableString,
|
|
7292
7704
|
checkedAt: Type7.Union([integer, nil]),
|
|
7293
7705
|
checkState: Type7.Enum(["unknown", "checking", "ok", "failed", "development"]),
|
|
7294
|
-
serverId: str
|
|
7706
|
+
serverId: str,
|
|
7707
|
+
shell: Type7.Optional(ShellStatusSchema)
|
|
7295
7708
|
});
|
|
7296
7709
|
NekoApplicationInfo = contract("neko.application-info", {
|
|
7297
7710
|
version: 1,
|
|
@@ -7672,8 +8085,8 @@ function createGatewayConnection(link, serverId) {
|
|
|
7672
8085
|
let business;
|
|
7673
8086
|
let protocolChecked = false;
|
|
7674
8087
|
function bindBusiness() {
|
|
7675
|
-
const directory = get(NekoSessionDirectory), management = get(NekoSessionManagement), models = get(NekoModels), workspaces = get(NekoWorkspaces), workspaceFiles = get(NekoWorkspaceFiles), credentials = get(NekoCredentials), searchSettings = get(NekoSearchSettings), contextSettings = get(NekoContextSettings), skillMarket = get(NekoSkillMarket);
|
|
7676
|
-
keep.push(directory.state.subscribe(() => {}), workspaces.state.subscribe(() => {}), credentials.state.subscribe(() => {}), searchSettings.state.subscribe(() => {}), contextSettings.state.subscribe(() => {}));
|
|
8088
|
+
const directory = get(NekoSessionDirectory), management = get(NekoSessionManagement), models = get(NekoModels), workspaces = get(NekoWorkspaces), workspaceFiles = get(NekoWorkspaceFiles), credentials = get(NekoCredentials), searchSettings = get(NekoSearchSettings), contextSettings = get(NekoContextSettings), defaultModel = get(NekoDefaultModel), skillMarket = get(NekoSkillMarket);
|
|
8089
|
+
keep.push(directory.state.subscribe(() => {}), workspaces.state.subscribe(() => {}), credentials.state.subscribe(() => {}), searchSettings.state.subscribe(() => {}), contextSettings.state.subscribe(() => {}), defaultModel.state.subscribe(() => {}));
|
|
7677
8090
|
return {
|
|
7678
8091
|
directory,
|
|
7679
8092
|
management,
|
|
@@ -7683,6 +8096,7 @@ function createGatewayConnection(link, serverId) {
|
|
|
7683
8096
|
credentials,
|
|
7684
8097
|
searchSettings,
|
|
7685
8098
|
contextSettings,
|
|
8099
|
+
defaultModel,
|
|
7686
8100
|
skillMarket
|
|
7687
8101
|
};
|
|
7688
8102
|
}
|
|
@@ -7709,6 +8123,7 @@ function createGatewayConnection(link, serverId) {
|
|
|
7709
8123
|
credentials,
|
|
7710
8124
|
searchSettings,
|
|
7711
8125
|
contextSettings,
|
|
8126
|
+
defaultModel,
|
|
7712
8127
|
workspaceFiles,
|
|
7713
8128
|
skillMarket
|
|
7714
8129
|
} = services();
|
|
@@ -7720,6 +8135,7 @@ function createGatewayConnection(link, serverId) {
|
|
|
7720
8135
|
credentials.ready,
|
|
7721
8136
|
searchSettings.ready,
|
|
7722
8137
|
contextSettings.ready,
|
|
8138
|
+
defaultModel.ready,
|
|
7723
8139
|
workspaceFiles.ready,
|
|
7724
8140
|
skillMarket.ready
|
|
7725
8141
|
]), signal);
|
|
@@ -7763,6 +8179,9 @@ function createGatewayConnection(link, serverId) {
|
|
|
7763
8179
|
get contextSettings() {
|
|
7764
8180
|
return services().contextSettings;
|
|
7765
8181
|
},
|
|
8182
|
+
get defaultModel() {
|
|
8183
|
+
return services().defaultModel;
|
|
8184
|
+
},
|
|
7766
8185
|
get skillMarket() {
|
|
7767
8186
|
return services().skillMarket;
|
|
7768
8187
|
},
|
|
@@ -20022,6 +20441,10 @@ function formatTokens(tokens) {
|
|
|
20022
20441
|
return `${(tokens / 1000).toFixed(1)}k`;
|
|
20023
20442
|
return String(tokens);
|
|
20024
20443
|
}
|
|
20444
|
+
function modelDisplayName(model) {
|
|
20445
|
+
const slash = model.indexOf("/");
|
|
20446
|
+
return slash < 0 || slash === model.length - 1 ? model : model.slice(slash + 1);
|
|
20447
|
+
}
|
|
20025
20448
|
|
|
20026
20449
|
// packages/tui/src/labels.ts
|
|
20027
20450
|
var commands, labels;
|
|
@@ -20036,13 +20459,13 @@ var init_labels = __esm(() => {
|
|
|
20036
20459
|
{ name: "exit", description: "Exit neko TUI" },
|
|
20037
20460
|
{ name: "new", description: "Create a new session" },
|
|
20038
20461
|
{ name: "sessions", description: "Switch or create a session" },
|
|
20039
|
-
{ name: "login", description: "
|
|
20462
|
+
{ name: "login", description: "Manage model providers and API keys" },
|
|
20040
20463
|
{ name: "model", description: "Choose a model" },
|
|
20041
20464
|
{ name: "agents", description: "View agent tasks and activity" },
|
|
20042
20465
|
{ name: "jobs", description: "View background jobs" },
|
|
20043
20466
|
{ name: "todos", description: "View the assistant's todo list" },
|
|
20044
20467
|
{ name: "question", description: "Answer the assistant's question (Ctrl+O)" },
|
|
20045
|
-
{ name: "search", description: "Choose the web search provider (Tavily/DeepSeek)" },
|
|
20468
|
+
{ name: "search", description: "Choose the web search provider (Tavily/DeepSeek); K sets its key" },
|
|
20046
20469
|
{ name: "context", description: "Tune context compaction and tool result pruning" },
|
|
20047
20470
|
{ name: "theme", description: "Choose color theme (auto/light/dark)" },
|
|
20048
20471
|
{ name: "think", description: "Set thinking level (/think high)" },
|
|
@@ -20111,7 +20534,7 @@ class StatusFooter {
|
|
|
20111
20534
|
const statusText = truncateToWidth2(s.compaction ?? s.busy ?? labels.ready, Math.min(28, Math.floor(space / 2)));
|
|
20112
20535
|
const tone = this.#tone();
|
|
20113
20536
|
const statusStyled = tone === "error" ? theme.error(statusText) : tone === "warning" ? theme.warning(statusText) : tone === "accent" ? theme.accent(statusText) : theme.ok(statusText);
|
|
20114
|
-
const modelShort = s.model
|
|
20537
|
+
const modelShort = modelDisplayName(s.model);
|
|
20115
20538
|
const identity = [
|
|
20116
20539
|
{ text: basename4(s.cwd) || s.cwd, slot: theme.muted },
|
|
20117
20540
|
{ text: modelShort, slot: theme.model },
|
|
@@ -20660,16 +21083,21 @@ import {
|
|
|
20660
21083
|
isKeyRelease as isKeyRelease2,
|
|
20661
21084
|
matchesKey as matchesKey5,
|
|
20662
21085
|
renderFakeCursor,
|
|
20663
|
-
truncateToWidth as truncateToWidth3
|
|
21086
|
+
truncateToWidth as truncateToWidth3,
|
|
21087
|
+
visibleWidth as visibleWidth3
|
|
20664
21088
|
} from "@earendil-works/pi-tui";
|
|
20665
21089
|
|
|
20666
21090
|
class LoginPanel {
|
|
20667
21091
|
height = 10;
|
|
20668
21092
|
focused = false;
|
|
20669
21093
|
open = false;
|
|
20670
|
-
#
|
|
21094
|
+
#level = "list";
|
|
21095
|
+
#filter = new Input({ prompt: "\u276F ", placeholder: "Type to filter" });
|
|
21096
|
+
#index = 0;
|
|
21097
|
+
#inputs = new Map;
|
|
20671
21098
|
#state;
|
|
20672
21099
|
#provider = "deepseek";
|
|
21100
|
+
#group = "model";
|
|
20673
21101
|
#message = "";
|
|
20674
21102
|
#busy = false;
|
|
20675
21103
|
#confirmingRemove = false;
|
|
@@ -20680,24 +21108,35 @@ class LoginPanel {
|
|
|
20680
21108
|
#closed;
|
|
20681
21109
|
#save;
|
|
20682
21110
|
#remove;
|
|
21111
|
+
#toggle;
|
|
20683
21112
|
#autoCloseMs;
|
|
20684
21113
|
constructor(options) {
|
|
20685
21114
|
this.#changed = options.changed;
|
|
20686
21115
|
this.#closed = options.closed;
|
|
20687
21116
|
this.#save = options.save;
|
|
20688
21117
|
this.#remove = options.remove;
|
|
21118
|
+
this.#toggle = options.toggle;
|
|
20689
21119
|
this.#autoCloseMs = options.autoCloseMs ?? AUTO_CLOSE_MS;
|
|
20690
21120
|
}
|
|
20691
|
-
show(state, subscribe) {
|
|
21121
|
+
show(state, subscribe, initial) {
|
|
20692
21122
|
this.#generation++;
|
|
20693
21123
|
this.#clearCloseTimer();
|
|
20694
21124
|
this.open = true;
|
|
21125
|
+
this.#level = "list";
|
|
20695
21126
|
this.#busy = false;
|
|
20696
21127
|
this.#confirmingRemove = false;
|
|
20697
21128
|
this.#message = "";
|
|
20698
|
-
this.#
|
|
21129
|
+
this.#inputs.clear();
|
|
21130
|
+
this.#filter = new Input({ prompt: "\u276F ", placeholder: "Type to filter" });
|
|
21131
|
+
this.#index = 0;
|
|
20699
21132
|
this.#provider = "deepseek";
|
|
21133
|
+
this.#group = "model";
|
|
20700
21134
|
this.#state = state;
|
|
21135
|
+
if (initial) {
|
|
21136
|
+
this.#provider = initial.provider;
|
|
21137
|
+
this.#group = initial.group;
|
|
21138
|
+
this.#level = "key";
|
|
21139
|
+
}
|
|
20701
21140
|
this.#unsubscribe?.();
|
|
20702
21141
|
this.#unsubscribe = subscribe((value) => {
|
|
20703
21142
|
if (this.open) {
|
|
@@ -20712,26 +21151,96 @@ class LoginPanel {
|
|
|
20712
21151
|
this.#clearCloseTimer();
|
|
20713
21152
|
this.open = false;
|
|
20714
21153
|
this.#confirmingRemove = false;
|
|
20715
|
-
this.#
|
|
21154
|
+
this.#inputs.clear();
|
|
21155
|
+
this.#filter = new Input({ prompt: "\u276F ", placeholder: "Type to filter" });
|
|
20716
21156
|
this.#unsubscribe?.();
|
|
20717
21157
|
this.#unsubscribe = undefined;
|
|
20718
21158
|
this.#closed();
|
|
20719
21159
|
this.#changed();
|
|
20720
21160
|
}
|
|
20721
21161
|
invalidate() {}
|
|
21162
|
+
get #input() {
|
|
21163
|
+
let input = this.#inputs.get(this.#provider);
|
|
21164
|
+
if (!input) {
|
|
21165
|
+
input = new Input;
|
|
21166
|
+
this.#inputs.set(this.#provider, input);
|
|
21167
|
+
}
|
|
21168
|
+
return input;
|
|
21169
|
+
}
|
|
21170
|
+
get #status() {
|
|
21171
|
+
return this.#state?.statuses[this.#provider];
|
|
21172
|
+
}
|
|
21173
|
+
#visible() {
|
|
21174
|
+
const query = compact(this.#filter.getValue());
|
|
21175
|
+
const all = listRows();
|
|
21176
|
+
if (query === "")
|
|
21177
|
+
return all;
|
|
21178
|
+
return all.filter(({ spec }) => compact(`${spec.name} ${spec.id} ${spec.envVar}`).includes(query));
|
|
21179
|
+
}
|
|
21180
|
+
#isEnabled(spec) {
|
|
21181
|
+
return isLocked(spec.id) || this.#state?.statuses[spec.id]?.enabled === true;
|
|
21182
|
+
}
|
|
20722
21183
|
handleInput(data) {
|
|
20723
21184
|
if (isKeyRelease2(data))
|
|
20724
21185
|
return;
|
|
20725
|
-
if (matchesKey5(data, "
|
|
21186
|
+
if (matchesKey5(data, "ctrl+c")) {
|
|
21187
|
+
this.close();
|
|
21188
|
+
return;
|
|
21189
|
+
}
|
|
21190
|
+
if (this.#level === "list")
|
|
21191
|
+
this.#handleList(data);
|
|
21192
|
+
else
|
|
21193
|
+
this.#handleKey(data);
|
|
21194
|
+
this.#changed();
|
|
21195
|
+
}
|
|
21196
|
+
#handleList(data) {
|
|
21197
|
+
if (matchesKey5(data, "escape")) {
|
|
20726
21198
|
this.close();
|
|
20727
21199
|
return;
|
|
20728
21200
|
}
|
|
21201
|
+
if (this.#busy)
|
|
21202
|
+
return;
|
|
21203
|
+
const items = this.#visible();
|
|
21204
|
+
if (matchesKey5(data, "up") || matchesKey5(data, "down")) {
|
|
21205
|
+
const delta = matchesKey5(data, "up") ? -1 : 1;
|
|
21206
|
+
this.#index = Math.max(0, Math.min(items.length - 1, this.#index + delta));
|
|
21207
|
+
this.#message = "";
|
|
21208
|
+
} else if (matchesKey5(data, "space")) {
|
|
21209
|
+
const row = items[this.#index];
|
|
21210
|
+
if (row)
|
|
21211
|
+
this.#toggleEnabled(row);
|
|
21212
|
+
} else if (matchesKey5(data, "enter")) {
|
|
21213
|
+
const row = items[this.#index];
|
|
21214
|
+
if (!row)
|
|
21215
|
+
return;
|
|
21216
|
+
this.#provider = row.spec.id;
|
|
21217
|
+
this.#group = row.group;
|
|
21218
|
+
this.#level = "key";
|
|
21219
|
+
this.#message = "";
|
|
21220
|
+
} else {
|
|
21221
|
+
const before = this.#filter.getValue();
|
|
21222
|
+
this.#filter.handleInput(data);
|
|
21223
|
+
if (this.#filter.getValue() !== before) {
|
|
21224
|
+
this.#index = 0;
|
|
21225
|
+
this.#message = "";
|
|
21226
|
+
}
|
|
21227
|
+
}
|
|
21228
|
+
}
|
|
21229
|
+
#handleKey(data) {
|
|
21230
|
+
if (matchesKey5(data, "escape")) {
|
|
21231
|
+
if (this.#busy || this.#closeTimer !== undefined)
|
|
21232
|
+
this.close();
|
|
21233
|
+
else
|
|
21234
|
+
this.#backToList();
|
|
21235
|
+
return;
|
|
21236
|
+
}
|
|
20729
21237
|
if (this.#busy)
|
|
20730
21238
|
return;
|
|
20731
21239
|
const confirming = this.#confirmingRemove;
|
|
20732
21240
|
this.#confirmingRemove = false;
|
|
20733
21241
|
if (confirming && !matchesKey5(data, "ctrl+r"))
|
|
20734
21242
|
this.#message = "";
|
|
21243
|
+
const input = this.#input;
|
|
20735
21244
|
if (matchesKey5(data, "enter"))
|
|
20736
21245
|
this.#apply(false);
|
|
20737
21246
|
else if (matchesKey5(data, "ctrl+r") && this.#status?.savedConfigured) {
|
|
@@ -20741,23 +21250,52 @@ class LoginPanel {
|
|
|
20741
21250
|
this.#confirmingRemove = true;
|
|
20742
21251
|
this.#message = "Press Ctrl+R again to remove the saved key.";
|
|
20743
21252
|
}
|
|
20744
|
-
} else if (matchesKey5(data, "tab")
|
|
20745
|
-
this.#
|
|
21253
|
+
} else if (matchesKey5(data, "tab"))
|
|
21254
|
+
this.#backToList();
|
|
20746
21255
|
else {
|
|
20747
|
-
|
|
20748
|
-
if (
|
|
20749
|
-
|
|
21256
|
+
input.handleInput(data);
|
|
21257
|
+
if (input.getValue().length > 4096)
|
|
21258
|
+
input.setValue(input.getValue().slice(0, 4096));
|
|
20750
21259
|
}
|
|
20751
|
-
this.#changed();
|
|
20752
|
-
}
|
|
20753
|
-
get #status() {
|
|
20754
|
-
return this.#state?.statuses[this.#provider];
|
|
20755
21260
|
}
|
|
20756
|
-
#
|
|
20757
|
-
const index =
|
|
20758
|
-
|
|
21261
|
+
#backToList() {
|
|
21262
|
+
const index = this.#visible().findIndex((row) => row.spec.id === this.#provider && row.group === this.#group);
|
|
21263
|
+
if (index >= 0)
|
|
21264
|
+
this.#index = index;
|
|
21265
|
+
this.#level = "list";
|
|
21266
|
+
this.#confirmingRemove = false;
|
|
20759
21267
|
this.#message = "";
|
|
20760
21268
|
}
|
|
21269
|
+
async#toggleEnabled({ spec, group }) {
|
|
21270
|
+
if (group === "search") {
|
|
21271
|
+
this.#message = isModelProvider(spec.id) ? `${spec.name} shares its key with the model provider and has no enable switch here.` : `${spec.name} is a web search credential and has no enable switch.`;
|
|
21272
|
+
return;
|
|
21273
|
+
}
|
|
21274
|
+
if (isLocked(spec.id)) {
|
|
21275
|
+
this.#message = `${spec.name} is always enabled.`;
|
|
21276
|
+
return;
|
|
21277
|
+
}
|
|
21278
|
+
if (!isModelProvider(spec.id))
|
|
21279
|
+
return;
|
|
21280
|
+
const enable = !this.#isEnabled(spec);
|
|
21281
|
+
const generation = this.#generation;
|
|
21282
|
+
this.#busy = true;
|
|
21283
|
+
this.#message = enable ? "Enabling..." : "Disabling...";
|
|
21284
|
+
this.#changed();
|
|
21285
|
+
try {
|
|
21286
|
+
await this.#toggle(spec.id, enable);
|
|
21287
|
+
if (generation === this.#generation)
|
|
21288
|
+
this.#message = enable ? `${spec.name} enabled. Press Enter to add a key.` : `${spec.name} disabled.`;
|
|
21289
|
+
} catch (error) {
|
|
21290
|
+
if (generation === this.#generation)
|
|
21291
|
+
this.#message = error instanceof Error ? error.message : "Could not update the provider. Try again.";
|
|
21292
|
+
} finally {
|
|
21293
|
+
if (generation === this.#generation) {
|
|
21294
|
+
this.#busy = false;
|
|
21295
|
+
this.#changed();
|
|
21296
|
+
}
|
|
21297
|
+
}
|
|
21298
|
+
}
|
|
20761
21299
|
#clearCloseTimer() {
|
|
20762
21300
|
if (this.#closeTimer !== undefined)
|
|
20763
21301
|
clearTimeout(this.#closeTimer);
|
|
@@ -20796,7 +21334,7 @@ class LoginPanel {
|
|
|
20796
21334
|
else
|
|
20797
21335
|
await this.#save(provider, key);
|
|
20798
21336
|
if (generation === this.#generation) {
|
|
20799
|
-
this.#
|
|
21337
|
+
this.#inputs.set(provider, new Input);
|
|
20800
21338
|
this.#message = remove ? "Saved key removed." : "Saved. Used by the next request.";
|
|
20801
21339
|
this.#scheduleAutoClose(generation);
|
|
20802
21340
|
}
|
|
@@ -20810,34 +21348,100 @@ class LoginPanel {
|
|
|
20810
21348
|
}
|
|
20811
21349
|
}
|
|
20812
21350
|
}
|
|
21351
|
+
#frame(rows, help, width) {
|
|
21352
|
+
const body = Math.max(0, this.height - 1);
|
|
21353
|
+
while (rows.length < body)
|
|
21354
|
+
rows.push("");
|
|
21355
|
+
return [...rows.slice(0, body), help].map((line) => theme.panel(` ${truncateToWidth3(line, Math.max(1, width - 2), "...", true)} `));
|
|
21356
|
+
}
|
|
20813
21357
|
render(width) {
|
|
21358
|
+
return this.#level === "list" ? this.#renderList(width) : this.#renderKey(width);
|
|
21359
|
+
}
|
|
21360
|
+
#renderList(width) {
|
|
21361
|
+
const w = Math.max(1, width - 2);
|
|
21362
|
+
const items = this.#visible();
|
|
21363
|
+
this.#index = Math.max(0, Math.min(this.#index, items.length - 1));
|
|
21364
|
+
const selected = items[this.#index];
|
|
21365
|
+
const rows = [theme.accent(style.bold("Model providers and API keys"))];
|
|
21366
|
+
if (this.height >= 4) {
|
|
21367
|
+
this.#filter.focused = this.focused;
|
|
21368
|
+
rows.push(...this.#filter.render(w).slice(0, 1));
|
|
21369
|
+
}
|
|
21370
|
+
const showStatus = this.height >= 6;
|
|
21371
|
+
const entries = [];
|
|
21372
|
+
let group;
|
|
21373
|
+
for (const row of items) {
|
|
21374
|
+
const next = row.group === "search" ? "Web search" : "Model providers";
|
|
21375
|
+
if (next !== group)
|
|
21376
|
+
entries.push({ header: next });
|
|
21377
|
+
group = next;
|
|
21378
|
+
entries.push({ row });
|
|
21379
|
+
}
|
|
21380
|
+
const count = Math.max(1, this.height - rows.length - 1 - (showStatus ? 1 : 0));
|
|
21381
|
+
const at = entries.findIndex((entry) => ("row" in entry) && entry.row === selected);
|
|
21382
|
+
const start = Math.max(0, Math.min(at - Math.floor(count / 2), entries.length - count));
|
|
21383
|
+
for (const entry of entries.slice(start, start + count)) {
|
|
21384
|
+
if ("header" in entry) {
|
|
21385
|
+
rows.push(theme.dim(` ${entry.header}`));
|
|
21386
|
+
continue;
|
|
21387
|
+
}
|
|
21388
|
+
const { spec, group: rowGroup } = entry.row;
|
|
21389
|
+
const status = this.#state?.statuses[spec.id];
|
|
21390
|
+
const enabled = this.#isEnabled(spec);
|
|
21391
|
+
const modelRow = rowGroup === "model";
|
|
21392
|
+
const mark = modelRow ? enabled ? "[x]" : "[ ]" : " ";
|
|
21393
|
+
const right = status ? `${status.source} \xB7 env ${status.environmentConfigured ? "set" : "unset"}` : "loading";
|
|
21394
|
+
const leftWidth = Math.max(1, w - 3 - visibleWidth3(right));
|
|
21395
|
+
const left = truncateToWidth3(`${mark} ${spec.name}${modelRow && isLocked(spec.id) ? " (always on)" : ""}`, leftWidth, "...");
|
|
21396
|
+
const label = `${left}${" ".repeat(Math.max(1, leftWidth - visibleWidth3(left) + 1))}${right}`;
|
|
21397
|
+
if (entry.row === selected)
|
|
21398
|
+
rows.push(theme.selection(`\u276F ${label}`));
|
|
21399
|
+
else
|
|
21400
|
+
rows.push(modelRow && !enabled ? theme.dim(` ${label}`) : ` ${label}`);
|
|
21401
|
+
}
|
|
21402
|
+
if (items.length === 0)
|
|
21403
|
+
rows.push("No matching providers");
|
|
21404
|
+
if (showStatus) {
|
|
21405
|
+
while (rows.length < this.height - 2)
|
|
21406
|
+
rows.push("");
|
|
21407
|
+
const hint = selected ? selected.group === "search" && isModelProvider(selected.spec.id) ? `Same key as the ${selected.spec.name} model provider. Environment variable: ${selected.spec.envVar}` : `Environment variable: ${selected.spec.envVar}` : "";
|
|
21408
|
+
rows.push(this.#message || (hint ? theme.dim(hint) : ""));
|
|
21409
|
+
}
|
|
21410
|
+
const help = this.#busy ? shortcutHints(["Esc", "Close"]) : shortcutHints(["\u2191\u2193", "Select"], ["Space", "Toggle"], ["Enter", "Key"], ["Esc", "Close"]);
|
|
21411
|
+
return this.#frame(rows, help, width);
|
|
21412
|
+
}
|
|
21413
|
+
#renderKey(width) {
|
|
20814
21414
|
const state = this.#status;
|
|
20815
|
-
const spec =
|
|
20816
|
-
const tabs = CREDENTIAL_PROVIDERS.map((item) => item.id === this.#provider ? `[${item.id}]` : ` ${item.id} `).join(" ");
|
|
21415
|
+
const spec = CREDENTIAL_PROVIDERS.find((item) => item.id === this.#provider) ?? CREDENTIAL_PROVIDERS[0];
|
|
20817
21416
|
const source = state?.source === "keyless" ? "keyless (rate-limited)" : state?.source ?? "Loading...";
|
|
20818
21417
|
const keyLength = this.#input.getValue().length;
|
|
20819
21418
|
const marker = this.focused ? CURSOR_MARKER2 : "";
|
|
20820
21419
|
const keyRow = keyLength > 0 ? `API Key: ********${marker}${this.focused ? renderFakeCursor(" ") : ""} (${keyLength} characters)` : `API Key: ${marker}${this.focused ? `${renderFakeCursor("(")}paste key)` : "(paste key)"}`;
|
|
20821
|
-
const
|
|
20822
|
-
|
|
20823
|
-
|
|
20824
|
-
|
|
20825
|
-
|
|
20826
|
-
|
|
20827
|
-
|
|
20828
|
-
];
|
|
20829
|
-
|
|
20830
|
-
|
|
20831
|
-
|
|
20832
|
-
|
|
20833
|
-
|
|
20834
|
-
|
|
20835
|
-
|
|
20836
|
-
|
|
21420
|
+
const tag = this.#group === "search" ? isModelProvider(spec.id) ? "web search, same key as model provider" : "web search" : this.#isEnabled(spec) ? "enabled" : "disabled";
|
|
21421
|
+
const defaultMessage = spec.keyless ? `Without a key, ${spec.name} runs in rate-limited keyless mode.` : this.#group === "model" && !this.#isEnabled(spec) ? "Provider is disabled. Esc, then Space enables it." : "Saved key overrides environment.";
|
|
21422
|
+
const provider = `Provider: ${spec.name} (${tag})`;
|
|
21423
|
+
const message = this.#message || defaultMessage;
|
|
21424
|
+
const environment = `Environment: ${spec.envVar} ${state ? state.environmentConfigured ? "Configured" : "Not configured" : "Loading..."}`;
|
|
21425
|
+
const sourceRow = `Active source: ${source}`;
|
|
21426
|
+
const rows = this.height - 1 >= 6 ? [provider, environment, sourceRow, keyRow, message, spec.signupUrl] : [provider, keyRow, message, environment, sourceRow, spec.signupUrl];
|
|
21427
|
+
const help = this.#busy ? shortcutHints(["Esc", "Close"]) : this.#confirmingRemove ? shortcutHints(["Ctrl+R", "Confirm remove"], ["Esc", "Back"]) : state?.savedConfigured ? shortcutHints(["Enter", "Save"], ["Ctrl+R", "Remove"], ["Tab", "Providers"], ["Esc", "Back"]) : shortcutHints(["Enter", "Save"], ["Tab", "Providers"], ["Esc", "Back"]);
|
|
21428
|
+
return this.#frame(rows, help, width);
|
|
21429
|
+
}
|
|
21430
|
+
}
|
|
21431
|
+
var AUTO_CLOSE_MS = 800, isLocked = (id) => LOCKED_PROVIDERS.includes(id), SEARCH_ALIASES, listRows = () => {
|
|
21432
|
+
const all = CREDENTIAL_PROVIDERS.map((spec) => ({ spec, group: spec.kind === "search" ? "search" : "model" }));
|
|
21433
|
+
for (const id of SEARCH_ALIASES) {
|
|
21434
|
+
const spec = CREDENTIAL_PROVIDERS.find((item) => item.id === id);
|
|
21435
|
+
if (spec)
|
|
21436
|
+
all.push({ spec, group: "search" });
|
|
21437
|
+
}
|
|
21438
|
+
return all;
|
|
21439
|
+
}, compact = (value) => value.toLowerCase().replace(/\s+/g, "");
|
|
20837
21440
|
var init_login = __esm(() => {
|
|
20838
21441
|
init_src();
|
|
20839
21442
|
init_theme();
|
|
20840
21443
|
init_shortcut_hints();
|
|
21444
|
+
SEARCH_ALIASES = ["deepseek"];
|
|
20841
21445
|
});
|
|
20842
21446
|
|
|
20843
21447
|
// packages/tui/src/components/notice.ts
|
|
@@ -20861,7 +21465,7 @@ import {
|
|
|
20861
21465
|
isKeyRelease as isKeyRelease3,
|
|
20862
21466
|
matchesKey as matchesKey6,
|
|
20863
21467
|
truncateToWidth as truncateToWidth4,
|
|
20864
|
-
visibleWidth as
|
|
21468
|
+
visibleWidth as visibleWidth4,
|
|
20865
21469
|
wrapTextWithAnsi as wrapTextWithAnsi3
|
|
20866
21470
|
} from "@earendil-works/pi-tui";
|
|
20867
21471
|
|
|
@@ -20869,7 +21473,9 @@ class PickerPanel {
|
|
|
20869
21473
|
pointerSelection = false;
|
|
20870
21474
|
agentNavigation = false;
|
|
20871
21475
|
emptyText;
|
|
21476
|
+
groupedFilter = false;
|
|
20872
21477
|
listKey;
|
|
21478
|
+
help;
|
|
20873
21479
|
search = new Input2({ prompt: "\u276F ", placeholder: "Type to filter" });
|
|
20874
21480
|
#items = [];
|
|
20875
21481
|
#index = 0;
|
|
@@ -20877,6 +21483,7 @@ class PickerPanel {
|
|
|
20877
21483
|
#generation = 0;
|
|
20878
21484
|
#state = "closed";
|
|
20879
21485
|
#error = "";
|
|
21486
|
+
#hint = "";
|
|
20880
21487
|
#title = "";
|
|
20881
21488
|
#load;
|
|
20882
21489
|
#pick;
|
|
@@ -20896,6 +21503,9 @@ class PickerPanel {
|
|
|
20896
21503
|
set focused(value) {
|
|
20897
21504
|
this.search.focused = value;
|
|
20898
21505
|
}
|
|
21506
|
+
set hint(value) {
|
|
21507
|
+
this.#hint = value;
|
|
21508
|
+
}
|
|
20899
21509
|
invalidate() {}
|
|
20900
21510
|
async show(title, load, pick) {
|
|
20901
21511
|
if (this.open)
|
|
@@ -20939,6 +21549,9 @@ class PickerPanel {
|
|
|
20939
21549
|
this.pointerSelection = false;
|
|
20940
21550
|
this.agentNavigation = false;
|
|
20941
21551
|
this.emptyText = undefined;
|
|
21552
|
+
this.groupedFilter = false;
|
|
21553
|
+
this.help = undefined;
|
|
21554
|
+
this.#hint = "";
|
|
20942
21555
|
this.listKey = undefined;
|
|
20943
21556
|
++this.#generation;
|
|
20944
21557
|
for (const detail of this.#details)
|
|
@@ -20969,9 +21582,23 @@ class PickerPanel {
|
|
|
20969
21582
|
#details = [];
|
|
20970
21583
|
#filtered() {
|
|
20971
21584
|
const query = this.search.getValue().toLowerCase();
|
|
20972
|
-
if (query
|
|
21585
|
+
if (query === "")
|
|
21586
|
+
return this.#items;
|
|
21587
|
+
if (!this.groupedFilter)
|
|
20973
21588
|
return this.#items.filter((item) => !item.header && this.#matches(item, query));
|
|
20974
|
-
|
|
21589
|
+
const result = [];
|
|
21590
|
+
let header;
|
|
21591
|
+
for (const item of this.#items) {
|
|
21592
|
+
if (item.header)
|
|
21593
|
+
header = item;
|
|
21594
|
+
else if (this.#matches(item, query)) {
|
|
21595
|
+
if (header)
|
|
21596
|
+
result.push(header);
|
|
21597
|
+
header = undefined;
|
|
21598
|
+
result.push(item);
|
|
21599
|
+
}
|
|
21600
|
+
}
|
|
21601
|
+
return result;
|
|
20975
21602
|
}
|
|
20976
21603
|
#matches(item, query) {
|
|
20977
21604
|
return `${item.label} ${item.description ?? ""} ${item.searchText ?? ""}`.toLowerCase().includes(query);
|
|
@@ -21019,6 +21646,7 @@ class PickerPanel {
|
|
|
21019
21646
|
}
|
|
21020
21647
|
if (this.#state !== "ready")
|
|
21021
21648
|
return;
|
|
21649
|
+
this.#hint = "";
|
|
21022
21650
|
const items = this.#filtered();
|
|
21023
21651
|
if (this.listKey?.(data, items[this.#index]?.value)) {
|
|
21024
21652
|
this.changed();
|
|
@@ -21032,6 +21660,11 @@ class PickerPanel {
|
|
|
21032
21660
|
const item = items[this.#index];
|
|
21033
21661
|
if (!item || item.header)
|
|
21034
21662
|
return;
|
|
21663
|
+
if (item.blockedHint) {
|
|
21664
|
+
this.#hint = item.blockedHint;
|
|
21665
|
+
this.changed();
|
|
21666
|
+
return;
|
|
21667
|
+
}
|
|
21035
21668
|
const generation = this.#generation;
|
|
21036
21669
|
this.#state = "applying";
|
|
21037
21670
|
this.#pick(item.value).then((result) => {
|
|
@@ -21112,8 +21745,9 @@ class PickerPanel {
|
|
|
21112
21745
|
}
|
|
21113
21746
|
if (height >= 4)
|
|
21114
21747
|
rows.push(...this.search.render(w).slice(0, 1));
|
|
21115
|
-
const help = this.#state === "error" ? labels.retry : this.#state === "ready" ? this.agentNavigation ? shortcutHints(["\u2191\u2193", "Select"], ["\u2192/Enter", "Open"], ["\u2190", "Main"]) : labels.select : labels.close;
|
|
21116
|
-
const
|
|
21748
|
+
const help = this.#state === "error" ? labels.retry : this.#state === "ready" ? this.agentNavigation ? shortcutHints(["\u2191\u2193", "Select"], ["\u2192/Enter", "Open"], ["\u2190", "Main"]) : this.help ?? labels.select : labels.close;
|
|
21749
|
+
const hint = this.#state === "ready" && height >= 5 ? this.#hint : "";
|
|
21750
|
+
const available = Math.max(1, height - rows.length - 1 - (hint ? 1 : 0));
|
|
21117
21751
|
if (this.#state !== "ready")
|
|
21118
21752
|
rows.push(this.#state === "error" ? theme.error(`Error: ${this.#error}`) : this.#state === "applying" ? labels.applying : labels.loading);
|
|
21119
21753
|
else {
|
|
@@ -21125,7 +21759,7 @@ class PickerPanel {
|
|
|
21125
21759
|
this.#index = first;
|
|
21126
21760
|
}
|
|
21127
21761
|
const selected = items[this.#index];
|
|
21128
|
-
const details = height >= 9 && selected?.description ? wrapTextWithAnsi3(selected.description, w).slice(0, 2) : [];
|
|
21762
|
+
const details = height >= 9 && !hint && selected?.description ? wrapTextWithAnsi3(selected.description, w).slice(0, 2) : [];
|
|
21129
21763
|
const count = Math.max(1, available - details.length);
|
|
21130
21764
|
const start = Math.max(0, Math.min(this.#index - Math.floor(count / 2), items.length - count));
|
|
21131
21765
|
for (const [i, item] of items.slice(start, start + count).entries()) {
|
|
@@ -21134,9 +21768,9 @@ class PickerPanel {
|
|
|
21134
21768
|
continue;
|
|
21135
21769
|
}
|
|
21136
21770
|
const right = item.rightLabel ?? "";
|
|
21137
|
-
const leftWidth = Math.max(1, w - 3 -
|
|
21771
|
+
const leftWidth = Math.max(1, w - 3 - visibleWidth4(right));
|
|
21138
21772
|
const left = truncateToWidth4(item.label, leftWidth, "...");
|
|
21139
|
-
const label = right ? `${left}${" ".repeat(Math.max(1, leftWidth -
|
|
21773
|
+
const label = right ? `${left}${" ".repeat(Math.max(1, leftWidth - visibleWidth4(left) + 1))}${right}` : left;
|
|
21140
21774
|
const rendered = start + i === this.#index ? theme.selection(`\u276F ${label}`) : ` ${label}`;
|
|
21141
21775
|
if (this.pointerSelection)
|
|
21142
21776
|
this.#pointerRows.set(rows.length, start + i);
|
|
@@ -21144,9 +21778,11 @@ class PickerPanel {
|
|
|
21144
21778
|
}
|
|
21145
21779
|
if (items.length === 0)
|
|
21146
21780
|
rows.push(this.emptyText !== undefined && this.#items.length === 0 ? this.emptyText : labels.noResults);
|
|
21147
|
-
while (rows.length < height - 1 - details.length)
|
|
21781
|
+
while (rows.length < height - 1 - details.length - (hint ? 1 : 0))
|
|
21148
21782
|
rows.push("");
|
|
21149
21783
|
rows.push(...details.map((line) => theme.dim(line)));
|
|
21784
|
+
if (hint)
|
|
21785
|
+
rows.push(theme.warning(hint));
|
|
21150
21786
|
}
|
|
21151
21787
|
while (rows.length < height - 1)
|
|
21152
21788
|
rows.push("");
|
|
@@ -21899,10 +22535,151 @@ function followTerminalTheme(ui, apply) {
|
|
|
21899
22535
|
}
|
|
21900
22536
|
var THEME_QUERY_TIMEOUT_MS = 100;
|
|
21901
22537
|
|
|
22538
|
+
// packages/tui/src/image-input.ts
|
|
22539
|
+
import { execFile as execFile2 } from "child_process";
|
|
22540
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
22541
|
+
import { readFile as readFile6, stat as stat4 } from "fs/promises";
|
|
22542
|
+
import { homedir as homedir6 } from "os";
|
|
22543
|
+
import { basename as basename5, join as join8, resolve as resolve5 } from "path";
|
|
22544
|
+
import { promisify as promisify2 } from "util";
|
|
22545
|
+
import { isKeyRelease as isKeyRelease4, isKeyRepeat as isKeyRepeat2, matchesKey as matchesKey8 } from "@earendil-works/pi-tui";
|
|
22546
|
+
function imagePaths(text, platform = process.platform) {
|
|
22547
|
+
const paths = [];
|
|
22548
|
+
let word = "", quote = "", escaped = false;
|
|
22549
|
+
for (const c of text.trim()) {
|
|
22550
|
+
if (escaped) {
|
|
22551
|
+
word += c;
|
|
22552
|
+
escaped = false;
|
|
22553
|
+
} else if (c === "\\" && quote !== "'" && platform !== "win32") {
|
|
22554
|
+
escaped = true;
|
|
22555
|
+
} else if (quote) {
|
|
22556
|
+
if (c === quote)
|
|
22557
|
+
quote = "";
|
|
22558
|
+
else
|
|
22559
|
+
word += c;
|
|
22560
|
+
} else if (c === '"' || c === "'")
|
|
22561
|
+
quote = c;
|
|
22562
|
+
else if (/\s/.test(c)) {
|
|
22563
|
+
if (word) {
|
|
22564
|
+
paths.push(word);
|
|
22565
|
+
word = "";
|
|
22566
|
+
}
|
|
22567
|
+
} else
|
|
22568
|
+
word += c;
|
|
22569
|
+
}
|
|
22570
|
+
if (quote || escaped)
|
|
22571
|
+
return null;
|
|
22572
|
+
if (word)
|
|
22573
|
+
paths.push(word);
|
|
22574
|
+
return paths.length && paths.every((p) => /\.(png|jpe?g|webp)$/i.test(p)) ? paths : null;
|
|
22575
|
+
}
|
|
22576
|
+
function expandHome(path, platform = process.platform) {
|
|
22577
|
+
if (path.startsWith("~/") || platform === "win32" && path.startsWith("~\\"))
|
|
22578
|
+
return join8(homedir6(), path.slice(2));
|
|
22579
|
+
return path;
|
|
22580
|
+
}
|
|
22581
|
+
async function readImageFile(path, cwd, platform = process.platform) {
|
|
22582
|
+
const full = resolve5(cwd, expandHome(path, platform));
|
|
22583
|
+
const info = await stat4(full);
|
|
22584
|
+
if (!info.isFile() || info.size > 20 * 1024 * 1024)
|
|
22585
|
+
throw new Error("Image must be a file no larger than 20 MiB");
|
|
22586
|
+
const bytes = await readFile6(full);
|
|
22587
|
+
const after = await stat4(full);
|
|
22588
|
+
if (bytes.length !== info.size || after.size !== info.size || after.mtimeMs !== info.mtimeMs)
|
|
22589
|
+
throw new Error("Image changed while reading");
|
|
22590
|
+
const mimeType = /\.png$/i.test(full) ? "image/png" : /\.webp$/i.test(full) ? "image/webp" : "image/jpeg";
|
|
22591
|
+
return { name: basename5(full), mimeType, bytes };
|
|
22592
|
+
}
|
|
22593
|
+
async function command(file, args, encoding = "utf8", timeout = 2000) {
|
|
22594
|
+
return (await exec(file, args, { timeout, maxBuffer: 30 * 1024 * 1024, encoding })).stdout;
|
|
22595
|
+
}
|
|
22596
|
+
async function clipboard(options = {}) {
|
|
22597
|
+
const run = options.command ?? command;
|
|
22598
|
+
const platform = options.platform ?? process.platform;
|
|
22599
|
+
if (platform === "darwin") {
|
|
22600
|
+
const files = await run("osascript", [
|
|
22601
|
+
"-e",
|
|
22602
|
+
`try
|
|
22603
|
+
set fs to the clipboard as \xABclass furl\xBB
|
|
22604
|
+
return POSIX path of fs
|
|
22605
|
+
on error
|
|
22606
|
+
return ""
|
|
22607
|
+
end try`
|
|
22608
|
+
]).catch(() => "");
|
|
22609
|
+
if (String(files).trim())
|
|
22610
|
+
return /\.(png|jpe?g|webp)$/i.test(String(files).trim()) ? { paths: [String(files).trim()] } : { text: String(files).trim() };
|
|
22611
|
+
const image = String(await run("osascript", ["-e", `try
|
|
22612
|
+
return the clipboard as \xABclass PNGf\xBB
|
|
22613
|
+
on error
|
|
22614
|
+
return ""
|
|
22615
|
+
end try`]).catch(() => ""));
|
|
22616
|
+
const hex = /\u00ABdata PNGf([0-9A-Fa-f]+)\u00BB/.exec(image)?.[1];
|
|
22617
|
+
if (hex)
|
|
22618
|
+
return { image: { name: "Clipboard.png", mimeType: "image/png", bytes: Buffer.from(hex, "hex") } };
|
|
22619
|
+
return { text: String(await run("pbpaste", [])) };
|
|
22620
|
+
}
|
|
22621
|
+
if (platform === "win32")
|
|
22622
|
+
return windowsClipboard(run);
|
|
22623
|
+
let windowsError;
|
|
22624
|
+
if (platform === "linux" && (options.wsl ?? runningInWsl())) {
|
|
22625
|
+
try {
|
|
22626
|
+
return await windowsClipboard(run);
|
|
22627
|
+
} catch (error) {
|
|
22628
|
+
windowsError = error;
|
|
22629
|
+
}
|
|
22630
|
+
}
|
|
22631
|
+
const wayland = options.wayland ?? !!process.env.WAYLAND_DISPLAY;
|
|
22632
|
+
const tool = wayland ? "wl-paste" : "xclip";
|
|
22633
|
+
try {
|
|
22634
|
+
const bytes = await run(tool, wayland ? ["--type", "image/png"] : ["-selection", "clipboard", "-t", "image/png", "-o"], "buffer");
|
|
22635
|
+
if (bytes.length)
|
|
22636
|
+
return { image: { name: "Clipboard.png", mimeType: "image/png", bytes: Buffer.from(bytes) } };
|
|
22637
|
+
} catch {}
|
|
22638
|
+
try {
|
|
22639
|
+
return { text: String(await run(tool, wayland ? ["--no-newline"] : ["-selection", "clipboard", "-o"])) };
|
|
22640
|
+
} catch (error) {
|
|
22641
|
+
throw windowsError ?? error;
|
|
22642
|
+
}
|
|
22643
|
+
}
|
|
22644
|
+
async function windowsClipboard(run) {
|
|
22645
|
+
const value = String(await run("powershell.exe", ["-NoProfile", "-STA", "-Command", WINDOWS_CLIPBOARD_SCRIPT], "utf8", 8000));
|
|
22646
|
+
if (value.startsWith("IMAGE:"))
|
|
22647
|
+
return {
|
|
22648
|
+
image: { name: "Clipboard.png", mimeType: "image/png", bytes: Buffer.from(value.slice(6).trim(), "base64") }
|
|
22649
|
+
};
|
|
22650
|
+
return { text: value.replace(/\r?\n$/, "") };
|
|
22651
|
+
}
|
|
22652
|
+
function runningInWsl() {
|
|
22653
|
+
wslHost ??= process.platform === "linux" && (Boolean(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP || process.env.WSLENV) || /microsoft|wsl/i.test(readFileOrEmpty("/proc/version")));
|
|
22654
|
+
return wslHost;
|
|
22655
|
+
}
|
|
22656
|
+
function readFileOrEmpty(path) {
|
|
22657
|
+
try {
|
|
22658
|
+
return readFileSync2(path, "utf8");
|
|
22659
|
+
} catch {
|
|
22660
|
+
return "";
|
|
22661
|
+
}
|
|
22662
|
+
}
|
|
22663
|
+
function clipboardShortcutKey(platform = process.platform, wsl = runningInWsl()) {
|
|
22664
|
+
return platform === "win32" || platform === "linux" && wsl ? "alt+v" : "ctrl+v";
|
|
22665
|
+
}
|
|
22666
|
+
function isClipboardShortcut(data, platform = process.platform, wsl = runningInWsl()) {
|
|
22667
|
+
return !isKeyRelease4(data) && !isKeyRepeat2(data) && matchesKey8(data, clipboardShortcutKey(platform, wsl));
|
|
22668
|
+
}
|
|
22669
|
+
var exec, WINDOWS_CLIPBOARD_SCRIPT, wslHost;
|
|
22670
|
+
var init_image_input = __esm(() => {
|
|
22671
|
+
exec = promisify2(execFile2);
|
|
22672
|
+
WINDOWS_CLIPBOARD_SCRIPT = [
|
|
22673
|
+
"try{[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false)}catch{}",
|
|
22674
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
22675
|
+
"$i=[Windows.Forms.Clipboard]::GetImage()",
|
|
22676
|
+
'if($i){$m=New-Object IO.MemoryStream; $i.Save($m,[Drawing.Imaging.ImageFormat]::Png); "IMAGE:"+[Convert]::ToBase64String($m.ToArray());$i.Dispose();$m.Dispose()}else{Get-Clipboard -Raw}'
|
|
22677
|
+
].join("; ");
|
|
22678
|
+
});
|
|
22679
|
+
|
|
21902
22680
|
// packages/tui/src/export-command.ts
|
|
21903
22681
|
import { open as open3, unlink as unlink2 } from "fs/promises";
|
|
21904
|
-
import {
|
|
21905
|
-
import { resolve as resolve5 } from "path";
|
|
22682
|
+
import { resolve as resolve6 } from "path";
|
|
21906
22683
|
function parseExportArgs(input) {
|
|
21907
22684
|
const words = [];
|
|
21908
22685
|
let word = "";
|
|
@@ -21952,7 +22729,7 @@ async function exportToFile(input) {
|
|
|
21952
22729
|
const format = input.path === undefined ? "markdown" : exportFormatFromPath(input.path);
|
|
21953
22730
|
if (format === null)
|
|
21954
22731
|
throw new Error("Unsupported export format: use a .md, .html or .json path");
|
|
21955
|
-
const target =
|
|
22732
|
+
const target = resolve6(input.cwd, input.path === undefined ? exportFileName(input.source.title, format, input.source.exportedAt) : expandHome(input.path, input.platform));
|
|
21956
22733
|
let handle;
|
|
21957
22734
|
try {
|
|
21958
22735
|
handle = await open3(target, "wx");
|
|
@@ -21981,6 +22758,7 @@ async function exportToFile(input) {
|
|
|
21981
22758
|
var EXPORT_USAGE = "Usage: /export [path.md|path.html|path.json] [--thinking]";
|
|
21982
22759
|
var init_export_command = __esm(() => {
|
|
21983
22760
|
init_conversation_export();
|
|
22761
|
+
init_image_input();
|
|
21984
22762
|
});
|
|
21985
22763
|
|
|
21986
22764
|
// packages/core/src/file-mentions.ts
|
|
@@ -22290,7 +23068,11 @@ var init_ws_listener = __esm(() => {
|
|
|
22290
23068
|
async function connectNekoGateway(address, signal) {
|
|
22291
23069
|
signal?.throwIfAborted();
|
|
22292
23070
|
const link = address.transport === "unix" ? await createUnixLink(address.socketPath, signal) : createWsLink(address.url, signal);
|
|
22293
|
-
|
|
23071
|
+
return connectNekoGatewayLink(link, address.serverId, signal);
|
|
23072
|
+
}
|
|
23073
|
+
async function connectNekoGatewayLink(link, serverId, signal) {
|
|
23074
|
+
signal?.throwIfAborted();
|
|
23075
|
+
const connection = createGatewayConnection(link, serverId);
|
|
22294
23076
|
const controller = new AbortController;
|
|
22295
23077
|
const abort = () => controller.abort(signal?.reason);
|
|
22296
23078
|
signal?.addEventListener("abort", abort, { once: true });
|
|
@@ -22339,8 +23121,8 @@ var init_principal = __esm(() => {
|
|
|
22339
23121
|
|
|
22340
23122
|
// packages/gateway/src/mobile/store.ts
|
|
22341
23123
|
import { randomBytes } from "crypto";
|
|
22342
|
-
import { chmod as chmod2, mkdir as mkdir5, readFile as
|
|
22343
|
-
import { join as
|
|
23124
|
+
import { chmod as chmod2, mkdir as mkdir5, readFile as readFile7, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
23125
|
+
import { join as join9 } from "path";
|
|
22344
23126
|
function defaultState() {
|
|
22345
23127
|
return {
|
|
22346
23128
|
formatVersion: MOBILE_STATE_FORMAT_VERSION,
|
|
@@ -22460,9 +23242,10 @@ async function writeFileAtomic(filePath, contents, mode) {
|
|
|
22460
23242
|
await writeFile3(tempPath, contents, { mode });
|
|
22461
23243
|
if (POSIX)
|
|
22462
23244
|
await chmod2(tempPath, mode);
|
|
22463
|
-
await
|
|
22464
|
-
}
|
|
23245
|
+
await renameWithRetry(tempPath, filePath);
|
|
23246
|
+
} catch (error) {
|
|
22465
23247
|
await rm3(tempPath, { force: true });
|
|
23248
|
+
throw error;
|
|
22466
23249
|
}
|
|
22467
23250
|
}
|
|
22468
23251
|
async function writeSecretFile(filePath, contents) {
|
|
@@ -22470,7 +23253,7 @@ async function writeSecretFile(filePath, contents) {
|
|
|
22470
23253
|
}
|
|
22471
23254
|
async function readSecretFile(filePath) {
|
|
22472
23255
|
try {
|
|
22473
|
-
return await
|
|
23256
|
+
return await readFile7(filePath, "utf8");
|
|
22474
23257
|
} catch (error) {
|
|
22475
23258
|
if (isNotFound(error))
|
|
22476
23259
|
return;
|
|
@@ -22480,7 +23263,7 @@ async function readSecretFile(filePath) {
|
|
|
22480
23263
|
async function loadState(statePath) {
|
|
22481
23264
|
let raw;
|
|
22482
23265
|
try {
|
|
22483
|
-
raw = await
|
|
23266
|
+
raw = await readFile7(statePath, "utf8");
|
|
22484
23267
|
} catch (error) {
|
|
22485
23268
|
if (isNotFound(error))
|
|
22486
23269
|
return freezeState(defaultState());
|
|
@@ -22504,21 +23287,21 @@ class MobileStore {
|
|
|
22504
23287
|
queue = Promise.resolve();
|
|
22505
23288
|
constructor(mobileDir, initial) {
|
|
22506
23289
|
this.mobileDir = mobileDir;
|
|
22507
|
-
this.tlsDir =
|
|
22508
|
-
this.statePath =
|
|
22509
|
-
this.adminTokenPath =
|
|
23290
|
+
this.tlsDir = join9(mobileDir, "tls");
|
|
23291
|
+
this.statePath = join9(mobileDir, "state.json");
|
|
23292
|
+
this.adminTokenPath = join9(mobileDir, "admin-token");
|
|
22510
23293
|
this.current = initial;
|
|
22511
23294
|
}
|
|
22512
23295
|
static async open(nekoDir) {
|
|
22513
|
-
const mobileDir =
|
|
22514
|
-
const tlsDir =
|
|
23296
|
+
const mobileDir = join9(nekoDir, "mobile");
|
|
23297
|
+
const tlsDir = join9(mobileDir, "tls");
|
|
22515
23298
|
await mkdir5(mobileDir, { recursive: true, mode: 448 });
|
|
22516
23299
|
await mkdir5(tlsDir, { recursive: true, mode: 448 });
|
|
22517
23300
|
if (POSIX) {
|
|
22518
23301
|
await chmod2(mobileDir, 448);
|
|
22519
23302
|
await chmod2(tlsDir, 448);
|
|
22520
23303
|
}
|
|
22521
|
-
return new MobileStore(mobileDir, await loadState(
|
|
23304
|
+
return new MobileStore(mobileDir, await loadState(join9(mobileDir, "state.json")));
|
|
22522
23305
|
}
|
|
22523
23306
|
getState() {
|
|
22524
23307
|
return this.current;
|
|
@@ -22640,6 +23423,7 @@ class MobileStore {
|
|
|
22640
23423
|
}
|
|
22641
23424
|
var MOBILE_STATE_FORMAT_VERSION = 1, MOBILE_DEFAULT_PORT = 8323, MOBILE_ADMIN_TOKEN_BYTES = 32, POSIX, ADMIN_TOKEN_PATTERN, CREDENTIAL_HASH_PATTERN, tempCounter = 0;
|
|
22642
23425
|
var init_store = __esm(() => {
|
|
23426
|
+
init_src();
|
|
22643
23427
|
POSIX = process.platform !== "win32";
|
|
22644
23428
|
ADMIN_TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
22645
23429
|
CREDENTIAL_HASH_PATTERN = /^[0-9a-fA-F]{64}$/;
|
|
@@ -41082,7 +41866,7 @@ import {
|
|
|
41082
41866
|
randomBytes as randomBytes2,
|
|
41083
41867
|
X509Certificate
|
|
41084
41868
|
} from "crypto";
|
|
41085
|
-
import { join as
|
|
41869
|
+
import { join as join10 } from "path";
|
|
41086
41870
|
function mobileCommonName(serverId) {
|
|
41087
41871
|
return `neko-${serverId.slice(0, 8).toLowerCase()}.invalid`;
|
|
41088
41872
|
}
|
|
@@ -41174,8 +41958,8 @@ class MobileCertificate {
|
|
|
41174
41958
|
this.current = initial;
|
|
41175
41959
|
}
|
|
41176
41960
|
static async load(store, serverId) {
|
|
41177
|
-
const certPath =
|
|
41178
|
-
const keyPath =
|
|
41961
|
+
const certPath = join10(store.tlsDir, TLS_CERT_FILE);
|
|
41962
|
+
const keyPath = join10(store.tlsDir, TLS_KEY_FILE);
|
|
41179
41963
|
const certPem = await readSecretFile(certPath);
|
|
41180
41964
|
const keyPem = await readSecretFile(keyPath);
|
|
41181
41965
|
if (certPem === undefined && keyPem === undefined) {
|
|
@@ -41705,10 +42489,10 @@ var init_relay = __esm(() => {
|
|
|
41705
42489
|
});
|
|
41706
42490
|
|
|
41707
42491
|
// packages/gateway/src/mobile/wsl.ts
|
|
41708
|
-
import { execFile as
|
|
41709
|
-
import { accessSync as accessSync2, constants as constants2, readFileSync as
|
|
42492
|
+
import { execFile as execFile3 } from "child_process";
|
|
42493
|
+
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync3 } from "fs";
|
|
41710
42494
|
import { networkInterfaces as networkInterfaces2 } from "os";
|
|
41711
|
-
import { delimiter, join as
|
|
42495
|
+
import { delimiter, join as join11 } from "path";
|
|
41712
42496
|
function detectWsl(overrides = {}) {
|
|
41713
42497
|
const deps = { ...defaultProbeDeps, ...overrides };
|
|
41714
42498
|
if (deps.platform !== "linux")
|
|
@@ -41719,7 +42503,7 @@ function detectWsl(overrides = {}) {
|
|
|
41719
42503
|
if (!inWsl)
|
|
41720
42504
|
return;
|
|
41721
42505
|
const candidates = [
|
|
41722
|
-
...(env.PATH ?? "").split(delimiter).filter((dir) => dir !== "").map((dir) =>
|
|
42506
|
+
...(env.PATH ?? "").split(delimiter).filter((dir) => dir !== "").map((dir) => join11(dir, "powershell.exe")),
|
|
41723
42507
|
POWERSHELL_FALLBACK
|
|
41724
42508
|
];
|
|
41725
42509
|
return { powershell: candidates.find((candidate) => deps.isExecutable(candidate)) };
|
|
@@ -41808,7 +42592,7 @@ function primaryWslAddress(interfaces = networkInterfaces2()) {
|
|
|
41808
42592
|
return eth0?.address ?? wslOwnAddresses(interfaces)[0];
|
|
41809
42593
|
}
|
|
41810
42594
|
var POWERSHELL_FALLBACK = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe", defaultProbeDeps, WINDOWS_ADDRESSES_SCRIPT, WSL_ADAPTER_PATTERN, runPowerShell = (powershell, script, timeoutMs) => new Promise((resolve, reject) => {
|
|
41811
|
-
|
|
42595
|
+
execFile3(powershell, ["-NoProfile", "-NonInteractive", "-Command", script], { timeout: timeoutMs, windowsHide: true, maxBuffer: 1024 * 1024, encoding: "utf8" }, (error, stdout) => error ? reject(error) : resolve(stdout));
|
|
41812
42596
|
});
|
|
41813
42597
|
var init_wsl = __esm(() => {
|
|
41814
42598
|
init_network();
|
|
@@ -41817,7 +42601,7 @@ var init_wsl = __esm(() => {
|
|
|
41817
42601
|
env: process.env,
|
|
41818
42602
|
readProcVersion() {
|
|
41819
42603
|
try {
|
|
41820
|
-
return
|
|
42604
|
+
return readFileSync3("/proc/version", "utf8");
|
|
41821
42605
|
} catch {
|
|
41822
42606
|
return;
|
|
41823
42607
|
}
|
|
@@ -41889,6 +42673,10 @@ function toMessage(error) {
|
|
|
41889
42673
|
}
|
|
41890
42674
|
return String(error);
|
|
41891
42675
|
}
|
|
42676
|
+
function normalizeLocalAddress(address) {
|
|
42677
|
+
const mapped = address?.startsWith("::ffff:") ? address.slice("::ffff:".length) : undefined;
|
|
42678
|
+
return mapped !== undefined && isIpv4Literal(mapped) ? mapped : address;
|
|
42679
|
+
}
|
|
41892
42680
|
|
|
41893
42681
|
class MobileGateway {
|
|
41894
42682
|
serverId;
|
|
@@ -42217,11 +43005,12 @@ class MobileGateway {
|
|
|
42217
43005
|
connection.sendNotification(method, params);
|
|
42218
43006
|
}
|
|
42219
43007
|
advertisedAddress(localAddress) {
|
|
43008
|
+
const normalized = normalizeLocalAddress(localAddress);
|
|
42220
43009
|
const state = this.stateValue;
|
|
42221
|
-
if (state.status === "listening" && state.mode === "wsl-relay" &&
|
|
43010
|
+
if (state.status === "listening" && state.mode === "wsl-relay" && normalized === state.bindAddress) {
|
|
42222
43011
|
return state.address;
|
|
42223
43012
|
}
|
|
42224
|
-
return
|
|
43013
|
+
return normalized;
|
|
42225
43014
|
}
|
|
42226
43015
|
requireStore() {
|
|
42227
43016
|
if (this.storeHandle === undefined)
|
|
@@ -42479,10 +43268,10 @@ var init_listener = __esm(() => {
|
|
|
42479
43268
|
});
|
|
42480
43269
|
|
|
42481
43270
|
// packages/gateway/src/port-file.ts
|
|
42482
|
-
import { readFile as
|
|
42483
|
-
import { join as
|
|
43271
|
+
import { readFile as readFile8, rm as rm4, writeFile as writeFile4 } from "fs/promises";
|
|
43272
|
+
import { join as join12 } from "path";
|
|
42484
43273
|
function gatewayPortFilePath(nekoDir, serverId) {
|
|
42485
|
-
return
|
|
43274
|
+
return join12(nekoDir, `${serverId}.port`);
|
|
42486
43275
|
}
|
|
42487
43276
|
async function writeGatewayPortFile(nekoDir, serverId, wsUrl) {
|
|
42488
43277
|
await writeFile4(gatewayPortFilePath(nekoDir, serverId), `${wsUrl}
|
|
@@ -42491,7 +43280,7 @@ async function writeGatewayPortFile(nekoDir, serverId, wsUrl) {
|
|
|
42491
43280
|
async function readGatewayPortFile(nekoDir, serverId) {
|
|
42492
43281
|
let content;
|
|
42493
43282
|
try {
|
|
42494
|
-
content = await
|
|
43283
|
+
content = await readFile8(gatewayPortFilePath(nekoDir, serverId), "utf8");
|
|
42495
43284
|
} catch {
|
|
42496
43285
|
return;
|
|
42497
43286
|
}
|
|
@@ -42838,7 +43627,7 @@ var init_subagent_host = __esm(() => {
|
|
|
42838
43627
|
|
|
42839
43628
|
// packages/gateway/src/session-host.ts
|
|
42840
43629
|
import { open as open4 } from "fs/promises";
|
|
42841
|
-
import { basename as
|
|
43630
|
+
import { basename as basename6, extname } from "path";
|
|
42842
43631
|
import {
|
|
42843
43632
|
ChordError as ChordError4,
|
|
42844
43633
|
local as local2,
|
|
@@ -43033,7 +43822,7 @@ function serveSessions(host, runtime, context) {
|
|
|
43033
43822
|
if (!info.isFile())
|
|
43034
43823
|
throw new Error("Image must be a file");
|
|
43035
43824
|
ctx.signal?.throwIfAborted();
|
|
43036
|
-
id = uploads.value.begin({ name:
|
|
43825
|
+
id = uploads.value.begin({ name: basename6(target), mimeType, size: info.size });
|
|
43037
43826
|
let offset = 0;
|
|
43038
43827
|
while (offset < info.size) {
|
|
43039
43828
|
ctx.signal?.throwIfAborted();
|
|
@@ -43050,9 +43839,9 @@ function serveSessions(host, runtime, context) {
|
|
|
43050
43839
|
ctx.signal?.throwIfAborted();
|
|
43051
43840
|
uploads.value.finish(id);
|
|
43052
43841
|
const raw = uploads.value.resolve([id])[0];
|
|
43053
|
-
await prepareImage(Buffer.from(raw.content.data, "base64"), mimeType,
|
|
43842
|
+
await prepareImage(Buffer.from(raw.content.data, "base64"), mimeType, basename6(target), model);
|
|
43054
43843
|
ctx.signal?.throwIfAborted();
|
|
43055
|
-
return { id, name:
|
|
43844
|
+
return { id, name: basename6(target) };
|
|
43056
43845
|
} catch (error) {
|
|
43057
43846
|
if (id)
|
|
43058
43847
|
uploads.value.release(id);
|
|
@@ -43111,8 +43900,6 @@ function serveSessions(host, runtime, context) {
|
|
|
43111
43900
|
for (const id of input.images ?? [])
|
|
43112
43901
|
uploads.value.release(id);
|
|
43113
43902
|
} catch (error) {
|
|
43114
|
-
if (error instanceof Error && error.message.startsWith("DEEPSEEK_KEY_REQUIRED:"))
|
|
43115
|
-
throw new ChordError4("invalid", "DEEPSEEK_KEY_REQUIRED: Configure DeepSeek in /login or Web settings.");
|
|
43116
43903
|
throw new ChordError4("invalid", error instanceof Error ? error.message : "Cannot submit message");
|
|
43117
43904
|
}
|
|
43118
43905
|
return null;
|
|
@@ -43248,7 +44035,7 @@ function strFromU8(dat, latin1) {
|
|
|
43248
44035
|
} else {
|
|
43249
44036
|
var _a = dutf8(dat), { s, r } = _a;
|
|
43250
44037
|
if (r.length)
|
|
43251
|
-
|
|
44038
|
+
err2(8);
|
|
43252
44039
|
return s;
|
|
43253
44040
|
}
|
|
43254
44041
|
}
|
|
@@ -43257,7 +44044,7 @@ function unzipSync(data, opts) {
|
|
|
43257
44044
|
var e = data.length - 22;
|
|
43258
44045
|
for (;b4(data, e) != 101010256; --e) {
|
|
43259
44046
|
if (!e || data.length - e > 65558)
|
|
43260
|
-
|
|
44047
|
+
err2(13);
|
|
43261
44048
|
}
|
|
43262
44049
|
var c = b2(data, e + 8);
|
|
43263
44050
|
if (!c)
|
|
@@ -43287,7 +44074,7 @@ function unzipSync(data, opts) {
|
|
|
43287
44074
|
else if (c_2 == 8)
|
|
43288
44075
|
files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });
|
|
43289
44076
|
else
|
|
43290
|
-
|
|
44077
|
+
err2(14, "unknown compression type " + c_2);
|
|
43291
44078
|
}
|
|
43292
44079
|
}
|
|
43293
44080
|
return files;
|
|
@@ -43360,11 +44147,11 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43360
44147
|
if (e == null || e > v.length)
|
|
43361
44148
|
e = v.length;
|
|
43362
44149
|
return new u8(v.subarray(s, e));
|
|
43363
|
-
}, ec,
|
|
44150
|
+
}, ec, err2 = function(ind, msg, nt) {
|
|
43364
44151
|
var e = new Error(msg || ec[ind]);
|
|
43365
44152
|
e.code = ind;
|
|
43366
44153
|
if (Error.captureStackTrace)
|
|
43367
|
-
Error.captureStackTrace(e,
|
|
44154
|
+
Error.captureStackTrace(e, err2);
|
|
43368
44155
|
if (!nt)
|
|
43369
44156
|
throw e;
|
|
43370
44157
|
return e;
|
|
@@ -43396,7 +44183,7 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43396
44183
|
var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
|
|
43397
44184
|
if (t > sl) {
|
|
43398
44185
|
if (noSt)
|
|
43399
|
-
|
|
44186
|
+
err2(0);
|
|
43400
44187
|
break;
|
|
43401
44188
|
}
|
|
43402
44189
|
if (resize)
|
|
@@ -43442,10 +44229,10 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43442
44229
|
lm = hMap(lt, lbt, 1);
|
|
43443
44230
|
dm = hMap(dt, dbt, 1);
|
|
43444
44231
|
} else
|
|
43445
|
-
|
|
44232
|
+
err2(1);
|
|
43446
44233
|
if (pos > tbts) {
|
|
43447
44234
|
if (noSt)
|
|
43448
|
-
|
|
44235
|
+
err2(0);
|
|
43449
44236
|
break;
|
|
43450
44237
|
}
|
|
43451
44238
|
}
|
|
@@ -43458,11 +44245,11 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43458
44245
|
pos += c & 15;
|
|
43459
44246
|
if (pos > tbts) {
|
|
43460
44247
|
if (noSt)
|
|
43461
|
-
|
|
44248
|
+
err2(0);
|
|
43462
44249
|
break;
|
|
43463
44250
|
}
|
|
43464
44251
|
if (!c)
|
|
43465
|
-
|
|
44252
|
+
err2(2);
|
|
43466
44253
|
if (sym < 256)
|
|
43467
44254
|
buf[bt++] = sym;
|
|
43468
44255
|
else if (sym == 256) {
|
|
@@ -43477,7 +44264,7 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43477
44264
|
}
|
|
43478
44265
|
var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
|
|
43479
44266
|
if (!d)
|
|
43480
|
-
|
|
44267
|
+
err2(3);
|
|
43481
44268
|
pos += d & 15;
|
|
43482
44269
|
var dt = fd[dsym];
|
|
43483
44270
|
if (dsym > 3) {
|
|
@@ -43486,7 +44273,7 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43486
44273
|
}
|
|
43487
44274
|
if (pos > tbts) {
|
|
43488
44275
|
if (noSt)
|
|
43489
|
-
|
|
44276
|
+
err2(0);
|
|
43490
44277
|
break;
|
|
43491
44278
|
}
|
|
43492
44279
|
if (resize)
|
|
@@ -43495,7 +44282,7 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43495
44282
|
if (bt < dt) {
|
|
43496
44283
|
var shift = dl - dt, dend = Math.min(dt, end);
|
|
43497
44284
|
if (shift + bt < 0)
|
|
43498
|
-
|
|
44285
|
+
err2(3);
|
|
43499
44286
|
for (;bt < dend; ++bt)
|
|
43500
44287
|
buf[bt] = dict[shift + bt];
|
|
43501
44288
|
}
|
|
@@ -43550,7 +44337,7 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43550
44337
|
}
|
|
43551
44338
|
}
|
|
43552
44339
|
if (z < 2)
|
|
43553
|
-
|
|
44340
|
+
err2(13);
|
|
43554
44341
|
}
|
|
43555
44342
|
return [sc, su, off, 0];
|
|
43556
44343
|
};
|
|
@@ -43619,13 +44406,14 @@ var init_esm = __esm(() => {
|
|
|
43619
44406
|
|
|
43620
44407
|
// packages/gateway/src/skill-market.ts
|
|
43621
44408
|
import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
|
|
43622
|
-
import { mkdir as mkdir6, readdir as readdir2,
|
|
44409
|
+
import { mkdir as mkdir6, readdir as readdir2, rm as rm5, stat as stat5, writeFile as writeFile5 } from "fs/promises";
|
|
43623
44410
|
import { homedir as homedir7 } from "os";
|
|
43624
|
-
import { dirname as dirname8, join as
|
|
44411
|
+
import { dirname as dirname8, join as join13 } from "path";
|
|
43625
44412
|
function createSkillMarketClient(options) {
|
|
43626
44413
|
const home = options.home ?? homedir7();
|
|
43627
44414
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
43628
44415
|
const now = options.now ?? Date.now;
|
|
44416
|
+
const placeRename = options.renameImpl;
|
|
43629
44417
|
let base;
|
|
43630
44418
|
try {
|
|
43631
44419
|
base = new URL(options.marketUrl);
|
|
@@ -43730,6 +44518,8 @@ function createSkillMarketClient(options) {
|
|
|
43730
44518
|
throw new SkillMarketError("market_unavailable", "Project-scope install needs a workspaceRoot");
|
|
43731
44519
|
return projectSkillsDir(workspaceRoot);
|
|
43732
44520
|
};
|
|
44521
|
+
const BACKUP_PREFIX = ".bak-";
|
|
44522
|
+
const UUID_LENGTH = 36;
|
|
43733
44523
|
const sweepTempDirs = async (skillsDir) => {
|
|
43734
44524
|
const parent = dirname8(skillsDir);
|
|
43735
44525
|
let names = [];
|
|
@@ -43738,12 +44528,27 @@ function createSkillMarketClient(options) {
|
|
|
43738
44528
|
} catch {
|
|
43739
44529
|
return;
|
|
43740
44530
|
}
|
|
43741
|
-
await Promise.all(names.filter((n) => n.startsWith(".tmp-")).map((n) => rm5(
|
|
44531
|
+
await Promise.all(names.filter((n) => n.startsWith(".tmp-")).map((n) => rm5(join13(parent, n), { recursive: true, force: true }).catch(() => {})));
|
|
44532
|
+
for (const n of names.filter((n) => n.startsWith(BACKUP_PREFIX))) {
|
|
44533
|
+
const backup = join13(parent, n);
|
|
44534
|
+
const skill = n.slice(BACKUP_PREFIX.length + UUID_LENGTH + 1);
|
|
44535
|
+
if (skill === "" || skill.includes("/") || skill.includes("\\") || skill === "." || skill === "..")
|
|
44536
|
+
continue;
|
|
44537
|
+
const target = join13(skillsDir, skill);
|
|
44538
|
+
if (await stat5(target).catch(() => {
|
|
44539
|
+
return;
|
|
44540
|
+
}) === undefined) {
|
|
44541
|
+
await mkdir6(skillsDir, { recursive: true }).catch(() => {});
|
|
44542
|
+
await renameWithRetry(backup, target).catch(() => {});
|
|
44543
|
+
} else {
|
|
44544
|
+
await rm5(backup, { recursive: true, force: true }).catch(() => {});
|
|
44545
|
+
}
|
|
44546
|
+
}
|
|
43742
44547
|
};
|
|
43743
44548
|
const install = async ({ name, scope, workspaceRoot, overwrite }) => {
|
|
43744
44549
|
const skillsDir = skillsDirFor(scope, workspaceRoot);
|
|
43745
|
-
const target =
|
|
43746
|
-
const exists = await
|
|
44550
|
+
const target = join13(skillsDir, name);
|
|
44551
|
+
const exists = await stat5(target).catch(() => {
|
|
43747
44552
|
return;
|
|
43748
44553
|
}) !== undefined;
|
|
43749
44554
|
if (exists && overwrite !== true)
|
|
@@ -43751,24 +44556,34 @@ function createSkillMarketClient(options) {
|
|
|
43751
44556
|
const skill = await detail(name);
|
|
43752
44557
|
const files = unpack(skill, await download(skill));
|
|
43753
44558
|
await sweepTempDirs(skillsDir);
|
|
43754
|
-
const temp =
|
|
44559
|
+
const temp = join13(dirname8(skillsDir), `.tmp-${randomUUID4()}`);
|
|
43755
44560
|
try {
|
|
43756
44561
|
for (const [relative, bytes] of files) {
|
|
43757
|
-
const dest =
|
|
44562
|
+
const dest = join13(temp, ...relative.split("/"));
|
|
43758
44563
|
await mkdir6(dirname8(dest), { recursive: true });
|
|
43759
44564
|
await writeFile5(dest, bytes);
|
|
43760
44565
|
}
|
|
43761
|
-
if (exists)
|
|
43762
|
-
await rm5(target, { recursive: true, force: true });
|
|
43763
44566
|
await mkdir6(skillsDir, { recursive: true });
|
|
43764
|
-
|
|
44567
|
+
if (exists) {
|
|
44568
|
+
const backup = join13(dirname8(skillsDir), `${BACKUP_PREFIX}${randomUUID4()}-${name}`);
|
|
44569
|
+
await renameWithRetry(target, backup);
|
|
44570
|
+
try {
|
|
44571
|
+
await renameWithRetry(temp, target, placeRename ? { rename: placeRename } : {});
|
|
44572
|
+
} catch (error) {
|
|
44573
|
+
await renameWithRetry(backup, target).catch(() => {});
|
|
44574
|
+
throw error;
|
|
44575
|
+
}
|
|
44576
|
+
await rm5(backup, { recursive: true, force: true }).catch(() => {});
|
|
44577
|
+
} else {
|
|
44578
|
+
await renameWithRetry(temp, target, placeRename ? { rename: placeRename } : {});
|
|
44579
|
+
}
|
|
43765
44580
|
} finally {
|
|
43766
44581
|
await rm5(temp, { recursive: true, force: true }).catch(() => {});
|
|
43767
44582
|
}
|
|
43768
44583
|
let shadowedBy;
|
|
43769
44584
|
if (scope === "user" && workspaceRoot !== undefined && workspaceRoot !== "") {
|
|
43770
|
-
const projectDir =
|
|
43771
|
-
if (await
|
|
44585
|
+
const projectDir = join13(projectSkillsDir(workspaceRoot), name);
|
|
44586
|
+
if (await stat5(projectDir).catch(() => {
|
|
43772
44587
|
return;
|
|
43773
44588
|
}) !== undefined)
|
|
43774
44589
|
shadowedBy = "project";
|
|
@@ -43777,8 +44592,8 @@ function createSkillMarketClient(options) {
|
|
|
43777
44592
|
};
|
|
43778
44593
|
const uninstall = async ({ name, scope, workspaceRoot }) => {
|
|
43779
44594
|
const skillsDir = skillsDirFor(scope, workspaceRoot);
|
|
43780
|
-
const target =
|
|
43781
|
-
if (await
|
|
44595
|
+
const target = join13(skillsDir, name);
|
|
44596
|
+
if (await stat5(target).catch(() => {
|
|
43782
44597
|
return;
|
|
43783
44598
|
}) === undefined)
|
|
43784
44599
|
throw new SkillMarketError("skill_not_installed", `Skill ${name} is not installed at ${scope} level`);
|
|
@@ -43809,7 +44624,7 @@ function createSkillMarketClient(options) {
|
|
|
43809
44624
|
uninstall
|
|
43810
44625
|
};
|
|
43811
44626
|
}
|
|
43812
|
-
var SkillMarketError, CATALOG_TTL_MS = 60000, ZIP_CACHE_MAX = 16, MAX_PACKAGE_BYTES, userSkillsDir2 = (home) =>
|
|
44627
|
+
var SkillMarketError, CATALOG_TTL_MS = 60000, ZIP_CACHE_MAX = 16, MAX_PACKAGE_BYTES, userSkillsDir2 = (home) => join13(home, ".agents", "skills"), projectSkillsDir = (workspaceRoot) => join13(workspaceRoot, ".agents", "skills"), activeLevelOf = (state) => state.project ? "project" : state.user ? "user" : state.builtin ? "builtin" : null, normalizeEntry = (raw) => {
|
|
43813
44628
|
const row = raw ?? {};
|
|
43814
44629
|
const metadata = row.metadata;
|
|
43815
44630
|
const source = metadata !== null && typeof metadata === "object" ? metadata.source : undefined;
|
|
@@ -43841,8 +44656,8 @@ var init_skill_market = __esm(() => {
|
|
|
43841
44656
|
|
|
43842
44657
|
// packages/gateway/src/workspaces.ts
|
|
43843
44658
|
import { constants as constants3 } from "fs";
|
|
43844
|
-
import { access, readdir as readdir3, stat as
|
|
43845
|
-
import { dirname as dirname9, isAbsolute as isAbsolute4, join as
|
|
44659
|
+
import { access, readdir as readdir3, stat as stat6 } from "fs/promises";
|
|
44660
|
+
import { dirname as dirname9, isAbsolute as isAbsolute4, join as join14 } from "path";
|
|
43846
44661
|
async function browseWorkspaces(options) {
|
|
43847
44662
|
const requested = options.path.replace(/^\/+([A-Za-z]:[\\/])/, "$1");
|
|
43848
44663
|
if (process.platform === "win32" && (requested === "/" || requested === "\\")) {
|
|
@@ -43869,11 +44684,11 @@ async function browseWorkspaces(options) {
|
|
|
43869
44684
|
let next = offset;
|
|
43870
44685
|
for (;next < entries.length && directories.length < 100; next++) {
|
|
43871
44686
|
const entry = entries[next];
|
|
43872
|
-
const target =
|
|
44687
|
+
const target = join14(path, entry.name);
|
|
43873
44688
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
43874
44689
|
continue;
|
|
43875
44690
|
try {
|
|
43876
|
-
if (!(await
|
|
44691
|
+
if (!(await stat6(target)).isDirectory())
|
|
43877
44692
|
continue;
|
|
43878
44693
|
await access(target, constants3.R_OK | constants3.X_OK);
|
|
43879
44694
|
directories.push({ name: entry.name, path: target });
|
|
@@ -43912,12 +44727,15 @@ async function createNekoServerServices(options) {
|
|
|
43912
44727
|
const updates = new UpdateChecker(options.updates);
|
|
43913
44728
|
await updates.local();
|
|
43914
44729
|
const updateListeners = new Set;
|
|
43915
|
-
const info = (value) =>
|
|
43916
|
-
|
|
44730
|
+
const info = (value) => {
|
|
44731
|
+
const shell = runtime.shellStatus();
|
|
44732
|
+
return { ...value, serverId: options.serverId, ...shell === undefined ? {} : { shell } };
|
|
44733
|
+
};
|
|
44734
|
+
let published = JSON.stringify(info(updates.state)), closed = false;
|
|
43917
44735
|
const publishUpdates = () => {
|
|
43918
44736
|
if (closed)
|
|
43919
44737
|
return;
|
|
43920
|
-
const next = JSON.stringify(updates.state);
|
|
44738
|
+
const next = JSON.stringify(info(updates.state));
|
|
43921
44739
|
if (next === published)
|
|
43922
44740
|
return;
|
|
43923
44741
|
published = next;
|
|
@@ -43936,6 +44754,7 @@ async function createNekoServerServices(options) {
|
|
|
43936
44754
|
variant: "ready",
|
|
43937
44755
|
info: current,
|
|
43938
44756
|
check: async () => {
|
|
44757
|
+
runtime.shellStatus({ refresh: true });
|
|
43939
44758
|
await updates.check(true);
|
|
43940
44759
|
publishUpdates();
|
|
43941
44760
|
return info(updates.state);
|
|
@@ -43982,13 +44801,20 @@ async function createNekoServerServices(options) {
|
|
|
43982
44801
|
status: info.status,
|
|
43983
44802
|
awaitingAnswer: info.awaitingAnswer
|
|
43984
44803
|
}));
|
|
43985
|
-
const readStatuses = async () =>
|
|
43986
|
-
|
|
44804
|
+
const readStatuses = async () => {
|
|
44805
|
+
const enabled = new Set(runtime.enabledProviders());
|
|
44806
|
+
return Object.fromEntries(await Promise.all(CREDENTIAL_PROVIDERS.map(async (spec) => {
|
|
44807
|
+
const status = await runtime.credentialStatus(spec.id);
|
|
44808
|
+
return [spec.id, spec.kind === "model" ? { ...status, enabled: enabled.has(spec.id) } : status];
|
|
44809
|
+
})));
|
|
44810
|
+
};
|
|
44811
|
+
const [sessions, workspaces, statuses, search, contextSettings0, defaultModel0] = await Promise.all([
|
|
43987
44812
|
list(),
|
|
43988
44813
|
runtime.listWorkspaces(),
|
|
43989
44814
|
readStatuses(),
|
|
43990
44815
|
runtime.searchSettings(),
|
|
43991
|
-
runtime.contextSettings()
|
|
44816
|
+
runtime.contextSettings(),
|
|
44817
|
+
runtime.defaultModel()
|
|
43992
44818
|
]);
|
|
43993
44819
|
let mutationTail = Promise.resolve();
|
|
43994
44820
|
const serialize = (fn) => {
|
|
@@ -44005,6 +44831,7 @@ async function createNekoServerServices(options) {
|
|
|
44005
44831
|
revision: 1,
|
|
44006
44832
|
...contextSettings0
|
|
44007
44833
|
});
|
|
44834
|
+
const defaultModel = self2.state("defaultModel", DefaultModelStateSchema, defaultModel0);
|
|
44008
44835
|
const refreshContextSettings = async (_input, ctx) => {
|
|
44009
44836
|
const next = await runtime.contextSettings();
|
|
44010
44837
|
const { revision, ...current } = contextSettings.value;
|
|
@@ -44012,6 +44839,12 @@ async function createNekoServerServices(options) {
|
|
|
44012
44839
|
contextSettings.replace(ctx, { revision: revision + 1, ...next });
|
|
44013
44840
|
return null;
|
|
44014
44841
|
};
|
|
44842
|
+
const refreshDefaultModel = async (_input, ctx) => {
|
|
44843
|
+
const next = await runtime.defaultModel();
|
|
44844
|
+
if (JSON.stringify(next) !== JSON.stringify(defaultModel.value))
|
|
44845
|
+
defaultModel.replace(ctx, next);
|
|
44846
|
+
return null;
|
|
44847
|
+
};
|
|
44015
44848
|
const refreshSearchSettings = async (_input, ctx) => {
|
|
44016
44849
|
const next = await runtime.searchSettings();
|
|
44017
44850
|
const current = searchSettings.value;
|
|
@@ -44040,6 +44873,7 @@ async function createNekoServerServices(options) {
|
|
|
44040
44873
|
credentials,
|
|
44041
44874
|
searchSettings,
|
|
44042
44875
|
contextSettings,
|
|
44876
|
+
defaultModel,
|
|
44043
44877
|
async refreshCredentials(_input, ctx) {
|
|
44044
44878
|
credentials.replace(ctx, {
|
|
44045
44879
|
revision: credentials.value.revision + 1,
|
|
@@ -44048,7 +44882,8 @@ async function createNekoServerServices(options) {
|
|
|
44048
44882
|
return null;
|
|
44049
44883
|
},
|
|
44050
44884
|
refreshSearchSettings,
|
|
44051
|
-
refreshContextSettings
|
|
44885
|
+
refreshContextSettings,
|
|
44886
|
+
refreshDefaultModel
|
|
44052
44887
|
};
|
|
44053
44888
|
}, { lifetime: "host" });
|
|
44054
44889
|
const shared = (self2) => self2.get(ServerState, {});
|
|
@@ -44071,7 +44906,17 @@ async function createNekoServerServices(options) {
|
|
|
44071
44906
|
remove: async (input, ctx) => {
|
|
44072
44907
|
await runtime.removeCredential(input.provider);
|
|
44073
44908
|
return hub.refreshCredentials(null, ctx);
|
|
44074
|
-
}
|
|
44909
|
+
},
|
|
44910
|
+
setEnabled: (input, ctx) => serialize(async () => {
|
|
44911
|
+
if (!isModelProvider(input.provider))
|
|
44912
|
+
throw new ChordError5("invalid", "Unknown model provider.");
|
|
44913
|
+
try {
|
|
44914
|
+
await runtime.setProviderEnabled(input.provider, input.enabled);
|
|
44915
|
+
} catch (error) {
|
|
44916
|
+
throw new ChordError5("invalid", error instanceof Error ? error.message : "Cannot change provider.");
|
|
44917
|
+
}
|
|
44918
|
+
return hub.refreshCredentials(null, ctx);
|
|
44919
|
+
})
|
|
44075
44920
|
};
|
|
44076
44921
|
});
|
|
44077
44922
|
host.serve(NekoSearchSettings, (_input, _principal, self2) => {
|
|
@@ -44100,9 +44945,24 @@ async function createNekoServerServices(options) {
|
|
|
44100
44945
|
})
|
|
44101
44946
|
};
|
|
44102
44947
|
});
|
|
44948
|
+
host.serve(NekoDefaultModel, (_input, _principal, self2) => {
|
|
44949
|
+
const hub = shared(self2);
|
|
44950
|
+
return {
|
|
44951
|
+
variant: "ready",
|
|
44952
|
+
state: hub.defaultModel,
|
|
44953
|
+
set: (input, ctx) => serialize(async () => {
|
|
44954
|
+
await runtime.setDefaultModel(input);
|
|
44955
|
+
return hub.refreshDefaultModel(null, ctx);
|
|
44956
|
+
}),
|
|
44957
|
+
clear: (_input, ctx) => serialize(async () => {
|
|
44958
|
+
await runtime.setDefaultModel(null);
|
|
44959
|
+
return hub.refreshDefaultModel(null, ctx);
|
|
44960
|
+
})
|
|
44961
|
+
};
|
|
44962
|
+
});
|
|
44103
44963
|
host.serve(NekoModels, () => ({
|
|
44104
44964
|
variant: "ready",
|
|
44105
|
-
list: async () =>
|
|
44965
|
+
list: async () => (await runtime.listSelectableModels()).map((value) => ({
|
|
44106
44966
|
...value,
|
|
44107
44967
|
input: [...value.input],
|
|
44108
44968
|
supportedThinkingLevels: [...value.supportedThinkingLevels]
|
|
@@ -44194,23 +45054,24 @@ var init_server_services = __esm(() => {
|
|
|
44194
45054
|
credentials: CredentialStateSchema,
|
|
44195
45055
|
searchSettings: SearchSettingsStateSchema,
|
|
44196
45056
|
contextSettings: ContextSettingsStateSchema,
|
|
45057
|
+
defaultModel: DefaultModelStateSchema,
|
|
44197
45058
|
refreshCredentials: method2(Type12.Null(), Type12.Null()),
|
|
44198
45059
|
refreshSearchSettings: method2(Type12.Null(), Type12.Null()),
|
|
44199
|
-
refreshContextSettings: method2(Type12.Null(), Type12.Null())
|
|
45060
|
+
refreshContextSettings: method2(Type12.Null(), Type12.Null()),
|
|
45061
|
+
refreshDefaultModel: method2(Type12.Null(), Type12.Null())
|
|
44200
45062
|
}
|
|
44201
45063
|
});
|
|
44202
45064
|
});
|
|
44203
45065
|
|
|
44204
45066
|
// packages/gateway/src/gateway.ts
|
|
44205
45067
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
44206
|
-
import { mkdir as mkdir7, readFile as
|
|
44207
|
-
import { basename as
|
|
45068
|
+
import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
|
|
45069
|
+
import { basename as basename7, dirname as dirname10, join as join15 } from "path";
|
|
44208
45070
|
import { createHost as createHost2 } from "@earendil-works/chord";
|
|
44209
45071
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT4 } from "@earendil-works/chord/context";
|
|
44210
45072
|
async function startGateway(options = {}) {
|
|
44211
45073
|
const context = options.context ?? BACKGROUND_CONTEXT4;
|
|
44212
45074
|
const fileConfig = loadNekoConfig(options.configDir);
|
|
44213
|
-
const model = options.model ?? fileConfig.defaultModel ?? DEFAULT_MODEL;
|
|
44214
45075
|
const storagePath = options.storagePath ?? fileConfig.storagePath ?? DEFAULT_STORAGE_PATH;
|
|
44215
45076
|
const host = options.host ?? fileConfig.gateway?.host ?? DEFAULT_HOST;
|
|
44216
45077
|
const port = options.port !== undefined ? options.port : fileConfig.gateway?.port ?? (process.platform === "win32" ? 0 : DEFAULT_PORT);
|
|
@@ -44218,7 +45079,7 @@ async function startGateway(options = {}) {
|
|
|
44218
45079
|
const marketUrl = options.marketUrl ?? fileConfig.skills?.marketUrl ?? DEFAULT_SKILL_MARKET_URL;
|
|
44219
45080
|
const nekoDir = dirname10(storagePath);
|
|
44220
45081
|
const serverId = options.serverId ?? await loadOrCreateServerId(nekoDir);
|
|
44221
|
-
const socketPath = options.socketPath !== undefined ? options.socketPath : fileConfig.gateway?.socketPath !== undefined ? fileConfig.gateway.socketPath : process.platform === "win32" ? null :
|
|
45082
|
+
const socketPath = options.socketPath !== undefined ? options.socketPath : fileConfig.gateway?.socketPath !== undefined ? fileConfig.gateway.socketPath : process.platform === "win32" ? null : join15(nekoDir, `${serverId}.sock`);
|
|
44222
45083
|
const runtime = await createNekoRuntime({
|
|
44223
45084
|
context,
|
|
44224
45085
|
onReport: (error) => options.onError?.(error instanceof Error ? error : new Error(String(error))),
|
|
@@ -44228,12 +45089,15 @@ async function startGateway(options = {}) {
|
|
|
44228
45089
|
...options.apiKey !== undefined ? { apiKey: options.apiKey } : {},
|
|
44229
45090
|
...searchProvider !== undefined ? { searchProvider } : {},
|
|
44230
45091
|
...fileConfig.context !== undefined ? { contextSettings: fileConfig.context } : {},
|
|
44231
|
-
model,
|
|
45092
|
+
...fileConfig.defaultModel !== undefined ? { model: fileConfig.defaultModel } : {},
|
|
45093
|
+
...options.model !== undefined ? { modelOverride: options.model } : {},
|
|
44232
45094
|
thinkingLevel: options.thinkingLevel ?? fileConfig.thinkingLevel ?? "high",
|
|
44233
45095
|
...options.cwd !== undefined ? { cwd: options.cwd } : {},
|
|
44234
45096
|
...options.providers !== undefined ? { providers: options.providers } : {},
|
|
44235
45097
|
...options.extensions !== undefined ? { extensions: options.extensions } : {},
|
|
44236
|
-
...options.promptHome !== undefined ? { promptHome: options.promptHome } : {}
|
|
45098
|
+
...options.promptHome !== undefined ? { promptHome: options.promptHome } : {},
|
|
45099
|
+
...fileConfig.shell?.path !== undefined ? { shellPath: fileConfig.shell.path } : {},
|
|
45100
|
+
...options.shellResolver !== undefined ? { shellResolver: options.shellResolver } : {}
|
|
44237
45101
|
});
|
|
44238
45102
|
try {
|
|
44239
45103
|
return await bindGateway(runtime, {
|
|
@@ -44243,12 +45107,12 @@ async function startGateway(options = {}) {
|
|
|
44243
45107
|
socketPath,
|
|
44244
45108
|
serverId,
|
|
44245
45109
|
nekoDir,
|
|
44246
|
-
model,
|
|
44247
45110
|
marketUrl,
|
|
44248
45111
|
promptHome: options.promptHome,
|
|
44249
45112
|
updates: options.updates,
|
|
44250
45113
|
updateIntervalMs: options.updateIntervalMs,
|
|
44251
45114
|
mobileServerName: options.mobileServerName,
|
|
45115
|
+
mobileNetwork: options.mobileNetwork,
|
|
44252
45116
|
onError: options.onError
|
|
44253
45117
|
});
|
|
44254
45118
|
} catch (error) {
|
|
@@ -44283,12 +45147,13 @@ async function bindGateway(runtime, options) {
|
|
|
44283
45147
|
wsListener = createWsListener({ host, port, onError: options.onError });
|
|
44284
45148
|
listeners.push(wsListener);
|
|
44285
45149
|
}
|
|
45150
|
+
const acceptLocal = (link) => chord.accept(link, {
|
|
45151
|
+
principal: { kind: "local", connectionId: randomUUID6() },
|
|
45152
|
+
onProtocolMismatch: ({ peer }) => options.onError?.(new Error(`Rejected a client on Chord protocol ${peer ?? "unknown"}; restart or update it.`))
|
|
45153
|
+
});
|
|
44286
45154
|
try {
|
|
44287
45155
|
for (const listener of listeners)
|
|
44288
|
-
await listener.start(
|
|
44289
|
-
principal: { kind: "local", connectionId: randomUUID6() },
|
|
44290
|
-
onProtocolMismatch: ({ peer }) => options.onError?.(new Error(`Rejected a client on Chord protocol ${peer ?? "unknown"}; restart or update it.`))
|
|
44291
|
-
}));
|
|
45156
|
+
await listener.start(acceptLocal);
|
|
44292
45157
|
} catch (error) {
|
|
44293
45158
|
await Promise.allSettled(listeners.map((listener) => listener.close()));
|
|
44294
45159
|
await chord.close();
|
|
@@ -44304,6 +45169,7 @@ async function bindGateway(runtime, options) {
|
|
|
44304
45169
|
serverId,
|
|
44305
45170
|
serverName: options.mobileServerName,
|
|
44306
45171
|
onError: options.onError,
|
|
45172
|
+
network: options.mobileNetwork,
|
|
44307
45173
|
chord: {
|
|
44308
45174
|
accept: (link, acceptOptions) => chord.accept(link, acceptOptions),
|
|
44309
45175
|
invalidatePrincipal: (match) => chord.invalidatePrincipal(match)
|
|
@@ -44328,6 +45194,9 @@ async function bindGateway(runtime, options) {
|
|
|
44328
45194
|
port: wsListener?.port,
|
|
44329
45195
|
runtime,
|
|
44330
45196
|
mobile,
|
|
45197
|
+
accept(link) {
|
|
45198
|
+
acceptLocal(link);
|
|
45199
|
+
},
|
|
44331
45200
|
async close() {
|
|
44332
45201
|
if (closing)
|
|
44333
45202
|
return closing;
|
|
@@ -44368,16 +45237,16 @@ ${connectHint}`);
|
|
|
44368
45237
|
return error instanceof Error ? error : new Error(message);
|
|
44369
45238
|
}
|
|
44370
45239
|
function serverIdFromSocketPath(socketPath) {
|
|
44371
|
-
const name =
|
|
45240
|
+
const name = basename7(socketPath);
|
|
44372
45241
|
if (!name.endsWith(".sock"))
|
|
44373
45242
|
return;
|
|
44374
45243
|
const candidate = name.slice(0, -".sock".length);
|
|
44375
45244
|
return isServerId(candidate) ? candidate : undefined;
|
|
44376
45245
|
}
|
|
44377
45246
|
async function loadOrCreateServerId(nekoDir) {
|
|
44378
|
-
const file =
|
|
45247
|
+
const file = join15(nekoDir, SERVER_ID_FILENAME);
|
|
44379
45248
|
try {
|
|
44380
|
-
const existing = (await
|
|
45249
|
+
const existing = (await readFile9(file, "utf8")).trim();
|
|
44381
45250
|
if (existing !== "")
|
|
44382
45251
|
return existing;
|
|
44383
45252
|
} catch {}
|
|
@@ -44416,10 +45285,11 @@ function gatewayCommand(args) {
|
|
|
44416
45285
|
async function stopGatewayProcess(child, options = {}) {
|
|
44417
45286
|
if (child.pid === undefined || child.exitCode !== null || child.signalCode !== null)
|
|
44418
45287
|
return;
|
|
45288
|
+
const viaStdin = options.viaStdin === true && child.stdin != null;
|
|
44419
45289
|
await new Promise((resolve) => {
|
|
44420
45290
|
let forcedTimer;
|
|
44421
45291
|
const timer = setTimeout(() => {
|
|
44422
|
-
child.kill("SIGKILL");
|
|
45292
|
+
child.kill(viaStdin ? undefined : "SIGKILL");
|
|
44423
45293
|
forcedTimer = setTimeout(resolve, options.forcedMs ?? FORCED_STOP_MS);
|
|
44424
45294
|
}, options.gracefulMs ?? GRACEFUL_STOP_MS);
|
|
44425
45295
|
child.once("exit", () => {
|
|
@@ -44427,7 +45297,10 @@ async function stopGatewayProcess(child, options = {}) {
|
|
|
44427
45297
|
clearTimeout(forcedTimer);
|
|
44428
45298
|
resolve();
|
|
44429
45299
|
});
|
|
44430
|
-
|
|
45300
|
+
if (viaStdin)
|
|
45301
|
+
child.stdin?.end();
|
|
45302
|
+
else
|
|
45303
|
+
child.kill("SIGTERM");
|
|
44431
45304
|
});
|
|
44432
45305
|
}
|
|
44433
45306
|
var GRACEFUL_STOP_MS = 3000, FORCED_STOP_MS = 2000;
|
|
@@ -44457,14 +45330,15 @@ function installShutdown(options = {}) {
|
|
|
44457
45330
|
exit(1);
|
|
44458
45331
|
});
|
|
44459
45332
|
};
|
|
44460
|
-
const
|
|
45333
|
+
const begin = (reason) => {
|
|
44461
45334
|
if (controller.signal.aborted) {
|
|
44462
45335
|
options.log?.("\u518D\u6B21\u6536\u5230\u7EC8\u6B62\u4FE1\u53F7\uFF0C\u5F3A\u5236\u9000\u51FA\u3002");
|
|
44463
45336
|
exit(130);
|
|
44464
45337
|
return;
|
|
44465
45338
|
}
|
|
44466
|
-
options.log?.(`
|
|
44467
|
-
\u6B63\u5728\u5173\u95ED\u2026`
|
|
45339
|
+
options.log?.(reason === undefined ? `
|
|
45340
|
+
\u6B63\u5728\u5173\u95ED\u2026` : `
|
|
45341
|
+
\u6B63\u5728\u5173\u95ED\uFF08${reason}\uFF09\u2026`);
|
|
44468
45342
|
controller.abort();
|
|
44469
45343
|
timer = setTimeout(() => {
|
|
44470
45344
|
options.log?.("\u5173\u95ED\u8D85\u65F6\uFF0C\u5F3A\u5236\u9000\u51FA\u3002");
|
|
@@ -44473,6 +45347,7 @@ function installShutdown(options = {}) {
|
|
|
44473
45347
|
timer.unref?.();
|
|
44474
45348
|
runCleanup();
|
|
44475
45349
|
};
|
|
45350
|
+
const onSignal = () => begin(undefined);
|
|
44476
45351
|
const installed = [];
|
|
44477
45352
|
for (const signal of shutdownSignals(proc.platform)) {
|
|
44478
45353
|
try {
|
|
@@ -44487,6 +45362,9 @@ function installShutdown(options = {}) {
|
|
|
44487
45362
|
if (controller.signal.aborted)
|
|
44488
45363
|
runCleanup();
|
|
44489
45364
|
},
|
|
45365
|
+
trigger(reason) {
|
|
45366
|
+
begin(reason);
|
|
45367
|
+
},
|
|
44490
45368
|
dispose() {
|
|
44491
45369
|
clearTimeout(timer);
|
|
44492
45370
|
for (const signal of installed)
|
|
@@ -44512,13 +45390,13 @@ var init_src2 = __esm(() => {
|
|
|
44512
45390
|
|
|
44513
45391
|
// packages/tui/src/gateway.ts
|
|
44514
45392
|
import { spawn as spawn3 } from "child_process";
|
|
44515
|
-
import { existsSync as existsSync3, readFileSync as
|
|
44516
|
-
import { basename as
|
|
45393
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
45394
|
+
import { basename as basename8, dirname as dirname11, join as join16 } from "path";
|
|
44517
45395
|
async function connectToGateway(options = {}) {
|
|
44518
45396
|
const config = loadNekoConfig(options.configDir);
|
|
44519
45397
|
const storagePath = options.storagePath ?? config.storagePath ?? DEFAULT_STORAGE_PATH;
|
|
44520
45398
|
const nekoDir = dirname11(storagePath);
|
|
44521
|
-
const fileServerId = readServerId(
|
|
45399
|
+
const fileServerId = readServerId(join16(nekoDir, SERVER_ID_FILENAME2));
|
|
44522
45400
|
const platform = options.platform ?? process.platform;
|
|
44523
45401
|
if (platform === "win32") {
|
|
44524
45402
|
if (fileServerId !== undefined) {
|
|
@@ -44535,8 +45413,8 @@ async function connectToGateway(options = {}) {
|
|
|
44535
45413
|
}
|
|
44536
45414
|
return spawnAndConnect({ ...options, platform, storagePath, socketPath: undefined, nekoDir, fileServerId });
|
|
44537
45415
|
}
|
|
44538
|
-
const socketPath = options.socketPath ?? config.gateway?.socketPath ?? (fileServerId !== undefined ?
|
|
44539
|
-
const serverIdFor = (path) => fileServerId ?? serverIdFromSocketPath2(path) ?? readServerId(
|
|
45416
|
+
const socketPath = options.socketPath ?? config.gateway?.socketPath ?? (fileServerId !== undefined ? join16(nekoDir, `${fileServerId}.sock`) : undefined);
|
|
45417
|
+
const serverIdFor = (path) => fileServerId ?? serverIdFromSocketPath2(path) ?? readServerId(join16(nekoDir, SERVER_ID_FILENAME2));
|
|
44540
45418
|
if (socketPath !== undefined && existsSync3(socketPath)) {
|
|
44541
45419
|
const serverId = serverIdFor(socketPath);
|
|
44542
45420
|
if (serverId !== undefined) {
|
|
@@ -44553,7 +45431,9 @@ async function connectToGateway(options = {}) {
|
|
|
44553
45431
|
}
|
|
44554
45432
|
async function spawnAndConnect(plan) {
|
|
44555
45433
|
const command = gatewayCommand(gatewayArgs(plan));
|
|
44556
|
-
const child = spawn3(command.binary, command.args, {
|
|
45434
|
+
const child = spawn3(command.binary, command.args, {
|
|
45435
|
+
stdio: [plan.platform === "win32" ? "pipe" : "ignore", "pipe", "pipe"]
|
|
45436
|
+
});
|
|
44557
45437
|
const outputTail = captureOutput(child);
|
|
44558
45438
|
let spawnError;
|
|
44559
45439
|
child.on("error", (error) => {
|
|
@@ -44562,7 +45442,7 @@ async function spawnAndConnect(plan) {
|
|
|
44562
45442
|
try {
|
|
44563
45443
|
return await connectSpawned(plan, child, outputTail, () => spawnError);
|
|
44564
45444
|
} catch (error) {
|
|
44565
|
-
await stopGatewayProcess(child);
|
|
45445
|
+
await stopGatewayProcess(child, { viaStdin: plan.platform === "win32" });
|
|
44566
45446
|
throw error;
|
|
44567
45447
|
}
|
|
44568
45448
|
}
|
|
@@ -44570,7 +45450,7 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
44570
45450
|
const timeoutMs = plan.spawnTimeoutMs ?? 20000;
|
|
44571
45451
|
if (plan.platform === "win32") {
|
|
44572
45452
|
const timeoutAt = Date.now() + timeoutMs;
|
|
44573
|
-
const serverId = plan.fileServerId ?? await waitForValue(() => readServerId(
|
|
45453
|
+
const serverId = plan.fileServerId ?? await waitForValue(() => readServerId(join16(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
|
|
44574
45454
|
let lastError = new Error("Gateway port file unavailable");
|
|
44575
45455
|
while (Date.now() < timeoutAt) {
|
|
44576
45456
|
if (child.exitCode !== null || child.signalCode !== null || spawnError() !== undefined) {
|
|
@@ -44591,7 +45471,7 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
44591
45471
|
spawned: true,
|
|
44592
45472
|
async close() {
|
|
44593
45473
|
await client.close().catch(() => {});
|
|
44594
|
-
await stopGatewayProcess(child);
|
|
45474
|
+
await stopGatewayProcess(child, { viaStdin: plan.platform === "win32" });
|
|
44595
45475
|
await removeGatewayPortFile(plan.nekoDir, serverId).catch(() => {});
|
|
44596
45476
|
}
|
|
44597
45477
|
};
|
|
@@ -44607,11 +45487,11 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
44607
45487
|
}
|
|
44608
45488
|
let socketPath = plan.socketPath;
|
|
44609
45489
|
if (socketPath === undefined) {
|
|
44610
|
-
const generated = await waitForValue(() => readServerId(
|
|
44611
|
-
socketPath =
|
|
45490
|
+
const generated = await waitForValue(() => readServerId(join16(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
|
|
45491
|
+
socketPath = join16(plan.nekoDir, `${generated}.sock`);
|
|
44612
45492
|
}
|
|
44613
45493
|
await waitForValue(() => existsSync3(socketPath) ? true : undefined, `gateway ready (socket: ${socketPath})`, timeoutMs, child, outputTail, spawnError);
|
|
44614
|
-
const serverId = plan.fileServerId ?? serverIdFromSocketPath2(socketPath) ?? readServerId(
|
|
45494
|
+
const serverId = plan.fileServerId ?? serverIdFromSocketPath2(socketPath) ?? readServerId(join16(plan.nekoDir, SERVER_ID_FILENAME2));
|
|
44615
45495
|
if (serverId === undefined) {
|
|
44616
45496
|
throw new Error(`Cannot determine gateway serverId (socket: ${socketPath})`);
|
|
44617
45497
|
}
|
|
@@ -44628,7 +45508,7 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
44628
45508
|
spawned: true,
|
|
44629
45509
|
async close() {
|
|
44630
45510
|
await client.close().catch(() => {});
|
|
44631
|
-
await stopGatewayProcess(child);
|
|
45511
|
+
await stopGatewayProcess(child, { viaStdin: plan.platform === "win32" });
|
|
44632
45512
|
}
|
|
44633
45513
|
};
|
|
44634
45514
|
} catch (error) {
|
|
@@ -44645,6 +45525,7 @@ function gatewayArgs(plan) {
|
|
|
44645
45525
|
if (plan.platform === "win32") {
|
|
44646
45526
|
return [
|
|
44647
45527
|
"--no-socket",
|
|
45528
|
+
"--stop-on-stdin",
|
|
44648
45529
|
"--storage",
|
|
44649
45530
|
plan.storagePath,
|
|
44650
45531
|
...plan.credentialsPath ? ["--credentials", plan.credentialsPath] : [],
|
|
@@ -44663,14 +45544,14 @@ function gatewayArgs(plan) {
|
|
|
44663
45544
|
}
|
|
44664
45545
|
function readServerId(file) {
|
|
44665
45546
|
try {
|
|
44666
|
-
const value =
|
|
45547
|
+
const value = readFileSync4(file, "utf8").trim();
|
|
44667
45548
|
return isServerId(value) ? value : undefined;
|
|
44668
45549
|
} catch {
|
|
44669
45550
|
return;
|
|
44670
45551
|
}
|
|
44671
45552
|
}
|
|
44672
45553
|
function serverIdFromSocketPath2(socketPath) {
|
|
44673
|
-
const name =
|
|
45554
|
+
const name = basename8(socketPath);
|
|
44674
45555
|
if (!name.endsWith(".sock"))
|
|
44675
45556
|
return;
|
|
44676
45557
|
const candidate = name.slice(0, -".sock".length);
|
|
@@ -44742,8 +45623,8 @@ var init_gateway2 = __esm(() => {
|
|
|
44742
45623
|
});
|
|
44743
45624
|
|
|
44744
45625
|
// packages/tui/src/help-ui.ts
|
|
44745
|
-
import { getKeybindings, matchesKey as
|
|
44746
|
-
function helpLines(platform = process.platform) {
|
|
45626
|
+
import { getKeybindings, matchesKey as matchesKey9, TUI_KEYBINDINGS, wrapTextWithAnsi as wrapTextWithAnsi4 } from "@earendil-works/pi-tui";
|
|
45627
|
+
function helpLines(platform = process.platform, wsl = runningInWsl()) {
|
|
44747
45628
|
const rows = [];
|
|
44748
45629
|
const section = (title, hints) => {
|
|
44749
45630
|
rows.push(theme.accent(title), ...hints.map(([key, description]) => shortcutHints([key, description])), "");
|
|
@@ -44756,7 +45637,7 @@ function helpLines(platform = process.platform) {
|
|
|
44756
45637
|
["Ctrl+C", "Stop while running; exit when idle"],
|
|
44757
45638
|
["Ctrl+S", "Open sessions"],
|
|
44758
45639
|
["Ctrl+O", "Open or close the assistant's question"],
|
|
44759
|
-
[platform
|
|
45640
|
+
[keyLabel(clipboardShortcutKey(platform, wsl)), "Paste clipboard image or text"],
|
|
44760
45641
|
["/", "Complete commands"],
|
|
44761
45642
|
["@", "Complete workspace files"],
|
|
44762
45643
|
["/skill:", "Complete installed skills"]
|
|
@@ -44815,7 +45696,8 @@ function helpLines(platform = process.platform) {
|
|
|
44815
45696
|
]);
|
|
44816
45697
|
section("Web search", [
|
|
44817
45698
|
["/search", "Choose the web search provider"],
|
|
44818
|
-
["Enter", "Apply the highlighted provider"]
|
|
45699
|
+
["Enter", "Apply the highlighted provider"],
|
|
45700
|
+
["K", "Set the highlighted provider's API key (opens /login)"]
|
|
44819
45701
|
]);
|
|
44820
45702
|
section("Context", [
|
|
44821
45703
|
["/context", "Tune compaction and tool result pruning"],
|
|
@@ -44874,13 +45756,13 @@ class HelpDetail {
|
|
|
44874
45756
|
this.#offset = Math.max(0, Math.min(this.#max, this.#offset + delta));
|
|
44875
45757
|
}
|
|
44876
45758
|
handleKey(data) {
|
|
44877
|
-
if (
|
|
45759
|
+
if (matchesKey9(data, "up"))
|
|
44878
45760
|
this.scroll(-1);
|
|
44879
|
-
else if (
|
|
45761
|
+
else if (matchesKey9(data, "down"))
|
|
44880
45762
|
this.scroll(1);
|
|
44881
|
-
else if (
|
|
45763
|
+
else if (matchesKey9(data, "pageUp"))
|
|
44882
45764
|
this.scroll(-this.#page);
|
|
44883
|
-
else if (
|
|
45765
|
+
else if (matchesKey9(data, "pageDown"))
|
|
44884
45766
|
this.scroll(this.#page);
|
|
44885
45767
|
else
|
|
44886
45768
|
return false;
|
|
@@ -44897,117 +45779,11 @@ class HelpDetail {
|
|
|
44897
45779
|
var keyLabel = (key) => key.split("+").map((part) => ({ escape: "Esc", pageUp: "PgUp", pageDown: "PgDn" })[part] ?? part[0].toUpperCase() + part.slice(1)).join("+");
|
|
44898
45780
|
var init_help_ui = __esm(() => {
|
|
44899
45781
|
init_shortcut_hints();
|
|
45782
|
+
init_image_input();
|
|
44900
45783
|
init_labels();
|
|
44901
45784
|
init_theme();
|
|
44902
45785
|
});
|
|
44903
45786
|
|
|
44904
|
-
// packages/tui/src/image-input.ts
|
|
44905
|
-
import { execFile as execFile3 } from "child_process";
|
|
44906
|
-
import { readFile as readFile9, stat as stat5 } from "fs/promises";
|
|
44907
|
-
import { homedir as homedir8 } from "os";
|
|
44908
|
-
import { basename as basename8, resolve as resolve6 } from "path";
|
|
44909
|
-
import { promisify as promisify2 } from "util";
|
|
44910
|
-
import { isKeyRelease as isKeyRelease4, isKeyRepeat as isKeyRepeat2, matchesKey as matchesKey9 } from "@earendil-works/pi-tui";
|
|
44911
|
-
function imagePaths(text, platform = process.platform) {
|
|
44912
|
-
const paths = [];
|
|
44913
|
-
let word = "", quote = "", escaped = false;
|
|
44914
|
-
for (const c of text.trim()) {
|
|
44915
|
-
if (escaped) {
|
|
44916
|
-
word += c;
|
|
44917
|
-
escaped = false;
|
|
44918
|
-
} else if (c === "\\" && quote !== "'" && platform !== "win32") {
|
|
44919
|
-
escaped = true;
|
|
44920
|
-
} else if (quote) {
|
|
44921
|
-
if (c === quote)
|
|
44922
|
-
quote = "";
|
|
44923
|
-
else
|
|
44924
|
-
word += c;
|
|
44925
|
-
} else if (c === '"' || c === "'")
|
|
44926
|
-
quote = c;
|
|
44927
|
-
else if (/\s/.test(c)) {
|
|
44928
|
-
if (word) {
|
|
44929
|
-
paths.push(word);
|
|
44930
|
-
word = "";
|
|
44931
|
-
}
|
|
44932
|
-
} else
|
|
44933
|
-
word += c;
|
|
44934
|
-
}
|
|
44935
|
-
if (quote || escaped)
|
|
44936
|
-
return null;
|
|
44937
|
-
if (word)
|
|
44938
|
-
paths.push(word);
|
|
44939
|
-
return paths.length && paths.every((p) => /\.(png|jpe?g|webp)$/i.test(p)) ? paths : null;
|
|
44940
|
-
}
|
|
44941
|
-
async function readImageFile(path, cwd) {
|
|
44942
|
-
const full = resolve6(cwd, path.startsWith("~/") ? homedir8() + path.slice(1) : path);
|
|
44943
|
-
const info = await stat5(full);
|
|
44944
|
-
if (!info.isFile() || info.size > 20 * 1024 * 1024)
|
|
44945
|
-
throw new Error("Image must be a file no larger than 20 MiB");
|
|
44946
|
-
const bytes = await readFile9(full);
|
|
44947
|
-
const after = await stat5(full);
|
|
44948
|
-
if (bytes.length !== info.size || after.size !== info.size || after.mtimeMs !== info.mtimeMs)
|
|
44949
|
-
throw new Error("Image changed while reading");
|
|
44950
|
-
const mimeType = /\.png$/i.test(full) ? "image/png" : /\.webp$/i.test(full) ? "image/webp" : "image/jpeg";
|
|
44951
|
-
return { name: basename8(full), mimeType, bytes };
|
|
44952
|
-
}
|
|
44953
|
-
async function command(file, args, encoding = "utf8") {
|
|
44954
|
-
return (await exec(file, args, { timeout: 2000, maxBuffer: 30 * 1024 * 1024, encoding })).stdout;
|
|
44955
|
-
}
|
|
44956
|
-
async function clipboard(options = {}) {
|
|
44957
|
-
const run = options.command ?? command;
|
|
44958
|
-
const platform = options.platform ?? process.platform;
|
|
44959
|
-
if (platform === "darwin") {
|
|
44960
|
-
const files = await run("osascript", [
|
|
44961
|
-
"-e",
|
|
44962
|
-
`try
|
|
44963
|
-
set fs to the clipboard as \xABclass furl\xBB
|
|
44964
|
-
return POSIX path of fs
|
|
44965
|
-
on error
|
|
44966
|
-
return ""
|
|
44967
|
-
end try`
|
|
44968
|
-
]).catch(() => "");
|
|
44969
|
-
if (String(files).trim())
|
|
44970
|
-
return /\.(png|jpe?g|webp)$/i.test(String(files).trim()) ? { paths: [String(files).trim()] } : { text: String(files).trim() };
|
|
44971
|
-
const image = String(await run("osascript", ["-e", `try
|
|
44972
|
-
return the clipboard as \xABclass PNGf\xBB
|
|
44973
|
-
on error
|
|
44974
|
-
return ""
|
|
44975
|
-
end try`]).catch(() => ""));
|
|
44976
|
-
const hex = /\u00ABdata PNGf([0-9A-Fa-f]+)\u00BB/.exec(image)?.[1];
|
|
44977
|
-
if (hex)
|
|
44978
|
-
return { image: { name: "Clipboard.png", mimeType: "image/png", bytes: Buffer.from(hex, "hex") } };
|
|
44979
|
-
return { text: String(await run("pbpaste", [])) };
|
|
44980
|
-
}
|
|
44981
|
-
if (platform === "win32") {
|
|
44982
|
-
const value = String(await run("powershell.exe", [
|
|
44983
|
-
"-NoProfile",
|
|
44984
|
-
"-STA",
|
|
44985
|
-
"-Command",
|
|
44986
|
-
'Add-Type -AssemblyName System.Windows.Forms; $i=[Windows.Forms.Clipboard]::GetImage(); if($i){$m=New-Object IO.MemoryStream; $i.Save($m,[Drawing.Imaging.ImageFormat]::Png); "IMAGE:"+[Convert]::ToBase64String($m.ToArray());$i.Dispose();$m.Dispose()}else{Get-Clipboard -Raw}'
|
|
44987
|
-
]));
|
|
44988
|
-
if (value.startsWith("IMAGE:"))
|
|
44989
|
-
return {
|
|
44990
|
-
image: { name: "Clipboard.png", mimeType: "image/png", bytes: Buffer.from(value.slice(6).trim(), "base64") }
|
|
44991
|
-
};
|
|
44992
|
-
return { text: value };
|
|
44993
|
-
}
|
|
44994
|
-
const wayland = options.wayland ?? !!process.env.WAYLAND_DISPLAY;
|
|
44995
|
-
const tool = wayland ? "wl-paste" : "xclip";
|
|
44996
|
-
try {
|
|
44997
|
-
const bytes = await run(tool, wayland ? ["--type", "image/png"] : ["-selection", "clipboard", "-t", "image/png", "-o"], "buffer");
|
|
44998
|
-
if (bytes.length)
|
|
44999
|
-
return { image: { name: "Clipboard.png", mimeType: "image/png", bytes: Buffer.from(bytes) } };
|
|
45000
|
-
} catch {}
|
|
45001
|
-
return { text: String(await run(tool, wayland ? ["--no-newline"] : ["-selection", "clipboard", "-o"])) };
|
|
45002
|
-
}
|
|
45003
|
-
function isClipboardShortcut(data, platform = process.platform) {
|
|
45004
|
-
return !isKeyRelease4(data) && !isKeyRepeat2(data) && matchesKey9(data, platform === "win32" ? "alt+v" : "ctrl+v");
|
|
45005
|
-
}
|
|
45006
|
-
var exec;
|
|
45007
|
-
var init_image_input = __esm(() => {
|
|
45008
|
-
exec = promisify2(execFile3);
|
|
45009
|
-
});
|
|
45010
|
-
|
|
45011
45787
|
// packages/tui/src/question-dock.ts
|
|
45012
45788
|
import {
|
|
45013
45789
|
Input as Input3,
|
|
@@ -45015,7 +45791,7 @@ import {
|
|
|
45015
45791
|
isKeyRepeat as isKeyRepeat3,
|
|
45016
45792
|
matchesKey as matchesKey10,
|
|
45017
45793
|
truncateToWidth as truncateToWidth6,
|
|
45018
|
-
visibleWidth as
|
|
45794
|
+
visibleWidth as visibleWidth5,
|
|
45019
45795
|
wrapTextWithAnsi as wrapTextWithAnsi5
|
|
45020
45796
|
} from "@earendil-works/pi-tui";
|
|
45021
45797
|
function settlementText(record, mine) {
|
|
@@ -45494,7 +46270,7 @@ class QuestionDock {
|
|
|
45494
46270
|
`\u2753 \u7B49\u5F85\u56DE\u7B54 (${n}) \xB7 Ctrl+O \u6216 /question`,
|
|
45495
46271
|
"\u2753 \u7B49\u5F85\u56DE\u7B54 \xB7 /question"
|
|
45496
46272
|
];
|
|
45497
|
-
const text = candidates.find((candidate) =>
|
|
46273
|
+
const text = candidates.find((candidate) => visibleWidth5(candidate) <= width - 1) ?? candidates.at(-1) ?? "";
|
|
45498
46274
|
return theme.warning(truncateToWidth6(` ${text}`, width));
|
|
45499
46275
|
}
|
|
45500
46276
|
#panel(entries, width) {
|
|
@@ -45539,7 +46315,7 @@ class QuestionDock {
|
|
|
45539
46315
|
["", maxScroll > 0 ? range : ""],
|
|
45540
46316
|
["", ""]
|
|
45541
46317
|
].map(([a, b]) => title + theme.dim([a, b].filter(Boolean).map((part) => ` \xB7 ${part}`).join("")));
|
|
45542
|
-
const rows = [headers.find((header) =>
|
|
46318
|
+
const rows = [headers.find((header) => visibleWidth5(header) <= width) ?? headers.at(-1) ?? title];
|
|
45543
46319
|
for (const line of bodyLines.slice(scroll, scroll + bodyRows))
|
|
45544
46320
|
rows.push(`${theme.dim("\u2502 ")}${line}`);
|
|
45545
46321
|
if (form) {
|
|
@@ -45619,7 +46395,7 @@ class QuestionDock {
|
|
|
45619
46395
|
];
|
|
45620
46396
|
}
|
|
45621
46397
|
const lines = candidates.map((parts) => ` ${shortcutHints(...parts)}`);
|
|
45622
|
-
return lines.find((line) =>
|
|
46398
|
+
return lines.find((line) => visibleWidth5(line) <= width) ?? truncateToWidth6(lines.at(-1) ?? "", width);
|
|
45623
46399
|
}
|
|
45624
46400
|
}
|
|
45625
46401
|
var QUESTION_MAX_ROWS = 14, QUESTION_MIN_ROWS = 6, SKIP_LABEL = "\u8DF3\u8FC7\u6B64\u95EE\u9898", QUESTION_KEY = "ctrl+o", canAutoOpen = (focus) => focus.online && focus.usable && focus.editorText === "" && !focus.autocomplete && !focus.overlayOpen && !focus.approvalFocused && !focus.sessionOperation && !focus.queueSelecting, keyOf = (session, id, version) => `${session}\x00${id}\x00${version}`, messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
@@ -45759,6 +46535,95 @@ var init_input_router = __esm(() => {
|
|
|
45759
46535
|
init_question_dock();
|
|
45760
46536
|
});
|
|
45761
46537
|
|
|
46538
|
+
// packages/tui/src/provider-ui.ts
|
|
46539
|
+
function providerSendBlock(model, credentials) {
|
|
46540
|
+
const status = model ? credentials?.statuses[model.provider] : undefined;
|
|
46541
|
+
if (!model || !status)
|
|
46542
|
+
return;
|
|
46543
|
+
const name = providerName(model.provider);
|
|
46544
|
+
if (status.enabled === false)
|
|
46545
|
+
return `${name} is disabled. Enable it with /login or switch model.`;
|
|
46546
|
+
if (status.source === "none")
|
|
46547
|
+
return `Configure ${name} with /login before sending.`;
|
|
46548
|
+
return;
|
|
46549
|
+
}
|
|
46550
|
+
function modelItems(models, current, defaultModel) {
|
|
46551
|
+
const currentKey = current ? modelKey(current) : undefined;
|
|
46552
|
+
const defaultKey = defaultModel ? modelKey(defaultModel) : undefined;
|
|
46553
|
+
const order = new Map(MODEL_PROVIDERS.map((spec, index) => [spec.id, index + 1]));
|
|
46554
|
+
const rank = (provider) => provider === current?.provider ? 0 : order.get(provider) ?? order.size + 1;
|
|
46555
|
+
const providers = [...new Set(models.map((model) => model.provider))].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
|
|
46556
|
+
const items = [];
|
|
46557
|
+
for (const provider of providers) {
|
|
46558
|
+
const group = models.filter((model) => model.provider === provider).sort((a, b) => Number(modelKey(b) === currentKey) - Number(modelKey(a) === currentKey) || a.name.localeCompare(b.name));
|
|
46559
|
+
const name = providerName(provider);
|
|
46560
|
+
const available = group.some((model) => model.available);
|
|
46561
|
+
items.push({
|
|
46562
|
+
value: `header:${provider}`,
|
|
46563
|
+
label: `${name}${available ? "" : " \xB7 needs key"}`,
|
|
46564
|
+
header: true
|
|
46565
|
+
});
|
|
46566
|
+
for (const model of group) {
|
|
46567
|
+
const key = modelKey(model);
|
|
46568
|
+
const image = model.input.includes("image");
|
|
46569
|
+
const tags = [
|
|
46570
|
+
...key === defaultKey ? ["default"] : [],
|
|
46571
|
+
...model.available ? [] : ["needs key"],
|
|
46572
|
+
formatTokens(model.contextWindow),
|
|
46573
|
+
...model.reasoning ? ["Thinking"] : [],
|
|
46574
|
+
...image ? ["Images"] : []
|
|
46575
|
+
];
|
|
46576
|
+
items.push({
|
|
46577
|
+
value: key,
|
|
46578
|
+
label: `${key}${key === currentKey ? " (Current)" : ""}${key === defaultKey ? " \u2605" : ""}`,
|
|
46579
|
+
rightLabel: tags.join(" \xB7 "),
|
|
46580
|
+
searchText: `${model.name} ${name}`,
|
|
46581
|
+
description: `${model.name} | ${formatTokens(model.contextWindow)} context${model.reasoning ? " | Thinking" : ""} | ${image ? "Images" : "Text only"}`,
|
|
46582
|
+
...model.available ? {} : { blockedHint: needsKeyHint(provider) }
|
|
46583
|
+
});
|
|
46584
|
+
}
|
|
46585
|
+
}
|
|
46586
|
+
return items;
|
|
46587
|
+
}
|
|
46588
|
+
function openModelPanel(panel, client, current, applyModel, notify) {
|
|
46589
|
+
let models = [];
|
|
46590
|
+
const currentKey = current ? modelKey(current) : null;
|
|
46591
|
+
const effectiveDefault = () => client.defaultModel.state.value?.effective ?? null;
|
|
46592
|
+
const items = () => modelItems(models, current, effectiveDefault());
|
|
46593
|
+
const stop = client.defaultModel.state.subscribe(() => panel.update(items()));
|
|
46594
|
+
panel.groupedFilter = true;
|
|
46595
|
+
panel.help = shortcutHints(["\u2191\u2193", "Select"], ["Enter", "Open"], ["D", "Toggle default"], ["Esc", "Close"]);
|
|
46596
|
+
panel.listKey = (data, value) => {
|
|
46597
|
+
if (data.toLowerCase() !== "d" || panel.search.getValue() !== "" || value === undefined)
|
|
46598
|
+
return false;
|
|
46599
|
+
const model = models.find((m) => modelKey(m) === value);
|
|
46600
|
+
if (!model)
|
|
46601
|
+
return false;
|
|
46602
|
+
if (!model.available) {
|
|
46603
|
+
panel.hint = needsKeyHint(model.provider);
|
|
46604
|
+
return true;
|
|
46605
|
+
}
|
|
46606
|
+
const effective = effectiveDefault();
|
|
46607
|
+
const toggle = effective !== null && modelKey(effective) === value ? client.defaultModel.clear(null) : client.defaultModel.set({ provider: model.provider, modelId: model.modelId });
|
|
46608
|
+
toggle.catch((error) => notify(error instanceof Error ? error.message : String(error)));
|
|
46609
|
+
return true;
|
|
46610
|
+
};
|
|
46611
|
+
panel.show("Models", async () => {
|
|
46612
|
+
models = await client.models.list(null);
|
|
46613
|
+
return items();
|
|
46614
|
+
}, async (value) => {
|
|
46615
|
+
const selected = models.find((m) => modelKey(m) === value);
|
|
46616
|
+
if (selected?.available && value !== currentKey)
|
|
46617
|
+
await applyModel(selected);
|
|
46618
|
+
});
|
|
46619
|
+
return stop;
|
|
46620
|
+
}
|
|
46621
|
+
var modelKey = (model) => `${model.provider}/${model.modelId}`, providerName = (id) => CREDENTIAL_PROVIDERS.find((spec) => spec.id === id)?.name ?? id, isProviderRejection = (message) => /^(PROVIDER_KEY_REQUIRED|PROVIDER_DISABLED):/.test(message), needsKeyHint = (provider) => `${providerName(provider)} needs a key. Use /login to configure it.`;
|
|
46622
|
+
var init_provider_ui = __esm(() => {
|
|
46623
|
+
init_src();
|
|
46624
|
+
init_shortcut_hints();
|
|
46625
|
+
});
|
|
46626
|
+
|
|
45762
46627
|
// packages/tui/src/queue-ui.ts
|
|
45763
46628
|
import { matchesKey as matchesKey12, truncateToWidth as truncateToWidth7 } from "@earendil-works/pi-tui";
|
|
45764
46629
|
|
|
@@ -45866,10 +46731,10 @@ function searchItems(settings, credentials) {
|
|
|
45866
46731
|
const status = credentials?.statuses[provider];
|
|
45867
46732
|
return status ? SOURCES2[status.source] : "loading";
|
|
45868
46733
|
};
|
|
45869
|
-
const deepseekMissing = credentials?.statuses.deepseek
|
|
46734
|
+
const deepseekMissing = credentials?.statuses.deepseek?.source === "none";
|
|
45870
46735
|
const descriptions = {
|
|
45871
46736
|
tavily: `Tavily search (${source("tavily")}). Default`,
|
|
45872
|
-
deepseek: deepseekMissing ? "No DeepSeek key.
|
|
46737
|
+
deepseek: deepseekMissing ? "No DeepSeek key. Press k (or use /login) to set one before choosing it" : `DeepSeek search and fetch with your DeepSeek API key (${source("deepseek")})`
|
|
45873
46738
|
};
|
|
45874
46739
|
return SEARCH_PROVIDERS.map((value) => ({
|
|
45875
46740
|
value,
|
|
@@ -45877,11 +46742,23 @@ function searchItems(settings, credentials) {
|
|
|
45877
46742
|
description: descriptions[value]
|
|
45878
46743
|
}));
|
|
45879
46744
|
}
|
|
45880
|
-
function openSearchPanel(panel, client, notify) {
|
|
46745
|
+
function openSearchPanel(panel, client, notify, openKey) {
|
|
45881
46746
|
const items = () => searchItems(client.searchSettings.state.value, client.credentials.state.value);
|
|
45882
46747
|
const refresh = () => panel.update(items());
|
|
45883
46748
|
const stops = [client.searchSettings.state.subscribe(refresh), client.credentials.state.subscribe(refresh)];
|
|
45884
46749
|
panel.emptyText = SEARCH_EMPTY;
|
|
46750
|
+
if (openKey) {
|
|
46751
|
+
panel.help = shortcutHints(["\u2191\u2193", "Select"], ["Enter", "Choose"], ["K", "Set key"], ["Esc", "Close"]);
|
|
46752
|
+
panel.listKey = (data, value) => {
|
|
46753
|
+
if (data.toLowerCase() !== "k" || panel.search.getValue() !== "" || value === undefined)
|
|
46754
|
+
return false;
|
|
46755
|
+
if (!SEARCH_PROVIDERS.includes(value))
|
|
46756
|
+
return false;
|
|
46757
|
+
panel.close();
|
|
46758
|
+
openKey(value);
|
|
46759
|
+
return true;
|
|
46760
|
+
};
|
|
46761
|
+
}
|
|
45885
46762
|
panel.show("Web search", async () => items(), async (value) => {
|
|
45886
46763
|
await client.searchSettings.set({ provider: value });
|
|
45887
46764
|
notify(`Web search provider set to ${NAMES[value]}.`);
|
|
@@ -45894,6 +46771,7 @@ function openSearchPanel(panel, client, notify) {
|
|
|
45894
46771
|
var SEARCH_EMPTY = "Search settings are unavailable.", NAMES, SOURCES2;
|
|
45895
46772
|
var init_search_ui = __esm(() => {
|
|
45896
46773
|
init_search_providers();
|
|
46774
|
+
init_shortcut_hints();
|
|
45897
46775
|
NAMES = { tavily: "Tavily", deepseek: "DeepSeek" };
|
|
45898
46776
|
SOURCES2 = {
|
|
45899
46777
|
saved: "saved key",
|
|
@@ -45904,6 +46782,31 @@ var init_search_ui = __esm(() => {
|
|
|
45904
46782
|
};
|
|
45905
46783
|
});
|
|
45906
46784
|
|
|
46785
|
+
// packages/tui/src/shell-notice.ts
|
|
46786
|
+
function shellNoticeText(shell) {
|
|
46787
|
+
if (shell.status !== "missing")
|
|
46788
|
+
return;
|
|
46789
|
+
if (shell.reason === "invalid_config")
|
|
46790
|
+
return `${shell.detail ?? "shell.path is invalid."} Bash and background jobs are unavailable. Fix shell.path in neko.config.json.`;
|
|
46791
|
+
return "Git Bash not found: bash and background jobs are unavailable. Install Git for Windows (https://git-scm.com/download/win) or set shell.path in neko.config.json.";
|
|
46792
|
+
}
|
|
46793
|
+
function createShellNotifier(show) {
|
|
46794
|
+
let last;
|
|
46795
|
+
return (shell) => {
|
|
46796
|
+
if (shell === undefined)
|
|
46797
|
+
return;
|
|
46798
|
+
const text = shellNoticeText(shell);
|
|
46799
|
+
if (text === undefined) {
|
|
46800
|
+
last = undefined;
|
|
46801
|
+
return;
|
|
46802
|
+
}
|
|
46803
|
+
if (text === last)
|
|
46804
|
+
return;
|
|
46805
|
+
last = text;
|
|
46806
|
+
show(text);
|
|
46807
|
+
};
|
|
46808
|
+
}
|
|
46809
|
+
|
|
45907
46810
|
// packages/tui/src/skill-market-ui.ts
|
|
45908
46811
|
import { ChordError as ChordError6 } from "@earendil-works/chord";
|
|
45909
46812
|
import { Markdown as Markdown3, matchesKey as matchesKey13, wrapTextWithAnsi as wrapTextWithAnsi6 } from "@earendil-works/pi-tui";
|
|
@@ -46424,7 +47327,7 @@ var init_subagents_ui = __esm(() => {
|
|
|
46424
47327
|
});
|
|
46425
47328
|
|
|
46426
47329
|
// packages/tui/src/todos-ui.ts
|
|
46427
|
-
import { matchesKey as matchesKey15, truncateToWidth as truncateToWidth8, visibleWidth as
|
|
47330
|
+
import { matchesKey as matchesKey15, truncateToWidth as truncateToWidth8, visibleWidth as visibleWidth6, wrapTextWithAnsi as wrapTextWithAnsi7 } from "@earendil-works/pi-tui";
|
|
46428
47331
|
function todoProgressText(list) {
|
|
46429
47332
|
if (!list || list.items.length === 0)
|
|
46430
47333
|
return;
|
|
@@ -46487,7 +47390,7 @@ class TodosDetail {
|
|
|
46487
47390
|
rows.push(row === 0 ? styled(item.status, glyph, `${number}${line}`) : theme.dim(`${indent}${line}`));
|
|
46488
47391
|
});
|
|
46489
47392
|
});
|
|
46490
|
-
const legend = height >= 5 &&
|
|
47393
|
+
const legend = height >= 5 && visibleWidth6(LEGEND) <= width ? [theme.dim(LEGEND)] : [];
|
|
46491
47394
|
const pinned = offline.length + 1 + legend.length;
|
|
46492
47395
|
const room = Math.max(1, height - pinned);
|
|
46493
47396
|
this.#max = Math.max(0, rows.length - room);
|
|
@@ -46686,6 +47589,7 @@ class NekoTuiApp {
|
|
|
46686
47589
|
#notices = new Container6;
|
|
46687
47590
|
#versionNotice = new Container6;
|
|
46688
47591
|
#versionStop;
|
|
47592
|
+
#shellStop;
|
|
46689
47593
|
#versionTimer;
|
|
46690
47594
|
#terminal = new ProcessTerminal;
|
|
46691
47595
|
#panel;
|
|
@@ -46715,6 +47619,7 @@ class NekoTuiApp {
|
|
|
46715
47619
|
#queueRow = new QueueRow;
|
|
46716
47620
|
#searchStop;
|
|
46717
47621
|
#contextStop;
|
|
47622
|
+
#modelStop;
|
|
46718
47623
|
#agentDetails = new Map;
|
|
46719
47624
|
#sessionId = "";
|
|
46720
47625
|
#viewUnsubscribe;
|
|
@@ -46773,6 +47678,8 @@ class NekoTuiApp {
|
|
|
46773
47678
|
this.#searchStop = undefined;
|
|
46774
47679
|
this.#contextStop?.();
|
|
46775
47680
|
this.#contextStop = undefined;
|
|
47681
|
+
this.#modelStop?.();
|
|
47682
|
+
this.#modelStop = undefined;
|
|
46776
47683
|
clearInterval(this.#sessionPanelTimer);
|
|
46777
47684
|
this.#sessionPanelTimer = undefined;
|
|
46778
47685
|
this.#ui.setFocus(this.#question.open ? this.#question : this.#editor);
|
|
@@ -46801,6 +47708,9 @@ class NekoTuiApp {
|
|
|
46801
47708
|
},
|
|
46802
47709
|
remove: async (provider) => {
|
|
46803
47710
|
await this.#gateway.client.credentials.remove({ provider });
|
|
47711
|
+
},
|
|
47712
|
+
toggle: async (provider, enabled) => {
|
|
47713
|
+
await this.#gateway.client.credentials.setEnabled({ provider, enabled });
|
|
46804
47714
|
}
|
|
46805
47715
|
});
|
|
46806
47716
|
const complete = fileCompletion(this.#commands, options.cwd ?? process.cwd(), (text) => this.addNotice(text), (query, signal) => this.#gateway.client.host.root((ctx) => this.#gateway.client.workspaceFiles.search({ workspaceRoot: this.#lastView?.cwd ?? options.cwd ?? process.cwd(), query }, ctx), { signal }));
|
|
@@ -46849,7 +47759,7 @@ class NekoTuiApp {
|
|
|
46849
47759
|
showAgents: () => this.#showAgents(),
|
|
46850
47760
|
toggleQuestion: () => this.#toggleQuestion(),
|
|
46851
47761
|
clipboardKey: (data) => {
|
|
46852
|
-
if (!matchesKey17(data,
|
|
47762
|
+
if (!matchesKey17(data, clipboardShortcutKey()))
|
|
46853
47763
|
return false;
|
|
46854
47764
|
if (isClipboardShortcut(data))
|
|
46855
47765
|
this.pasteClipboard();
|
|
@@ -46938,15 +47848,20 @@ class NekoTuiApp {
|
|
|
46938
47848
|
this.#ui.requestRender();
|
|
46939
47849
|
};
|
|
46940
47850
|
this.#versionStop = this.#gateway.client.application.info.subscribe(() => void refreshVersion());
|
|
47851
|
+
const notifyShell = createShellNotifier((text) => this.addNotice(text, "warning", { wrap: true, ttlMs: 20000 }));
|
|
47852
|
+
const checkShell = () => notifyShell(this.#gateway.client.application.info.value?.shell);
|
|
47853
|
+
this.#shellStop = this.#gateway.client.application.info.subscribe(checkShell);
|
|
46941
47854
|
this.#versionTimer = setInterval(() => void refreshVersion(), 30000);
|
|
46942
47855
|
refreshVersion();
|
|
46943
47856
|
await this.switchSession(initialSessionId);
|
|
46944
47857
|
this.#ui.start();
|
|
46945
47858
|
this.#ui.requestRender(true);
|
|
47859
|
+
checkShell();
|
|
46946
47860
|
loadAllHighlightLanguages();
|
|
46947
47861
|
this.#startThemeDetection();
|
|
46948
47862
|
await this.#exited;
|
|
46949
47863
|
this.#versionStop?.();
|
|
47864
|
+
this.#shellStop?.();
|
|
46950
47865
|
clearInterval(this.#versionTimer);
|
|
46951
47866
|
this.#viewUnsubscribe?.();
|
|
46952
47867
|
this.#directoryUnsubscribe?.();
|
|
@@ -47003,7 +47918,7 @@ class NekoTuiApp {
|
|
|
47003
47918
|
chat.compact({ instructions }).catch((e) => {
|
|
47004
47919
|
if (session === this.#sessionId) {
|
|
47005
47920
|
this.#editor.setText(text);
|
|
47006
|
-
this.addNotice(messageOf2(e), "error");
|
|
47921
|
+
this.addNotice(messageOf2(e), isProviderRejection(messageOf2(e)) ? "warning" : "error");
|
|
47007
47922
|
}
|
|
47008
47923
|
});
|
|
47009
47924
|
return;
|
|
@@ -47017,14 +47932,7 @@ class NekoTuiApp {
|
|
|
47017
47932
|
return;
|
|
47018
47933
|
}
|
|
47019
47934
|
if (trimmed === "/login") {
|
|
47020
|
-
|
|
47021
|
-
const service = this.#gateway.client.credentials;
|
|
47022
|
-
this.#login.show(service.state.value, (fn) => service.state.subscribe((value) => {
|
|
47023
|
-
if (value)
|
|
47024
|
-
fn(value);
|
|
47025
|
-
}));
|
|
47026
|
-
this.#ui.setFocus(this.#login);
|
|
47027
|
-
}
|
|
47935
|
+
this.#showLogin();
|
|
47028
47936
|
return;
|
|
47029
47937
|
}
|
|
47030
47938
|
if (trimmed.startsWith("/login ")) {
|
|
@@ -47080,9 +47988,10 @@ class NekoTuiApp {
|
|
|
47080
47988
|
const chat = this.#chat;
|
|
47081
47989
|
if (chat === undefined)
|
|
47082
47990
|
return;
|
|
47083
|
-
|
|
47991
|
+
const blocked = providerSendBlock(this.#lastView?.agent.model, this.#gateway.client.credentials.state.value);
|
|
47992
|
+
if (blocked) {
|
|
47084
47993
|
this.#editor.setText(text);
|
|
47085
|
-
this.addNotice(
|
|
47994
|
+
this.addNotice(blocked, "warning");
|
|
47086
47995
|
return;
|
|
47087
47996
|
}
|
|
47088
47997
|
this.sendImages(text);
|
|
@@ -47163,8 +48072,10 @@ class NekoTuiApp {
|
|
|
47163
48072
|
this.#imageDrafts.get(session)?.delete(ref);
|
|
47164
48073
|
this.#retryRequests.delete(session);
|
|
47165
48074
|
} catch (e) {
|
|
47166
|
-
if (session === this.#sessionId)
|
|
47167
|
-
|
|
48075
|
+
if (session === this.#sessionId) {
|
|
48076
|
+
const message = messageOf2(e);
|
|
48077
|
+
this.addNotice(message, isProviderRejection(message) ? "warning" : "error");
|
|
48078
|
+
}
|
|
47168
48079
|
} finally {
|
|
47169
48080
|
for (const id of ids)
|
|
47170
48081
|
await chat.releaseImage(id).catch(() => {});
|
|
@@ -47345,22 +48256,10 @@ class NekoTuiApp {
|
|
|
47345
48256
|
return;
|
|
47346
48257
|
const chat = this.#chat;
|
|
47347
48258
|
const current = this.#lastView?.agent.model;
|
|
47348
|
-
|
|
47349
|
-
|
|
47350
|
-
|
|
47351
|
-
models = await this.#gateway.client.models.list(null);
|
|
47352
|
-
return [...models].sort((a, b) => Number(keyOf2(b) === currentKey) - Number(keyOf2(a) === currentKey)).map((model) => ({
|
|
47353
|
-
value: keyOf2(model),
|
|
47354
|
-
label: `${model.name}${keyOf2(model) === currentKey ? " (Current)" : ""}`,
|
|
47355
|
-
description: `${keyOf2(model)} | ${formatContextWindow(model.contextWindow)} context${model.reasoning ? " | Thinking" : ""} | ${model.input.includes("image") ? "Images" : "Text only"}`
|
|
47356
|
-
}));
|
|
47357
|
-
}, async (value) => {
|
|
47358
|
-
const selected = models.find((m) => keyOf2(m) === value);
|
|
47359
|
-
if (selected && value !== currentKey)
|
|
47360
|
-
await chat?.configure({ model: { provider: selected.provider, modelId: selected.modelId } });
|
|
47361
|
-
});
|
|
48259
|
+
this.#modelStop = openModelPanel(this.#panel, this.#gateway.client, current, async (selected) => {
|
|
48260
|
+
await chat?.configure({ model: { provider: selected.provider, modelId: selected.modelId } });
|
|
48261
|
+
}, (message) => this.addNotice(message, "error"));
|
|
47362
48262
|
this.#ui.setFocus(this.#panel);
|
|
47363
|
-
await ready;
|
|
47364
48263
|
}
|
|
47365
48264
|
async openThemeOverlay() {
|
|
47366
48265
|
if (this.#exitStarted || this.#panel.open || this.#login.open)
|
|
@@ -47402,8 +48301,12 @@ class NekoTuiApp {
|
|
|
47402
48301
|
return;
|
|
47403
48302
|
try {
|
|
47404
48303
|
const model = (await this.#gateway.client.models.list(null)).find((m) => m.provider === ref.provider && m.modelId === ref.modelId);
|
|
47405
|
-
if (chat !== this.#chat || this.#exitStarted || this.#panel.open || this.#login.open
|
|
48304
|
+
if (chat !== this.#chat || this.#exitStarted || this.#panel.open || this.#login.open)
|
|
48305
|
+
return;
|
|
48306
|
+
if (!model) {
|
|
48307
|
+
this.addNotice("The current model is unavailable. Choose another with /model.", "warning");
|
|
47406
48308
|
return;
|
|
48309
|
+
}
|
|
47407
48310
|
if (raw) {
|
|
47408
48311
|
const level = model.supportedThinkingLevels.find((value) => value === raw);
|
|
47409
48312
|
if (!level) {
|
|
@@ -47562,10 +48465,20 @@ class NekoTuiApp {
|
|
|
47562
48465
|
});
|
|
47563
48466
|
return { consume: true };
|
|
47564
48467
|
}
|
|
48468
|
+
#showLogin(initial) {
|
|
48469
|
+
if (this.#panel.open || this.#login.open)
|
|
48470
|
+
return;
|
|
48471
|
+
const service = this.#gateway.client.credentials;
|
|
48472
|
+
this.#login.show(service.state.value, (fn) => service.state.subscribe((value) => {
|
|
48473
|
+
if (value)
|
|
48474
|
+
fn(value);
|
|
48475
|
+
}), initial);
|
|
48476
|
+
this.#ui.setFocus(this.#login);
|
|
48477
|
+
}
|
|
47565
48478
|
#showSearch() {
|
|
47566
48479
|
if (this.#exitStarted || this.#panel.open || this.#login.open)
|
|
47567
48480
|
return;
|
|
47568
|
-
this.#searchStop = openSearchPanel(this.#panel, this.#gateway.client, (message) => this.addNotice(message));
|
|
48481
|
+
this.#searchStop = openSearchPanel(this.#panel, this.#gateway.client, (message) => this.addNotice(message), (provider) => this.#showLogin({ provider, group: "search" }));
|
|
47569
48482
|
this.#ui.setFocus(this.#panel);
|
|
47570
48483
|
}
|
|
47571
48484
|
#showContext() {
|
|
@@ -47655,7 +48568,8 @@ class NekoTuiApp {
|
|
|
47655
48568
|
if (this.#imageWarningModel !== imageModel && view.items.some((i) => (i.type === "user" || i.type === "tool") && i.images?.length)) {
|
|
47656
48569
|
this.#imageWarningModel = imageModel;
|
|
47657
48570
|
this.#gateway.client.models.list(null).then((models) => {
|
|
47658
|
-
|
|
48571
|
+
const model = models.find((m) => m.provider === view.agent.model?.provider && m.modelId === view.agent.model?.modelId);
|
|
48572
|
+
if (this.#imageWarningModel === imageModel && model && !model.input.includes("image"))
|
|
47659
48573
|
this.addNotice("Text-only model: previous images are unavailable to the model; written analysis is retained.", "warning");
|
|
47660
48574
|
}).catch(() => {});
|
|
47661
48575
|
}
|
|
@@ -47849,7 +48763,7 @@ class NekoTuiApp {
|
|
|
47849
48763
|
#syncWelcome(view) {
|
|
47850
48764
|
const empty = view.items.length === 0 && view.streaming === null && !view.busy;
|
|
47851
48765
|
if (empty) {
|
|
47852
|
-
const modelShort = view.model
|
|
48766
|
+
const modelShort = modelDisplayName(view.model);
|
|
47853
48767
|
this.#welcome.setModelLine(view.agent.thinkingLevel !== null ? `${modelShort} \xB7 Think ${view.agent.thinkingLevel}` : modelShort);
|
|
47854
48768
|
if (!this.#welcomeAttached) {
|
|
47855
48769
|
this.#imageTranscript.addChild(this.#welcome);
|
|
@@ -47957,12 +48871,6 @@ function hiddenCommands(view) {
|
|
|
47957
48871
|
hidden.add("question");
|
|
47958
48872
|
return hidden;
|
|
47959
48873
|
}
|
|
47960
|
-
function keyOf2(model) {
|
|
47961
|
-
return `${model.provider}/${model.modelId}`;
|
|
47962
|
-
}
|
|
47963
|
-
function formatContextWindow(tokens) {
|
|
47964
|
-
return formatTokens(tokens);
|
|
47965
|
-
}
|
|
47966
48874
|
async function runTui(options = {}) {
|
|
47967
48875
|
options = { ...options, cwd: normalizeWorkspace(options.cwd) };
|
|
47968
48876
|
const gateway = await connectToGateway({
|
|
@@ -47986,7 +48894,7 @@ async function initialSessionId(gateway, cwd) {
|
|
|
47986
48894
|
return first.id;
|
|
47987
48895
|
return (await gateway.client.management.create({ ...cwd !== undefined ? { cwd } : {} })).id;
|
|
47988
48896
|
}
|
|
47989
|
-
var EXIT_COMMANDS, SESSIONS_COMMANDS, NEW_SESSION_VALUE = "__new__";
|
|
48897
|
+
var EXIT_COMMANDS, SESSIONS_COMMANDS, NEW_SESSION_VALUE = "__new__", keyOf2;
|
|
47990
48898
|
var init_tui = __esm(() => {
|
|
47991
48899
|
init_src();
|
|
47992
48900
|
init_application_updates();
|
|
@@ -48016,6 +48924,7 @@ var init_tui = __esm(() => {
|
|
|
48016
48924
|
init_jobs_ui();
|
|
48017
48925
|
init_labels();
|
|
48018
48926
|
init_layout();
|
|
48927
|
+
init_provider_ui();
|
|
48019
48928
|
init_question_dock();
|
|
48020
48929
|
init_queue_ui();
|
|
48021
48930
|
init_search_ui();
|
|
@@ -48027,6 +48936,7 @@ var init_tui = __esm(() => {
|
|
|
48027
48936
|
init_workspace2();
|
|
48028
48937
|
EXIT_COMMANDS = new Set(["/exit", "/quit"]);
|
|
48029
48938
|
SESSIONS_COMMANDS = new Set(["/sessions", "/session"]);
|
|
48939
|
+
keyOf2 = modelKey;
|
|
48030
48940
|
});
|
|
48031
48941
|
|
|
48032
48942
|
// packages/tui/src/main.ts
|
|
@@ -48110,7 +49020,7 @@ var USAGE = `\u7528\u6CD5: neko tui [\u9009\u9879]
|
|
|
48110
49020
|
|
|
48111
49021
|
\u6062\u590D\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6700\u8FD1\u4F7F\u7528\u7684\u4F1A\u8BDD\uFF0C\u4E0D\u5B58\u5728\u5219\u521B\u5EFA\u3002
|
|
48112
49022
|
\u6309\u9700\u62C9\u8D77\u7F51\u5173\uFF0C\u5C5E\u4E3B TUI \u9000\u51FA\u65F6\u968F\u4E4B\u505C\u6B62\u3002
|
|
48113
|
-
\u53EF\u7528 /login \u6216 DEEPSEEK_API_KEY \u914D\u7F6E\u51ED\u636E\uFF1B/search \u9009\u62E9\u8054\u7F51\u641C\u7D22\u670D\u52A1\uFF08tavily / deepseek\uFF0C\u9ED8\u8BA4 tavily\uFF09\uFF1B/context \u8C03\u6574\u4E0A\u4E0B\u6587\u538B\u7F29\u4E0E\u5DE5\u5177\u7ED3\u679C\u88C1\u526A\u3002`;
|
|
49023
|
+
\u53EF\u7528 /login \u6216 DEEPSEEK_API_KEY \u7B49\u73AF\u5883\u53D8\u91CF\u914D\u7F6E\u51ED\u636E\uFF08\u9664 DeepSeek \u5916\u7684 provider \u9700\u5148\u5728 /login \u542F\u7528\uFF09\uFF1B/search \u9009\u62E9\u8054\u7F51\u641C\u7D22\u670D\u52A1\uFF08tavily / deepseek\uFF0C\u9ED8\u8BA4 tavily\uFF09\uFF1B/context \u8C03\u6574\u4E0A\u4E0B\u6587\u538B\u7F29\u4E0E\u5DE5\u5177\u7ED3\u679C\u88C1\u526A\uFF1B/model \u4E2D\u53EF\u628A\u53EF\u9009\u6A21\u578B\u300C\u8BBE\u4E3A\u9ED8\u8BA4\u300D\uFF0C\u4F18\u5148\u7EA7\u4F4E\u4E8E --model\u3001\u9AD8\u4E8E\u914D\u7F6E\u6587\u4EF6 defaultModel\u3002`;
|
|
48114
49024
|
var init_main = __esm(async () => {
|
|
48115
49025
|
init_tui();
|
|
48116
49026
|
try {
|
|
@@ -48142,7 +49052,7 @@ var init_open_browser = () => {};
|
|
|
48142
49052
|
import { spawn as spawn4 } from "child_process";
|
|
48143
49053
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
48144
49054
|
import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile7 } from "fs/promises";
|
|
48145
|
-
import { dirname as dirname12, join as
|
|
49055
|
+
import { dirname as dirname12, join as join17 } from "path";
|
|
48146
49056
|
async function ensureNekoGateway(options) {
|
|
48147
49057
|
const timeoutMs = options.spawnTimeoutMs ?? 30000;
|
|
48148
49058
|
const timeoutAt = Date.now() + timeoutMs;
|
|
@@ -48184,11 +49094,11 @@ async function ensureNekoGateway(options) {
|
|
|
48184
49094
|
}
|
|
48185
49095
|
throw new Error(`\u7B49\u5F85 neko gateway \u5C31\u7EEA\u8D85\u65F6\uFF08${options.spawnTimeoutMs ?? 30000}ms\uFF09\uFF1A${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
48186
49096
|
} catch (error) {
|
|
48187
|
-
await
|
|
49097
|
+
await stopSpawnedGateway(child);
|
|
48188
49098
|
throw error;
|
|
48189
49099
|
}
|
|
48190
49100
|
}
|
|
48191
|
-
const socketPath = options.socketPath ??
|
|
49101
|
+
const socketPath = options.socketPath ?? join17(nekoDir, `${serverId}.sock`);
|
|
48192
49102
|
const host = options.host ?? "127.0.0.1";
|
|
48193
49103
|
const port = options.port ?? 8321;
|
|
48194
49104
|
const wsUrl = `ws://${host.includes(":") && !host.startsWith("[") ? `[${host}]` : host}:${port}`;
|
|
@@ -48227,15 +49137,18 @@ async function ensureNekoGateway(options) {
|
|
|
48227
49137
|
}
|
|
48228
49138
|
throw new Error(`\u7B49\u5F85 neko gateway \u5C31\u7EEA\u8D85\u65F6\uFF08${options.spawnTimeoutMs ?? 30000}ms\uFF09\uFF1A${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
48229
49139
|
} catch (error) {
|
|
48230
|
-
await
|
|
49140
|
+
await stopSpawnedGateway(child);
|
|
48231
49141
|
throw error;
|
|
48232
49142
|
}
|
|
48233
49143
|
}
|
|
49144
|
+
function stopSpawnedGateway(child) {
|
|
49145
|
+
return stopGatewayProcess(child, { viaStdin: process.platform === "win32" });
|
|
49146
|
+
}
|
|
48234
49147
|
function spawnGatewayProcess(options, socketPath, serverId) {
|
|
48235
49148
|
const args = [
|
|
48236
49149
|
"--host",
|
|
48237
49150
|
options.host ?? "127.0.0.1",
|
|
48238
|
-
...process.platform === "win32" ? ["--no-socket"] : ["--port", String(options.port ?? 8321), "--socket-path", socketPath ?? ""],
|
|
49151
|
+
...process.platform === "win32" ? ["--no-socket", "--stop-on-stdin"] : ["--port", String(options.port ?? 8321), "--socket-path", socketPath ?? ""],
|
|
48239
49152
|
"--server-id",
|
|
48240
49153
|
serverId,
|
|
48241
49154
|
"--storage",
|
|
@@ -48245,7 +49158,10 @@ function spawnGatewayProcess(options, socketPath, serverId) {
|
|
|
48245
49158
|
...options.model !== undefined ? ["--model", `${options.model.provider}/${options.model.modelId}`] : []
|
|
48246
49159
|
];
|
|
48247
49160
|
const command = gatewayCommand(args);
|
|
48248
|
-
return spawn4(command.binary, command.args, {
|
|
49161
|
+
return spawn4(command.binary, command.args, {
|
|
49162
|
+
env: process.env,
|
|
49163
|
+
stdio: [process.platform === "win32" ? "pipe" : "ignore", "pipe", "pipe"]
|
|
49164
|
+
});
|
|
48249
49165
|
}
|
|
48250
49166
|
function pipeChildLogs(child, onLog) {
|
|
48251
49167
|
if (onLog === undefined)
|
|
@@ -48302,7 +49218,7 @@ function makeHandle(serverId, socketPath, wsUrl, spawned, nekoDir) {
|
|
|
48302
49218
|
spawned,
|
|
48303
49219
|
async close() {
|
|
48304
49220
|
if (spawned !== null) {
|
|
48305
|
-
await
|
|
49221
|
+
await stopSpawnedGateway(spawned);
|
|
48306
49222
|
if (process.platform === "win32")
|
|
48307
49223
|
await removeGatewayPortFile(nekoDir, serverId).catch(() => {});
|
|
48308
49224
|
}
|
|
@@ -48310,7 +49226,7 @@ function makeHandle(serverId, socketPath, wsUrl, spawned, nekoDir) {
|
|
|
48310
49226
|
};
|
|
48311
49227
|
}
|
|
48312
49228
|
async function loadOrCreateServerId2(nekoDir) {
|
|
48313
|
-
const file =
|
|
49229
|
+
const file = join17(nekoDir, SERVER_ID_FILENAME3);
|
|
48314
49230
|
try {
|
|
48315
49231
|
const existing = (await readFile10(file, "utf8")).trim();
|
|
48316
49232
|
if (existing !== "")
|
|
@@ -48635,9 +49551,9 @@ var init_mobile_admin = __esm(() => {
|
|
|
48635
49551
|
|
|
48636
49552
|
// packages/web/src/open-target.ts
|
|
48637
49553
|
import { spawn as spawn5 } from "child_process";
|
|
48638
|
-
import { existsSync as existsSync4, readFileSync as
|
|
48639
|
-
import { homedir as
|
|
48640
|
-
import { posix, win32 } from "path";
|
|
49554
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
|
|
49555
|
+
import { homedir as homedir8 } from "os";
|
|
49556
|
+
import { posix, win32 as win322 } from "path";
|
|
48641
49557
|
function isOpenTargetId(value) {
|
|
48642
49558
|
return value === "vscode" || value === "file-manager";
|
|
48643
49559
|
}
|
|
@@ -48656,9 +49572,9 @@ function systemOpenHost() {
|
|
|
48656
49572
|
const platform = process.platform;
|
|
48657
49573
|
return {
|
|
48658
49574
|
platform,
|
|
48659
|
-
wsl: detectWsl2(platform, process.env, () =>
|
|
49575
|
+
wsl: detectWsl2(platform, process.env, () => readFileSync5("/proc/version", "utf8")),
|
|
48660
49576
|
env: process.env,
|
|
48661
|
-
home:
|
|
49577
|
+
home: homedir8(),
|
|
48662
49578
|
exists: existsSync4
|
|
48663
49579
|
};
|
|
48664
49580
|
}
|
|
@@ -48671,7 +49587,7 @@ function fileManagerLabel(host) {
|
|
|
48671
49587
|
}
|
|
48672
49588
|
function findOnPath(host, names) {
|
|
48673
49589
|
const windows = host.platform === "win32";
|
|
48674
|
-
const pathApi = windows ?
|
|
49590
|
+
const pathApi = windows ? win322 : posix;
|
|
48675
49591
|
const raw = host.env[windows ? "Path" : "PATH"] ?? host.env.PATH ?? host.env.Path ?? "";
|
|
48676
49592
|
for (const entry of raw.split(windows ? ";" : ":")) {
|
|
48677
49593
|
if (entry === "")
|
|
@@ -48687,7 +49603,7 @@ function findOnPath(host, names) {
|
|
|
48687
49603
|
function windowsCodeExecutable(host) {
|
|
48688
49604
|
const shim = findOnPath(host, ["code.cmd"]);
|
|
48689
49605
|
if (shim !== undefined) {
|
|
48690
|
-
const exe =
|
|
49606
|
+
const exe = win322.join(win322.dirname(win322.dirname(shim)), "Code.exe");
|
|
48691
49607
|
if (host.exists(exe))
|
|
48692
49608
|
return exe;
|
|
48693
49609
|
}
|
|
@@ -48698,7 +49614,7 @@ function windowsCodeExecutable(host) {
|
|
|
48698
49614
|
const base = host.env[variable];
|
|
48699
49615
|
if (base === undefined || base === "")
|
|
48700
49616
|
continue;
|
|
48701
|
-
const exe =
|
|
49617
|
+
const exe = win322.join(base, suffix);
|
|
48702
49618
|
if (host.exists(exe))
|
|
48703
49619
|
return exe;
|
|
48704
49620
|
}
|
|
@@ -48743,8 +49659,7 @@ function spawnDetached(spec) {
|
|
|
48743
49659
|
const child = spawn5(spec.command, [...spec.args], {
|
|
48744
49660
|
cwd: spec.cwd,
|
|
48745
49661
|
detached: true,
|
|
48746
|
-
stdio: "ignore"
|
|
48747
|
-
windowsHide: true
|
|
49662
|
+
stdio: "ignore"
|
|
48748
49663
|
});
|
|
48749
49664
|
const timer = setTimeout(() => reject(new OpenError("launch_failed", "\u542F\u52A8\u8D85\u65F6")), LAUNCH_TIMEOUT_MS);
|
|
48750
49665
|
child.once("error", (error) => {
|
|
@@ -48785,7 +49700,7 @@ var init_open_target = __esm(() => {
|
|
|
48785
49700
|
});
|
|
48786
49701
|
|
|
48787
49702
|
// packages/web/src/open-api.ts
|
|
48788
|
-
import { realpath as realpath4, stat as
|
|
49703
|
+
import { realpath as realpath4, stat as stat7 } from "fs/promises";
|
|
48789
49704
|
import { isAbsolute as isAbsolute5 } from "path";
|
|
48790
49705
|
async function validateDirectory(value) {
|
|
48791
49706
|
if (typeof value !== "string" || value === "" || value.length > MAX_PATH_LENGTH)
|
|
@@ -48794,7 +49709,7 @@ async function validateDirectory(value) {
|
|
|
48794
49709
|
return;
|
|
48795
49710
|
try {
|
|
48796
49711
|
const resolved = await realpath4(value);
|
|
48797
|
-
return (await
|
|
49712
|
+
return (await stat7(resolved)).isDirectory() ? resolved : undefined;
|
|
48798
49713
|
} catch {
|
|
48799
49714
|
return;
|
|
48800
49715
|
}
|
|
@@ -48856,7 +49771,7 @@ var init_open_api = __esm(() => {
|
|
|
48856
49771
|
|
|
48857
49772
|
// packages/web/src/server.ts
|
|
48858
49773
|
import { createReadStream } from "fs";
|
|
48859
|
-
import { stat as
|
|
49774
|
+
import { stat as stat8 } from "fs/promises";
|
|
48860
49775
|
import { createServer as createServer3 } from "http";
|
|
48861
49776
|
import { dirname as dirname13, extname as extname2, resolve as resolve7, sep as sep3 } from "path";
|
|
48862
49777
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
@@ -48988,18 +49903,18 @@ async function serveStatic(pathname, method, response, staticDir) {
|
|
|
48988
49903
|
return;
|
|
48989
49904
|
}
|
|
48990
49905
|
let file = candidate;
|
|
48991
|
-
let stats = await
|
|
49906
|
+
let stats = await stat8(file).catch(() => {
|
|
48992
49907
|
return;
|
|
48993
49908
|
});
|
|
48994
49909
|
if (stats?.isDirectory()) {
|
|
48995
49910
|
file = resolve7(file, "index.html");
|
|
48996
|
-
stats = await
|
|
49911
|
+
stats = await stat8(file).catch(() => {
|
|
48997
49912
|
return;
|
|
48998
49913
|
});
|
|
48999
49914
|
}
|
|
49000
49915
|
if (stats === undefined && extname2(candidate) === "") {
|
|
49001
49916
|
file = resolve7(staticDir, "index.html");
|
|
49002
|
-
stats = await
|
|
49917
|
+
stats = await stat8(file).catch(() => {
|
|
49003
49918
|
return;
|
|
49004
49919
|
});
|
|
49005
49920
|
}
|
|
@@ -49154,11 +50069,13 @@ var USAGE2 = `\u7528\u6CD5: neko web [\u9009\u9879]
|
|
|
49154
50069
|
--server-id <id> \u7F51\u5173\u903B\u8F91\u8EAB\u4EFD\uFF08\u9ED8\u8BA4\u8BFB\u53D6/\u751F\u6210 <\u6570\u636E\u76EE\u5F55>/gateway-server-id\uFF09
|
|
49155
50070
|
--storage <path> SQLite \u5B58\u50A8\u6587\u4EF6\uFF08\u9ED8\u8BA4 ~/.neko/neko.sqlite\uFF09
|
|
49156
50071
|
--cwd <dir> \u4F1A\u8BDD\u5DE5\u5177\u7684\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09
|
|
49157
|
-
--model <ref> \u6A21\u578B\uFF0C\u683C\u5F0F provider/modelId\
|
|
50072
|
+
--model <ref> \u6A21\u578B\uFF0C\u683C\u5F0F provider/modelId\uFF08modelId \u53EF\u542B /\uFF0C\u9ED8\u8BA4 deepseek/deepseek-flash\uFF09
|
|
49158
50073
|
--no-open \u4E0D\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF08\u9002\u7528\u4E8E\u8FDC\u7A0B\u6216\u65E0\u684C\u9762\u73AF\u5883\uFF09
|
|
49159
50074
|
-h, --help \u663E\u793A\u6B64\u5E2E\u52A9
|
|
49160
50075
|
|
|
49161
|
-
\u53EF\u5728 Web \u8BBE\u7F6E\u9875\u914D\u7F6E Key\
|
|
50076
|
+
\u53EF\u5728 Web \u8BBE\u7F6E\u9875\u300C\u6A21\u578B\u670D\u52A1\u300D\u542F\u7528 provider\uFF08\u5982 OpenRouter\u3001OpenCode Go\uFF09\u5E76\u914D\u7F6E Key\uFF0C\u8054\u7F51\u641C\u7D22\u5728\u300C\u8054\u7F51\u641C\u7D22\u300D\u9875\u8BBE\u7F6E\uFF1B
|
|
50077
|
+
\u6A21\u578B\u9009\u62E9\u5668\u4E2D\u53EF\u628A\u53EF\u9009\u6A21\u578B\u300C\u8BBE\u4E3A\u9ED8\u8BA4\u300D\uFF0C\u4F18\u5148\u7EA7\u4F4E\u4E8E --model\u3001\u9AD8\u4E8E\u914D\u7F6E\u6587\u4EF6 defaultModel\uFF1B
|
|
50078
|
+
\u7F51\u5173\u5B50\u8FDB\u7A0B\u4E5F\u4F1A\u7EE7\u627F DEEPSEEK_API_KEY / OPENROUTER_API_KEY / OPENCODE_API_KEY \u7B49\u73AF\u5883\u53D8\u91CF\u3002`, server, shutdown;
|
|
49162
50079
|
var init_main2 = __esm(async () => {
|
|
49163
50080
|
init_src2();
|
|
49164
50081
|
init_open_browser();
|
|
@@ -49284,6 +50201,9 @@ function parseGatewayArgs(argv) {
|
|
|
49284
50201
|
options.configDir = next;
|
|
49285
50202
|
i++;
|
|
49286
50203
|
break;
|
|
50204
|
+
case "--stop-on-stdin":
|
|
50205
|
+
options.stopOnStdin = true;
|
|
50206
|
+
break;
|
|
49287
50207
|
default:
|
|
49288
50208
|
throw new Error(`\u672A\u77E5\u9009\u9879: ${arg}`);
|
|
49289
50209
|
}
|
|
@@ -49301,14 +50221,17 @@ var GATEWAY_USAGE = `\u7528\u6CD5: neko serve [\u9009\u9879]
|
|
|
49301
50221
|
--no-socket \u7981\u7528 Unix socket \u4F20\u8F93\uFF08\u4EC5 WebSocket\uFF09
|
|
49302
50222
|
--server-id <id> \u903B\u8F91\u670D\u52A1\u5668\u8EAB\u4EFD\uFF08\u9ED8\u8BA4\u8BFB\u53D6/\u751F\u6210 <\u6570\u636E\u76EE\u5F55>/gateway-server-id\uFF09
|
|
49303
50223
|
--cwd <dir> \u4F1A\u8BDD\u5DE5\u5177\u7684\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09
|
|
49304
|
-
--model <ref> \u6A21\u578B\uFF0C\u683C\u5F0F provider/modelId\
|
|
50224
|
+
--model <ref> \u6A21\u578B\uFF0C\u683C\u5F0F provider/modelId\uFF08modelId \u53EF\u542B /\uFF0C\u9ED8\u8BA4 deepseek/deepseek-flash\uFF09
|
|
49305
50225
|
--storage <path> SQLite \u5B58\u50A8\u6587\u4EF6\uFF08\u9ED8\u8BA4 ~/.neko/neko.sqlite\uFF09
|
|
49306
50226
|
--credentials <path> \u7528\u6237\u51ED\u636E\u6587\u4EF6\uFF08\u9ED8\u8BA4 ~/.neko/credentials.json\uFF09
|
|
49307
50227
|
--config <dir> neko.config.json \u67E5\u627E\u8D77\u59CB\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09
|
|
50228
|
+
--stop-on-stdin \u6807\u51C6\u8F93\u5165\u7ED3\u675F\u65F6\u5173\u505C\u7F51\u5173\uFF08\u4F9B Windows \u4E0A\u62C9\u8D77\u7F51\u5173\u7684\u524D\u7AEF\u4F7F\u7528\uFF09
|
|
49308
50229
|
-h, --help \u663E\u793A\u6B64\u5E2E\u52A9
|
|
49309
50230
|
|
|
49310
|
-
\u53EF\u901A\u8FC7\u524D\u7AEF\u8BBE\u7F6E\u6216
|
|
49311
|
-
\
|
|
50231
|
+
\u53EF\u901A\u8FC7\u524D\u7AEF\u8BBE\u7F6E\u6216\u73AF\u5883\u53D8\u91CF\uFF08DEEPSEEK_API_KEY / OPENROUTER_API_KEY / OPENCODE_API_KEY / TAVILY_API_KEY \u7B49\uFF09\u914D\u7F6E\u51ED\u636E\uFF1B
|
|
50232
|
+
\u9664 DeepSeek \u5916\u7684\u6A21\u578B provider \u9700\u5148\u5728\u524D\u7AEF\u542F\u7528\uFF0C--model \u6307\u5411\u672A\u542F\u7528\u7684 provider \u65F6\u63D0\u4EA4\u4F1A\u88AB\u62D2\u7EDD\uFF1B
|
|
50233
|
+
\u8054\u7F51\u641C\u7D22\u670D\u52A1\u53EF\u5728\u524D\u7AEF\u8BBE\u7F6E\uFF0C\u6216\u7528\u914D\u7F6E\u6587\u4EF6 webSearch.provider\uFF08tavily / deepseek\uFF0C\u9ED8\u8BA4 tavily\uFF09\uFF1B
|
|
50234
|
+
\u9ED8\u8BA4\u6A21\u578B\u8FD8\u53EF\u5728 TUI /model \u6216 Web \u6A21\u578B\u9009\u62E9\u5668\u4E2D\u300C\u8BBE\u4E3A\u9ED8\u8BA4\u300D\uFF0C\u4F18\u5148\u7EA7\u4F4E\u4E8E --model\u3001\u9AD8\u4E8E\u914D\u7F6E\u6587\u4EF6 defaultModel\u3002`;
|
|
49312
50235
|
|
|
49313
50236
|
// packages/gateway/src/main.ts
|
|
49314
50237
|
var argv, shutdown2, options, gateway;
|
|
@@ -49335,6 +50258,18 @@ var init_main3 = __esm(async () => {
|
|
|
49335
50258
|
console.log(` WebSocket: ${gateway.wsUrl}`);
|
|
49336
50259
|
console.log("\u6309 Ctrl+C \u505C\u6B62\u3002");
|
|
49337
50260
|
shutdown2.setCleanup(() => gateway.close());
|
|
50261
|
+
if (options.stopOnStdin === true) {
|
|
50262
|
+
let triggered = false;
|
|
50263
|
+
const onStdinEnd = () => {
|
|
50264
|
+
if (triggered)
|
|
50265
|
+
return;
|
|
50266
|
+
triggered = true;
|
|
50267
|
+
shutdown2.trigger("stdin \u5DF2\u5173\u95ED");
|
|
50268
|
+
};
|
|
50269
|
+
process.stdin.once("end", onStdinEnd);
|
|
50270
|
+
process.stdin.once("close", onStdinEnd);
|
|
50271
|
+
process.stdin.resume();
|
|
50272
|
+
}
|
|
49338
50273
|
});
|
|
49339
50274
|
|
|
49340
50275
|
// packages/cli/src/main.ts
|
|
@@ -49352,8 +50287,10 @@ var USAGE3 = `\u7528\u6CD5: neko <\u547D\u4EE4> [\u9009\u9879]
|
|
|
49352
50287
|
-h, --help \u663E\u793A\u6B64\u5E2E\u52A9
|
|
49353
50288
|
|
|
49354
50289
|
\u5404\u547D\u4EE4\u7684\u9009\u9879\u8BE6\u89C1 neko <\u547D\u4EE4> --help\u3002
|
|
49355
|
-
\u53EF\u901A\u8FC7 TUI /login\u3001Web \u8BBE\u7F6E\u9875\u6216
|
|
49356
|
-
\
|
|
50290
|
+
\u53EF\u901A\u8FC7 TUI /login\u3001Web \u8BBE\u7F6E\u9875\u6216\u73AF\u5883\u53D8\u91CF\uFF08DEEPSEEK_API_KEY / OPENROUTER_API_KEY / OPENCODE_API_KEY / TAVILY_API_KEY \u7B49\uFF09\u914D\u7F6E\u51ED\u636E\uFF1B
|
|
50291
|
+
\u9664 DeepSeek \u5916\u7684\u6A21\u578B provider\uFF08\u5982 OpenRouter\u3001OpenCode Go\uFF09\u9700\u5148\u5728 /login \u6216 Web \u8BBE\u7F6E\u9875\u300C\u6A21\u578B\u670D\u52A1\u300D\u542F\u7528\uFF0C--model \u683C\u5F0F\u4E3A provider/modelId\u3002
|
|
50292
|
+
\u8054\u7F51\u641C\u7D22\u670D\u52A1\uFF08\u9ED8\u8BA4 Tavily\uFF0C\u53EF\u6539\u9009 DeepSeek\uFF09\u53EF\u7528 TUI /search\u3001Web \u8BBE\u7F6E\u9875\u300C\u8054\u7F51\u641C\u7D22\u300D\u6216\u914D\u7F6E\u6587\u4EF6 webSearch.provider \u9009\u62E9\u3002
|
|
50293
|
+
\u9ED8\u8BA4\u6A21\u578B\u53EF\u5728 TUI /model \u6216 Web \u6A21\u578B\u9009\u62E9\u5668\u300C\u8BBE\u4E3A\u9ED8\u8BA4\u300D\uFF0C\u4F18\u5148\u7EA7\u4F4E\u4E8E --model\u3001\u9AD8\u4E8E\u914D\u7F6E\u6587\u4EF6 defaultModel\u3002`;
|
|
49357
50294
|
var [command2, ...rest] = process.argv.slice(2);
|
|
49358
50295
|
switch (command2) {
|
|
49359
50296
|
case "update": {
|