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/gateway.js
CHANGED
|
@@ -16497,14 +16497,17 @@ var GATEWAY_USAGE = `\u7528\u6CD5: neko serve [\u9009\u9879]
|
|
|
16497
16497
|
--no-socket \u7981\u7528 Unix socket \u4F20\u8F93\uFF08\u4EC5 WebSocket\uFF09
|
|
16498
16498
|
--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
|
|
16499
16499
|
--cwd <dir> \u4F1A\u8BDD\u5DE5\u5177\u7684\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09
|
|
16500
|
-
--model <ref> \u6A21\u578B\uFF0C\u683C\u5F0F provider/modelId\
|
|
16500
|
+
--model <ref> \u6A21\u578B\uFF0C\u683C\u5F0F provider/modelId\uFF08modelId \u53EF\u542B /\uFF0C\u9ED8\u8BA4 deepseek/deepseek-flash\uFF09
|
|
16501
16501
|
--storage <path> SQLite \u5B58\u50A8\u6587\u4EF6\uFF08\u9ED8\u8BA4 ~/.neko/neko.sqlite\uFF09
|
|
16502
16502
|
--credentials <path> \u7528\u6237\u51ED\u636E\u6587\u4EF6\uFF08\u9ED8\u8BA4 ~/.neko/credentials.json\uFF09
|
|
16503
16503
|
--config <dir> neko.config.json \u67E5\u627E\u8D77\u59CB\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09
|
|
16504
|
+
--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
|
|
16504
16505
|
-h, --help \u663E\u793A\u6B64\u5E2E\u52A9
|
|
16505
16506
|
|
|
16506
|
-
\u53EF\u901A\u8FC7\u524D\u7AEF\u8BBE\u7F6E\u6216
|
|
16507
|
-
\
|
|
16507
|
+
\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
|
|
16508
|
+
\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
|
|
16509
|
+
\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
|
|
16510
|
+
\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`;
|
|
16508
16511
|
function parseGatewayArgs(argv) {
|
|
16509
16512
|
let port;
|
|
16510
16513
|
const options = {};
|
|
@@ -16582,6 +16585,9 @@ function parseGatewayArgs(argv) {
|
|
|
16582
16585
|
options.configDir = next;
|
|
16583
16586
|
i++;
|
|
16584
16587
|
break;
|
|
16588
|
+
case "--stop-on-stdin":
|
|
16589
|
+
options.stopOnStdin = true;
|
|
16590
|
+
break;
|
|
16585
16591
|
default:
|
|
16586
16592
|
throw new Error(`\u672A\u77E5\u9009\u9879: ${arg}`);
|
|
16587
16593
|
}
|
|
@@ -16888,6 +16894,16 @@ function validate(config, source) {
|
|
|
16888
16894
|
throw new Error(`neko config: ${source}: "skills.marketUrl" must be a string.`);
|
|
16889
16895
|
}
|
|
16890
16896
|
}
|
|
16897
|
+
const shell = config.shell;
|
|
16898
|
+
if (shell !== undefined) {
|
|
16899
|
+
if (typeof shell !== "object" || shell === null || Array.isArray(shell)) {
|
|
16900
|
+
throw new Error(`neko config: ${source}: "shell" must be an object.`);
|
|
16901
|
+
}
|
|
16902
|
+
const { path } = shell;
|
|
16903
|
+
if (path !== undefined && typeof path !== "string") {
|
|
16904
|
+
throw new Error(`neko config: ${source}: "shell.path" must be a string.`);
|
|
16905
|
+
}
|
|
16906
|
+
}
|
|
16891
16907
|
const webSearch = config.webSearch;
|
|
16892
16908
|
if (webSearch !== undefined) {
|
|
16893
16909
|
if (typeof webSearch !== "object" || webSearch === null || Array.isArray(webSearch)) {
|
|
@@ -16907,30 +16923,113 @@ function validate(config, source) {
|
|
|
16907
16923
|
}
|
|
16908
16924
|
}
|
|
16909
16925
|
// packages/core/src/credentials.ts
|
|
16910
|
-
import { chmod, mkdir, open, readFile,
|
|
16926
|
+
import { chmod, mkdir, open, readFile, stat, unlink } from "fs/promises";
|
|
16911
16927
|
import { homedir as homedir2 } from "os";
|
|
16912
16928
|
import { dirname as dirname2, join as join2 } from "path";
|
|
16913
16929
|
|
|
16930
|
+
// packages/core/src/rename-with-retry.ts
|
|
16931
|
+
import { rename } from "fs/promises";
|
|
16932
|
+
var TRANSIENT = new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
16933
|
+
var FIRST_DELAY_MS = 10;
|
|
16934
|
+
var MAX_TOTAL_MS = 1000;
|
|
16935
|
+
async function renameWithRetry(from, to, deps = {}) {
|
|
16936
|
+
const doRename = deps.rename ?? rename;
|
|
16937
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
16938
|
+
let delay = FIRST_DELAY_MS;
|
|
16939
|
+
let waited = 0;
|
|
16940
|
+
for (;; ) {
|
|
16941
|
+
try {
|
|
16942
|
+
await doRename(from, to);
|
|
16943
|
+
return;
|
|
16944
|
+
} catch (error) {
|
|
16945
|
+
const code = error.code;
|
|
16946
|
+
if (typeof code !== "string" || !TRANSIENT.has(code) || waited >= MAX_TOTAL_MS)
|
|
16947
|
+
throw error;
|
|
16948
|
+
const step = Math.min(delay, MAX_TOTAL_MS - waited);
|
|
16949
|
+
await sleep(step);
|
|
16950
|
+
waited += step;
|
|
16951
|
+
delay *= 2;
|
|
16952
|
+
}
|
|
16953
|
+
}
|
|
16954
|
+
}
|
|
16955
|
+
|
|
16914
16956
|
// packages/core/src/credential-providers.ts
|
|
16957
|
+
var model = (id, name, envVar, signupUrl) => ({ id, kind: "model", name, envVar, signupUrl, keyless: false });
|
|
16958
|
+
var MODEL_PROVIDERS = [
|
|
16959
|
+
model("deepseek", "DeepSeek", "DEEPSEEK_API_KEY", "https://platform.deepseek.com/api_keys"),
|
|
16960
|
+
model("openrouter", "OpenRouter", "OPENROUTER_API_KEY", "https://openrouter.ai/keys"),
|
|
16961
|
+
model("opencode-go", "OpenCode Go", "OPENCODE_API_KEY", "https://opencode.ai/auth"),
|
|
16962
|
+
model("opencode", "OpenCode Zen", "OPENCODE_API_KEY", "https://opencode.ai/auth"),
|
|
16963
|
+
model("openai", "OpenAI", "OPENAI_API_KEY", "https://platform.openai.com/api-keys"),
|
|
16964
|
+
model("google", "Google", "GEMINI_API_KEY", "https://aistudio.google.com/apikey"),
|
|
16965
|
+
model("groq", "Groq", "GROQ_API_KEY", "https://console.groq.com/keys"),
|
|
16966
|
+
model("mistral", "Mistral", "MISTRAL_API_KEY", "https://console.mistral.ai/api-keys"),
|
|
16967
|
+
model("xai", "xAI", "XAI_API_KEY", "https://console.x.ai"),
|
|
16968
|
+
model("together", "Together", "TOGETHER_API_KEY", "https://api.together.ai/settings/api-keys"),
|
|
16969
|
+
model("fireworks", "Fireworks", "FIREWORKS_API_KEY", "https://fireworks.ai/account/api-keys"),
|
|
16970
|
+
model("cerebras", "Cerebras", "CEREBRAS_API_KEY", "https://cloud.cerebras.ai"),
|
|
16971
|
+
model("nvidia", "NVIDIA", "NVIDIA_API_KEY", "https://build.nvidia.com/settings/api-keys"),
|
|
16972
|
+
model("huggingface", "Hugging Face", "HF_TOKEN", "https://huggingface.co/settings/tokens"),
|
|
16973
|
+
model("vercel-ai-gateway", "Vercel AI Gateway", "AI_GATEWAY_API_KEY", "https://vercel.com/ai-gateway"),
|
|
16974
|
+
model("moonshotai", "Moonshot AI", "MOONSHOT_API_KEY", "https://platform.moonshot.ai/console/api-keys"),
|
|
16975
|
+
model("moonshotai-cn", "Moonshot AI CN", "MOONSHOT_API_KEY", "https://platform.moonshot.cn/console/api-keys"),
|
|
16976
|
+
model("minimax", "MiniMax", "MINIMAX_API_KEY", "https://platform.minimax.io"),
|
|
16977
|
+
model("minimax-cn", "MiniMax CN", "MINIMAX_CN_API_KEY", "https://platform.minimaxi.com"),
|
|
16978
|
+
model("zai", "Z.AI", "ZAI_API_KEY", "https://z.ai/manage-apikey/apikey-list"),
|
|
16979
|
+
model("zai-coding-cn", "Z.AI Coding CN", "ZAI_CODING_CN_API_KEY", "https://bigmodel.cn/usercenter/proj-mgmt/apikeys"),
|
|
16980
|
+
model("qwen-token-plan", "Qwen Token Plan", "QWEN_TOKEN_PLAN_API_KEY", "https://modelstudio.console.alibabacloud.com"),
|
|
16981
|
+
model("qwen-token-plan-cn", "Qwen Token Plan CN", "QWEN_TOKEN_PLAN_CN_API_KEY", "https://bailian.console.aliyun.com"),
|
|
16982
|
+
model("qwen-token-plan-individual", "Qwen Token Plan Individual", "QWEN_TOKEN_PLAN_API_KEY", "https://modelstudio.console.alibabacloud.com"),
|
|
16983
|
+
model("xiaomi", "Xiaomi MiMo", "XIAOMI_API_KEY", "https://platform.xiaomimimo.com"),
|
|
16984
|
+
model("xiaomi-token-plan-cn", "Xiaomi Token Plan CN", "XIAOMI_TOKEN_PLAN_CN_API_KEY", "https://platform.xiaomimimo.com"),
|
|
16985
|
+
model("xiaomi-token-plan-ams", "Xiaomi Token Plan AMS", "XIAOMI_TOKEN_PLAN_AMS_API_KEY", "https://platform.xiaomimimo.com"),
|
|
16986
|
+
model("xiaomi-token-plan-sgp", "Xiaomi Token Plan SGP", "XIAOMI_TOKEN_PLAN_SGP_API_KEY", "https://platform.xiaomimimo.com"),
|
|
16987
|
+
model("baseten", "Baseten", "BASETEN_API_KEY", "https://app.baseten.co/settings/api_keys"),
|
|
16988
|
+
model("ant-ling", "Ant Ling", "ANT_LING_API_KEY", "https://ling.tbox.cn"),
|
|
16989
|
+
model("kimi-coding", "Kimi For Coding", "KIMI_API_KEY", "https://www.kimi.com/code/console"),
|
|
16990
|
+
model("meta", "Meta", "META_API_KEY", "https://llama.developer.meta.com")
|
|
16991
|
+
];
|
|
16992
|
+
var SEARCH_CREDENTIAL_PROVIDERS = [
|
|
16993
|
+
{
|
|
16994
|
+
id: "tavily",
|
|
16995
|
+
kind: "search",
|
|
16996
|
+
name: "Tavily",
|
|
16997
|
+
envVar: "TAVILY_API_KEY",
|
|
16998
|
+
signupUrl: "https://app.tavily.com",
|
|
16999
|
+
keyless: true
|
|
17000
|
+
}
|
|
17001
|
+
];
|
|
16915
17002
|
var CREDENTIAL_PROVIDERS = [
|
|
16916
|
-
|
|
16917
|
-
|
|
17003
|
+
...MODEL_PROVIDERS,
|
|
17004
|
+
...SEARCH_CREDENTIAL_PROVIDERS
|
|
16918
17005
|
];
|
|
17006
|
+
var DEFAULT_ENABLED_PROVIDERS = ["deepseek"];
|
|
17007
|
+
var LOCKED_PROVIDERS = ["deepseek"];
|
|
17008
|
+
function isModelProvider(id) {
|
|
17009
|
+
return MODEL_PROVIDERS.some((item) => item.id === id);
|
|
17010
|
+
}
|
|
16919
17011
|
function credentialProvider(id) {
|
|
16920
17012
|
const spec = CREDENTIAL_PROVIDERS.find((item) => item.id === id);
|
|
16921
17013
|
if (!spec)
|
|
16922
17014
|
throw new Error(`Unknown credential provider: ${id}`);
|
|
16923
17015
|
return spec;
|
|
16924
17016
|
}
|
|
17017
|
+
function normalizeEnabledProviders(saved) {
|
|
17018
|
+
const wanted = new Set([...LOCKED_PROVIDERS, ...saved ?? DEFAULT_ENABLED_PROVIDERS]);
|
|
17019
|
+
return MODEL_PROVIDERS.map((item) => item.id).filter((id) => wanted.has(id));
|
|
17020
|
+
}
|
|
16925
17021
|
|
|
16926
17022
|
// packages/core/src/credentials.ts
|
|
16927
17023
|
import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
|
|
17024
|
+
var STALE_LOCK_MS = 30000;
|
|
16928
17025
|
var DEFAULT_CREDENTIALS_PATH = join2(homedir2(), ".neko", "credentials.json");
|
|
16929
17026
|
|
|
16930
17027
|
class FileCredentialStore {
|
|
16931
17028
|
path;
|
|
16932
|
-
|
|
17029
|
+
#renameDeps;
|
|
17030
|
+
constructor(path = DEFAULT_CREDENTIALS_PATH, renameDeps = {}) {
|
|
16933
17031
|
this.path = path;
|
|
17032
|
+
this.#renameDeps = renameDeps;
|
|
16934
17033
|
}
|
|
16935
17034
|
async#read() {
|
|
16936
17035
|
try {
|
|
@@ -16960,6 +17059,7 @@ class FileCredentialStore {
|
|
|
16960
17059
|
await mkdir(dirname2(this.path), { recursive: true, mode: 448 });
|
|
16961
17060
|
const lockPath = `${this.path}.lock`;
|
|
16962
17061
|
let lock;
|
|
17062
|
+
let reclaimed = false;
|
|
16963
17063
|
for (let attempt = 0;; attempt++) {
|
|
16964
17064
|
try {
|
|
16965
17065
|
lock = await open(lockPath, "wx", 384);
|
|
@@ -16967,6 +17067,16 @@ class FileCredentialStore {
|
|
|
16967
17067
|
} catch (error) {
|
|
16968
17068
|
if (error.code !== "EEXIST" || attempt >= 100)
|
|
16969
17069
|
throw new Error("Credential storage is busy or unavailable.");
|
|
17070
|
+
if (!reclaimed) {
|
|
17071
|
+
const info = await stat(lockPath).catch(() => {
|
|
17072
|
+
return;
|
|
17073
|
+
});
|
|
17074
|
+
if (info && Date.now() - info.mtimeMs > STALE_LOCK_MS) {
|
|
17075
|
+
reclaimed = true;
|
|
17076
|
+
await unlink(lockPath).catch(() => {});
|
|
17077
|
+
continue;
|
|
17078
|
+
}
|
|
17079
|
+
}
|
|
16970
17080
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
16971
17081
|
}
|
|
16972
17082
|
}
|
|
@@ -16988,7 +17098,7 @@ class FileCredentialStore {
|
|
|
16988
17098
|
} finally {
|
|
16989
17099
|
await file.close();
|
|
16990
17100
|
}
|
|
16991
|
-
await
|
|
17101
|
+
await renameWithRetry(temporary, this.path, this.#renameDeps);
|
|
16992
17102
|
return result;
|
|
16993
17103
|
} catch (error) {
|
|
16994
17104
|
if (callbackFailed)
|
|
@@ -17815,7 +17925,7 @@ var structuredOutputSchema = Type.Union([
|
|
|
17815
17925
|
next: Type.Union([Type.Integer(), Type.Null()])
|
|
17816
17926
|
}, { additionalProperties: false })
|
|
17817
17927
|
]);
|
|
17818
|
-
function createSubagentExtension(prepare, discard) {
|
|
17928
|
+
function createSubagentExtension(prepare, discard, guard) {
|
|
17819
17929
|
const finish = async (tx, input, status, report) => {
|
|
17820
17930
|
const round = (await tx.doc(DelegationsDoc, input.parentId)).items.find((r) => r.taskId === input.taskId);
|
|
17821
17931
|
round.status = status;
|
|
@@ -17959,6 +18069,8 @@ function createSubagentExtension(prepare, discard) {
|
|
|
17959
18069
|
if (!args.task.trim() || !args.title.trim() || Buffer.byteLength(args.task) > 32768)
|
|
17960
18070
|
throw new Error("Task/title must be nonempty; task limit is 32 KiB.");
|
|
17961
18071
|
const agent = await api.agent(context);
|
|
18072
|
+
if (agent.model)
|
|
18073
|
+
await guard?.(agent.model);
|
|
17962
18074
|
const round = await api.commit(async (tx) => {
|
|
17963
18075
|
const rounds = await refreshDelegations(tx, api.conversationId);
|
|
17964
18076
|
const receipt = rounds.find((r) => r.taskId === api.taskId);
|
|
@@ -18868,7 +18980,7 @@ import { homedir as homedir4 } from "os";
|
|
|
18868
18980
|
import { join as join5 } from "path";
|
|
18869
18981
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT3 } from "@earendil-works/chord/context";
|
|
18870
18982
|
import { clampThinkingLevel, createModels, getSupportedThinkingLevels as getSupportedThinkingLevels2 } from "@earendil-works/pi-ai/models";
|
|
18871
|
-
import {
|
|
18983
|
+
import { builtinProviders } from "@earendil-works/pi-ai/providers/all";
|
|
18872
18984
|
import {
|
|
18873
18985
|
AssistantEntry as AssistantEntry2,
|
|
18874
18986
|
createRegistry,
|
|
@@ -19011,7 +19123,7 @@ var SearchText = defineTool2({
|
|
|
19011
19123
|
complete = false;
|
|
19012
19124
|
continue;
|
|
19013
19125
|
}
|
|
19014
|
-
const lines = new TextDecoder("utf-8", { fatal: true }).decode(data).split(/\r?\n/);
|
|
19126
|
+
const lines = new TextDecoder("utf-8", { fatal: true }).decode(data).replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
19015
19127
|
for (let i = 0;i < lines.length; i++) {
|
|
19016
19128
|
ctx.abortSignal?.throwIfAborted();
|
|
19017
19129
|
const value = lines[i];
|
|
@@ -19444,8 +19556,11 @@ class JobManager {
|
|
|
19444
19556
|
outcome = { status: "failed", exitCode: null, reason: "timeout" };
|
|
19445
19557
|
else if (result.error.code === "aborted")
|
|
19446
19558
|
outcome = { status: "stopped", exitCode: null, reason: "aborted" };
|
|
19447
|
-
else
|
|
19559
|
+
else {
|
|
19560
|
+
entry.buffer.append(`${result.error.message}
|
|
19561
|
+
`);
|
|
19448
19562
|
outcome = { status: "failed", exitCode: null, reason: "spawn-error" };
|
|
19563
|
+
}
|
|
19449
19564
|
}
|
|
19450
19565
|
if (this.#closing) {
|
|
19451
19566
|
await new Promise((resolve) => {
|
|
@@ -19842,10 +19957,15 @@ var TOOL_GUIDELINES = {
|
|
|
19842
19957
|
]
|
|
19843
19958
|
};
|
|
19844
19959
|
var GENERIC_RULES = ["Be concise in your responses", "Show file paths clearly when working with files"];
|
|
19845
|
-
var
|
|
19960
|
+
var 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.";
|
|
19961
|
+
var toolRules = (name, platform) => {
|
|
19962
|
+
const rules = TOOL_GUIDELINES[name] ?? [];
|
|
19963
|
+
return platform === "win32" && (name === "bash" || name === "job") ? [...rules, WINDOWS_SHELL_RULE] : rules;
|
|
19964
|
+
};
|
|
19965
|
+
var buildRules = (toolNames, platform) => {
|
|
19846
19966
|
const rules = [];
|
|
19847
19967
|
const seen = new Set;
|
|
19848
|
-
for (const rule of [...toolNames.flatMap((name) =>
|
|
19968
|
+
for (const rule of [...toolNames.flatMap((name) => toolRules(name, platform)), ...GENERIC_RULES]) {
|
|
19849
19969
|
if (seen.has(rule))
|
|
19850
19970
|
continue;
|
|
19851
19971
|
seen.add(rule);
|
|
@@ -19862,7 +19982,7 @@ ${content}
|
|
|
19862
19982
|
].join(`
|
|
19863
19983
|
|
|
19864
19984
|
`);
|
|
19865
|
-
var createNekoPrompt = (loadResources) => {
|
|
19985
|
+
var createNekoPrompt = (loadResources, platform = process.platform) => {
|
|
19866
19986
|
const built = new WeakMap;
|
|
19867
19987
|
const build = async (input, context) => {
|
|
19868
19988
|
const cwd = input.agent.cwd;
|
|
@@ -19870,7 +19990,7 @@ var createNekoPrompt = (loadResources) => {
|
|
|
19870
19990
|
const resources = cwd ? await loadResources(input.conversationId, cwd) : { contextFiles: [], skills: [] };
|
|
19871
19991
|
const sections = {
|
|
19872
19992
|
preamble: role ? ROLE_PROMPTS[role] + SUBAGENT_DELIVERY : PREAMBLE,
|
|
19873
|
-
rules: buildRules(input.agent.tools.map((tool) => tool.name)),
|
|
19993
|
+
rules: buildRules(input.agent.tools.map((tool) => tool.name), platform),
|
|
19874
19994
|
project_context: renderProjectContext(resources.contextFiles),
|
|
19875
19995
|
skills: formatSkillsForPrompt(resources.skills) || undefined,
|
|
19876
19996
|
cwd: cwd?.replace(/\\/g, "/")
|
|
@@ -19918,7 +20038,7 @@ var readResources = async (workspace, home, builtinDir, report, invocationName)
|
|
|
19918
20038
|
const contextFiles = [];
|
|
19919
20039
|
for (const path of [userAgentsFile(home), workspaceAgentsFile(workspace)]) {
|
|
19920
20040
|
try {
|
|
19921
|
-
const content = await readFile3(path, "utf8");
|
|
20041
|
+
const content = (await readFile3(path, "utf8")).replace(/^\uFEFF/, "");
|
|
19922
20042
|
if (Buffer.byteLength(content, "utf8") > 64 * 1024)
|
|
19923
20043
|
report({
|
|
19924
20044
|
path,
|
|
@@ -20443,7 +20563,7 @@ function mergeUsage(base, extra) {
|
|
|
20443
20563
|
}
|
|
20444
20564
|
return result;
|
|
20445
20565
|
}
|
|
20446
|
-
function namingTask() {
|
|
20566
|
+
function namingTask(canUse) {
|
|
20447
20567
|
let active = 0;
|
|
20448
20568
|
const waits = new Set;
|
|
20449
20569
|
return defineTask3({
|
|
@@ -20480,7 +20600,7 @@ function namingTask() {
|
|
|
20480
20600
|
try {
|
|
20481
20601
|
const saved = await runtime.snapshot(SessionMetadataDoc, runtime.conversationId, context);
|
|
20482
20602
|
const model = saved?.model && runtime.models.getModel(saved.model.provider, saved.model.modelId);
|
|
20483
|
-
if (!saved || !model || saved.attempts >= 2 || !getSupportedThinkingLevels(model).includes("off")) {
|
|
20603
|
+
if (!saved || !model || saved.attempts >= 2 || !getSupportedThinkingLevels(model).includes("off") || !await canUse(model.provider, model.id)) {
|
|
20484
20604
|
await runtime.commit(async (tx) => {
|
|
20485
20605
|
(await tx.doc(SessionMetadataDoc, runtime.conversationId)).naming = "fallback";
|
|
20486
20606
|
return { status: "terminal", outcome: { status: "completed", result: null } };
|
|
@@ -20560,12 +20680,13 @@ class SessionMetadataManager {
|
|
|
20560
20680
|
#known = new Set;
|
|
20561
20681
|
#unsubscribe;
|
|
20562
20682
|
#live = new Map;
|
|
20563
|
-
task
|
|
20683
|
+
task;
|
|
20564
20684
|
harness;
|
|
20565
20685
|
storage;
|
|
20566
20686
|
context;
|
|
20567
20687
|
report;
|
|
20568
|
-
constructor(harness, storage, context, registry, report = console.error) {
|
|
20688
|
+
constructor(harness, storage, context, registry, report = console.error, canUseModel = async () => true) {
|
|
20689
|
+
this.task = namingTask(canUseModel);
|
|
20569
20690
|
this.harness = harness;
|
|
20570
20691
|
this.storage = storage;
|
|
20571
20692
|
this.context = context;
|
|
@@ -21374,11 +21495,152 @@ function createTavilyProvider(resolveKey, fetchImpl = fetch) {
|
|
|
21374
21495
|
};
|
|
21375
21496
|
}
|
|
21376
21497
|
|
|
21498
|
+
// packages/core/src/windows-shell.ts
|
|
21499
|
+
import { spawnSync } from "child_process";
|
|
21500
|
+
import { statSync as statSync2 } from "fs";
|
|
21501
|
+
import { win32 } from "path";
|
|
21502
|
+
function isWslBashPath(path) {
|
|
21503
|
+
const normalized = path.replace(/\//g, "\\").toLowerCase();
|
|
21504
|
+
return /^[a-z]:\\windows\\(?:system32|sysnative)\\bash\.exe$/.test(normalized) || /\\microsoft\\windowsapps\\(?:[^\\]+\\)*bash\.exe$/.test(normalized);
|
|
21505
|
+
}
|
|
21506
|
+
function envValue(env, name) {
|
|
21507
|
+
const wanted = name.toLowerCase();
|
|
21508
|
+
for (const [key, value] of Object.entries(env))
|
|
21509
|
+
if (key.toLowerCase() === wanted && value)
|
|
21510
|
+
return value;
|
|
21511
|
+
return;
|
|
21512
|
+
}
|
|
21513
|
+
function bashBesideGit(gitExe) {
|
|
21514
|
+
const dir = win32.dirname(gitExe);
|
|
21515
|
+
const parent = win32.dirname(dir);
|
|
21516
|
+
const name = win32.basename(dir).toLowerCase();
|
|
21517
|
+
if (name === "cmd")
|
|
21518
|
+
return win32.join(parent, "bin", "bash.exe");
|
|
21519
|
+
if (name === "bin") {
|
|
21520
|
+
const root = win32.basename(parent).toLowerCase() === "mingw64" ? win32.dirname(parent) : parent;
|
|
21521
|
+
return win32.join(root, "bin", "bash.exe");
|
|
21522
|
+
}
|
|
21523
|
+
return;
|
|
21524
|
+
}
|
|
21525
|
+
function configProblem(path, kind) {
|
|
21526
|
+
if (isWslBashPath(path))
|
|
21527
|
+
return "it is WSL's bash, which is not supported (commands must run in Git Bash)";
|
|
21528
|
+
if (kind === "missing")
|
|
21529
|
+
return "the file does not exist";
|
|
21530
|
+
if (kind === "directory")
|
|
21531
|
+
return "it is a directory, not bash.exe";
|
|
21532
|
+
return;
|
|
21533
|
+
}
|
|
21534
|
+
function resolveWindowsShell(deps, configPath) {
|
|
21535
|
+
if (configPath !== undefined && configPath !== "") {
|
|
21536
|
+
const problem = configProblem(configPath, deps.kind(configPath));
|
|
21537
|
+
if (problem !== undefined) {
|
|
21538
|
+
return {
|
|
21539
|
+
kind: "missing",
|
|
21540
|
+
reason: "invalid_config",
|
|
21541
|
+
detail: `shell.path "${configPath}" is invalid: ${problem}.`,
|
|
21542
|
+
searched: [configPath]
|
|
21543
|
+
};
|
|
21544
|
+
}
|
|
21545
|
+
return { kind: "found", path: configPath, source: "config" };
|
|
21546
|
+
}
|
|
21547
|
+
const searched = [];
|
|
21548
|
+
const check = (path) => {
|
|
21549
|
+
if (!searched.includes(path))
|
|
21550
|
+
searched.push(path);
|
|
21551
|
+
return !isWslBashPath(path) && deps.kind(path) === "file";
|
|
21552
|
+
};
|
|
21553
|
+
const roots = [
|
|
21554
|
+
envValue(deps.env, "ProgramFiles"),
|
|
21555
|
+
envValue(deps.env, "ProgramFiles(x86)"),
|
|
21556
|
+
(() => {
|
|
21557
|
+
const local = envValue(deps.env, "LOCALAPPDATA");
|
|
21558
|
+
return local === undefined ? undefined : win32.join(local, "Programs");
|
|
21559
|
+
})()
|
|
21560
|
+
];
|
|
21561
|
+
for (const root of roots) {
|
|
21562
|
+
if (root === undefined)
|
|
21563
|
+
continue;
|
|
21564
|
+
const candidate = win32.join(root, "Git", "bin", "bash.exe");
|
|
21565
|
+
if (check(candidate))
|
|
21566
|
+
return { kind: "found", path: candidate, source: "auto" };
|
|
21567
|
+
}
|
|
21568
|
+
for (const gitExe of deps.where("git.exe")) {
|
|
21569
|
+
const candidate = bashBesideGit(gitExe);
|
|
21570
|
+
if (candidate !== undefined && check(candidate))
|
|
21571
|
+
return { kind: "found", path: candidate, source: "auto" };
|
|
21572
|
+
}
|
|
21573
|
+
for (const candidate of deps.where("bash.exe")) {
|
|
21574
|
+
if (check(candidate))
|
|
21575
|
+
return { kind: "found", path: candidate, source: "auto" };
|
|
21576
|
+
}
|
|
21577
|
+
return { kind: "missing", reason: "not_found", detail: "No Git Bash was found.", searched };
|
|
21578
|
+
}
|
|
21579
|
+
function summarizeWindowsShell(status) {
|
|
21580
|
+
return status.kind === "found" ? { status: "ready", path: status.path, source: status.source } : { status: "missing", reason: status.reason, detail: status.detail };
|
|
21581
|
+
}
|
|
21582
|
+
function windowsShellMessage(status) {
|
|
21583
|
+
const lines = status.reason === "invalid_config" ? [
|
|
21584
|
+
`Cannot run shell commands: ${status.detail}`,
|
|
21585
|
+
"Fix shell.path in neko.config.json, or remove it to auto-detect Git Bash."
|
|
21586
|
+
] : [
|
|
21587
|
+
"Cannot run shell commands: Git Bash was not found. neko runs commands in Git for Windows' bash, not PowerShell, cmd or WSL.",
|
|
21588
|
+
"Install Git for Windows (https://git-scm.com/download/win), or set shell.path in neko.config.json to its bash.exe."
|
|
21589
|
+
];
|
|
21590
|
+
if (status.searched.length > 0)
|
|
21591
|
+
lines.push("Checked:", ...status.searched.map((path) => ` ${path}`));
|
|
21592
|
+
lines.push("File tools (read, write, edit) still work.");
|
|
21593
|
+
return lines.join(`
|
|
21594
|
+
`);
|
|
21595
|
+
}
|
|
21596
|
+
function systemWindowsShellDeps() {
|
|
21597
|
+
return {
|
|
21598
|
+
env: process.env,
|
|
21599
|
+
kind(path) {
|
|
21600
|
+
try {
|
|
21601
|
+
return statSync2(path).isDirectory() ? "directory" : "file";
|
|
21602
|
+
} catch {
|
|
21603
|
+
return "missing";
|
|
21604
|
+
}
|
|
21605
|
+
},
|
|
21606
|
+
where(program) {
|
|
21607
|
+
const result = spawnSync("where", [program], { encoding: "utf8", timeout: 5000, windowsHide: true });
|
|
21608
|
+
if (result.status !== 0 || typeof result.stdout !== "string")
|
|
21609
|
+
return [];
|
|
21610
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== "");
|
|
21611
|
+
}
|
|
21612
|
+
};
|
|
21613
|
+
}
|
|
21614
|
+
var WINDOWS_SHELL_RETRY_MS = 30000;
|
|
21615
|
+
function createWindowsShellResolver(options) {
|
|
21616
|
+
const deps = options?.deps ?? systemWindowsShellDeps();
|
|
21617
|
+
const now = options?.now ?? Date.now;
|
|
21618
|
+
let cached;
|
|
21619
|
+
return {
|
|
21620
|
+
status(call) {
|
|
21621
|
+
if (cached !== undefined && call?.refresh !== true) {
|
|
21622
|
+
if (cached.status.kind === "found") {
|
|
21623
|
+
if (deps.kind(cached.status.path) === "file")
|
|
21624
|
+
return cached.status;
|
|
21625
|
+
} else if (now() - cached.at < WINDOWS_SHELL_RETRY_MS) {
|
|
21626
|
+
return cached.status;
|
|
21627
|
+
}
|
|
21628
|
+
}
|
|
21629
|
+
cached = { status: resolveWindowsShell(deps, options?.configPath), at: now() };
|
|
21630
|
+
return cached.status;
|
|
21631
|
+
}
|
|
21632
|
+
};
|
|
21633
|
+
}
|
|
21634
|
+
|
|
21377
21635
|
// packages/core/src/workspace-files.ts
|
|
21378
|
-
import { open as open2, realpath as realpath2, stat } from "fs/promises";
|
|
21636
|
+
import { open as open2, realpath as realpath2, stat as stat2 } from "fs/promises";
|
|
21379
21637
|
import { basename as basename2, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve4, sep as sep2 } from "path";
|
|
21380
21638
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT2, withAbortSignal as withAbortSignal3 } from "@earendil-works/chord/context";
|
|
21381
|
-
import {
|
|
21639
|
+
import {
|
|
21640
|
+
ExecutionError,
|
|
21641
|
+
err,
|
|
21642
|
+
getOrThrow as getOrThrow2
|
|
21643
|
+
} from "@earendil-works/pi-durable/env";
|
|
21382
21644
|
import { Connection, LocalExecutionEnv, localDaemon } from "@earendil-works/pi-env";
|
|
21383
21645
|
var BROWSE_LIMITS = { children: 1000, readBytes: 512 * 1024 };
|
|
21384
21646
|
var ENV_TEMPLATE = /\.(example|sample|template)$/;
|
|
@@ -21416,6 +21678,19 @@ var FILE_CACHE_LIMITS = {
|
|
|
21416
21678
|
concurrent: 2
|
|
21417
21679
|
};
|
|
21418
21680
|
|
|
21681
|
+
class ShellUnavailableEnv extends LocalExecutionEnv {
|
|
21682
|
+
#message;
|
|
21683
|
+
constructor(options, message) {
|
|
21684
|
+
super(options);
|
|
21685
|
+
this.#message = message;
|
|
21686
|
+
}
|
|
21687
|
+
async exec(command, options, context) {
|
|
21688
|
+
if (typeof command === "string")
|
|
21689
|
+
return err(new ExecutionError("shell_unavailable", this.#message));
|
|
21690
|
+
return super.exec(command, options, context);
|
|
21691
|
+
}
|
|
21692
|
+
}
|
|
21693
|
+
|
|
21419
21694
|
class WorkspaceFiles {
|
|
21420
21695
|
#connection;
|
|
21421
21696
|
#starting;
|
|
@@ -21430,8 +21705,10 @@ class WorkspaceFiles {
|
|
|
21430
21705
|
#waiting = new Set;
|
|
21431
21706
|
#timer;
|
|
21432
21707
|
limits;
|
|
21433
|
-
|
|
21708
|
+
#shell;
|
|
21709
|
+
constructor(limits = FILE_CACHE_LIMITS, shell) {
|
|
21434
21710
|
this.limits = limits;
|
|
21711
|
+
this.#shell = shell;
|
|
21435
21712
|
this.#timer = setInterval(() => {
|
|
21436
21713
|
for (const [key, item] of this.#cache)
|
|
21437
21714
|
if (!item.waiters && Date.now() - item.used >= this.limits.idleMs)
|
|
@@ -21450,20 +21727,31 @@ class WorkspaceFiles {
|
|
|
21450
21727
|
queued: this.#waiting.size
|
|
21451
21728
|
};
|
|
21452
21729
|
}
|
|
21453
|
-
|
|
21730
|
+
shellStatus(options) {
|
|
21731
|
+
return this.#shell?.status(options);
|
|
21732
|
+
}
|
|
21733
|
+
#open() {
|
|
21454
21734
|
if (this.#closed)
|
|
21455
21735
|
throw new Error("File environment closed");
|
|
21456
21736
|
this.#connection ??= new Connection({ command: [localDaemon()] });
|
|
21457
|
-
return
|
|
21458
|
-
|
|
21737
|
+
return this.#connection;
|
|
21738
|
+
}
|
|
21739
|
+
env(cwd) {
|
|
21740
|
+
const options = {
|
|
21741
|
+
connection: this.#open(),
|
|
21459
21742
|
cwd,
|
|
21460
21743
|
...process.platform === "win32" ? { shellEnv: { PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" } } : {}
|
|
21461
|
-
}
|
|
21744
|
+
};
|
|
21745
|
+
const shell = this.#shell?.status();
|
|
21746
|
+
if (shell === undefined)
|
|
21747
|
+
return new LocalExecutionEnv(options);
|
|
21748
|
+
if (shell.kind === "found")
|
|
21749
|
+
return new LocalExecutionEnv({ ...options, shellPath: shell.path });
|
|
21750
|
+
return new ShellUnavailableEnv(options, windowsShellMessage(shell));
|
|
21462
21751
|
}
|
|
21463
21752
|
async#ready() {
|
|
21464
21753
|
if (!this.#starting) {
|
|
21465
|
-
this
|
|
21466
|
-
const connection = this.#connection;
|
|
21754
|
+
const connection = this.#open();
|
|
21467
21755
|
let timer;
|
|
21468
21756
|
const work = Promise.race([
|
|
21469
21757
|
connection.info().then(() => connection.session()),
|
|
@@ -21540,7 +21828,7 @@ class WorkspaceFiles {
|
|
|
21540
21828
|
{ path: item.root, recursive: true, exclude: { names: [".git"] } }
|
|
21541
21829
|
];
|
|
21542
21830
|
const git = resolve4(item.root, ".git");
|
|
21543
|
-
if (await
|
|
21831
|
+
if (await stat2(git).then(() => true, () => false))
|
|
21544
21832
|
targets.push({ path: git, recursive: true, exclude: { names: ["objects", "logs", "refs"] } });
|
|
21545
21833
|
const result = await env.watch(targets, (change) => {
|
|
21546
21834
|
for (const user of current.users) {
|
|
@@ -21648,7 +21936,7 @@ class WorkspaceFiles {
|
|
|
21648
21936
|
const slash = query.lastIndexOf("/"), prefix = slash < 0 ? "" : query.slice(0, slash + 1), needle = query.slice(slash + 1);
|
|
21649
21937
|
root = await realpath2(root);
|
|
21650
21938
|
const base = await workspaceTarget(root, prefix);
|
|
21651
|
-
if (!(await
|
|
21939
|
+
if (!(await stat2(base)).isDirectory())
|
|
21652
21940
|
throw new Error("Workspace directory is unavailable.");
|
|
21653
21941
|
return this.#withListing(root, base, signal, async (listing) => {
|
|
21654
21942
|
const pattern = new RegExp([...needle].map((c) => c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*"), /[A-Z]/.test(needle) ? "" : "i");
|
|
@@ -21678,7 +21966,7 @@ class WorkspaceFiles {
|
|
|
21678
21966
|
const clean = dir === "" ? "" : relativeQuery(dir).replace(/\/+$/, "");
|
|
21679
21967
|
root = await realpath2(root);
|
|
21680
21968
|
const target = await workspaceTarget(root, clean);
|
|
21681
|
-
if (!(await
|
|
21969
|
+
if (!(await stat2(target)).isDirectory())
|
|
21682
21970
|
throw new Error("Not a directory.");
|
|
21683
21971
|
const prefix = clean === "" ? "" : `${clean}/`;
|
|
21684
21972
|
return this.#withListing(root, root, signal, async (listing) => {
|
|
@@ -21720,7 +22008,7 @@ class WorkspaceFiles {
|
|
|
21720
22008
|
throw new Error("File not found.");
|
|
21721
22009
|
throw error;
|
|
21722
22010
|
}
|
|
21723
|
-
const info = await
|
|
22011
|
+
const info = await stat2(target);
|
|
21724
22012
|
if (!info.isFile())
|
|
21725
22013
|
throw new Error("Not a file.");
|
|
21726
22014
|
const base = { path: clean, size: info.size, mtimeMs: info.mtimeMs };
|
|
@@ -21852,6 +22140,22 @@ var SearchSettingsDoc = defineDoc7({
|
|
|
21852
22140
|
fork: "initial",
|
|
21853
22141
|
initial: () => ({ provider: null })
|
|
21854
22142
|
});
|
|
22143
|
+
var ProviderSettingsDoc = defineDoc7({
|
|
22144
|
+
kind: "app.neko.provider-settings",
|
|
22145
|
+
version: 1,
|
|
22146
|
+
scope: "conversation",
|
|
22147
|
+
history: "latest",
|
|
22148
|
+
fork: "initial",
|
|
22149
|
+
initial: () => ({ enabled: null })
|
|
22150
|
+
});
|
|
22151
|
+
var DefaultModelDoc = defineDoc7({
|
|
22152
|
+
kind: "app.neko.default-model",
|
|
22153
|
+
version: 1,
|
|
22154
|
+
scope: "conversation",
|
|
22155
|
+
history: "latest",
|
|
22156
|
+
fork: "initial",
|
|
22157
|
+
initial: () => ({ model: null })
|
|
22158
|
+
});
|
|
21855
22159
|
var ContextSettingsDoc = defineDoc7({
|
|
21856
22160
|
kind: "app.neko.context-settings",
|
|
21857
22161
|
version: 1,
|
|
@@ -21872,6 +22176,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
21872
22176
|
const context = options.context ?? BACKGROUND_CONTEXT3;
|
|
21873
22177
|
const defaultCwd = normalizeWorkspace(options.cwd);
|
|
21874
22178
|
const model = options.model ?? DEFAULT_MODEL;
|
|
22179
|
+
const modelOverride = options.modelOverride;
|
|
21875
22180
|
const providers = options.providers ?? [];
|
|
21876
22181
|
const credentials = options.credentials ?? (options.storage === "memory" && !options.credentialsPath ? new InMemoryCredentialStore : new FileCredentialStore(options.credentialsPath));
|
|
21877
22182
|
const runtimeKeys = new Map;
|
|
@@ -21891,16 +22196,47 @@ async function createNekoRuntime(options = {}) {
|
|
|
21891
22196
|
delete: (id) => credentials.delete(id)
|
|
21892
22197
|
} : credentials;
|
|
21893
22198
|
const models = createModels({ credentials: effectiveCredentials });
|
|
21894
|
-
|
|
22199
|
+
for (const provider of builtinProviders())
|
|
22200
|
+
if (isModelProvider(provider.id))
|
|
22201
|
+
models.setProvider(provider);
|
|
21895
22202
|
for (const provider of providers)
|
|
21896
22203
|
models.setProvider(provider);
|
|
22204
|
+
let enabled = new Set(normalizeEnabledProviders(null));
|
|
22205
|
+
const guardProvider = async (ref) => {
|
|
22206
|
+
if (!ref || !isModelProvider(ref.provider))
|
|
22207
|
+
return;
|
|
22208
|
+
const spec = credentialProvider(ref.provider);
|
|
22209
|
+
if (!enabled.has(ref.provider))
|
|
22210
|
+
throw new Error(`PROVIDER_DISABLED: ${spec.name} is not enabled. Enable it in /login or Web settings, or switch model.`);
|
|
22211
|
+
if (!await models.checkAuth(ref.provider))
|
|
22212
|
+
throw new Error(`PROVIDER_KEY_REQUIRED: Configure a ${spec.name} API key in /login or Web settings.`);
|
|
22213
|
+
};
|
|
21897
22214
|
const resolveLevel = (ref, level) => {
|
|
21898
22215
|
const info = models.getModel(ref.provider, ref.modelId);
|
|
21899
22216
|
if (!info)
|
|
21900
22217
|
throw new Error("Unknown model");
|
|
21901
22218
|
return clampThinkingLevel(info, level);
|
|
21902
22219
|
};
|
|
21903
|
-
|
|
22220
|
+
let modelInfoCache;
|
|
22221
|
+
const allModels = () => {
|
|
22222
|
+
modelInfoCache ??= models.getModels().map((model) => ({
|
|
22223
|
+
provider: model.provider,
|
|
22224
|
+
modelId: model.id,
|
|
22225
|
+
name: model.name,
|
|
22226
|
+
contextWindow: model.contextWindow,
|
|
22227
|
+
input: model.input,
|
|
22228
|
+
...model.inputLimits ? { inputLimits: model.inputLimits } : {},
|
|
22229
|
+
reasoning: model.reasoning,
|
|
22230
|
+
supportedThinkingLevels: getSupportedThinkingLevels2(model),
|
|
22231
|
+
thinkingLevelAdjustments: Object.fromEntries(["off", "minimal", "low", "medium", "high", "xhigh", "max"].map((level) => [
|
|
22232
|
+
level,
|
|
22233
|
+
clampThinkingLevel(model, level)
|
|
22234
|
+
]))
|
|
22235
|
+
}));
|
|
22236
|
+
return modelInfoCache;
|
|
22237
|
+
};
|
|
22238
|
+
const shellResolver = options.shellResolver ?? (process.platform === "win32" ? createWindowsShellResolver({ configPath: options.shellPath }) : undefined);
|
|
22239
|
+
const workspaceFiles = new WorkspaceFiles(undefined, shellResolver);
|
|
21904
22240
|
const storage = options.storage === "memory" ? new MemoryStorage : await openNodeSqliteStorage(options.storagePath ?? DEFAULT_STORAGE_PATH);
|
|
21905
22241
|
const registry = createRegistry();
|
|
21906
22242
|
registry.install(createCodingTools({ images: await createNodePhotonImages() }));
|
|
@@ -21926,7 +22262,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
21926
22262
|
registry.install(createNekoPrompt(resourceSnapshots.current));
|
|
21927
22263
|
registry.install(createSubagentExtension(async (id, requestId, cwd) => {
|
|
21928
22264
|
resourceSnapshots.stage(id, requestId, cwd, await loadResources(cwd));
|
|
21929
|
-
}, resourceSnapshots.discard));
|
|
22265
|
+
}, resourceSnapshots.discard, (ref) => guardProvider(ref)));
|
|
21930
22266
|
const jobs = new JobManager;
|
|
21931
22267
|
registry.install(jobs.extension);
|
|
21932
22268
|
registry.install(createTodoExtension());
|
|
@@ -21944,7 +22280,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
21944
22280
|
registry,
|
|
21945
22281
|
settings: {
|
|
21946
22282
|
get compaction() {
|
|
21947
|
-
return compactionPolicy(contextState,
|
|
22283
|
+
return compactionPolicy(contextState, allModels().filter((model) => !isModelProvider(model.provider) || enabled.has(model.provider)).map((model) => model.contextWindow));
|
|
21948
22284
|
}
|
|
21949
22285
|
},
|
|
21950
22286
|
onReport: options.onReport,
|
|
@@ -21986,13 +22322,15 @@ async function createNekoRuntime(options = {}) {
|
|
|
21986
22322
|
throw error;
|
|
21987
22323
|
}
|
|
21988
22324
|
await questions.reconcileAll();
|
|
22325
|
+
const rootModel = modelOverride ?? model;
|
|
21989
22326
|
const conversation = await harness.root(context, {
|
|
21990
22327
|
agent: {
|
|
21991
|
-
model,
|
|
21992
|
-
thinkingLevel: resolveLevel(
|
|
22328
|
+
model: rootModel,
|
|
22329
|
+
thinkingLevel: resolveLevel(rootModel, options.thinkingLevel ?? "high"),
|
|
21993
22330
|
cwd: defaultCwd
|
|
21994
22331
|
}
|
|
21995
22332
|
});
|
|
22333
|
+
enabled = new Set(normalizeEnabledProviders((await harness.snapshot(ProviderSettingsDoc, conversation.id, context))?.enabled));
|
|
21996
22334
|
const savedSearch = (await harness.snapshot(SearchSettingsDoc, conversation.id, context))?.provider;
|
|
21997
22335
|
if (isSearchProvider(savedSearch))
|
|
21998
22336
|
searchProvider = savedSearch;
|
|
@@ -22038,7 +22376,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
22038
22376
|
};
|
|
22039
22377
|
if (existing.length === 0)
|
|
22040
22378
|
await touchConversation(conversation.id);
|
|
22041
|
-
const metadata = new SessionMetadataManager(harness, storage, context, registry, options.onReport);
|
|
22379
|
+
const metadata = new SessionMetadataManager(harness, storage, context, registry, options.onReport, (provider, modelId) => guardProvider({ provider, modelId }).then(() => true, () => false));
|
|
22042
22380
|
if (existing.length === 0)
|
|
22043
22381
|
await metadata.initialize(conversation.id);
|
|
22044
22382
|
for (const record of existing)
|
|
@@ -22099,9 +22437,8 @@ async function createNekoRuntime(options = {}) {
|
|
|
22099
22437
|
data.skill = skill;
|
|
22100
22438
|
});
|
|
22101
22439
|
}
|
|
22102
|
-
if (!duplicate
|
|
22103
|
-
|
|
22104
|
-
}
|
|
22440
|
+
if (!duplicate)
|
|
22441
|
+
await guardProvider(agent.model);
|
|
22105
22442
|
if (!duplicate && agent.model) {
|
|
22106
22443
|
const resolved = models.getModel(agent.model.provider, agent.model.modelId);
|
|
22107
22444
|
if (resolved)
|
|
@@ -22233,8 +22570,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
22233
22570
|
},
|
|
22234
22571
|
async compact(instructions) {
|
|
22235
22572
|
const agent = await target.agent(context);
|
|
22236
|
-
|
|
22237
|
-
throw new Error("DEEPSEEK_KEY_REQUIRED: Configure DeepSeek in /login or Web settings.");
|
|
22573
|
+
await guardProvider(agent.model);
|
|
22238
22574
|
await admit(target.id, async () => {
|
|
22239
22575
|
const live = await harness.snapshot(importLiveDoc, target.id, context);
|
|
22240
22576
|
if (live?.compactions?.length || (await readCompaction(harness, storage, target.id, context))?.phase === "waiting-to-apply")
|
|
@@ -22410,6 +22746,10 @@ async function createNekoRuntime(options = {}) {
|
|
|
22410
22746
|
questions,
|
|
22411
22747
|
closed: harness.closed,
|
|
22412
22748
|
workspaceFiles,
|
|
22749
|
+
shellStatus(call) {
|
|
22750
|
+
const status = workspaceFiles.shellStatus(call);
|
|
22751
|
+
return status === undefined ? undefined : summarizeWindowsShell(status);
|
|
22752
|
+
},
|
|
22413
22753
|
conversation,
|
|
22414
22754
|
root: rootSession,
|
|
22415
22755
|
submit: rootSession.submit,
|
|
@@ -22460,11 +22800,13 @@ async function createNekoRuntime(options = {}) {
|
|
|
22460
22800
|
},
|
|
22461
22801
|
async createConversation(createOptions) {
|
|
22462
22802
|
const workspace = await addWorkspace(createOptions?.cwd ?? defaultCwd);
|
|
22803
|
+
const savedDefault = (await harness.snapshot(DefaultModelDoc, conversation.id, context))?.model ?? null;
|
|
22804
|
+
const effective = createOptions?.model ?? modelOverride ?? savedDefault ?? model;
|
|
22463
22805
|
const created = await harness.createConversation({
|
|
22464
22806
|
ownership: { kind: "ownerless" },
|
|
22465
22807
|
agent: {
|
|
22466
|
-
model:
|
|
22467
|
-
thinkingLevel: resolveLevel(
|
|
22808
|
+
model: effective,
|
|
22809
|
+
thinkingLevel: resolveLevel(effective, createOptions?.thinkingLevel ?? options.thinkingLevel ?? "high"),
|
|
22468
22810
|
cwd: workspace.path
|
|
22469
22811
|
}
|
|
22470
22812
|
}, context);
|
|
@@ -22494,6 +22836,8 @@ async function createNekoRuntime(options = {}) {
|
|
|
22494
22836
|
const targetModel = ref && models.getModel(ref.provider, ref.modelId);
|
|
22495
22837
|
if (!targetModel)
|
|
22496
22838
|
throw new Error("Unknown model");
|
|
22839
|
+
if (change.model && isModelProvider(ref.provider) && !enabled.has(ref.provider))
|
|
22840
|
+
throw new Error(`PROVIDER_DISABLED: ${credentialProvider(ref.provider).name} is not enabled.`);
|
|
22497
22841
|
await session.conversation.configure({ model: ref, thinkingLevel: clampThinkingLevel(targetModel, change.thinkingLevel ?? current.thinkingLevel) }, context);
|
|
22498
22842
|
return true;
|
|
22499
22843
|
});
|
|
@@ -22548,20 +22892,62 @@ async function createNekoRuntime(options = {}) {
|
|
|
22548
22892
|
}
|
|
22549
22893
|
},
|
|
22550
22894
|
listModels() {
|
|
22551
|
-
return
|
|
22552
|
-
|
|
22553
|
-
|
|
22554
|
-
|
|
22555
|
-
|
|
22556
|
-
|
|
22557
|
-
|
|
22558
|
-
|
|
22559
|
-
|
|
22560
|
-
|
|
22561
|
-
|
|
22562
|
-
|
|
22563
|
-
|
|
22564
|
-
|
|
22895
|
+
return allModels();
|
|
22896
|
+
},
|
|
22897
|
+
async listSelectableModels() {
|
|
22898
|
+
const availability = new Map;
|
|
22899
|
+
const result = [];
|
|
22900
|
+
for (const info of allModels()) {
|
|
22901
|
+
const managed = isModelProvider(info.provider);
|
|
22902
|
+
if (managed && !enabled.has(info.provider))
|
|
22903
|
+
continue;
|
|
22904
|
+
if (!availability.has(info.provider))
|
|
22905
|
+
availability.set(info.provider, !managed || Boolean(await models.checkAuth(info.provider)));
|
|
22906
|
+
result.push({ ...info, available: availability.get(info.provider) ?? false });
|
|
22907
|
+
}
|
|
22908
|
+
return result;
|
|
22909
|
+
},
|
|
22910
|
+
enabledProviders() {
|
|
22911
|
+
return MODEL_PROVIDERS.map((item) => item.id).filter((id) => enabled.has(id));
|
|
22912
|
+
},
|
|
22913
|
+
async setProviderEnabled(provider, value) {
|
|
22914
|
+
if (!isModelProvider(provider))
|
|
22915
|
+
throw new Error("Unknown model provider.");
|
|
22916
|
+
if (typeof value !== "boolean")
|
|
22917
|
+
throw new Error("Enabled must be a boolean.");
|
|
22918
|
+
if (!value && LOCKED_PROVIDERS.includes(provider))
|
|
22919
|
+
throw new Error("DeepSeek is always enabled.");
|
|
22920
|
+
const next = new Set(enabled);
|
|
22921
|
+
if (value)
|
|
22922
|
+
next.add(provider);
|
|
22923
|
+
else
|
|
22924
|
+
next.delete(provider);
|
|
22925
|
+
const list = normalizeEnabledProviders([...next]);
|
|
22926
|
+
await harness.commit(async (tx) => {
|
|
22927
|
+
(await tx.doc(ProviderSettingsDoc, conversation.id)).enabled = list;
|
|
22928
|
+
}, context);
|
|
22929
|
+
enabled = new Set(list);
|
|
22930
|
+
},
|
|
22931
|
+
async defaultModel() {
|
|
22932
|
+
const saved = (await harness.snapshot(DefaultModelDoc, conversation.id, context))?.model ?? null;
|
|
22933
|
+
if (modelOverride)
|
|
22934
|
+
return { effective: modelOverride, source: "cli" };
|
|
22935
|
+
if (saved)
|
|
22936
|
+
return { effective: saved, source: "saved" };
|
|
22937
|
+
if (options.model)
|
|
22938
|
+
return { effective: model, source: "config" };
|
|
22939
|
+
return { effective: model, source: "default" };
|
|
22940
|
+
},
|
|
22941
|
+
async setDefaultModel(ref) {
|
|
22942
|
+
if (ref !== null) {
|
|
22943
|
+
if (!models.getModel(ref.provider, ref.modelId))
|
|
22944
|
+
throw new Error("Unknown model");
|
|
22945
|
+
if (isModelProvider(ref.provider) && !enabled.has(ref.provider))
|
|
22946
|
+
throw new Error(`PROVIDER_DISABLED: ${credentialProvider(ref.provider).name} is not enabled.`);
|
|
22947
|
+
}
|
|
22948
|
+
await harness.commit(async (tx) => {
|
|
22949
|
+
(await tx.doc(DefaultModelDoc, conversation.id)).model = ref;
|
|
22950
|
+
}, context);
|
|
22565
22951
|
},
|
|
22566
22952
|
async getAgent(id) {
|
|
22567
22953
|
const session = await sessionFor(id);
|
|
@@ -22626,8 +23012,8 @@ var nullableString = Type7.Union([str, nil]);
|
|
|
22626
23012
|
var phase = Type7.Enum(["pending", "running", "done"]);
|
|
22627
23013
|
var level = Type7.Enum(["project", "user", "builtin"]);
|
|
22628
23014
|
var thinking = Type7.Enum(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
22629
|
-
var
|
|
22630
|
-
var change = Type7.Object({ model: Type7.Optional(
|
|
23015
|
+
var model2 = Type7.Object({ provider: str, modelId: str });
|
|
23016
|
+
var change = Type7.Object({ model: Type7.Optional(model2), thinkingLevel: Type7.Optional(thinking) }, { additionalProperties: false });
|
|
22631
23017
|
var usage = Type7.Object({ input: num, output: num, cacheRead: num, cacheWrite: num, cost: num });
|
|
22632
23018
|
var image = Type7.Object({
|
|
22633
23019
|
name: str,
|
|
@@ -22865,7 +23251,7 @@ var ChatViewSchema = Type7.Object({
|
|
|
22865
23251
|
subagents: Type7.Optional(Type7.Array(DelegationSchema)),
|
|
22866
23252
|
taskUsage: Type7.Optional(usage),
|
|
22867
23253
|
model: str,
|
|
22868
|
-
agent: Type7.Object({ model: Type7.Union([
|
|
23254
|
+
agent: Type7.Object({ model: Type7.Union([model2, nil]), thinkingLevel: Type7.Union([thinking, nil]) }),
|
|
22869
23255
|
cwd: str,
|
|
22870
23256
|
busy: bool,
|
|
22871
23257
|
status: nullableString,
|
|
@@ -22950,7 +23336,7 @@ var SessionSummarySchema = Type7.Object({
|
|
|
22950
23336
|
firstPromptPreview: str,
|
|
22951
23337
|
createdAt: num,
|
|
22952
23338
|
lastMessageAt: Type7.Union([num, nil]),
|
|
22953
|
-
model: Type7.Union([
|
|
23339
|
+
model: Type7.Union([model2, nil]),
|
|
22954
23340
|
status: Type7.Enum(["idle", "running", "error"]),
|
|
22955
23341
|
awaitingAnswer: Type7.Optional(bool),
|
|
22956
23342
|
id: str
|
|
@@ -22961,11 +23347,12 @@ var WorkspaceStateSchema = Type7.Object({ revision: integer, workspaces: Type7.A
|
|
|
22961
23347
|
var credentialStatus = Type7.Object({
|
|
22962
23348
|
savedConfigured: bool,
|
|
22963
23349
|
environmentConfigured: bool,
|
|
22964
|
-
source: Type7.Enum(["saved", "environment", "runtime", "keyless", "none"])
|
|
23350
|
+
source: Type7.Enum(["saved", "environment", "runtime", "keyless", "none"]),
|
|
23351
|
+
enabled: Type7.Optional(bool)
|
|
22965
23352
|
});
|
|
22966
23353
|
var CredentialStateSchema = Type7.Object({
|
|
22967
23354
|
revision: integer,
|
|
22968
|
-
statuses: Type7.
|
|
23355
|
+
statuses: Type7.Record(str, credentialStatus)
|
|
22969
23356
|
});
|
|
22970
23357
|
var AttachmentSchema = Type7.Object({ sessionId: str, attachmentId: str });
|
|
22971
23358
|
var chatInput = Type7.Object({ sessionId: str, attachmentId: str }, { additionalProperties: false });
|
|
@@ -23007,6 +23394,7 @@ var modelInfo = Type7.Object({
|
|
|
23007
23394
|
provider: str,
|
|
23008
23395
|
modelId: str,
|
|
23009
23396
|
name: str,
|
|
23397
|
+
available: bool,
|
|
23010
23398
|
contextWindow: num,
|
|
23011
23399
|
input: Type7.Array(str),
|
|
23012
23400
|
inputLimits: Type7.Optional(inputLimits),
|
|
@@ -23112,16 +23500,17 @@ var NekoWorkspaces = contract("neko.workspaces", {
|
|
|
23112
23500
|
}
|
|
23113
23501
|
}
|
|
23114
23502
|
});
|
|
23115
|
-
var provider = Type7.
|
|
23503
|
+
var provider = Type7.String({ minLength: 1, maxLength: 64 });
|
|
23116
23504
|
var NekoCredentials = contract("neko.credentials", {
|
|
23117
|
-
version:
|
|
23505
|
+
version: 2,
|
|
23118
23506
|
input: Type7.Object({}, { additionalProperties: false }),
|
|
23119
23507
|
variants: {
|
|
23120
23508
|
ready: {
|
|
23121
23509
|
state: { state: CredentialStateSchema },
|
|
23122
23510
|
methods: {
|
|
23123
23511
|
save: rpc(Type7.Object({ provider, key: Type7.String({ minLength: 1, maxLength: 8192 }) }, { additionalProperties: false }), nil),
|
|
23124
|
-
remove: rpc(Type7.Object({ provider }, { additionalProperties: false }), nil)
|
|
23512
|
+
remove: rpc(Type7.Object({ provider }, { additionalProperties: false }), nil),
|
|
23513
|
+
setEnabled: rpc(Type7.Object({ provider, enabled: bool }, { additionalProperties: false }), nil)
|
|
23125
23514
|
}
|
|
23126
23515
|
}
|
|
23127
23516
|
}
|
|
@@ -23198,6 +23587,24 @@ var NekoContextSettings = contract("neko.context-settings", {
|
|
|
23198
23587
|
}
|
|
23199
23588
|
}
|
|
23200
23589
|
});
|
|
23590
|
+
var defaultModelSource = Type7.Enum(["cli", "saved", "config", "default"]);
|
|
23591
|
+
var DefaultModelStateSchema = Type7.Object({
|
|
23592
|
+
effective: model2,
|
|
23593
|
+
source: defaultModelSource
|
|
23594
|
+
});
|
|
23595
|
+
var NekoDefaultModel = contract("neko.default-model", {
|
|
23596
|
+
version: 1,
|
|
23597
|
+
input: Type7.Object({}, { additionalProperties: false }),
|
|
23598
|
+
variants: {
|
|
23599
|
+
ready: {
|
|
23600
|
+
state: { state: DefaultModelStateSchema },
|
|
23601
|
+
methods: {
|
|
23602
|
+
set: rpc(model2, nil),
|
|
23603
|
+
clear: rpc(nil, nil)
|
|
23604
|
+
}
|
|
23605
|
+
}
|
|
23606
|
+
}
|
|
23607
|
+
});
|
|
23201
23608
|
var FileCandidatesSchema = Type7.Object({
|
|
23202
23609
|
entries: Type7.Array(Type7.Object({ name: str, path: str, kind: Type7.Enum(["file", "directory"]) }), { maxItems: 100 }),
|
|
23203
23610
|
limited: bool,
|
|
@@ -23245,6 +23652,14 @@ var NekoSkillMarket = contract("neko.skill-market", {
|
|
|
23245
23652
|
}
|
|
23246
23653
|
}
|
|
23247
23654
|
});
|
|
23655
|
+
var ShellStatusSchema = Type7.Union([
|
|
23656
|
+
Type7.Object({ status: Type7.Literal("ready"), path: str, source: Type7.Enum(["config", "auto"]) }),
|
|
23657
|
+
Type7.Object({
|
|
23658
|
+
status: Type7.Literal("missing"),
|
|
23659
|
+
reason: Type7.Enum(["not_found", "invalid_config"]),
|
|
23660
|
+
detail: Type7.Optional(str)
|
|
23661
|
+
})
|
|
23662
|
+
]);
|
|
23248
23663
|
var ApplicationInfoSchema = Type7.Object({
|
|
23249
23664
|
runningVersion: str,
|
|
23250
23665
|
installedVersion: nullableString,
|
|
@@ -23252,7 +23667,8 @@ var ApplicationInfoSchema = Type7.Object({
|
|
|
23252
23667
|
latestVersion: nullableString,
|
|
23253
23668
|
checkedAt: Type7.Union([integer, nil]),
|
|
23254
23669
|
checkState: Type7.Enum(["unknown", "checking", "ok", "failed", "development"]),
|
|
23255
|
-
serverId: str
|
|
23670
|
+
serverId: str,
|
|
23671
|
+
shell: Type7.Optional(ShellStatusSchema)
|
|
23256
23672
|
});
|
|
23257
23673
|
var NekoApplicationInfo = contract("neko.application-info", {
|
|
23258
23674
|
version: 1,
|
|
@@ -23290,7 +23706,7 @@ function isMobilePrincipal(principal) {
|
|
|
23290
23706
|
|
|
23291
23707
|
// packages/gateway/src/mobile/store.ts
|
|
23292
23708
|
import { randomBytes } from "crypto";
|
|
23293
|
-
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile4,
|
|
23709
|
+
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile4, rm, writeFile } from "fs/promises";
|
|
23294
23710
|
import { join as join6 } from "path";
|
|
23295
23711
|
var MOBILE_STATE_FORMAT_VERSION = 1;
|
|
23296
23712
|
var MOBILE_DEFAULT_PORT = 8323;
|
|
@@ -23418,9 +23834,10 @@ async function writeFileAtomic(filePath, contents, mode) {
|
|
|
23418
23834
|
await writeFile(tempPath, contents, { mode });
|
|
23419
23835
|
if (POSIX)
|
|
23420
23836
|
await chmod2(tempPath, mode);
|
|
23421
|
-
await
|
|
23422
|
-
}
|
|
23837
|
+
await renameWithRetry(tempPath, filePath);
|
|
23838
|
+
} catch (error) {
|
|
23423
23839
|
await rm(tempPath, { force: true });
|
|
23840
|
+
throw error;
|
|
23424
23841
|
}
|
|
23425
23842
|
}
|
|
23426
23843
|
async function writeSecretFile(filePath, contents) {
|
|
@@ -23976,8 +24393,8 @@ function subagentClient(connect) {
|
|
|
23976
24393
|
import { createHash as createHash2 } from "crypto";
|
|
23977
24394
|
|
|
23978
24395
|
// packages/core/src/application-version.ts
|
|
23979
|
-
var RUNNING_VERSION = "0.4.
|
|
23980
|
-
var APPLICATION_PROTOCOL =
|
|
24396
|
+
var RUNNING_VERSION = "0.4.7";
|
|
24397
|
+
var APPLICATION_PROTOCOL = 8;
|
|
23981
24398
|
function stableVersion(value) {
|
|
23982
24399
|
return typeof value === "string" && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value) && value.split(".").every((n) => Number.isSafeInteger(Number(n)));
|
|
23983
24400
|
}
|
|
@@ -26626,6 +27043,10 @@ function toMessage(error) {
|
|
|
26626
27043
|
}
|
|
26627
27044
|
return String(error);
|
|
26628
27045
|
}
|
|
27046
|
+
function normalizeLocalAddress(address) {
|
|
27047
|
+
const mapped = address?.startsWith("::ffff:") ? address.slice("::ffff:".length) : undefined;
|
|
27048
|
+
return mapped !== undefined && isIpv4Literal(mapped) ? mapped : address;
|
|
27049
|
+
}
|
|
26629
27050
|
|
|
26630
27051
|
class MobileGateway {
|
|
26631
27052
|
serverId;
|
|
@@ -26954,11 +27375,12 @@ class MobileGateway {
|
|
|
26954
27375
|
connection.sendNotification(method, params);
|
|
26955
27376
|
}
|
|
26956
27377
|
advertisedAddress(localAddress) {
|
|
27378
|
+
const normalized = normalizeLocalAddress(localAddress);
|
|
26957
27379
|
const state = this.stateValue;
|
|
26958
|
-
if (state.status === "listening" && state.mode === "wsl-relay" &&
|
|
27380
|
+
if (state.status === "listening" && state.mode === "wsl-relay" && normalized === state.bindAddress) {
|
|
26959
27381
|
return state.address;
|
|
26960
27382
|
}
|
|
26961
|
-
return
|
|
27383
|
+
return normalized;
|
|
26962
27384
|
}
|
|
26963
27385
|
requireStore() {
|
|
26964
27386
|
if (this.storeHandle === undefined)
|
|
@@ -27219,7 +27641,7 @@ import { randomUUID as randomUUID5 } from "crypto";
|
|
|
27219
27641
|
import { ChordError as ChordError5, local as local3, method as method2 } from "@earendil-works/chord";
|
|
27220
27642
|
|
|
27221
27643
|
// packages/core/src/application-updates.ts
|
|
27222
|
-
import { mkdir as mkdir3, readFile as readFile6,
|
|
27644
|
+
import { mkdir as mkdir3, readFile as readFile6, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
27223
27645
|
import { homedir as homedir5 } from "os";
|
|
27224
27646
|
import { dirname as dirname5, join as join10 } from "path";
|
|
27225
27647
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -27349,7 +27771,7 @@ class UpdateChecker {
|
|
|
27349
27771
|
try {
|
|
27350
27772
|
await mkdir3(dirname5(this.#cachePath), { recursive: true });
|
|
27351
27773
|
await writeFile3(temporary, JSON.stringify({ latestVersion: value.version, checkedAt: now }), { mode: 384 });
|
|
27352
|
-
await
|
|
27774
|
+
await renameWithRetry(temporary, this.#cachePath);
|
|
27353
27775
|
} catch {} finally {
|
|
27354
27776
|
await rm3(temporary, { force: true }).catch(() => {});
|
|
27355
27777
|
}
|
|
@@ -27995,8 +28417,6 @@ function serveSessions(host, runtime, context) {
|
|
|
27995
28417
|
for (const id of input.images ?? [])
|
|
27996
28418
|
uploads.value.release(id);
|
|
27997
28419
|
} catch (error) {
|
|
27998
|
-
if (error instanceof Error && error.message.startsWith("DEEPSEEK_KEY_REQUIRED:"))
|
|
27999
|
-
throw new ChordError4("invalid", "DEEPSEEK_KEY_REQUIRED: Configure DeepSeek in /login or Web settings.");
|
|
28000
28420
|
throw new ChordError4("invalid", error instanceof Error ? error.message : "Cannot submit message");
|
|
28001
28421
|
}
|
|
28002
28422
|
return null;
|
|
@@ -28085,7 +28505,7 @@ function serveSessions(host, runtime, context) {
|
|
|
28085
28505
|
|
|
28086
28506
|
// packages/gateway/src/skill-market.ts
|
|
28087
28507
|
import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
|
|
28088
|
-
import { mkdir as mkdir4, readdir as readdir2,
|
|
28508
|
+
import { mkdir as mkdir4, readdir as readdir2, rm as rm4, stat as stat3, writeFile as writeFile4 } from "fs/promises";
|
|
28089
28509
|
import { homedir as homedir6 } from "os";
|
|
28090
28510
|
import { dirname as dirname6, join as join11 } from "path";
|
|
28091
28511
|
|
|
@@ -28230,11 +28650,11 @@ var ec = [
|
|
|
28230
28650
|
"stream finishing",
|
|
28231
28651
|
"invalid zip data"
|
|
28232
28652
|
];
|
|
28233
|
-
var
|
|
28653
|
+
var err2 = function(ind, msg, nt) {
|
|
28234
28654
|
var e = new Error(msg || ec[ind]);
|
|
28235
28655
|
e.code = ind;
|
|
28236
28656
|
if (Error.captureStackTrace)
|
|
28237
|
-
Error.captureStackTrace(e,
|
|
28657
|
+
Error.captureStackTrace(e, err2);
|
|
28238
28658
|
if (!nt)
|
|
28239
28659
|
throw e;
|
|
28240
28660
|
return e;
|
|
@@ -28267,7 +28687,7 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
28267
28687
|
var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
|
|
28268
28688
|
if (t > sl) {
|
|
28269
28689
|
if (noSt)
|
|
28270
|
-
|
|
28690
|
+
err2(0);
|
|
28271
28691
|
break;
|
|
28272
28692
|
}
|
|
28273
28693
|
if (resize)
|
|
@@ -28313,10 +28733,10 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
28313
28733
|
lm = hMap(lt, lbt, 1);
|
|
28314
28734
|
dm = hMap(dt, dbt, 1);
|
|
28315
28735
|
} else
|
|
28316
|
-
|
|
28736
|
+
err2(1);
|
|
28317
28737
|
if (pos > tbts) {
|
|
28318
28738
|
if (noSt)
|
|
28319
|
-
|
|
28739
|
+
err2(0);
|
|
28320
28740
|
break;
|
|
28321
28741
|
}
|
|
28322
28742
|
}
|
|
@@ -28329,11 +28749,11 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
28329
28749
|
pos += c & 15;
|
|
28330
28750
|
if (pos > tbts) {
|
|
28331
28751
|
if (noSt)
|
|
28332
|
-
|
|
28752
|
+
err2(0);
|
|
28333
28753
|
break;
|
|
28334
28754
|
}
|
|
28335
28755
|
if (!c)
|
|
28336
|
-
|
|
28756
|
+
err2(2);
|
|
28337
28757
|
if (sym < 256)
|
|
28338
28758
|
buf[bt++] = sym;
|
|
28339
28759
|
else if (sym == 256) {
|
|
@@ -28348,7 +28768,7 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
28348
28768
|
}
|
|
28349
28769
|
var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
|
|
28350
28770
|
if (!d)
|
|
28351
|
-
|
|
28771
|
+
err2(3);
|
|
28352
28772
|
pos += d & 15;
|
|
28353
28773
|
var dt = fd[dsym];
|
|
28354
28774
|
if (dsym > 3) {
|
|
@@ -28357,7 +28777,7 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
28357
28777
|
}
|
|
28358
28778
|
if (pos > tbts) {
|
|
28359
28779
|
if (noSt)
|
|
28360
|
-
|
|
28780
|
+
err2(0);
|
|
28361
28781
|
break;
|
|
28362
28782
|
}
|
|
28363
28783
|
if (resize)
|
|
@@ -28366,7 +28786,7 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
28366
28786
|
if (bt < dt) {
|
|
28367
28787
|
var shift = dl - dt, dend = Math.min(dt, end);
|
|
28368
28788
|
if (shift + bt < 0)
|
|
28369
|
-
|
|
28789
|
+
err2(3);
|
|
28370
28790
|
for (;bt < dend; ++bt)
|
|
28371
28791
|
buf[bt] = dict[shift + bt];
|
|
28372
28792
|
}
|
|
@@ -28426,7 +28846,7 @@ function strFromU8(dat, latin1) {
|
|
|
28426
28846
|
} else {
|
|
28427
28847
|
var _a = dutf8(dat), { s, r } = _a;
|
|
28428
28848
|
if (r.length)
|
|
28429
|
-
|
|
28849
|
+
err2(8);
|
|
28430
28850
|
return s;
|
|
28431
28851
|
}
|
|
28432
28852
|
}
|
|
@@ -28453,7 +28873,7 @@ var z64hs = function(d, b, l, z, sc, su, off) {
|
|
|
28453
28873
|
}
|
|
28454
28874
|
}
|
|
28455
28875
|
if (z < 2)
|
|
28456
|
-
|
|
28876
|
+
err2(13);
|
|
28457
28877
|
}
|
|
28458
28878
|
return [sc, su, off, 0];
|
|
28459
28879
|
};
|
|
@@ -28462,7 +28882,7 @@ function unzipSync(data, opts) {
|
|
|
28462
28882
|
var e = data.length - 22;
|
|
28463
28883
|
for (;b4(data, e) != 101010256; --e) {
|
|
28464
28884
|
if (!e || data.length - e > 65558)
|
|
28465
|
-
|
|
28885
|
+
err2(13);
|
|
28466
28886
|
}
|
|
28467
28887
|
var c = b2(data, e + 8);
|
|
28468
28888
|
if (!c)
|
|
@@ -28492,7 +28912,7 @@ function unzipSync(data, opts) {
|
|
|
28492
28912
|
else if (c_2 == 8)
|
|
28493
28913
|
files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });
|
|
28494
28914
|
else
|
|
28495
|
-
|
|
28915
|
+
err2(14, "unknown compression type " + c_2);
|
|
28496
28916
|
}
|
|
28497
28917
|
}
|
|
28498
28918
|
return files;
|
|
@@ -28533,6 +28953,7 @@ function createSkillMarketClient(options) {
|
|
|
28533
28953
|
const home = options.home ?? homedir6();
|
|
28534
28954
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
28535
28955
|
const now = options.now ?? Date.now;
|
|
28956
|
+
const placeRename = options.renameImpl;
|
|
28536
28957
|
let base;
|
|
28537
28958
|
try {
|
|
28538
28959
|
base = new URL(options.marketUrl);
|
|
@@ -28637,6 +29058,8 @@ function createSkillMarketClient(options) {
|
|
|
28637
29058
|
throw new SkillMarketError("market_unavailable", "Project-scope install needs a workspaceRoot");
|
|
28638
29059
|
return projectSkillsDir(workspaceRoot);
|
|
28639
29060
|
};
|
|
29061
|
+
const BACKUP_PREFIX = ".bak-";
|
|
29062
|
+
const UUID_LENGTH = 36;
|
|
28640
29063
|
const sweepTempDirs = async (skillsDir) => {
|
|
28641
29064
|
const parent = dirname6(skillsDir);
|
|
28642
29065
|
let names = [];
|
|
@@ -28646,11 +29069,26 @@ function createSkillMarketClient(options) {
|
|
|
28646
29069
|
return;
|
|
28647
29070
|
}
|
|
28648
29071
|
await Promise.all(names.filter((n) => n.startsWith(".tmp-")).map((n) => rm4(join11(parent, n), { recursive: true, force: true }).catch(() => {})));
|
|
29072
|
+
for (const n of names.filter((n) => n.startsWith(BACKUP_PREFIX))) {
|
|
29073
|
+
const backup = join11(parent, n);
|
|
29074
|
+
const skill = n.slice(BACKUP_PREFIX.length + UUID_LENGTH + 1);
|
|
29075
|
+
if (skill === "" || skill.includes("/") || skill.includes("\\") || skill === "." || skill === "..")
|
|
29076
|
+
continue;
|
|
29077
|
+
const target = join11(skillsDir, skill);
|
|
29078
|
+
if (await stat3(target).catch(() => {
|
|
29079
|
+
return;
|
|
29080
|
+
}) === undefined) {
|
|
29081
|
+
await mkdir4(skillsDir, { recursive: true }).catch(() => {});
|
|
29082
|
+
await renameWithRetry(backup, target).catch(() => {});
|
|
29083
|
+
} else {
|
|
29084
|
+
await rm4(backup, { recursive: true, force: true }).catch(() => {});
|
|
29085
|
+
}
|
|
29086
|
+
}
|
|
28649
29087
|
};
|
|
28650
29088
|
const install = async ({ name, scope, workspaceRoot, overwrite }) => {
|
|
28651
29089
|
const skillsDir = skillsDirFor(scope, workspaceRoot);
|
|
28652
29090
|
const target = join11(skillsDir, name);
|
|
28653
|
-
const exists = await
|
|
29091
|
+
const exists = await stat3(target).catch(() => {
|
|
28654
29092
|
return;
|
|
28655
29093
|
}) !== undefined;
|
|
28656
29094
|
if (exists && overwrite !== true)
|
|
@@ -28665,17 +29103,27 @@ function createSkillMarketClient(options) {
|
|
|
28665
29103
|
await mkdir4(dirname6(dest), { recursive: true });
|
|
28666
29104
|
await writeFile4(dest, bytes);
|
|
28667
29105
|
}
|
|
28668
|
-
if (exists)
|
|
28669
|
-
await rm4(target, { recursive: true, force: true });
|
|
28670
29106
|
await mkdir4(skillsDir, { recursive: true });
|
|
28671
|
-
|
|
29107
|
+
if (exists) {
|
|
29108
|
+
const backup = join11(dirname6(skillsDir), `${BACKUP_PREFIX}${randomUUID4()}-${name}`);
|
|
29109
|
+
await renameWithRetry(target, backup);
|
|
29110
|
+
try {
|
|
29111
|
+
await renameWithRetry(temp, target, placeRename ? { rename: placeRename } : {});
|
|
29112
|
+
} catch (error) {
|
|
29113
|
+
await renameWithRetry(backup, target).catch(() => {});
|
|
29114
|
+
throw error;
|
|
29115
|
+
}
|
|
29116
|
+
await rm4(backup, { recursive: true, force: true }).catch(() => {});
|
|
29117
|
+
} else {
|
|
29118
|
+
await renameWithRetry(temp, target, placeRename ? { rename: placeRename } : {});
|
|
29119
|
+
}
|
|
28672
29120
|
} finally {
|
|
28673
29121
|
await rm4(temp, { recursive: true, force: true }).catch(() => {});
|
|
28674
29122
|
}
|
|
28675
29123
|
let shadowedBy;
|
|
28676
29124
|
if (scope === "user" && workspaceRoot !== undefined && workspaceRoot !== "") {
|
|
28677
29125
|
const projectDir = join11(projectSkillsDir(workspaceRoot), name);
|
|
28678
|
-
if (await
|
|
29126
|
+
if (await stat3(projectDir).catch(() => {
|
|
28679
29127
|
return;
|
|
28680
29128
|
}) !== undefined)
|
|
28681
29129
|
shadowedBy = "project";
|
|
@@ -28685,7 +29133,7 @@ function createSkillMarketClient(options) {
|
|
|
28685
29133
|
const uninstall = async ({ name, scope, workspaceRoot }) => {
|
|
28686
29134
|
const skillsDir = skillsDirFor(scope, workspaceRoot);
|
|
28687
29135
|
const target = join11(skillsDir, name);
|
|
28688
|
-
if (await
|
|
29136
|
+
if (await stat3(target).catch(() => {
|
|
28689
29137
|
return;
|
|
28690
29138
|
}) === undefined)
|
|
28691
29139
|
throw new SkillMarketError("skill_not_installed", `Skill ${name} is not installed at ${scope} level`);
|
|
@@ -28719,7 +29167,7 @@ function createSkillMarketClient(options) {
|
|
|
28719
29167
|
|
|
28720
29168
|
// packages/gateway/src/workspaces.ts
|
|
28721
29169
|
import { constants as constants3 } from "fs";
|
|
28722
|
-
import { access, readdir as readdir3, stat as
|
|
29170
|
+
import { access, readdir as readdir3, stat as stat4 } from "fs/promises";
|
|
28723
29171
|
import { dirname as dirname7, isAbsolute as isAbsolute4, join as join12 } from "path";
|
|
28724
29172
|
async function browseWorkspaces(options) {
|
|
28725
29173
|
const requested = options.path.replace(/^\/+([A-Za-z]:[\\/])/, "$1");
|
|
@@ -28751,7 +29199,7 @@ async function browseWorkspaces(options) {
|
|
|
28751
29199
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
28752
29200
|
continue;
|
|
28753
29201
|
try {
|
|
28754
|
-
if (!(await
|
|
29202
|
+
if (!(await stat4(target)).isDirectory())
|
|
28755
29203
|
continue;
|
|
28756
29204
|
await access(target, constants3.R_OK | constants3.X_OK);
|
|
28757
29205
|
directories.push({ name: entry.name, path: target });
|
|
@@ -28788,9 +29236,11 @@ var ServerState = local3("neko.server-state", {
|
|
|
28788
29236
|
credentials: CredentialStateSchema,
|
|
28789
29237
|
searchSettings: SearchSettingsStateSchema,
|
|
28790
29238
|
contextSettings: ContextSettingsStateSchema,
|
|
29239
|
+
defaultModel: DefaultModelStateSchema,
|
|
28791
29240
|
refreshCredentials: method2(Type12.Null(), Type12.Null()),
|
|
28792
29241
|
refreshSearchSettings: method2(Type12.Null(), Type12.Null()),
|
|
28793
|
-
refreshContextSettings: method2(Type12.Null(), Type12.Null())
|
|
29242
|
+
refreshContextSettings: method2(Type12.Null(), Type12.Null()),
|
|
29243
|
+
refreshDefaultModel: method2(Type12.Null(), Type12.Null())
|
|
28794
29244
|
}
|
|
28795
29245
|
});
|
|
28796
29246
|
async function createNekoServerServices(options) {
|
|
@@ -28798,12 +29248,15 @@ async function createNekoServerServices(options) {
|
|
|
28798
29248
|
const updates = new UpdateChecker(options.updates);
|
|
28799
29249
|
await updates.local();
|
|
28800
29250
|
const updateListeners = new Set;
|
|
28801
|
-
const info = (value) =>
|
|
28802
|
-
|
|
29251
|
+
const info = (value) => {
|
|
29252
|
+
const shell = runtime.shellStatus();
|
|
29253
|
+
return { ...value, serverId: options.serverId, ...shell === undefined ? {} : { shell } };
|
|
29254
|
+
};
|
|
29255
|
+
let published = JSON.stringify(info(updates.state)), closed = false;
|
|
28803
29256
|
const publishUpdates = () => {
|
|
28804
29257
|
if (closed)
|
|
28805
29258
|
return;
|
|
28806
|
-
const next = JSON.stringify(updates.state);
|
|
29259
|
+
const next = JSON.stringify(info(updates.state));
|
|
28807
29260
|
if (next === published)
|
|
28808
29261
|
return;
|
|
28809
29262
|
published = next;
|
|
@@ -28822,6 +29275,7 @@ async function createNekoServerServices(options) {
|
|
|
28822
29275
|
variant: "ready",
|
|
28823
29276
|
info: current,
|
|
28824
29277
|
check: async () => {
|
|
29278
|
+
runtime.shellStatus({ refresh: true });
|
|
28825
29279
|
await updates.check(true);
|
|
28826
29280
|
publishUpdates();
|
|
28827
29281
|
return info(updates.state);
|
|
@@ -28868,13 +29322,20 @@ async function createNekoServerServices(options) {
|
|
|
28868
29322
|
status: info.status,
|
|
28869
29323
|
awaitingAnswer: info.awaitingAnswer
|
|
28870
29324
|
}));
|
|
28871
|
-
const readStatuses = async () =>
|
|
28872
|
-
|
|
29325
|
+
const readStatuses = async () => {
|
|
29326
|
+
const enabled = new Set(runtime.enabledProviders());
|
|
29327
|
+
return Object.fromEntries(await Promise.all(CREDENTIAL_PROVIDERS.map(async (spec) => {
|
|
29328
|
+
const status = await runtime.credentialStatus(spec.id);
|
|
29329
|
+
return [spec.id, spec.kind === "model" ? { ...status, enabled: enabled.has(spec.id) } : status];
|
|
29330
|
+
})));
|
|
29331
|
+
};
|
|
29332
|
+
const [sessions, workspaces, statuses, search, contextSettings0, defaultModel0] = await Promise.all([
|
|
28873
29333
|
list(),
|
|
28874
29334
|
runtime.listWorkspaces(),
|
|
28875
29335
|
readStatuses(),
|
|
28876
29336
|
runtime.searchSettings(),
|
|
28877
|
-
runtime.contextSettings()
|
|
29337
|
+
runtime.contextSettings(),
|
|
29338
|
+
runtime.defaultModel()
|
|
28878
29339
|
]);
|
|
28879
29340
|
let mutationTail = Promise.resolve();
|
|
28880
29341
|
const serialize = (fn) => {
|
|
@@ -28891,6 +29352,7 @@ async function createNekoServerServices(options) {
|
|
|
28891
29352
|
revision: 1,
|
|
28892
29353
|
...contextSettings0
|
|
28893
29354
|
});
|
|
29355
|
+
const defaultModel = self2.state("defaultModel", DefaultModelStateSchema, defaultModel0);
|
|
28894
29356
|
const refreshContextSettings = async (_input, ctx) => {
|
|
28895
29357
|
const next = await runtime.contextSettings();
|
|
28896
29358
|
const { revision, ...current } = contextSettings.value;
|
|
@@ -28898,6 +29360,12 @@ async function createNekoServerServices(options) {
|
|
|
28898
29360
|
contextSettings.replace(ctx, { revision: revision + 1, ...next });
|
|
28899
29361
|
return null;
|
|
28900
29362
|
};
|
|
29363
|
+
const refreshDefaultModel = async (_input, ctx) => {
|
|
29364
|
+
const next = await runtime.defaultModel();
|
|
29365
|
+
if (JSON.stringify(next) !== JSON.stringify(defaultModel.value))
|
|
29366
|
+
defaultModel.replace(ctx, next);
|
|
29367
|
+
return null;
|
|
29368
|
+
};
|
|
28901
29369
|
const refreshSearchSettings = async (_input, ctx) => {
|
|
28902
29370
|
const next = await runtime.searchSettings();
|
|
28903
29371
|
const current = searchSettings.value;
|
|
@@ -28926,6 +29394,7 @@ async function createNekoServerServices(options) {
|
|
|
28926
29394
|
credentials,
|
|
28927
29395
|
searchSettings,
|
|
28928
29396
|
contextSettings,
|
|
29397
|
+
defaultModel,
|
|
28929
29398
|
async refreshCredentials(_input, ctx) {
|
|
28930
29399
|
credentials.replace(ctx, {
|
|
28931
29400
|
revision: credentials.value.revision + 1,
|
|
@@ -28934,7 +29403,8 @@ async function createNekoServerServices(options) {
|
|
|
28934
29403
|
return null;
|
|
28935
29404
|
},
|
|
28936
29405
|
refreshSearchSettings,
|
|
28937
|
-
refreshContextSettings
|
|
29406
|
+
refreshContextSettings,
|
|
29407
|
+
refreshDefaultModel
|
|
28938
29408
|
};
|
|
28939
29409
|
}, { lifetime: "host" });
|
|
28940
29410
|
const shared = (self2) => self2.get(ServerState, {});
|
|
@@ -28957,7 +29427,17 @@ async function createNekoServerServices(options) {
|
|
|
28957
29427
|
remove: async (input, ctx) => {
|
|
28958
29428
|
await runtime.removeCredential(input.provider);
|
|
28959
29429
|
return hub.refreshCredentials(null, ctx);
|
|
28960
|
-
}
|
|
29430
|
+
},
|
|
29431
|
+
setEnabled: (input, ctx) => serialize(async () => {
|
|
29432
|
+
if (!isModelProvider(input.provider))
|
|
29433
|
+
throw new ChordError5("invalid", "Unknown model provider.");
|
|
29434
|
+
try {
|
|
29435
|
+
await runtime.setProviderEnabled(input.provider, input.enabled);
|
|
29436
|
+
} catch (error) {
|
|
29437
|
+
throw new ChordError5("invalid", error instanceof Error ? error.message : "Cannot change provider.");
|
|
29438
|
+
}
|
|
29439
|
+
return hub.refreshCredentials(null, ctx);
|
|
29440
|
+
})
|
|
28961
29441
|
};
|
|
28962
29442
|
});
|
|
28963
29443
|
host.serve(NekoSearchSettings, (_input, _principal, self2) => {
|
|
@@ -28986,9 +29466,24 @@ async function createNekoServerServices(options) {
|
|
|
28986
29466
|
})
|
|
28987
29467
|
};
|
|
28988
29468
|
});
|
|
29469
|
+
host.serve(NekoDefaultModel, (_input, _principal, self2) => {
|
|
29470
|
+
const hub = shared(self2);
|
|
29471
|
+
return {
|
|
29472
|
+
variant: "ready",
|
|
29473
|
+
state: hub.defaultModel,
|
|
29474
|
+
set: (input, ctx) => serialize(async () => {
|
|
29475
|
+
await runtime.setDefaultModel(input);
|
|
29476
|
+
return hub.refreshDefaultModel(null, ctx);
|
|
29477
|
+
}),
|
|
29478
|
+
clear: (_input, ctx) => serialize(async () => {
|
|
29479
|
+
await runtime.setDefaultModel(null);
|
|
29480
|
+
return hub.refreshDefaultModel(null, ctx);
|
|
29481
|
+
})
|
|
29482
|
+
};
|
|
29483
|
+
});
|
|
28989
29484
|
host.serve(NekoModels, () => ({
|
|
28990
29485
|
variant: "ready",
|
|
28991
|
-
list: async () =>
|
|
29486
|
+
list: async () => (await runtime.listSelectableModels()).map((value) => ({
|
|
28992
29487
|
...value,
|
|
28993
29488
|
input: [...value.input],
|
|
28994
29489
|
supportedThinkingLevels: [...value.supportedThinkingLevels]
|
|
@@ -29282,7 +29777,6 @@ var SERVER_ID_FILENAME = "gateway-server-id";
|
|
|
29282
29777
|
async function startGateway(options = {}) {
|
|
29283
29778
|
const context = options.context ?? BACKGROUND_CONTEXT4;
|
|
29284
29779
|
const fileConfig = loadNekoConfig(options.configDir);
|
|
29285
|
-
const model = options.model ?? fileConfig.defaultModel ?? DEFAULT_MODEL;
|
|
29286
29780
|
const storagePath = options.storagePath ?? fileConfig.storagePath ?? DEFAULT_STORAGE_PATH;
|
|
29287
29781
|
const host = options.host ?? fileConfig.gateway?.host ?? DEFAULT_HOST;
|
|
29288
29782
|
const port = options.port !== undefined ? options.port : fileConfig.gateway?.port ?? (process.platform === "win32" ? 0 : DEFAULT_PORT);
|
|
@@ -29300,12 +29794,15 @@ async function startGateway(options = {}) {
|
|
|
29300
29794
|
...options.apiKey !== undefined ? { apiKey: options.apiKey } : {},
|
|
29301
29795
|
...searchProvider !== undefined ? { searchProvider } : {},
|
|
29302
29796
|
...fileConfig.context !== undefined ? { contextSettings: fileConfig.context } : {},
|
|
29303
|
-
model,
|
|
29797
|
+
...fileConfig.defaultModel !== undefined ? { model: fileConfig.defaultModel } : {},
|
|
29798
|
+
...options.model !== undefined ? { modelOverride: options.model } : {},
|
|
29304
29799
|
thinkingLevel: options.thinkingLevel ?? fileConfig.thinkingLevel ?? "high",
|
|
29305
29800
|
...options.cwd !== undefined ? { cwd: options.cwd } : {},
|
|
29306
29801
|
...options.providers !== undefined ? { providers: options.providers } : {},
|
|
29307
29802
|
...options.extensions !== undefined ? { extensions: options.extensions } : {},
|
|
29308
|
-
...options.promptHome !== undefined ? { promptHome: options.promptHome } : {}
|
|
29803
|
+
...options.promptHome !== undefined ? { promptHome: options.promptHome } : {},
|
|
29804
|
+
...fileConfig.shell?.path !== undefined ? { shellPath: fileConfig.shell.path } : {},
|
|
29805
|
+
...options.shellResolver !== undefined ? { shellResolver: options.shellResolver } : {}
|
|
29309
29806
|
});
|
|
29310
29807
|
try {
|
|
29311
29808
|
return await bindGateway(runtime, {
|
|
@@ -29315,12 +29812,12 @@ async function startGateway(options = {}) {
|
|
|
29315
29812
|
socketPath,
|
|
29316
29813
|
serverId,
|
|
29317
29814
|
nekoDir,
|
|
29318
|
-
model,
|
|
29319
29815
|
marketUrl,
|
|
29320
29816
|
promptHome: options.promptHome,
|
|
29321
29817
|
updates: options.updates,
|
|
29322
29818
|
updateIntervalMs: options.updateIntervalMs,
|
|
29323
29819
|
mobileServerName: options.mobileServerName,
|
|
29820
|
+
mobileNetwork: options.mobileNetwork,
|
|
29324
29821
|
onError: options.onError
|
|
29325
29822
|
});
|
|
29326
29823
|
} catch (error) {
|
|
@@ -29355,12 +29852,13 @@ async function bindGateway(runtime, options) {
|
|
|
29355
29852
|
wsListener = createWsListener({ host, port, onError: options.onError });
|
|
29356
29853
|
listeners.push(wsListener);
|
|
29357
29854
|
}
|
|
29855
|
+
const acceptLocal = (link) => chord.accept(link, {
|
|
29856
|
+
principal: { kind: "local", connectionId: randomUUID6() },
|
|
29857
|
+
onProtocolMismatch: ({ peer }) => options.onError?.(new Error(`Rejected a client on Chord protocol ${peer ?? "unknown"}; restart or update it.`))
|
|
29858
|
+
});
|
|
29358
29859
|
try {
|
|
29359
29860
|
for (const listener of listeners)
|
|
29360
|
-
await listener.start(
|
|
29361
|
-
principal: { kind: "local", connectionId: randomUUID6() },
|
|
29362
|
-
onProtocolMismatch: ({ peer }) => options.onError?.(new Error(`Rejected a client on Chord protocol ${peer ?? "unknown"}; restart or update it.`))
|
|
29363
|
-
}));
|
|
29861
|
+
await listener.start(acceptLocal);
|
|
29364
29862
|
} catch (error) {
|
|
29365
29863
|
await Promise.allSettled(listeners.map((listener) => listener.close()));
|
|
29366
29864
|
await chord.close();
|
|
@@ -29376,6 +29874,7 @@ async function bindGateway(runtime, options) {
|
|
|
29376
29874
|
serverId,
|
|
29377
29875
|
serverName: options.mobileServerName,
|
|
29378
29876
|
onError: options.onError,
|
|
29877
|
+
network: options.mobileNetwork,
|
|
29379
29878
|
chord: {
|
|
29380
29879
|
accept: (link, acceptOptions) => chord.accept(link, acceptOptions),
|
|
29381
29880
|
invalidatePrincipal: (match) => chord.invalidatePrincipal(match)
|
|
@@ -29400,6 +29899,9 @@ async function bindGateway(runtime, options) {
|
|
|
29400
29899
|
port: wsListener?.port,
|
|
29401
29900
|
runtime,
|
|
29402
29901
|
mobile,
|
|
29902
|
+
accept(link) {
|
|
29903
|
+
acceptLocal(link);
|
|
29904
|
+
},
|
|
29403
29905
|
async close() {
|
|
29404
29906
|
if (closing)
|
|
29405
29907
|
return closing;
|
|
@@ -29485,14 +29987,15 @@ function installShutdown(options = {}) {
|
|
|
29485
29987
|
exit(1);
|
|
29486
29988
|
});
|
|
29487
29989
|
};
|
|
29488
|
-
const
|
|
29990
|
+
const begin = (reason) => {
|
|
29489
29991
|
if (controller.signal.aborted) {
|
|
29490
29992
|
options.log?.("\u518D\u6B21\u6536\u5230\u7EC8\u6B62\u4FE1\u53F7\uFF0C\u5F3A\u5236\u9000\u51FA\u3002");
|
|
29491
29993
|
exit(130);
|
|
29492
29994
|
return;
|
|
29493
29995
|
}
|
|
29494
|
-
options.log?.(`
|
|
29495
|
-
\u6B63\u5728\u5173\u95ED\u2026`
|
|
29996
|
+
options.log?.(reason === undefined ? `
|
|
29997
|
+
\u6B63\u5728\u5173\u95ED\u2026` : `
|
|
29998
|
+
\u6B63\u5728\u5173\u95ED\uFF08${reason}\uFF09\u2026`);
|
|
29496
29999
|
controller.abort();
|
|
29497
30000
|
timer = setTimeout(() => {
|
|
29498
30001
|
options.log?.("\u5173\u95ED\u8D85\u65F6\uFF0C\u5F3A\u5236\u9000\u51FA\u3002");
|
|
@@ -29501,6 +30004,7 @@ function installShutdown(options = {}) {
|
|
|
29501
30004
|
timer.unref?.();
|
|
29502
30005
|
runCleanup();
|
|
29503
30006
|
};
|
|
30007
|
+
const onSignal = () => begin(undefined);
|
|
29504
30008
|
const installed = [];
|
|
29505
30009
|
for (const signal of shutdownSignals(proc.platform)) {
|
|
29506
30010
|
try {
|
|
@@ -29515,6 +30019,9 @@ function installShutdown(options = {}) {
|
|
|
29515
30019
|
if (controller.signal.aborted)
|
|
29516
30020
|
runCleanup();
|
|
29517
30021
|
},
|
|
30022
|
+
trigger(reason) {
|
|
30023
|
+
begin(reason);
|
|
30024
|
+
},
|
|
29518
30025
|
dispose() {
|
|
29519
30026
|
clearTimeout(timer);
|
|
29520
30027
|
for (const signal of installed)
|
|
@@ -29545,3 +30052,15 @@ if (gateway.wsUrl !== undefined)
|
|
|
29545
30052
|
console.log(` WebSocket: ${gateway.wsUrl}`);
|
|
29546
30053
|
console.log("\u6309 Ctrl+C \u505C\u6B62\u3002");
|
|
29547
30054
|
shutdown.setCleanup(() => gateway.close());
|
|
30055
|
+
if (options.stopOnStdin === true) {
|
|
30056
|
+
let triggered = false;
|
|
30057
|
+
const onStdinEnd = () => {
|
|
30058
|
+
if (triggered)
|
|
30059
|
+
return;
|
|
30060
|
+
triggered = true;
|
|
30061
|
+
shutdown.trigger("stdin \u5DF2\u5173\u95ED");
|
|
30062
|
+
};
|
|
30063
|
+
process.stdin.once("end", onStdinEnd);
|
|
30064
|
+
process.stdin.once("close", onStdinEnd);
|
|
30065
|
+
process.stdin.resume();
|
|
30066
|
+
}
|