neko-harness 0.4.6 → 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 +5 -3
- package/dist/client/assets/{index--Ml3HA5z.js → index-DYhp2f2g.js} +57 -57
- package/dist/client/index.html +1 -1
- package/dist/gateway.js +373 -67
- package/dist/neko.js +666 -272
- package/package.json +1 -1
package/dist/gateway.js
CHANGED
|
@@ -16501,6 +16501,7 @@ var GATEWAY_USAGE = `\u7528\u6CD5: neko serve [\u9009\u9879]
|
|
|
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
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
|
|
@@ -16584,6 +16585,9 @@ function parseGatewayArgs(argv) {
|
|
|
16584
16585
|
options.configDir = next;
|
|
16585
16586
|
i++;
|
|
16586
16587
|
break;
|
|
16588
|
+
case "--stop-on-stdin":
|
|
16589
|
+
options.stopOnStdin = true;
|
|
16590
|
+
break;
|
|
16587
16591
|
default:
|
|
16588
16592
|
throw new Error(`\u672A\u77E5\u9009\u9879: ${arg}`);
|
|
16589
16593
|
}
|
|
@@ -16890,6 +16894,16 @@ function validate(config, source) {
|
|
|
16890
16894
|
throw new Error(`neko config: ${source}: "skills.marketUrl" must be a string.`);
|
|
16891
16895
|
}
|
|
16892
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
|
+
}
|
|
16893
16907
|
const webSearch = config.webSearch;
|
|
16894
16908
|
if (webSearch !== undefined) {
|
|
16895
16909
|
if (typeof webSearch !== "object" || webSearch === null || Array.isArray(webSearch)) {
|
|
@@ -16909,10 +16923,36 @@ function validate(config, source) {
|
|
|
16909
16923
|
}
|
|
16910
16924
|
}
|
|
16911
16925
|
// packages/core/src/credentials.ts
|
|
16912
|
-
import { chmod, mkdir, open, readFile,
|
|
16926
|
+
import { chmod, mkdir, open, readFile, stat, unlink } from "fs/promises";
|
|
16913
16927
|
import { homedir as homedir2 } from "os";
|
|
16914
16928
|
import { dirname as dirname2, join as join2 } from "path";
|
|
16915
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
|
+
|
|
16916
16956
|
// packages/core/src/credential-providers.ts
|
|
16917
16957
|
var model = (id, name, envVar, signupUrl) => ({ id, kind: "model", name, envVar, signupUrl, keyless: false });
|
|
16918
16958
|
var MODEL_PROVIDERS = [
|
|
@@ -16981,12 +17021,15 @@ function normalizeEnabledProviders(saved) {
|
|
|
16981
17021
|
|
|
16982
17022
|
// packages/core/src/credentials.ts
|
|
16983
17023
|
import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
|
|
17024
|
+
var STALE_LOCK_MS = 30000;
|
|
16984
17025
|
var DEFAULT_CREDENTIALS_PATH = join2(homedir2(), ".neko", "credentials.json");
|
|
16985
17026
|
|
|
16986
17027
|
class FileCredentialStore {
|
|
16987
17028
|
path;
|
|
16988
|
-
|
|
17029
|
+
#renameDeps;
|
|
17030
|
+
constructor(path = DEFAULT_CREDENTIALS_PATH, renameDeps = {}) {
|
|
16989
17031
|
this.path = path;
|
|
17032
|
+
this.#renameDeps = renameDeps;
|
|
16990
17033
|
}
|
|
16991
17034
|
async#read() {
|
|
16992
17035
|
try {
|
|
@@ -17016,6 +17059,7 @@ class FileCredentialStore {
|
|
|
17016
17059
|
await mkdir(dirname2(this.path), { recursive: true, mode: 448 });
|
|
17017
17060
|
const lockPath = `${this.path}.lock`;
|
|
17018
17061
|
let lock;
|
|
17062
|
+
let reclaimed = false;
|
|
17019
17063
|
for (let attempt = 0;; attempt++) {
|
|
17020
17064
|
try {
|
|
17021
17065
|
lock = await open(lockPath, "wx", 384);
|
|
@@ -17023,6 +17067,16 @@ class FileCredentialStore {
|
|
|
17023
17067
|
} catch (error) {
|
|
17024
17068
|
if (error.code !== "EEXIST" || attempt >= 100)
|
|
17025
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
|
+
}
|
|
17026
17080
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
17027
17081
|
}
|
|
17028
17082
|
}
|
|
@@ -17044,7 +17098,7 @@ class FileCredentialStore {
|
|
|
17044
17098
|
} finally {
|
|
17045
17099
|
await file.close();
|
|
17046
17100
|
}
|
|
17047
|
-
await
|
|
17101
|
+
await renameWithRetry(temporary, this.path, this.#renameDeps);
|
|
17048
17102
|
return result;
|
|
17049
17103
|
} catch (error) {
|
|
17050
17104
|
if (callbackFailed)
|
|
@@ -19069,7 +19123,7 @@ var SearchText = defineTool2({
|
|
|
19069
19123
|
complete = false;
|
|
19070
19124
|
continue;
|
|
19071
19125
|
}
|
|
19072
|
-
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/);
|
|
19073
19127
|
for (let i = 0;i < lines.length; i++) {
|
|
19074
19128
|
ctx.abortSignal?.throwIfAborted();
|
|
19075
19129
|
const value = lines[i];
|
|
@@ -19502,8 +19556,11 @@ class JobManager {
|
|
|
19502
19556
|
outcome = { status: "failed", exitCode: null, reason: "timeout" };
|
|
19503
19557
|
else if (result.error.code === "aborted")
|
|
19504
19558
|
outcome = { status: "stopped", exitCode: null, reason: "aborted" };
|
|
19505
|
-
else
|
|
19559
|
+
else {
|
|
19560
|
+
entry.buffer.append(`${result.error.message}
|
|
19561
|
+
`);
|
|
19506
19562
|
outcome = { status: "failed", exitCode: null, reason: "spawn-error" };
|
|
19563
|
+
}
|
|
19507
19564
|
}
|
|
19508
19565
|
if (this.#closing) {
|
|
19509
19566
|
await new Promise((resolve) => {
|
|
@@ -19900,10 +19957,15 @@ var TOOL_GUIDELINES = {
|
|
|
19900
19957
|
]
|
|
19901
19958
|
};
|
|
19902
19959
|
var GENERIC_RULES = ["Be concise in your responses", "Show file paths clearly when working with files"];
|
|
19903
|
-
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) => {
|
|
19904
19966
|
const rules = [];
|
|
19905
19967
|
const seen = new Set;
|
|
19906
|
-
for (const rule of [...toolNames.flatMap((name) =>
|
|
19968
|
+
for (const rule of [...toolNames.flatMap((name) => toolRules(name, platform)), ...GENERIC_RULES]) {
|
|
19907
19969
|
if (seen.has(rule))
|
|
19908
19970
|
continue;
|
|
19909
19971
|
seen.add(rule);
|
|
@@ -19920,7 +19982,7 @@ ${content}
|
|
|
19920
19982
|
].join(`
|
|
19921
19983
|
|
|
19922
19984
|
`);
|
|
19923
|
-
var createNekoPrompt = (loadResources) => {
|
|
19985
|
+
var createNekoPrompt = (loadResources, platform = process.platform) => {
|
|
19924
19986
|
const built = new WeakMap;
|
|
19925
19987
|
const build = async (input, context) => {
|
|
19926
19988
|
const cwd = input.agent.cwd;
|
|
@@ -19928,7 +19990,7 @@ var createNekoPrompt = (loadResources) => {
|
|
|
19928
19990
|
const resources = cwd ? await loadResources(input.conversationId, cwd) : { contextFiles: [], skills: [] };
|
|
19929
19991
|
const sections = {
|
|
19930
19992
|
preamble: role ? ROLE_PROMPTS[role] + SUBAGENT_DELIVERY : PREAMBLE,
|
|
19931
|
-
rules: buildRules(input.agent.tools.map((tool) => tool.name)),
|
|
19993
|
+
rules: buildRules(input.agent.tools.map((tool) => tool.name), platform),
|
|
19932
19994
|
project_context: renderProjectContext(resources.contextFiles),
|
|
19933
19995
|
skills: formatSkillsForPrompt(resources.skills) || undefined,
|
|
19934
19996
|
cwd: cwd?.replace(/\\/g, "/")
|
|
@@ -19976,7 +20038,7 @@ var readResources = async (workspace, home, builtinDir, report, invocationName)
|
|
|
19976
20038
|
const contextFiles = [];
|
|
19977
20039
|
for (const path of [userAgentsFile(home), workspaceAgentsFile(workspace)]) {
|
|
19978
20040
|
try {
|
|
19979
|
-
const content = await readFile3(path, "utf8");
|
|
20041
|
+
const content = (await readFile3(path, "utf8")).replace(/^\uFEFF/, "");
|
|
19980
20042
|
if (Buffer.byteLength(content, "utf8") > 64 * 1024)
|
|
19981
20043
|
report({
|
|
19982
20044
|
path,
|
|
@@ -21433,11 +21495,152 @@ function createTavilyProvider(resolveKey, fetchImpl = fetch) {
|
|
|
21433
21495
|
};
|
|
21434
21496
|
}
|
|
21435
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
|
+
|
|
21436
21635
|
// packages/core/src/workspace-files.ts
|
|
21437
|
-
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";
|
|
21438
21637
|
import { basename as basename2, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve4, sep as sep2 } from "path";
|
|
21439
21638
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT2, withAbortSignal as withAbortSignal3 } from "@earendil-works/chord/context";
|
|
21440
|
-
import {
|
|
21639
|
+
import {
|
|
21640
|
+
ExecutionError,
|
|
21641
|
+
err,
|
|
21642
|
+
getOrThrow as getOrThrow2
|
|
21643
|
+
} from "@earendil-works/pi-durable/env";
|
|
21441
21644
|
import { Connection, LocalExecutionEnv, localDaemon } from "@earendil-works/pi-env";
|
|
21442
21645
|
var BROWSE_LIMITS = { children: 1000, readBytes: 512 * 1024 };
|
|
21443
21646
|
var ENV_TEMPLATE = /\.(example|sample|template)$/;
|
|
@@ -21475,6 +21678,19 @@ var FILE_CACHE_LIMITS = {
|
|
|
21475
21678
|
concurrent: 2
|
|
21476
21679
|
};
|
|
21477
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
|
+
|
|
21478
21694
|
class WorkspaceFiles {
|
|
21479
21695
|
#connection;
|
|
21480
21696
|
#starting;
|
|
@@ -21489,8 +21705,10 @@ class WorkspaceFiles {
|
|
|
21489
21705
|
#waiting = new Set;
|
|
21490
21706
|
#timer;
|
|
21491
21707
|
limits;
|
|
21492
|
-
|
|
21708
|
+
#shell;
|
|
21709
|
+
constructor(limits = FILE_CACHE_LIMITS, shell) {
|
|
21493
21710
|
this.limits = limits;
|
|
21711
|
+
this.#shell = shell;
|
|
21494
21712
|
this.#timer = setInterval(() => {
|
|
21495
21713
|
for (const [key, item] of this.#cache)
|
|
21496
21714
|
if (!item.waiters && Date.now() - item.used >= this.limits.idleMs)
|
|
@@ -21509,20 +21727,31 @@ class WorkspaceFiles {
|
|
|
21509
21727
|
queued: this.#waiting.size
|
|
21510
21728
|
};
|
|
21511
21729
|
}
|
|
21512
|
-
|
|
21730
|
+
shellStatus(options) {
|
|
21731
|
+
return this.#shell?.status(options);
|
|
21732
|
+
}
|
|
21733
|
+
#open() {
|
|
21513
21734
|
if (this.#closed)
|
|
21514
21735
|
throw new Error("File environment closed");
|
|
21515
21736
|
this.#connection ??= new Connection({ command: [localDaemon()] });
|
|
21516
|
-
return
|
|
21517
|
-
|
|
21737
|
+
return this.#connection;
|
|
21738
|
+
}
|
|
21739
|
+
env(cwd) {
|
|
21740
|
+
const options = {
|
|
21741
|
+
connection: this.#open(),
|
|
21518
21742
|
cwd,
|
|
21519
21743
|
...process.platform === "win32" ? { shellEnv: { PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" } } : {}
|
|
21520
|
-
}
|
|
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));
|
|
21521
21751
|
}
|
|
21522
21752
|
async#ready() {
|
|
21523
21753
|
if (!this.#starting) {
|
|
21524
|
-
this
|
|
21525
|
-
const connection = this.#connection;
|
|
21754
|
+
const connection = this.#open();
|
|
21526
21755
|
let timer;
|
|
21527
21756
|
const work = Promise.race([
|
|
21528
21757
|
connection.info().then(() => connection.session()),
|
|
@@ -21599,7 +21828,7 @@ class WorkspaceFiles {
|
|
|
21599
21828
|
{ path: item.root, recursive: true, exclude: { names: [".git"] } }
|
|
21600
21829
|
];
|
|
21601
21830
|
const git = resolve4(item.root, ".git");
|
|
21602
|
-
if (await
|
|
21831
|
+
if (await stat2(git).then(() => true, () => false))
|
|
21603
21832
|
targets.push({ path: git, recursive: true, exclude: { names: ["objects", "logs", "refs"] } });
|
|
21604
21833
|
const result = await env.watch(targets, (change) => {
|
|
21605
21834
|
for (const user of current.users) {
|
|
@@ -21707,7 +21936,7 @@ class WorkspaceFiles {
|
|
|
21707
21936
|
const slash = query.lastIndexOf("/"), prefix = slash < 0 ? "" : query.slice(0, slash + 1), needle = query.slice(slash + 1);
|
|
21708
21937
|
root = await realpath2(root);
|
|
21709
21938
|
const base = await workspaceTarget(root, prefix);
|
|
21710
|
-
if (!(await
|
|
21939
|
+
if (!(await stat2(base)).isDirectory())
|
|
21711
21940
|
throw new Error("Workspace directory is unavailable.");
|
|
21712
21941
|
return this.#withListing(root, base, signal, async (listing) => {
|
|
21713
21942
|
const pattern = new RegExp([...needle].map((c) => c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*"), /[A-Z]/.test(needle) ? "" : "i");
|
|
@@ -21737,7 +21966,7 @@ class WorkspaceFiles {
|
|
|
21737
21966
|
const clean = dir === "" ? "" : relativeQuery(dir).replace(/\/+$/, "");
|
|
21738
21967
|
root = await realpath2(root);
|
|
21739
21968
|
const target = await workspaceTarget(root, clean);
|
|
21740
|
-
if (!(await
|
|
21969
|
+
if (!(await stat2(target)).isDirectory())
|
|
21741
21970
|
throw new Error("Not a directory.");
|
|
21742
21971
|
const prefix = clean === "" ? "" : `${clean}/`;
|
|
21743
21972
|
return this.#withListing(root, root, signal, async (listing) => {
|
|
@@ -21779,7 +22008,7 @@ class WorkspaceFiles {
|
|
|
21779
22008
|
throw new Error("File not found.");
|
|
21780
22009
|
throw error;
|
|
21781
22010
|
}
|
|
21782
|
-
const info = await
|
|
22011
|
+
const info = await stat2(target);
|
|
21783
22012
|
if (!info.isFile())
|
|
21784
22013
|
throw new Error("Not a file.");
|
|
21785
22014
|
const base = { path: clean, size: info.size, mtimeMs: info.mtimeMs };
|
|
@@ -22006,7 +22235,8 @@ async function createNekoRuntime(options = {}) {
|
|
|
22006
22235
|
}));
|
|
22007
22236
|
return modelInfoCache;
|
|
22008
22237
|
};
|
|
22009
|
-
const
|
|
22238
|
+
const shellResolver = options.shellResolver ?? (process.platform === "win32" ? createWindowsShellResolver({ configPath: options.shellPath }) : undefined);
|
|
22239
|
+
const workspaceFiles = new WorkspaceFiles(undefined, shellResolver);
|
|
22010
22240
|
const storage = options.storage === "memory" ? new MemoryStorage : await openNodeSqliteStorage(options.storagePath ?? DEFAULT_STORAGE_PATH);
|
|
22011
22241
|
const registry = createRegistry();
|
|
22012
22242
|
registry.install(createCodingTools({ images: await createNodePhotonImages() }));
|
|
@@ -22516,6 +22746,10 @@ async function createNekoRuntime(options = {}) {
|
|
|
22516
22746
|
questions,
|
|
22517
22747
|
closed: harness.closed,
|
|
22518
22748
|
workspaceFiles,
|
|
22749
|
+
shellStatus(call) {
|
|
22750
|
+
const status = workspaceFiles.shellStatus(call);
|
|
22751
|
+
return status === undefined ? undefined : summarizeWindowsShell(status);
|
|
22752
|
+
},
|
|
22519
22753
|
conversation,
|
|
22520
22754
|
root: rootSession,
|
|
22521
22755
|
submit: rootSession.submit,
|
|
@@ -23418,6 +23652,14 @@ var NekoSkillMarket = contract("neko.skill-market", {
|
|
|
23418
23652
|
}
|
|
23419
23653
|
}
|
|
23420
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
|
+
]);
|
|
23421
23663
|
var ApplicationInfoSchema = Type7.Object({
|
|
23422
23664
|
runningVersion: str,
|
|
23423
23665
|
installedVersion: nullableString,
|
|
@@ -23425,7 +23667,8 @@ var ApplicationInfoSchema = Type7.Object({
|
|
|
23425
23667
|
latestVersion: nullableString,
|
|
23426
23668
|
checkedAt: Type7.Union([integer, nil]),
|
|
23427
23669
|
checkState: Type7.Enum(["unknown", "checking", "ok", "failed", "development"]),
|
|
23428
|
-
serverId: str
|
|
23670
|
+
serverId: str,
|
|
23671
|
+
shell: Type7.Optional(ShellStatusSchema)
|
|
23429
23672
|
});
|
|
23430
23673
|
var NekoApplicationInfo = contract("neko.application-info", {
|
|
23431
23674
|
version: 1,
|
|
@@ -23463,7 +23706,7 @@ function isMobilePrincipal(principal) {
|
|
|
23463
23706
|
|
|
23464
23707
|
// packages/gateway/src/mobile/store.ts
|
|
23465
23708
|
import { randomBytes } from "crypto";
|
|
23466
|
-
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";
|
|
23467
23710
|
import { join as join6 } from "path";
|
|
23468
23711
|
var MOBILE_STATE_FORMAT_VERSION = 1;
|
|
23469
23712
|
var MOBILE_DEFAULT_PORT = 8323;
|
|
@@ -23591,9 +23834,10 @@ async function writeFileAtomic(filePath, contents, mode) {
|
|
|
23591
23834
|
await writeFile(tempPath, contents, { mode });
|
|
23592
23835
|
if (POSIX)
|
|
23593
23836
|
await chmod2(tempPath, mode);
|
|
23594
|
-
await
|
|
23595
|
-
}
|
|
23837
|
+
await renameWithRetry(tempPath, filePath);
|
|
23838
|
+
} catch (error) {
|
|
23596
23839
|
await rm(tempPath, { force: true });
|
|
23840
|
+
throw error;
|
|
23597
23841
|
}
|
|
23598
23842
|
}
|
|
23599
23843
|
async function writeSecretFile(filePath, contents) {
|
|
@@ -24149,7 +24393,7 @@ function subagentClient(connect) {
|
|
|
24149
24393
|
import { createHash as createHash2 } from "crypto";
|
|
24150
24394
|
|
|
24151
24395
|
// packages/core/src/application-version.ts
|
|
24152
|
-
var RUNNING_VERSION = "0.4.
|
|
24396
|
+
var RUNNING_VERSION = "0.4.7";
|
|
24153
24397
|
var APPLICATION_PROTOCOL = 8;
|
|
24154
24398
|
function stableVersion(value) {
|
|
24155
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)));
|
|
@@ -26799,6 +27043,10 @@ function toMessage(error) {
|
|
|
26799
27043
|
}
|
|
26800
27044
|
return String(error);
|
|
26801
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
|
+
}
|
|
26802
27050
|
|
|
26803
27051
|
class MobileGateway {
|
|
26804
27052
|
serverId;
|
|
@@ -27127,11 +27375,12 @@ class MobileGateway {
|
|
|
27127
27375
|
connection.sendNotification(method, params);
|
|
27128
27376
|
}
|
|
27129
27377
|
advertisedAddress(localAddress) {
|
|
27378
|
+
const normalized = normalizeLocalAddress(localAddress);
|
|
27130
27379
|
const state = this.stateValue;
|
|
27131
|
-
if (state.status === "listening" && state.mode === "wsl-relay" &&
|
|
27380
|
+
if (state.status === "listening" && state.mode === "wsl-relay" && normalized === state.bindAddress) {
|
|
27132
27381
|
return state.address;
|
|
27133
27382
|
}
|
|
27134
|
-
return
|
|
27383
|
+
return normalized;
|
|
27135
27384
|
}
|
|
27136
27385
|
requireStore() {
|
|
27137
27386
|
if (this.storeHandle === undefined)
|
|
@@ -27392,7 +27641,7 @@ import { randomUUID as randomUUID5 } from "crypto";
|
|
|
27392
27641
|
import { ChordError as ChordError5, local as local3, method as method2 } from "@earendil-works/chord";
|
|
27393
27642
|
|
|
27394
27643
|
// packages/core/src/application-updates.ts
|
|
27395
|
-
import { mkdir as mkdir3, readFile as readFile6,
|
|
27644
|
+
import { mkdir as mkdir3, readFile as readFile6, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
27396
27645
|
import { homedir as homedir5 } from "os";
|
|
27397
27646
|
import { dirname as dirname5, join as join10 } from "path";
|
|
27398
27647
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -27522,7 +27771,7 @@ class UpdateChecker {
|
|
|
27522
27771
|
try {
|
|
27523
27772
|
await mkdir3(dirname5(this.#cachePath), { recursive: true });
|
|
27524
27773
|
await writeFile3(temporary, JSON.stringify({ latestVersion: value.version, checkedAt: now }), { mode: 384 });
|
|
27525
|
-
await
|
|
27774
|
+
await renameWithRetry(temporary, this.#cachePath);
|
|
27526
27775
|
} catch {} finally {
|
|
27527
27776
|
await rm3(temporary, { force: true }).catch(() => {});
|
|
27528
27777
|
}
|
|
@@ -28256,7 +28505,7 @@ function serveSessions(host, runtime, context) {
|
|
|
28256
28505
|
|
|
28257
28506
|
// packages/gateway/src/skill-market.ts
|
|
28258
28507
|
import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
|
|
28259
|
-
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";
|
|
28260
28509
|
import { homedir as homedir6 } from "os";
|
|
28261
28510
|
import { dirname as dirname6, join as join11 } from "path";
|
|
28262
28511
|
|
|
@@ -28401,11 +28650,11 @@ var ec = [
|
|
|
28401
28650
|
"stream finishing",
|
|
28402
28651
|
"invalid zip data"
|
|
28403
28652
|
];
|
|
28404
|
-
var
|
|
28653
|
+
var err2 = function(ind, msg, nt) {
|
|
28405
28654
|
var e = new Error(msg || ec[ind]);
|
|
28406
28655
|
e.code = ind;
|
|
28407
28656
|
if (Error.captureStackTrace)
|
|
28408
|
-
Error.captureStackTrace(e,
|
|
28657
|
+
Error.captureStackTrace(e, err2);
|
|
28409
28658
|
if (!nt)
|
|
28410
28659
|
throw e;
|
|
28411
28660
|
return e;
|
|
@@ -28438,7 +28687,7 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
28438
28687
|
var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
|
|
28439
28688
|
if (t > sl) {
|
|
28440
28689
|
if (noSt)
|
|
28441
|
-
|
|
28690
|
+
err2(0);
|
|
28442
28691
|
break;
|
|
28443
28692
|
}
|
|
28444
28693
|
if (resize)
|
|
@@ -28484,10 +28733,10 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
28484
28733
|
lm = hMap(lt, lbt, 1);
|
|
28485
28734
|
dm = hMap(dt, dbt, 1);
|
|
28486
28735
|
} else
|
|
28487
|
-
|
|
28736
|
+
err2(1);
|
|
28488
28737
|
if (pos > tbts) {
|
|
28489
28738
|
if (noSt)
|
|
28490
|
-
|
|
28739
|
+
err2(0);
|
|
28491
28740
|
break;
|
|
28492
28741
|
}
|
|
28493
28742
|
}
|
|
@@ -28500,11 +28749,11 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
28500
28749
|
pos += c & 15;
|
|
28501
28750
|
if (pos > tbts) {
|
|
28502
28751
|
if (noSt)
|
|
28503
|
-
|
|
28752
|
+
err2(0);
|
|
28504
28753
|
break;
|
|
28505
28754
|
}
|
|
28506
28755
|
if (!c)
|
|
28507
|
-
|
|
28756
|
+
err2(2);
|
|
28508
28757
|
if (sym < 256)
|
|
28509
28758
|
buf[bt++] = sym;
|
|
28510
28759
|
else if (sym == 256) {
|
|
@@ -28519,7 +28768,7 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
28519
28768
|
}
|
|
28520
28769
|
var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
|
|
28521
28770
|
if (!d)
|
|
28522
|
-
|
|
28771
|
+
err2(3);
|
|
28523
28772
|
pos += d & 15;
|
|
28524
28773
|
var dt = fd[dsym];
|
|
28525
28774
|
if (dsym > 3) {
|
|
@@ -28528,7 +28777,7 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
28528
28777
|
}
|
|
28529
28778
|
if (pos > tbts) {
|
|
28530
28779
|
if (noSt)
|
|
28531
|
-
|
|
28780
|
+
err2(0);
|
|
28532
28781
|
break;
|
|
28533
28782
|
}
|
|
28534
28783
|
if (resize)
|
|
@@ -28537,7 +28786,7 @@ var inflt = function(dat, st, buf, dict) {
|
|
|
28537
28786
|
if (bt < dt) {
|
|
28538
28787
|
var shift = dl - dt, dend = Math.min(dt, end);
|
|
28539
28788
|
if (shift + bt < 0)
|
|
28540
|
-
|
|
28789
|
+
err2(3);
|
|
28541
28790
|
for (;bt < dend; ++bt)
|
|
28542
28791
|
buf[bt] = dict[shift + bt];
|
|
28543
28792
|
}
|
|
@@ -28597,7 +28846,7 @@ function strFromU8(dat, latin1) {
|
|
|
28597
28846
|
} else {
|
|
28598
28847
|
var _a = dutf8(dat), { s, r } = _a;
|
|
28599
28848
|
if (r.length)
|
|
28600
|
-
|
|
28849
|
+
err2(8);
|
|
28601
28850
|
return s;
|
|
28602
28851
|
}
|
|
28603
28852
|
}
|
|
@@ -28624,7 +28873,7 @@ var z64hs = function(d, b, l, z, sc, su, off) {
|
|
|
28624
28873
|
}
|
|
28625
28874
|
}
|
|
28626
28875
|
if (z < 2)
|
|
28627
|
-
|
|
28876
|
+
err2(13);
|
|
28628
28877
|
}
|
|
28629
28878
|
return [sc, su, off, 0];
|
|
28630
28879
|
};
|
|
@@ -28633,7 +28882,7 @@ function unzipSync(data, opts) {
|
|
|
28633
28882
|
var e = data.length - 22;
|
|
28634
28883
|
for (;b4(data, e) != 101010256; --e) {
|
|
28635
28884
|
if (!e || data.length - e > 65558)
|
|
28636
|
-
|
|
28885
|
+
err2(13);
|
|
28637
28886
|
}
|
|
28638
28887
|
var c = b2(data, e + 8);
|
|
28639
28888
|
if (!c)
|
|
@@ -28663,7 +28912,7 @@ function unzipSync(data, opts) {
|
|
|
28663
28912
|
else if (c_2 == 8)
|
|
28664
28913
|
files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });
|
|
28665
28914
|
else
|
|
28666
|
-
|
|
28915
|
+
err2(14, "unknown compression type " + c_2);
|
|
28667
28916
|
}
|
|
28668
28917
|
}
|
|
28669
28918
|
return files;
|
|
@@ -28704,6 +28953,7 @@ function createSkillMarketClient(options) {
|
|
|
28704
28953
|
const home = options.home ?? homedir6();
|
|
28705
28954
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
28706
28955
|
const now = options.now ?? Date.now;
|
|
28956
|
+
const placeRename = options.renameImpl;
|
|
28707
28957
|
let base;
|
|
28708
28958
|
try {
|
|
28709
28959
|
base = new URL(options.marketUrl);
|
|
@@ -28808,6 +29058,8 @@ function createSkillMarketClient(options) {
|
|
|
28808
29058
|
throw new SkillMarketError("market_unavailable", "Project-scope install needs a workspaceRoot");
|
|
28809
29059
|
return projectSkillsDir(workspaceRoot);
|
|
28810
29060
|
};
|
|
29061
|
+
const BACKUP_PREFIX = ".bak-";
|
|
29062
|
+
const UUID_LENGTH = 36;
|
|
28811
29063
|
const sweepTempDirs = async (skillsDir) => {
|
|
28812
29064
|
const parent = dirname6(skillsDir);
|
|
28813
29065
|
let names = [];
|
|
@@ -28817,11 +29069,26 @@ function createSkillMarketClient(options) {
|
|
|
28817
29069
|
return;
|
|
28818
29070
|
}
|
|
28819
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
|
+
}
|
|
28820
29087
|
};
|
|
28821
29088
|
const install = async ({ name, scope, workspaceRoot, overwrite }) => {
|
|
28822
29089
|
const skillsDir = skillsDirFor(scope, workspaceRoot);
|
|
28823
29090
|
const target = join11(skillsDir, name);
|
|
28824
|
-
const exists = await
|
|
29091
|
+
const exists = await stat3(target).catch(() => {
|
|
28825
29092
|
return;
|
|
28826
29093
|
}) !== undefined;
|
|
28827
29094
|
if (exists && overwrite !== true)
|
|
@@ -28836,17 +29103,27 @@ function createSkillMarketClient(options) {
|
|
|
28836
29103
|
await mkdir4(dirname6(dest), { recursive: true });
|
|
28837
29104
|
await writeFile4(dest, bytes);
|
|
28838
29105
|
}
|
|
28839
|
-
if (exists)
|
|
28840
|
-
await rm4(target, { recursive: true, force: true });
|
|
28841
29106
|
await mkdir4(skillsDir, { recursive: true });
|
|
28842
|
-
|
|
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
|
+
}
|
|
28843
29120
|
} finally {
|
|
28844
29121
|
await rm4(temp, { recursive: true, force: true }).catch(() => {});
|
|
28845
29122
|
}
|
|
28846
29123
|
let shadowedBy;
|
|
28847
29124
|
if (scope === "user" && workspaceRoot !== undefined && workspaceRoot !== "") {
|
|
28848
29125
|
const projectDir = join11(projectSkillsDir(workspaceRoot), name);
|
|
28849
|
-
if (await
|
|
29126
|
+
if (await stat3(projectDir).catch(() => {
|
|
28850
29127
|
return;
|
|
28851
29128
|
}) !== undefined)
|
|
28852
29129
|
shadowedBy = "project";
|
|
@@ -28856,7 +29133,7 @@ function createSkillMarketClient(options) {
|
|
|
28856
29133
|
const uninstall = async ({ name, scope, workspaceRoot }) => {
|
|
28857
29134
|
const skillsDir = skillsDirFor(scope, workspaceRoot);
|
|
28858
29135
|
const target = join11(skillsDir, name);
|
|
28859
|
-
if (await
|
|
29136
|
+
if (await stat3(target).catch(() => {
|
|
28860
29137
|
return;
|
|
28861
29138
|
}) === undefined)
|
|
28862
29139
|
throw new SkillMarketError("skill_not_installed", `Skill ${name} is not installed at ${scope} level`);
|
|
@@ -28890,7 +29167,7 @@ function createSkillMarketClient(options) {
|
|
|
28890
29167
|
|
|
28891
29168
|
// packages/gateway/src/workspaces.ts
|
|
28892
29169
|
import { constants as constants3 } from "fs";
|
|
28893
|
-
import { access, readdir as readdir3, stat as
|
|
29170
|
+
import { access, readdir as readdir3, stat as stat4 } from "fs/promises";
|
|
28894
29171
|
import { dirname as dirname7, isAbsolute as isAbsolute4, join as join12 } from "path";
|
|
28895
29172
|
async function browseWorkspaces(options) {
|
|
28896
29173
|
const requested = options.path.replace(/^\/+([A-Za-z]:[\\/])/, "$1");
|
|
@@ -28922,7 +29199,7 @@ async function browseWorkspaces(options) {
|
|
|
28922
29199
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
28923
29200
|
continue;
|
|
28924
29201
|
try {
|
|
28925
|
-
if (!(await
|
|
29202
|
+
if (!(await stat4(target)).isDirectory())
|
|
28926
29203
|
continue;
|
|
28927
29204
|
await access(target, constants3.R_OK | constants3.X_OK);
|
|
28928
29205
|
directories.push({ name: entry.name, path: target });
|
|
@@ -28971,12 +29248,15 @@ async function createNekoServerServices(options) {
|
|
|
28971
29248
|
const updates = new UpdateChecker(options.updates);
|
|
28972
29249
|
await updates.local();
|
|
28973
29250
|
const updateListeners = new Set;
|
|
28974
|
-
const info = (value) =>
|
|
28975
|
-
|
|
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;
|
|
28976
29256
|
const publishUpdates = () => {
|
|
28977
29257
|
if (closed)
|
|
28978
29258
|
return;
|
|
28979
|
-
const next = JSON.stringify(updates.state);
|
|
29259
|
+
const next = JSON.stringify(info(updates.state));
|
|
28980
29260
|
if (next === published)
|
|
28981
29261
|
return;
|
|
28982
29262
|
published = next;
|
|
@@ -28995,6 +29275,7 @@ async function createNekoServerServices(options) {
|
|
|
28995
29275
|
variant: "ready",
|
|
28996
29276
|
info: current,
|
|
28997
29277
|
check: async () => {
|
|
29278
|
+
runtime.shellStatus({ refresh: true });
|
|
28998
29279
|
await updates.check(true);
|
|
28999
29280
|
publishUpdates();
|
|
29000
29281
|
return info(updates.state);
|
|
@@ -29519,7 +29800,9 @@ async function startGateway(options = {}) {
|
|
|
29519
29800
|
...options.cwd !== undefined ? { cwd: options.cwd } : {},
|
|
29520
29801
|
...options.providers !== undefined ? { providers: options.providers } : {},
|
|
29521
29802
|
...options.extensions !== undefined ? { extensions: options.extensions } : {},
|
|
29522
|
-
...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 } : {}
|
|
29523
29806
|
});
|
|
29524
29807
|
try {
|
|
29525
29808
|
return await bindGateway(runtime, {
|
|
@@ -29534,6 +29817,7 @@ async function startGateway(options = {}) {
|
|
|
29534
29817
|
updates: options.updates,
|
|
29535
29818
|
updateIntervalMs: options.updateIntervalMs,
|
|
29536
29819
|
mobileServerName: options.mobileServerName,
|
|
29820
|
+
mobileNetwork: options.mobileNetwork,
|
|
29537
29821
|
onError: options.onError
|
|
29538
29822
|
});
|
|
29539
29823
|
} catch (error) {
|
|
@@ -29568,12 +29852,13 @@ async function bindGateway(runtime, options) {
|
|
|
29568
29852
|
wsListener = createWsListener({ host, port, onError: options.onError });
|
|
29569
29853
|
listeners.push(wsListener);
|
|
29570
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
|
+
});
|
|
29571
29859
|
try {
|
|
29572
29860
|
for (const listener of listeners)
|
|
29573
|
-
await listener.start(
|
|
29574
|
-
principal: { kind: "local", connectionId: randomUUID6() },
|
|
29575
|
-
onProtocolMismatch: ({ peer }) => options.onError?.(new Error(`Rejected a client on Chord protocol ${peer ?? "unknown"}; restart or update it.`))
|
|
29576
|
-
}));
|
|
29861
|
+
await listener.start(acceptLocal);
|
|
29577
29862
|
} catch (error) {
|
|
29578
29863
|
await Promise.allSettled(listeners.map((listener) => listener.close()));
|
|
29579
29864
|
await chord.close();
|
|
@@ -29589,6 +29874,7 @@ async function bindGateway(runtime, options) {
|
|
|
29589
29874
|
serverId,
|
|
29590
29875
|
serverName: options.mobileServerName,
|
|
29591
29876
|
onError: options.onError,
|
|
29877
|
+
network: options.mobileNetwork,
|
|
29592
29878
|
chord: {
|
|
29593
29879
|
accept: (link, acceptOptions) => chord.accept(link, acceptOptions),
|
|
29594
29880
|
invalidatePrincipal: (match) => chord.invalidatePrincipal(match)
|
|
@@ -29613,6 +29899,9 @@ async function bindGateway(runtime, options) {
|
|
|
29613
29899
|
port: wsListener?.port,
|
|
29614
29900
|
runtime,
|
|
29615
29901
|
mobile,
|
|
29902
|
+
accept(link) {
|
|
29903
|
+
acceptLocal(link);
|
|
29904
|
+
},
|
|
29616
29905
|
async close() {
|
|
29617
29906
|
if (closing)
|
|
29618
29907
|
return closing;
|
|
@@ -29698,14 +29987,15 @@ function installShutdown(options = {}) {
|
|
|
29698
29987
|
exit(1);
|
|
29699
29988
|
});
|
|
29700
29989
|
};
|
|
29701
|
-
const
|
|
29990
|
+
const begin = (reason) => {
|
|
29702
29991
|
if (controller.signal.aborted) {
|
|
29703
29992
|
options.log?.("\u518D\u6B21\u6536\u5230\u7EC8\u6B62\u4FE1\u53F7\uFF0C\u5F3A\u5236\u9000\u51FA\u3002");
|
|
29704
29993
|
exit(130);
|
|
29705
29994
|
return;
|
|
29706
29995
|
}
|
|
29707
|
-
options.log?.(`
|
|
29708
|
-
\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`);
|
|
29709
29999
|
controller.abort();
|
|
29710
30000
|
timer = setTimeout(() => {
|
|
29711
30001
|
options.log?.("\u5173\u95ED\u8D85\u65F6\uFF0C\u5F3A\u5236\u9000\u51FA\u3002");
|
|
@@ -29714,6 +30004,7 @@ function installShutdown(options = {}) {
|
|
|
29714
30004
|
timer.unref?.();
|
|
29715
30005
|
runCleanup();
|
|
29716
30006
|
};
|
|
30007
|
+
const onSignal = () => begin(undefined);
|
|
29717
30008
|
const installed = [];
|
|
29718
30009
|
for (const signal of shutdownSignals(proc.platform)) {
|
|
29719
30010
|
try {
|
|
@@ -29728,6 +30019,9 @@ function installShutdown(options = {}) {
|
|
|
29728
30019
|
if (controller.signal.aborted)
|
|
29729
30020
|
runCleanup();
|
|
29730
30021
|
},
|
|
30022
|
+
trigger(reason) {
|
|
30023
|
+
begin(reason);
|
|
30024
|
+
},
|
|
29731
30025
|
dispose() {
|
|
29732
30026
|
clearTimeout(timer);
|
|
29733
30027
|
for (const signal of installed)
|
|
@@ -29758,3 +30052,15 @@ if (gateway.wsUrl !== undefined)
|
|
|
29758
30052
|
console.log(` WebSocket: ${gateway.wsUrl}`);
|
|
29759
30053
|
console.log("\u6309 Ctrl+C \u505C\u6B62\u3002");
|
|
29760
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
|
+
}
|