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/neko.js
CHANGED
|
@@ -80,7 +80,7 @@ function updateAction(input) {
|
|
|
80
80
|
return "install";
|
|
81
81
|
return "none";
|
|
82
82
|
}
|
|
83
|
-
var RUNNING_VERSION = "0.4.
|
|
83
|
+
var RUNNING_VERSION = "0.4.7", APPLICATION_PROTOCOL = 8, ApplicationProtocolError;
|
|
84
84
|
var init_application_version = __esm(() => {
|
|
85
85
|
ApplicationProtocolError = class ApplicationProtocolError extends Error {
|
|
86
86
|
constructor() {
|
|
@@ -90,8 +90,35 @@ var init_application_version = __esm(() => {
|
|
|
90
90
|
};
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
+
// packages/core/src/rename-with-retry.ts
|
|
94
|
+
import { rename } from "fs/promises";
|
|
95
|
+
async function renameWithRetry(from, to, deps = {}) {
|
|
96
|
+
const doRename = deps.rename ?? rename;
|
|
97
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
98
|
+
let delay = FIRST_DELAY_MS;
|
|
99
|
+
let waited = 0;
|
|
100
|
+
for (;; ) {
|
|
101
|
+
try {
|
|
102
|
+
await doRename(from, to);
|
|
103
|
+
return;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
const code = error.code;
|
|
106
|
+
if (typeof code !== "string" || !TRANSIENT.has(code) || waited >= MAX_TOTAL_MS)
|
|
107
|
+
throw error;
|
|
108
|
+
const step = Math.min(delay, MAX_TOTAL_MS - waited);
|
|
109
|
+
await sleep(step);
|
|
110
|
+
waited += step;
|
|
111
|
+
delay *= 2;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
var TRANSIENT, FIRST_DELAY_MS = 10, MAX_TOTAL_MS = 1000;
|
|
116
|
+
var init_rename_with_retry = __esm(() => {
|
|
117
|
+
TRANSIENT = new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
118
|
+
});
|
|
119
|
+
|
|
93
120
|
// packages/core/src/application-updates.ts
|
|
94
|
-
import { mkdir, readFile,
|
|
121
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
95
122
|
import { homedir } from "os";
|
|
96
123
|
import { dirname, join } from "path";
|
|
97
124
|
import { fileURLToPath } from "url";
|
|
@@ -220,7 +247,7 @@ class UpdateChecker {
|
|
|
220
247
|
try {
|
|
221
248
|
await mkdir(dirname(this.#cachePath), { recursive: true });
|
|
222
249
|
await writeFile(temporary, JSON.stringify({ latestVersion: value.version, checkedAt: now }), { mode: 384 });
|
|
223
|
-
await
|
|
250
|
+
await renameWithRetry(temporary, this.#cachePath);
|
|
224
251
|
} catch {} finally {
|
|
225
252
|
await rm(temporary, { force: true }).catch(() => {});
|
|
226
253
|
}
|
|
@@ -238,6 +265,7 @@ class UpdateChecker {
|
|
|
238
265
|
var INSTALL_MANIFEST;
|
|
239
266
|
var init_application_updates = __esm(() => {
|
|
240
267
|
init_application_version();
|
|
268
|
+
init_rename_with_retry();
|
|
241
269
|
INSTALL_MANIFEST = stableVersion(RUNNING_VERSION) ? fileURLToPath(new URL("../package.json", import.meta.url)) : undefined;
|
|
242
270
|
});
|
|
243
271
|
|
|
@@ -665,6 +693,16 @@ function validate(config, source) {
|
|
|
665
693
|
throw new Error(`neko config: ${source}: "skills.marketUrl" must be a string.`);
|
|
666
694
|
}
|
|
667
695
|
}
|
|
696
|
+
const shell = config.shell;
|
|
697
|
+
if (shell !== undefined) {
|
|
698
|
+
if (typeof shell !== "object" || shell === null || Array.isArray(shell)) {
|
|
699
|
+
throw new Error(`neko config: ${source}: "shell" must be an object.`);
|
|
700
|
+
}
|
|
701
|
+
const { path } = shell;
|
|
702
|
+
if (path !== undefined && typeof path !== "string") {
|
|
703
|
+
throw new Error(`neko config: ${source}: "shell.path" must be a string.`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
668
706
|
const webSearch = config.webSearch;
|
|
669
707
|
if (webSearch !== undefined) {
|
|
670
708
|
if (typeof webSearch !== "object" || webSearch === null || Array.isArray(webSearch)) {
|
|
@@ -759,15 +797,17 @@ var init_credential_providers = __esm(() => {
|
|
|
759
797
|
});
|
|
760
798
|
|
|
761
799
|
// packages/core/src/credentials.ts
|
|
762
|
-
import { chmod, mkdir as mkdir3, open, readFile as readFile3,
|
|
800
|
+
import { chmod, mkdir as mkdir3, open, readFile as readFile3, stat as stat2, unlink } from "fs/promises";
|
|
763
801
|
import { homedir as homedir3 } from "os";
|
|
764
802
|
import { dirname as dirname4, join as join4 } from "path";
|
|
765
803
|
import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
|
|
766
804
|
|
|
767
805
|
class FileCredentialStore {
|
|
768
806
|
path;
|
|
769
|
-
|
|
807
|
+
#renameDeps;
|
|
808
|
+
constructor(path = DEFAULT_CREDENTIALS_PATH, renameDeps = {}) {
|
|
770
809
|
this.path = path;
|
|
810
|
+
this.#renameDeps = renameDeps;
|
|
771
811
|
}
|
|
772
812
|
async#read() {
|
|
773
813
|
try {
|
|
@@ -797,6 +837,7 @@ class FileCredentialStore {
|
|
|
797
837
|
await mkdir3(dirname4(this.path), { recursive: true, mode: 448 });
|
|
798
838
|
const lockPath = `${this.path}.lock`;
|
|
799
839
|
let lock;
|
|
840
|
+
let reclaimed = false;
|
|
800
841
|
for (let attempt = 0;; attempt++) {
|
|
801
842
|
try {
|
|
802
843
|
lock = await open(lockPath, "wx", 384);
|
|
@@ -804,6 +845,16 @@ class FileCredentialStore {
|
|
|
804
845
|
} catch (error) {
|
|
805
846
|
if (error.code !== "EEXIST" || attempt >= 100)
|
|
806
847
|
throw new Error("Credential storage is busy or unavailable.");
|
|
848
|
+
if (!reclaimed) {
|
|
849
|
+
const info = await stat2(lockPath).catch(() => {
|
|
850
|
+
return;
|
|
851
|
+
});
|
|
852
|
+
if (info && Date.now() - info.mtimeMs > STALE_LOCK_MS) {
|
|
853
|
+
reclaimed = true;
|
|
854
|
+
await unlink(lockPath).catch(() => {});
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
807
858
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
808
859
|
}
|
|
809
860
|
}
|
|
@@ -825,7 +876,7 @@ class FileCredentialStore {
|
|
|
825
876
|
} finally {
|
|
826
877
|
await file.close();
|
|
827
878
|
}
|
|
828
|
-
await
|
|
879
|
+
await renameWithRetry(temporary, this.path, this.#renameDeps);
|
|
829
880
|
return result;
|
|
830
881
|
} catch (error) {
|
|
831
882
|
if (callbackFailed)
|
|
@@ -851,8 +902,9 @@ class FileCredentialStore {
|
|
|
851
902
|
});
|
|
852
903
|
}
|
|
853
904
|
}
|
|
854
|
-
var DEFAULT_CREDENTIALS_PATH;
|
|
905
|
+
var STALE_LOCK_MS = 30000, DEFAULT_CREDENTIALS_PATH;
|
|
855
906
|
var init_credentials = __esm(() => {
|
|
907
|
+
init_rename_with_retry();
|
|
856
908
|
init_credential_providers();
|
|
857
909
|
DEFAULT_CREDENTIALS_PATH = join4(homedir3(), ".neko", "credentials.json");
|
|
858
910
|
});
|
|
@@ -2897,7 +2949,7 @@ var init_agent_file_search = __esm(() => {
|
|
|
2897
2949
|
complete = false;
|
|
2898
2950
|
continue;
|
|
2899
2951
|
}
|
|
2900
|
-
const lines = new TextDecoder("utf-8", { fatal: true }).decode(data).split(/\r?\n/);
|
|
2952
|
+
const lines = new TextDecoder("utf-8", { fatal: true }).decode(data).replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
2901
2953
|
for (let i = 0;i < lines.length; i++) {
|
|
2902
2954
|
ctx.abortSignal?.throwIfAborted();
|
|
2903
2955
|
const value = lines[i];
|
|
@@ -3287,8 +3339,11 @@ class JobManager {
|
|
|
3287
3339
|
outcome = { status: "failed", exitCode: null, reason: "timeout" };
|
|
3288
3340
|
else if (result.error.code === "aborted")
|
|
3289
3341
|
outcome = { status: "stopped", exitCode: null, reason: "aborted" };
|
|
3290
|
-
else
|
|
3342
|
+
else {
|
|
3343
|
+
entry.buffer.append(`${result.error.message}
|
|
3344
|
+
`);
|
|
3291
3345
|
outcome = { status: "failed", exitCode: null, reason: "spawn-error" };
|
|
3346
|
+
}
|
|
3292
3347
|
}
|
|
3293
3348
|
if (this.#closing) {
|
|
3294
3349
|
await new Promise((resolve) => {
|
|
@@ -3696,10 +3751,13 @@ var init_invoke = __esm(() => {
|
|
|
3696
3751
|
|
|
3697
3752
|
// packages/core/src/prompt/prompt.ts
|
|
3698
3753
|
import { defineExtension as defineExtension5, section } from "@earendil-works/pi-durable";
|
|
3699
|
-
var KEYS, PREAMBLE = "You are an expert coding assistant operating inside neko, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.", TOOL_GUIDELINES, GENERIC_RULES,
|
|
3754
|
+
var KEYS, PREAMBLE = "You are an expert coding assistant operating inside neko, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.", TOOL_GUIDELINES, GENERIC_RULES, WINDOWS_SHELL_RULE = "On Windows, commands run in Git Bash (not PowerShell, cmd or WSL); write paths with forward slashes, e.g. D:/code or /d/code.", toolRules = (name, platform) => {
|
|
3755
|
+
const rules = TOOL_GUIDELINES[name] ?? [];
|
|
3756
|
+
return platform === "win32" && (name === "bash" || name === "job") ? [...rules, WINDOWS_SHELL_RULE] : rules;
|
|
3757
|
+
}, buildRules = (toolNames, platform) => {
|
|
3700
3758
|
const rules = [];
|
|
3701
3759
|
const seen = new Set;
|
|
3702
|
-
for (const rule of [...toolNames.flatMap((name) =>
|
|
3760
|
+
for (const rule of [...toolNames.flatMap((name) => toolRules(name, platform)), ...GENERIC_RULES]) {
|
|
3703
3761
|
if (seen.has(rule))
|
|
3704
3762
|
continue;
|
|
3705
3763
|
seen.add(rule);
|
|
@@ -3714,7 +3772,7 @@ ${content}
|
|
|
3714
3772
|
</project_instructions>`)
|
|
3715
3773
|
].join(`
|
|
3716
3774
|
|
|
3717
|
-
`), createNekoPrompt = (loadResources) => {
|
|
3775
|
+
`), createNekoPrompt = (loadResources, platform = process.platform) => {
|
|
3718
3776
|
const built = new WeakMap;
|
|
3719
3777
|
const build = async (input, context) => {
|
|
3720
3778
|
const cwd = input.agent.cwd;
|
|
@@ -3722,7 +3780,7 @@ ${content}
|
|
|
3722
3780
|
const resources = cwd ? await loadResources(input.conversationId, cwd) : { contextFiles: [], skills: [] };
|
|
3723
3781
|
const sections = {
|
|
3724
3782
|
preamble: role ? ROLE_PROMPTS[role] + SUBAGENT_DELIVERY : PREAMBLE,
|
|
3725
|
-
rules: buildRules(input.agent.tools.map((tool) => tool.name)),
|
|
3783
|
+
rules: buildRules(input.agent.tools.map((tool) => tool.name), platform),
|
|
3726
3784
|
project_context: renderProjectContext(resources.contextFiles),
|
|
3727
3785
|
skills: formatSkillsForPrompt(resources.skills) || undefined,
|
|
3728
3786
|
cwd: cwd?.replace(/\\/g, "/")
|
|
@@ -3801,7 +3859,7 @@ var userAgentsFile = (home) => join6(home, ".agents", "AGENTS.md"), userSkillsDi
|
|
|
3801
3859
|
const contextFiles = [];
|
|
3802
3860
|
for (const path of [userAgentsFile(home), workspaceAgentsFile(workspace)]) {
|
|
3803
3861
|
try {
|
|
3804
|
-
const content = await readFile5(path, "utf8");
|
|
3862
|
+
const content = (await readFile5(path, "utf8")).replace(/^\uFEFF/, "");
|
|
3805
3863
|
if (Buffer.byteLength(content, "utf8") > 64 * 1024)
|
|
3806
3864
|
report({
|
|
3807
3865
|
path,
|
|
@@ -5271,11 +5329,153 @@ var init_tavily = __esm(() => {
|
|
|
5271
5329
|
init_http();
|
|
5272
5330
|
});
|
|
5273
5331
|
|
|
5332
|
+
// packages/core/src/windows-shell.ts
|
|
5333
|
+
import { spawnSync } from "child_process";
|
|
5334
|
+
import { statSync as statSync2 } from "fs";
|
|
5335
|
+
import { win32 } from "path";
|
|
5336
|
+
function isWslBashPath(path) {
|
|
5337
|
+
const normalized = path.replace(/\//g, "\\").toLowerCase();
|
|
5338
|
+
return /^[a-z]:\\windows\\(?:system32|sysnative)\\bash\.exe$/.test(normalized) || /\\microsoft\\windowsapps\\(?:[^\\]+\\)*bash\.exe$/.test(normalized);
|
|
5339
|
+
}
|
|
5340
|
+
function envValue(env, name) {
|
|
5341
|
+
const wanted = name.toLowerCase();
|
|
5342
|
+
for (const [key, value] of Object.entries(env))
|
|
5343
|
+
if (key.toLowerCase() === wanted && value)
|
|
5344
|
+
return value;
|
|
5345
|
+
return;
|
|
5346
|
+
}
|
|
5347
|
+
function bashBesideGit(gitExe) {
|
|
5348
|
+
const dir = win32.dirname(gitExe);
|
|
5349
|
+
const parent = win32.dirname(dir);
|
|
5350
|
+
const name = win32.basename(dir).toLowerCase();
|
|
5351
|
+
if (name === "cmd")
|
|
5352
|
+
return win32.join(parent, "bin", "bash.exe");
|
|
5353
|
+
if (name === "bin") {
|
|
5354
|
+
const root = win32.basename(parent).toLowerCase() === "mingw64" ? win32.dirname(parent) : parent;
|
|
5355
|
+
return win32.join(root, "bin", "bash.exe");
|
|
5356
|
+
}
|
|
5357
|
+
return;
|
|
5358
|
+
}
|
|
5359
|
+
function configProblem(path, kind) {
|
|
5360
|
+
if (isWslBashPath(path))
|
|
5361
|
+
return "it is WSL's bash, which is not supported (commands must run in Git Bash)";
|
|
5362
|
+
if (kind === "missing")
|
|
5363
|
+
return "the file does not exist";
|
|
5364
|
+
if (kind === "directory")
|
|
5365
|
+
return "it is a directory, not bash.exe";
|
|
5366
|
+
return;
|
|
5367
|
+
}
|
|
5368
|
+
function resolveWindowsShell(deps, configPath) {
|
|
5369
|
+
if (configPath !== undefined && configPath !== "") {
|
|
5370
|
+
const problem = configProblem(configPath, deps.kind(configPath));
|
|
5371
|
+
if (problem !== undefined) {
|
|
5372
|
+
return {
|
|
5373
|
+
kind: "missing",
|
|
5374
|
+
reason: "invalid_config",
|
|
5375
|
+
detail: `shell.path "${configPath}" is invalid: ${problem}.`,
|
|
5376
|
+
searched: [configPath]
|
|
5377
|
+
};
|
|
5378
|
+
}
|
|
5379
|
+
return { kind: "found", path: configPath, source: "config" };
|
|
5380
|
+
}
|
|
5381
|
+
const searched = [];
|
|
5382
|
+
const check = (path) => {
|
|
5383
|
+
if (!searched.includes(path))
|
|
5384
|
+
searched.push(path);
|
|
5385
|
+
return !isWslBashPath(path) && deps.kind(path) === "file";
|
|
5386
|
+
};
|
|
5387
|
+
const roots = [
|
|
5388
|
+
envValue(deps.env, "ProgramFiles"),
|
|
5389
|
+
envValue(deps.env, "ProgramFiles(x86)"),
|
|
5390
|
+
(() => {
|
|
5391
|
+
const local = envValue(deps.env, "LOCALAPPDATA");
|
|
5392
|
+
return local === undefined ? undefined : win32.join(local, "Programs");
|
|
5393
|
+
})()
|
|
5394
|
+
];
|
|
5395
|
+
for (const root of roots) {
|
|
5396
|
+
if (root === undefined)
|
|
5397
|
+
continue;
|
|
5398
|
+
const candidate = win32.join(root, "Git", "bin", "bash.exe");
|
|
5399
|
+
if (check(candidate))
|
|
5400
|
+
return { kind: "found", path: candidate, source: "auto" };
|
|
5401
|
+
}
|
|
5402
|
+
for (const gitExe of deps.where("git.exe")) {
|
|
5403
|
+
const candidate = bashBesideGit(gitExe);
|
|
5404
|
+
if (candidate !== undefined && check(candidate))
|
|
5405
|
+
return { kind: "found", path: candidate, source: "auto" };
|
|
5406
|
+
}
|
|
5407
|
+
for (const candidate of deps.where("bash.exe")) {
|
|
5408
|
+
if (check(candidate))
|
|
5409
|
+
return { kind: "found", path: candidate, source: "auto" };
|
|
5410
|
+
}
|
|
5411
|
+
return { kind: "missing", reason: "not_found", detail: "No Git Bash was found.", searched };
|
|
5412
|
+
}
|
|
5413
|
+
function summarizeWindowsShell(status) {
|
|
5414
|
+
return status.kind === "found" ? { status: "ready", path: status.path, source: status.source } : { status: "missing", reason: status.reason, detail: status.detail };
|
|
5415
|
+
}
|
|
5416
|
+
function windowsShellMessage(status) {
|
|
5417
|
+
const lines = status.reason === "invalid_config" ? [
|
|
5418
|
+
`Cannot run shell commands: ${status.detail}`,
|
|
5419
|
+
"Fix shell.path in neko.config.json, or remove it to auto-detect Git Bash."
|
|
5420
|
+
] : [
|
|
5421
|
+
"Cannot run shell commands: Git Bash was not found. neko runs commands in Git for Windows' bash, not PowerShell, cmd or WSL.",
|
|
5422
|
+
"Install Git for Windows (https://git-scm.com/download/win), or set shell.path in neko.config.json to its bash.exe."
|
|
5423
|
+
];
|
|
5424
|
+
if (status.searched.length > 0)
|
|
5425
|
+
lines.push("Checked:", ...status.searched.map((path) => ` ${path}`));
|
|
5426
|
+
lines.push("File tools (read, write, edit) still work.");
|
|
5427
|
+
return lines.join(`
|
|
5428
|
+
`);
|
|
5429
|
+
}
|
|
5430
|
+
function systemWindowsShellDeps() {
|
|
5431
|
+
return {
|
|
5432
|
+
env: process.env,
|
|
5433
|
+
kind(path) {
|
|
5434
|
+
try {
|
|
5435
|
+
return statSync2(path).isDirectory() ? "directory" : "file";
|
|
5436
|
+
} catch {
|
|
5437
|
+
return "missing";
|
|
5438
|
+
}
|
|
5439
|
+
},
|
|
5440
|
+
where(program) {
|
|
5441
|
+
const result = spawnSync("where", [program], { encoding: "utf8", timeout: 5000, windowsHide: true });
|
|
5442
|
+
if (result.status !== 0 || typeof result.stdout !== "string")
|
|
5443
|
+
return [];
|
|
5444
|
+
return result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== "");
|
|
5445
|
+
}
|
|
5446
|
+
};
|
|
5447
|
+
}
|
|
5448
|
+
function createWindowsShellResolver(options) {
|
|
5449
|
+
const deps = options?.deps ?? systemWindowsShellDeps();
|
|
5450
|
+
const now = options?.now ?? Date.now;
|
|
5451
|
+
let cached;
|
|
5452
|
+
return {
|
|
5453
|
+
status(call) {
|
|
5454
|
+
if (cached !== undefined && call?.refresh !== true) {
|
|
5455
|
+
if (cached.status.kind === "found") {
|
|
5456
|
+
if (deps.kind(cached.status.path) === "file")
|
|
5457
|
+
return cached.status;
|
|
5458
|
+
} else if (now() - cached.at < WINDOWS_SHELL_RETRY_MS) {
|
|
5459
|
+
return cached.status;
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
5462
|
+
cached = { status: resolveWindowsShell(deps, options?.configPath), at: now() };
|
|
5463
|
+
return cached.status;
|
|
5464
|
+
}
|
|
5465
|
+
};
|
|
5466
|
+
}
|
|
5467
|
+
var WINDOWS_SHELL_RETRY_MS = 30000;
|
|
5468
|
+
var init_windows_shell = () => {};
|
|
5469
|
+
|
|
5274
5470
|
// packages/core/src/workspace-files.ts
|
|
5275
|
-
import { open as open2, realpath as realpath3, stat as
|
|
5471
|
+
import { open as open2, realpath as realpath3, stat as stat3 } from "fs/promises";
|
|
5276
5472
|
import { basename as basename3, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve4, sep as sep2 } from "path";
|
|
5277
5473
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT2, withAbortSignal as withAbortSignal3 } from "@earendil-works/chord/context";
|
|
5278
|
-
import {
|
|
5474
|
+
import {
|
|
5475
|
+
ExecutionError,
|
|
5476
|
+
err,
|
|
5477
|
+
getOrThrow as getOrThrow2
|
|
5478
|
+
} from "@earendil-works/pi-durable/env";
|
|
5279
5479
|
import { Connection, LocalExecutionEnv, localDaemon } from "@earendil-works/pi-env";
|
|
5280
5480
|
function isProtectedPath(path) {
|
|
5281
5481
|
const name = basename3(path).toLowerCase();
|
|
@@ -5313,8 +5513,10 @@ class WorkspaceFiles {
|
|
|
5313
5513
|
#waiting = new Set;
|
|
5314
5514
|
#timer;
|
|
5315
5515
|
limits;
|
|
5316
|
-
|
|
5516
|
+
#shell;
|
|
5517
|
+
constructor(limits = FILE_CACHE_LIMITS, shell) {
|
|
5317
5518
|
this.limits = limits;
|
|
5519
|
+
this.#shell = shell;
|
|
5318
5520
|
this.#timer = setInterval(() => {
|
|
5319
5521
|
for (const [key, item] of this.#cache)
|
|
5320
5522
|
if (!item.waiters && Date.now() - item.used >= this.limits.idleMs)
|
|
@@ -5333,20 +5535,31 @@ class WorkspaceFiles {
|
|
|
5333
5535
|
queued: this.#waiting.size
|
|
5334
5536
|
};
|
|
5335
5537
|
}
|
|
5336
|
-
|
|
5538
|
+
shellStatus(options) {
|
|
5539
|
+
return this.#shell?.status(options);
|
|
5540
|
+
}
|
|
5541
|
+
#open() {
|
|
5337
5542
|
if (this.#closed)
|
|
5338
5543
|
throw new Error("File environment closed");
|
|
5339
5544
|
this.#connection ??= new Connection({ command: [localDaemon()] });
|
|
5340
|
-
return
|
|
5341
|
-
|
|
5545
|
+
return this.#connection;
|
|
5546
|
+
}
|
|
5547
|
+
env(cwd) {
|
|
5548
|
+
const options = {
|
|
5549
|
+
connection: this.#open(),
|
|
5342
5550
|
cwd,
|
|
5343
5551
|
...process.platform === "win32" ? { shellEnv: { PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" } } : {}
|
|
5344
|
-
}
|
|
5552
|
+
};
|
|
5553
|
+
const shell = this.#shell?.status();
|
|
5554
|
+
if (shell === undefined)
|
|
5555
|
+
return new LocalExecutionEnv(options);
|
|
5556
|
+
if (shell.kind === "found")
|
|
5557
|
+
return new LocalExecutionEnv({ ...options, shellPath: shell.path });
|
|
5558
|
+
return new ShellUnavailableEnv(options, windowsShellMessage(shell));
|
|
5345
5559
|
}
|
|
5346
5560
|
async#ready() {
|
|
5347
5561
|
if (!this.#starting) {
|
|
5348
|
-
this
|
|
5349
|
-
const connection = this.#connection;
|
|
5562
|
+
const connection = this.#open();
|
|
5350
5563
|
let timer;
|
|
5351
5564
|
const work = Promise.race([
|
|
5352
5565
|
connection.info().then(() => connection.session()),
|
|
@@ -5423,7 +5636,7 @@ class WorkspaceFiles {
|
|
|
5423
5636
|
{ path: item.root, recursive: true, exclude: { names: [".git"] } }
|
|
5424
5637
|
];
|
|
5425
5638
|
const git = resolve4(item.root, ".git");
|
|
5426
|
-
if (await
|
|
5639
|
+
if (await stat3(git).then(() => true, () => false))
|
|
5427
5640
|
targets.push({ path: git, recursive: true, exclude: { names: ["objects", "logs", "refs"] } });
|
|
5428
5641
|
const result = await env.watch(targets, (change) => {
|
|
5429
5642
|
for (const user of current.users) {
|
|
@@ -5531,7 +5744,7 @@ class WorkspaceFiles {
|
|
|
5531
5744
|
const slash = query.lastIndexOf("/"), prefix = slash < 0 ? "" : query.slice(0, slash + 1), needle = query.slice(slash + 1);
|
|
5532
5745
|
root = await realpath3(root);
|
|
5533
5746
|
const base = await workspaceTarget(root, prefix);
|
|
5534
|
-
if (!(await
|
|
5747
|
+
if (!(await stat3(base)).isDirectory())
|
|
5535
5748
|
throw new Error("Workspace directory is unavailable.");
|
|
5536
5749
|
return this.#withListing(root, base, signal, async (listing) => {
|
|
5537
5750
|
const pattern = new RegExp([...needle].map((c) => c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*"), /[A-Z]/.test(needle) ? "" : "i");
|
|
@@ -5561,7 +5774,7 @@ class WorkspaceFiles {
|
|
|
5561
5774
|
const clean = dir === "" ? "" : relativeQuery(dir).replace(/\/+$/, "");
|
|
5562
5775
|
root = await realpath3(root);
|
|
5563
5776
|
const target = await workspaceTarget(root, clean);
|
|
5564
|
-
if (!(await
|
|
5777
|
+
if (!(await stat3(target)).isDirectory())
|
|
5565
5778
|
throw new Error("Not a directory.");
|
|
5566
5779
|
const prefix = clean === "" ? "" : `${clean}/`;
|
|
5567
5780
|
return this.#withListing(root, root, signal, async (listing) => {
|
|
@@ -5603,7 +5816,7 @@ class WorkspaceFiles {
|
|
|
5603
5816
|
throw new Error("File not found.");
|
|
5604
5817
|
throw error;
|
|
5605
5818
|
}
|
|
5606
|
-
const info = await
|
|
5819
|
+
const info = await stat3(target);
|
|
5607
5820
|
if (!info.isFile())
|
|
5608
5821
|
throw new Error("Not a file.");
|
|
5609
5822
|
const base = { path: clean, size: info.size, mtimeMs: info.mtimeMs };
|
|
@@ -5706,8 +5919,9 @@ function cancellable(work, signal) {
|
|
|
5706
5919
|
abort();
|
|
5707
5920
|
});
|
|
5708
5921
|
}
|
|
5709
|
-
var BROWSE_LIMITS, ENV_TEMPLATE, FILE_CACHE_LIMITS;
|
|
5922
|
+
var BROWSE_LIMITS, ENV_TEMPLATE, FILE_CACHE_LIMITS, ShellUnavailableEnv;
|
|
5710
5923
|
var init_workspace_files = __esm(() => {
|
|
5924
|
+
init_windows_shell();
|
|
5711
5925
|
BROWSE_LIMITS = { children: 1000, readBytes: 512 * 1024 };
|
|
5712
5926
|
ENV_TEMPLATE = /\.(example|sample|template)$/;
|
|
5713
5927
|
FILE_CACHE_LIMITS = {
|
|
@@ -5722,6 +5936,18 @@ var init_workspace_files = __esm(() => {
|
|
|
5722
5936
|
idleMs: 300000,
|
|
5723
5937
|
concurrent: 2
|
|
5724
5938
|
};
|
|
5939
|
+
ShellUnavailableEnv = class ShellUnavailableEnv extends LocalExecutionEnv {
|
|
5940
|
+
#message;
|
|
5941
|
+
constructor(options, message) {
|
|
5942
|
+
super(options);
|
|
5943
|
+
this.#message = message;
|
|
5944
|
+
}
|
|
5945
|
+
async exec(command, options, context) {
|
|
5946
|
+
if (typeof command === "string")
|
|
5947
|
+
return err(new ExecutionError("shell_unavailable", this.#message));
|
|
5948
|
+
return super.exec(command, options, context);
|
|
5949
|
+
}
|
|
5950
|
+
};
|
|
5725
5951
|
});
|
|
5726
5952
|
|
|
5727
5953
|
// packages/core/src/runtime.ts
|
|
@@ -5807,7 +6033,8 @@ async function createNekoRuntime(options = {}) {
|
|
|
5807
6033
|
}));
|
|
5808
6034
|
return modelInfoCache;
|
|
5809
6035
|
};
|
|
5810
|
-
const
|
|
6036
|
+
const shellResolver = options.shellResolver ?? (process.platform === "win32" ? createWindowsShellResolver({ configPath: options.shellPath }) : undefined);
|
|
6037
|
+
const workspaceFiles = new WorkspaceFiles(undefined, shellResolver);
|
|
5811
6038
|
const storage = options.storage === "memory" ? new MemoryStorage : await openNodeSqliteStorage(options.storagePath ?? DEFAULT_STORAGE_PATH);
|
|
5812
6039
|
const registry = createRegistry();
|
|
5813
6040
|
registry.install(createCodingTools({ images: await createNodePhotonImages() }));
|
|
@@ -6317,6 +6544,10 @@ async function createNekoRuntime(options = {}) {
|
|
|
6317
6544
|
questions,
|
|
6318
6545
|
closed: harness.closed,
|
|
6319
6546
|
workspaceFiles,
|
|
6547
|
+
shellStatus(call) {
|
|
6548
|
+
const status = workspaceFiles.shellStatus(call);
|
|
6549
|
+
return status === undefined ? undefined : summarizeWindowsShell(status);
|
|
6550
|
+
},
|
|
6320
6551
|
conversation,
|
|
6321
6552
|
root: rootSession,
|
|
6322
6553
|
submit: rootSession.submit,
|
|
@@ -6584,6 +6815,7 @@ var init_runtime = __esm(() => {
|
|
|
6584
6815
|
init_deepseek();
|
|
6585
6816
|
init_web_search();
|
|
6586
6817
|
init_tavily();
|
|
6818
|
+
init_windows_shell();
|
|
6587
6819
|
init_workspace();
|
|
6588
6820
|
init_workspace_files();
|
|
6589
6821
|
DEFAULT_MODEL = { provider: "deepseek", modelId: "deepseek-flash" };
|
|
@@ -6670,6 +6902,7 @@ var init_src = __esm(() => {
|
|
|
6670
6902
|
init_projection();
|
|
6671
6903
|
init_frontmatter();
|
|
6672
6904
|
init_queued();
|
|
6905
|
+
init_rename_with_retry();
|
|
6673
6906
|
init_runtime();
|
|
6674
6907
|
init_search_providers();
|
|
6675
6908
|
init_session_metadata();
|
|
@@ -6678,6 +6911,7 @@ var init_src = __esm(() => {
|
|
|
6678
6911
|
init_todos();
|
|
6679
6912
|
init_tool_approvals();
|
|
6680
6913
|
init_user_questions();
|
|
6914
|
+
init_windows_shell();
|
|
6681
6915
|
init_workspace();
|
|
6682
6916
|
init_workspace_files();
|
|
6683
6917
|
});
|
|
@@ -6803,7 +7037,7 @@ function contract(name, def) {
|
|
|
6803
7037
|
]));
|
|
6804
7038
|
return remote(name, { ...def, host: hostNamed(GATEWAY_HOST), variants });
|
|
6805
7039
|
}
|
|
6806
|
-
var GATEWAY_HOST = "neko", rpc = (args, result) => [args, result], str, num, bool, nil, integer, nullableString, phase, level, thinking, model2, change, usage, image, skill, call, DelegationSchema, SubagentSummarySchema, jobStatus, JobSummarySchema, JobOutputSchema, QueuedInputSchema, WithdrawResultSchema, SteerResultSchema, TodoItemSchema, TodoListSchema, QuestionOptionSchema, questionStatus, QuestionSummarySchema, WorkspaceEntrySchema, WorkspaceDirSchema, WorkspaceFileSchema, ChangeSummarySchema, ChangedFileSchema, ChangeListSchema, ChangeOpSchema, ChangeDetailSchema, QuestionListSchema, QuestionRecordSchema, QuestionResponseSchema, ToolInteractionSchema, ChatViewSchema, SessionSummarySchema, DirectoryStateSchema, WorkspaceSummarySchema, WorkspaceStateSchema, credentialStatus, CredentialStateSchema, AttachmentSchema, chatInput, NekoSessionDirectory, NekoSessionManagement, resize, inputLimits, modelInfo, NekoModels, imageChunk, NekoSubagent, NekoChat, browse, page, NekoWorkspaces, provider, NekoCredentials, SearchSettingsStateSchema, NekoSearchSettings, contextSource, ContextSettingsStateSchema, nullable = (schema) => Type7.Union([schema, nil]), ContextSettingsPatchSchema, NekoContextSettings, defaultModelSource, DefaultModelStateSchema, NekoDefaultModel, FileCandidatesSchema, NekoWorkspaceFiles, marketSkill, preview, scope, installResult, NekoSkillMarket, ApplicationInfoSchema, NekoApplicationInfo;
|
|
7040
|
+
var GATEWAY_HOST = "neko", rpc = (args, result) => [args, result], str, num, bool, nil, integer, nullableString, phase, level, thinking, model2, change, usage, image, skill, call, DelegationSchema, SubagentSummarySchema, jobStatus, JobSummarySchema, JobOutputSchema, QueuedInputSchema, WithdrawResultSchema, SteerResultSchema, TodoItemSchema, TodoListSchema, QuestionOptionSchema, questionStatus, QuestionSummarySchema, WorkspaceEntrySchema, WorkspaceDirSchema, WorkspaceFileSchema, ChangeSummarySchema, ChangedFileSchema, ChangeListSchema, ChangeOpSchema, ChangeDetailSchema, QuestionListSchema, QuestionRecordSchema, QuestionResponseSchema, ToolInteractionSchema, ChatViewSchema, SessionSummarySchema, DirectoryStateSchema, WorkspaceSummarySchema, WorkspaceStateSchema, credentialStatus, CredentialStateSchema, AttachmentSchema, chatInput, NekoSessionDirectory, NekoSessionManagement, resize, inputLimits, modelInfo, NekoModels, imageChunk, NekoSubagent, NekoChat, browse, page, NekoWorkspaces, provider, NekoCredentials, SearchSettingsStateSchema, NekoSearchSettings, contextSource, ContextSettingsStateSchema, nullable = (schema) => Type7.Union([schema, nil]), ContextSettingsPatchSchema, NekoContextSettings, defaultModelSource, DefaultModelStateSchema, NekoDefaultModel, FileCandidatesSchema, NekoWorkspaceFiles, marketSkill, preview, scope, installResult, NekoSkillMarket, ShellStatusSchema, ApplicationInfoSchema, NekoApplicationInfo;
|
|
6807
7041
|
var init_contract = __esm(() => {
|
|
6808
7042
|
init_interactions();
|
|
6809
7043
|
str = Type7.String();
|
|
@@ -7454,6 +7688,14 @@ var init_contract = __esm(() => {
|
|
|
7454
7688
|
}
|
|
7455
7689
|
}
|
|
7456
7690
|
});
|
|
7691
|
+
ShellStatusSchema = Type7.Union([
|
|
7692
|
+
Type7.Object({ status: Type7.Literal("ready"), path: str, source: Type7.Enum(["config", "auto"]) }),
|
|
7693
|
+
Type7.Object({
|
|
7694
|
+
status: Type7.Literal("missing"),
|
|
7695
|
+
reason: Type7.Enum(["not_found", "invalid_config"]),
|
|
7696
|
+
detail: Type7.Optional(str)
|
|
7697
|
+
})
|
|
7698
|
+
]);
|
|
7457
7699
|
ApplicationInfoSchema = Type7.Object({
|
|
7458
7700
|
runningVersion: str,
|
|
7459
7701
|
installedVersion: nullableString,
|
|
@@ -7461,7 +7703,8 @@ var init_contract = __esm(() => {
|
|
|
7461
7703
|
latestVersion: nullableString,
|
|
7462
7704
|
checkedAt: Type7.Union([integer, nil]),
|
|
7463
7705
|
checkState: Type7.Enum(["unknown", "checking", "ok", "failed", "development"]),
|
|
7464
|
-
serverId: str
|
|
7706
|
+
serverId: str,
|
|
7707
|
+
shell: Type7.Optional(ShellStatusSchema)
|
|
7465
7708
|
});
|
|
7466
7709
|
NekoApplicationInfo = contract("neko.application-info", {
|
|
7467
7710
|
version: 1,
|
|
@@ -22292,10 +22535,151 @@ function followTerminalTheme(ui, apply) {
|
|
|
22292
22535
|
}
|
|
22293
22536
|
var THEME_QUERY_TIMEOUT_MS = 100;
|
|
22294
22537
|
|
|
22538
|
+
// packages/tui/src/image-input.ts
|
|
22539
|
+
import { execFile as execFile2 } from "child_process";
|
|
22540
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
22541
|
+
import { readFile as readFile6, stat as stat4 } from "fs/promises";
|
|
22542
|
+
import { homedir as homedir6 } from "os";
|
|
22543
|
+
import { basename as basename5, join as join8, resolve as resolve5 } from "path";
|
|
22544
|
+
import { promisify as promisify2 } from "util";
|
|
22545
|
+
import { isKeyRelease as isKeyRelease4, isKeyRepeat as isKeyRepeat2, matchesKey as matchesKey8 } from "@earendil-works/pi-tui";
|
|
22546
|
+
function imagePaths(text, platform = process.platform) {
|
|
22547
|
+
const paths = [];
|
|
22548
|
+
let word = "", quote = "", escaped = false;
|
|
22549
|
+
for (const c of text.trim()) {
|
|
22550
|
+
if (escaped) {
|
|
22551
|
+
word += c;
|
|
22552
|
+
escaped = false;
|
|
22553
|
+
} else if (c === "\\" && quote !== "'" && platform !== "win32") {
|
|
22554
|
+
escaped = true;
|
|
22555
|
+
} else if (quote) {
|
|
22556
|
+
if (c === quote)
|
|
22557
|
+
quote = "";
|
|
22558
|
+
else
|
|
22559
|
+
word += c;
|
|
22560
|
+
} else if (c === '"' || c === "'")
|
|
22561
|
+
quote = c;
|
|
22562
|
+
else if (/\s/.test(c)) {
|
|
22563
|
+
if (word) {
|
|
22564
|
+
paths.push(word);
|
|
22565
|
+
word = "";
|
|
22566
|
+
}
|
|
22567
|
+
} else
|
|
22568
|
+
word += c;
|
|
22569
|
+
}
|
|
22570
|
+
if (quote || escaped)
|
|
22571
|
+
return null;
|
|
22572
|
+
if (word)
|
|
22573
|
+
paths.push(word);
|
|
22574
|
+
return paths.length && paths.every((p) => /\.(png|jpe?g|webp)$/i.test(p)) ? paths : null;
|
|
22575
|
+
}
|
|
22576
|
+
function expandHome(path, platform = process.platform) {
|
|
22577
|
+
if (path.startsWith("~/") || platform === "win32" && path.startsWith("~\\"))
|
|
22578
|
+
return join8(homedir6(), path.slice(2));
|
|
22579
|
+
return path;
|
|
22580
|
+
}
|
|
22581
|
+
async function readImageFile(path, cwd, platform = process.platform) {
|
|
22582
|
+
const full = resolve5(cwd, expandHome(path, platform));
|
|
22583
|
+
const info = await stat4(full);
|
|
22584
|
+
if (!info.isFile() || info.size > 20 * 1024 * 1024)
|
|
22585
|
+
throw new Error("Image must be a file no larger than 20 MiB");
|
|
22586
|
+
const bytes = await readFile6(full);
|
|
22587
|
+
const after = await stat4(full);
|
|
22588
|
+
if (bytes.length !== info.size || after.size !== info.size || after.mtimeMs !== info.mtimeMs)
|
|
22589
|
+
throw new Error("Image changed while reading");
|
|
22590
|
+
const mimeType = /\.png$/i.test(full) ? "image/png" : /\.webp$/i.test(full) ? "image/webp" : "image/jpeg";
|
|
22591
|
+
return { name: basename5(full), mimeType, bytes };
|
|
22592
|
+
}
|
|
22593
|
+
async function command(file, args, encoding = "utf8", timeout = 2000) {
|
|
22594
|
+
return (await exec(file, args, { timeout, maxBuffer: 30 * 1024 * 1024, encoding })).stdout;
|
|
22595
|
+
}
|
|
22596
|
+
async function clipboard(options = {}) {
|
|
22597
|
+
const run = options.command ?? command;
|
|
22598
|
+
const platform = options.platform ?? process.platform;
|
|
22599
|
+
if (platform === "darwin") {
|
|
22600
|
+
const files = await run("osascript", [
|
|
22601
|
+
"-e",
|
|
22602
|
+
`try
|
|
22603
|
+
set fs to the clipboard as \xABclass furl\xBB
|
|
22604
|
+
return POSIX path of fs
|
|
22605
|
+
on error
|
|
22606
|
+
return ""
|
|
22607
|
+
end try`
|
|
22608
|
+
]).catch(() => "");
|
|
22609
|
+
if (String(files).trim())
|
|
22610
|
+
return /\.(png|jpe?g|webp)$/i.test(String(files).trim()) ? { paths: [String(files).trim()] } : { text: String(files).trim() };
|
|
22611
|
+
const image = String(await run("osascript", ["-e", `try
|
|
22612
|
+
return the clipboard as \xABclass PNGf\xBB
|
|
22613
|
+
on error
|
|
22614
|
+
return ""
|
|
22615
|
+
end try`]).catch(() => ""));
|
|
22616
|
+
const hex = /\u00ABdata PNGf([0-9A-Fa-f]+)\u00BB/.exec(image)?.[1];
|
|
22617
|
+
if (hex)
|
|
22618
|
+
return { image: { name: "Clipboard.png", mimeType: "image/png", bytes: Buffer.from(hex, "hex") } };
|
|
22619
|
+
return { text: String(await run("pbpaste", [])) };
|
|
22620
|
+
}
|
|
22621
|
+
if (platform === "win32")
|
|
22622
|
+
return windowsClipboard(run);
|
|
22623
|
+
let windowsError;
|
|
22624
|
+
if (platform === "linux" && (options.wsl ?? runningInWsl())) {
|
|
22625
|
+
try {
|
|
22626
|
+
return await windowsClipboard(run);
|
|
22627
|
+
} catch (error) {
|
|
22628
|
+
windowsError = error;
|
|
22629
|
+
}
|
|
22630
|
+
}
|
|
22631
|
+
const wayland = options.wayland ?? !!process.env.WAYLAND_DISPLAY;
|
|
22632
|
+
const tool = wayland ? "wl-paste" : "xclip";
|
|
22633
|
+
try {
|
|
22634
|
+
const bytes = await run(tool, wayland ? ["--type", "image/png"] : ["-selection", "clipboard", "-t", "image/png", "-o"], "buffer");
|
|
22635
|
+
if (bytes.length)
|
|
22636
|
+
return { image: { name: "Clipboard.png", mimeType: "image/png", bytes: Buffer.from(bytes) } };
|
|
22637
|
+
} catch {}
|
|
22638
|
+
try {
|
|
22639
|
+
return { text: String(await run(tool, wayland ? ["--no-newline"] : ["-selection", "clipboard", "-o"])) };
|
|
22640
|
+
} catch (error) {
|
|
22641
|
+
throw windowsError ?? error;
|
|
22642
|
+
}
|
|
22643
|
+
}
|
|
22644
|
+
async function windowsClipboard(run) {
|
|
22645
|
+
const value = String(await run("powershell.exe", ["-NoProfile", "-STA", "-Command", WINDOWS_CLIPBOARD_SCRIPT], "utf8", 8000));
|
|
22646
|
+
if (value.startsWith("IMAGE:"))
|
|
22647
|
+
return {
|
|
22648
|
+
image: { name: "Clipboard.png", mimeType: "image/png", bytes: Buffer.from(value.slice(6).trim(), "base64") }
|
|
22649
|
+
};
|
|
22650
|
+
return { text: value.replace(/\r?\n$/, "") };
|
|
22651
|
+
}
|
|
22652
|
+
function runningInWsl() {
|
|
22653
|
+
wslHost ??= process.platform === "linux" && (Boolean(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP || process.env.WSLENV) || /microsoft|wsl/i.test(readFileOrEmpty("/proc/version")));
|
|
22654
|
+
return wslHost;
|
|
22655
|
+
}
|
|
22656
|
+
function readFileOrEmpty(path) {
|
|
22657
|
+
try {
|
|
22658
|
+
return readFileSync2(path, "utf8");
|
|
22659
|
+
} catch {
|
|
22660
|
+
return "";
|
|
22661
|
+
}
|
|
22662
|
+
}
|
|
22663
|
+
function clipboardShortcutKey(platform = process.platform, wsl = runningInWsl()) {
|
|
22664
|
+
return platform === "win32" || platform === "linux" && wsl ? "alt+v" : "ctrl+v";
|
|
22665
|
+
}
|
|
22666
|
+
function isClipboardShortcut(data, platform = process.platform, wsl = runningInWsl()) {
|
|
22667
|
+
return !isKeyRelease4(data) && !isKeyRepeat2(data) && matchesKey8(data, clipboardShortcutKey(platform, wsl));
|
|
22668
|
+
}
|
|
22669
|
+
var exec, WINDOWS_CLIPBOARD_SCRIPT, wslHost;
|
|
22670
|
+
var init_image_input = __esm(() => {
|
|
22671
|
+
exec = promisify2(execFile2);
|
|
22672
|
+
WINDOWS_CLIPBOARD_SCRIPT = [
|
|
22673
|
+
"try{[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false)}catch{}",
|
|
22674
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
22675
|
+
"$i=[Windows.Forms.Clipboard]::GetImage()",
|
|
22676
|
+
'if($i){$m=New-Object IO.MemoryStream; $i.Save($m,[Drawing.Imaging.ImageFormat]::Png); "IMAGE:"+[Convert]::ToBase64String($m.ToArray());$i.Dispose();$m.Dispose()}else{Get-Clipboard -Raw}'
|
|
22677
|
+
].join("; ");
|
|
22678
|
+
});
|
|
22679
|
+
|
|
22295
22680
|
// packages/tui/src/export-command.ts
|
|
22296
22681
|
import { open as open3, unlink as unlink2 } from "fs/promises";
|
|
22297
|
-
import {
|
|
22298
|
-
import { resolve as resolve5 } from "path";
|
|
22682
|
+
import { resolve as resolve6 } from "path";
|
|
22299
22683
|
function parseExportArgs(input) {
|
|
22300
22684
|
const words = [];
|
|
22301
22685
|
let word = "";
|
|
@@ -22345,7 +22729,7 @@ async function exportToFile(input) {
|
|
|
22345
22729
|
const format = input.path === undefined ? "markdown" : exportFormatFromPath(input.path);
|
|
22346
22730
|
if (format === null)
|
|
22347
22731
|
throw new Error("Unsupported export format: use a .md, .html or .json path");
|
|
22348
|
-
const target =
|
|
22732
|
+
const target = resolve6(input.cwd, input.path === undefined ? exportFileName(input.source.title, format, input.source.exportedAt) : expandHome(input.path, input.platform));
|
|
22349
22733
|
let handle;
|
|
22350
22734
|
try {
|
|
22351
22735
|
handle = await open3(target, "wx");
|
|
@@ -22374,6 +22758,7 @@ async function exportToFile(input) {
|
|
|
22374
22758
|
var EXPORT_USAGE = "Usage: /export [path.md|path.html|path.json] [--thinking]";
|
|
22375
22759
|
var init_export_command = __esm(() => {
|
|
22376
22760
|
init_conversation_export();
|
|
22761
|
+
init_image_input();
|
|
22377
22762
|
});
|
|
22378
22763
|
|
|
22379
22764
|
// packages/core/src/file-mentions.ts
|
|
@@ -22683,7 +23068,11 @@ var init_ws_listener = __esm(() => {
|
|
|
22683
23068
|
async function connectNekoGateway(address, signal) {
|
|
22684
23069
|
signal?.throwIfAborted();
|
|
22685
23070
|
const link = address.transport === "unix" ? await createUnixLink(address.socketPath, signal) : createWsLink(address.url, signal);
|
|
22686
|
-
|
|
23071
|
+
return connectNekoGatewayLink(link, address.serverId, signal);
|
|
23072
|
+
}
|
|
23073
|
+
async function connectNekoGatewayLink(link, serverId, signal) {
|
|
23074
|
+
signal?.throwIfAborted();
|
|
23075
|
+
const connection = createGatewayConnection(link, serverId);
|
|
22687
23076
|
const controller = new AbortController;
|
|
22688
23077
|
const abort = () => controller.abort(signal?.reason);
|
|
22689
23078
|
signal?.addEventListener("abort", abort, { once: true });
|
|
@@ -22732,8 +23121,8 @@ var init_principal = __esm(() => {
|
|
|
22732
23121
|
|
|
22733
23122
|
// packages/gateway/src/mobile/store.ts
|
|
22734
23123
|
import { randomBytes } from "crypto";
|
|
22735
|
-
import { chmod as chmod2, mkdir as mkdir5, readFile as
|
|
22736
|
-
import { join as
|
|
23124
|
+
import { chmod as chmod2, mkdir as mkdir5, readFile as readFile7, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
23125
|
+
import { join as join9 } from "path";
|
|
22737
23126
|
function defaultState() {
|
|
22738
23127
|
return {
|
|
22739
23128
|
formatVersion: MOBILE_STATE_FORMAT_VERSION,
|
|
@@ -22853,9 +23242,10 @@ async function writeFileAtomic(filePath, contents, mode) {
|
|
|
22853
23242
|
await writeFile3(tempPath, contents, { mode });
|
|
22854
23243
|
if (POSIX)
|
|
22855
23244
|
await chmod2(tempPath, mode);
|
|
22856
|
-
await
|
|
22857
|
-
}
|
|
23245
|
+
await renameWithRetry(tempPath, filePath);
|
|
23246
|
+
} catch (error) {
|
|
22858
23247
|
await rm3(tempPath, { force: true });
|
|
23248
|
+
throw error;
|
|
22859
23249
|
}
|
|
22860
23250
|
}
|
|
22861
23251
|
async function writeSecretFile(filePath, contents) {
|
|
@@ -22863,7 +23253,7 @@ async function writeSecretFile(filePath, contents) {
|
|
|
22863
23253
|
}
|
|
22864
23254
|
async function readSecretFile(filePath) {
|
|
22865
23255
|
try {
|
|
22866
|
-
return await
|
|
23256
|
+
return await readFile7(filePath, "utf8");
|
|
22867
23257
|
} catch (error) {
|
|
22868
23258
|
if (isNotFound(error))
|
|
22869
23259
|
return;
|
|
@@ -22873,7 +23263,7 @@ async function readSecretFile(filePath) {
|
|
|
22873
23263
|
async function loadState(statePath) {
|
|
22874
23264
|
let raw;
|
|
22875
23265
|
try {
|
|
22876
|
-
raw = await
|
|
23266
|
+
raw = await readFile7(statePath, "utf8");
|
|
22877
23267
|
} catch (error) {
|
|
22878
23268
|
if (isNotFound(error))
|
|
22879
23269
|
return freezeState(defaultState());
|
|
@@ -22897,21 +23287,21 @@ class MobileStore {
|
|
|
22897
23287
|
queue = Promise.resolve();
|
|
22898
23288
|
constructor(mobileDir, initial) {
|
|
22899
23289
|
this.mobileDir = mobileDir;
|
|
22900
|
-
this.tlsDir =
|
|
22901
|
-
this.statePath =
|
|
22902
|
-
this.adminTokenPath =
|
|
23290
|
+
this.tlsDir = join9(mobileDir, "tls");
|
|
23291
|
+
this.statePath = join9(mobileDir, "state.json");
|
|
23292
|
+
this.adminTokenPath = join9(mobileDir, "admin-token");
|
|
22903
23293
|
this.current = initial;
|
|
22904
23294
|
}
|
|
22905
23295
|
static async open(nekoDir) {
|
|
22906
|
-
const mobileDir =
|
|
22907
|
-
const tlsDir =
|
|
23296
|
+
const mobileDir = join9(nekoDir, "mobile");
|
|
23297
|
+
const tlsDir = join9(mobileDir, "tls");
|
|
22908
23298
|
await mkdir5(mobileDir, { recursive: true, mode: 448 });
|
|
22909
23299
|
await mkdir5(tlsDir, { recursive: true, mode: 448 });
|
|
22910
23300
|
if (POSIX) {
|
|
22911
23301
|
await chmod2(mobileDir, 448);
|
|
22912
23302
|
await chmod2(tlsDir, 448);
|
|
22913
23303
|
}
|
|
22914
|
-
return new MobileStore(mobileDir, await loadState(
|
|
23304
|
+
return new MobileStore(mobileDir, await loadState(join9(mobileDir, "state.json")));
|
|
22915
23305
|
}
|
|
22916
23306
|
getState() {
|
|
22917
23307
|
return this.current;
|
|
@@ -23033,6 +23423,7 @@ class MobileStore {
|
|
|
23033
23423
|
}
|
|
23034
23424
|
var MOBILE_STATE_FORMAT_VERSION = 1, MOBILE_DEFAULT_PORT = 8323, MOBILE_ADMIN_TOKEN_BYTES = 32, POSIX, ADMIN_TOKEN_PATTERN, CREDENTIAL_HASH_PATTERN, tempCounter = 0;
|
|
23035
23425
|
var init_store = __esm(() => {
|
|
23426
|
+
init_src();
|
|
23036
23427
|
POSIX = process.platform !== "win32";
|
|
23037
23428
|
ADMIN_TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
23038
23429
|
CREDENTIAL_HASH_PATTERN = /^[0-9a-fA-F]{64}$/;
|
|
@@ -41475,7 +41866,7 @@ import {
|
|
|
41475
41866
|
randomBytes as randomBytes2,
|
|
41476
41867
|
X509Certificate
|
|
41477
41868
|
} from "crypto";
|
|
41478
|
-
import { join as
|
|
41869
|
+
import { join as join10 } from "path";
|
|
41479
41870
|
function mobileCommonName(serverId) {
|
|
41480
41871
|
return `neko-${serverId.slice(0, 8).toLowerCase()}.invalid`;
|
|
41481
41872
|
}
|
|
@@ -41567,8 +41958,8 @@ class MobileCertificate {
|
|
|
41567
41958
|
this.current = initial;
|
|
41568
41959
|
}
|
|
41569
41960
|
static async load(store, serverId) {
|
|
41570
|
-
const certPath =
|
|
41571
|
-
const keyPath =
|
|
41961
|
+
const certPath = join10(store.tlsDir, TLS_CERT_FILE);
|
|
41962
|
+
const keyPath = join10(store.tlsDir, TLS_KEY_FILE);
|
|
41572
41963
|
const certPem = await readSecretFile(certPath);
|
|
41573
41964
|
const keyPem = await readSecretFile(keyPath);
|
|
41574
41965
|
if (certPem === undefined && keyPem === undefined) {
|
|
@@ -42098,10 +42489,10 @@ var init_relay = __esm(() => {
|
|
|
42098
42489
|
});
|
|
42099
42490
|
|
|
42100
42491
|
// packages/gateway/src/mobile/wsl.ts
|
|
42101
|
-
import { execFile as
|
|
42102
|
-
import { accessSync as accessSync2, constants as constants2, readFileSync as
|
|
42492
|
+
import { execFile as execFile3 } from "child_process";
|
|
42493
|
+
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync3 } from "fs";
|
|
42103
42494
|
import { networkInterfaces as networkInterfaces2 } from "os";
|
|
42104
|
-
import { delimiter, join as
|
|
42495
|
+
import { delimiter, join as join11 } from "path";
|
|
42105
42496
|
function detectWsl(overrides = {}) {
|
|
42106
42497
|
const deps = { ...defaultProbeDeps, ...overrides };
|
|
42107
42498
|
if (deps.platform !== "linux")
|
|
@@ -42112,7 +42503,7 @@ function detectWsl(overrides = {}) {
|
|
|
42112
42503
|
if (!inWsl)
|
|
42113
42504
|
return;
|
|
42114
42505
|
const candidates = [
|
|
42115
|
-
...(env.PATH ?? "").split(delimiter).filter((dir) => dir !== "").map((dir) =>
|
|
42506
|
+
...(env.PATH ?? "").split(delimiter).filter((dir) => dir !== "").map((dir) => join11(dir, "powershell.exe")),
|
|
42116
42507
|
POWERSHELL_FALLBACK
|
|
42117
42508
|
];
|
|
42118
42509
|
return { powershell: candidates.find((candidate) => deps.isExecutable(candidate)) };
|
|
@@ -42201,7 +42592,7 @@ function primaryWslAddress(interfaces = networkInterfaces2()) {
|
|
|
42201
42592
|
return eth0?.address ?? wslOwnAddresses(interfaces)[0];
|
|
42202
42593
|
}
|
|
42203
42594
|
var POWERSHELL_FALLBACK = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe", defaultProbeDeps, WINDOWS_ADDRESSES_SCRIPT, WSL_ADAPTER_PATTERN, runPowerShell = (powershell, script, timeoutMs) => new Promise((resolve, reject) => {
|
|
42204
|
-
|
|
42595
|
+
execFile3(powershell, ["-NoProfile", "-NonInteractive", "-Command", script], { timeout: timeoutMs, windowsHide: true, maxBuffer: 1024 * 1024, encoding: "utf8" }, (error, stdout) => error ? reject(error) : resolve(stdout));
|
|
42205
42596
|
});
|
|
42206
42597
|
var init_wsl = __esm(() => {
|
|
42207
42598
|
init_network();
|
|
@@ -42210,7 +42601,7 @@ var init_wsl = __esm(() => {
|
|
|
42210
42601
|
env: process.env,
|
|
42211
42602
|
readProcVersion() {
|
|
42212
42603
|
try {
|
|
42213
|
-
return
|
|
42604
|
+
return readFileSync3("/proc/version", "utf8");
|
|
42214
42605
|
} catch {
|
|
42215
42606
|
return;
|
|
42216
42607
|
}
|
|
@@ -42282,6 +42673,10 @@ function toMessage(error) {
|
|
|
42282
42673
|
}
|
|
42283
42674
|
return String(error);
|
|
42284
42675
|
}
|
|
42676
|
+
function normalizeLocalAddress(address) {
|
|
42677
|
+
const mapped = address?.startsWith("::ffff:") ? address.slice("::ffff:".length) : undefined;
|
|
42678
|
+
return mapped !== undefined && isIpv4Literal(mapped) ? mapped : address;
|
|
42679
|
+
}
|
|
42285
42680
|
|
|
42286
42681
|
class MobileGateway {
|
|
42287
42682
|
serverId;
|
|
@@ -42610,11 +43005,12 @@ class MobileGateway {
|
|
|
42610
43005
|
connection.sendNotification(method, params);
|
|
42611
43006
|
}
|
|
42612
43007
|
advertisedAddress(localAddress) {
|
|
43008
|
+
const normalized = normalizeLocalAddress(localAddress);
|
|
42613
43009
|
const state = this.stateValue;
|
|
42614
|
-
if (state.status === "listening" && state.mode === "wsl-relay" &&
|
|
43010
|
+
if (state.status === "listening" && state.mode === "wsl-relay" && normalized === state.bindAddress) {
|
|
42615
43011
|
return state.address;
|
|
42616
43012
|
}
|
|
42617
|
-
return
|
|
43013
|
+
return normalized;
|
|
42618
43014
|
}
|
|
42619
43015
|
requireStore() {
|
|
42620
43016
|
if (this.storeHandle === undefined)
|
|
@@ -42872,10 +43268,10 @@ var init_listener = __esm(() => {
|
|
|
42872
43268
|
});
|
|
42873
43269
|
|
|
42874
43270
|
// packages/gateway/src/port-file.ts
|
|
42875
|
-
import { readFile as
|
|
42876
|
-
import { join as
|
|
43271
|
+
import { readFile as readFile8, rm as rm4, writeFile as writeFile4 } from "fs/promises";
|
|
43272
|
+
import { join as join12 } from "path";
|
|
42877
43273
|
function gatewayPortFilePath(nekoDir, serverId) {
|
|
42878
|
-
return
|
|
43274
|
+
return join12(nekoDir, `${serverId}.port`);
|
|
42879
43275
|
}
|
|
42880
43276
|
async function writeGatewayPortFile(nekoDir, serverId, wsUrl) {
|
|
42881
43277
|
await writeFile4(gatewayPortFilePath(nekoDir, serverId), `${wsUrl}
|
|
@@ -42884,7 +43280,7 @@ async function writeGatewayPortFile(nekoDir, serverId, wsUrl) {
|
|
|
42884
43280
|
async function readGatewayPortFile(nekoDir, serverId) {
|
|
42885
43281
|
let content;
|
|
42886
43282
|
try {
|
|
42887
|
-
content = await
|
|
43283
|
+
content = await readFile8(gatewayPortFilePath(nekoDir, serverId), "utf8");
|
|
42888
43284
|
} catch {
|
|
42889
43285
|
return;
|
|
42890
43286
|
}
|
|
@@ -43231,7 +43627,7 @@ var init_subagent_host = __esm(() => {
|
|
|
43231
43627
|
|
|
43232
43628
|
// packages/gateway/src/session-host.ts
|
|
43233
43629
|
import { open as open4 } from "fs/promises";
|
|
43234
|
-
import { basename as
|
|
43630
|
+
import { basename as basename6, extname } from "path";
|
|
43235
43631
|
import {
|
|
43236
43632
|
ChordError as ChordError4,
|
|
43237
43633
|
local as local2,
|
|
@@ -43426,7 +43822,7 @@ function serveSessions(host, runtime, context) {
|
|
|
43426
43822
|
if (!info.isFile())
|
|
43427
43823
|
throw new Error("Image must be a file");
|
|
43428
43824
|
ctx.signal?.throwIfAborted();
|
|
43429
|
-
id = uploads.value.begin({ name:
|
|
43825
|
+
id = uploads.value.begin({ name: basename6(target), mimeType, size: info.size });
|
|
43430
43826
|
let offset = 0;
|
|
43431
43827
|
while (offset < info.size) {
|
|
43432
43828
|
ctx.signal?.throwIfAborted();
|
|
@@ -43443,9 +43839,9 @@ function serveSessions(host, runtime, context) {
|
|
|
43443
43839
|
ctx.signal?.throwIfAborted();
|
|
43444
43840
|
uploads.value.finish(id);
|
|
43445
43841
|
const raw = uploads.value.resolve([id])[0];
|
|
43446
|
-
await prepareImage(Buffer.from(raw.content.data, "base64"), mimeType,
|
|
43842
|
+
await prepareImage(Buffer.from(raw.content.data, "base64"), mimeType, basename6(target), model);
|
|
43447
43843
|
ctx.signal?.throwIfAborted();
|
|
43448
|
-
return { id, name:
|
|
43844
|
+
return { id, name: basename6(target) };
|
|
43449
43845
|
} catch (error) {
|
|
43450
43846
|
if (id)
|
|
43451
43847
|
uploads.value.release(id);
|
|
@@ -43639,7 +44035,7 @@ function strFromU8(dat, latin1) {
|
|
|
43639
44035
|
} else {
|
|
43640
44036
|
var _a = dutf8(dat), { s, r } = _a;
|
|
43641
44037
|
if (r.length)
|
|
43642
|
-
|
|
44038
|
+
err2(8);
|
|
43643
44039
|
return s;
|
|
43644
44040
|
}
|
|
43645
44041
|
}
|
|
@@ -43648,7 +44044,7 @@ function unzipSync(data, opts) {
|
|
|
43648
44044
|
var e = data.length - 22;
|
|
43649
44045
|
for (;b4(data, e) != 101010256; --e) {
|
|
43650
44046
|
if (!e || data.length - e > 65558)
|
|
43651
|
-
|
|
44047
|
+
err2(13);
|
|
43652
44048
|
}
|
|
43653
44049
|
var c = b2(data, e + 8);
|
|
43654
44050
|
if (!c)
|
|
@@ -43678,7 +44074,7 @@ function unzipSync(data, opts) {
|
|
|
43678
44074
|
else if (c_2 == 8)
|
|
43679
44075
|
files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });
|
|
43680
44076
|
else
|
|
43681
|
-
|
|
44077
|
+
err2(14, "unknown compression type " + c_2);
|
|
43682
44078
|
}
|
|
43683
44079
|
}
|
|
43684
44080
|
return files;
|
|
@@ -43751,11 +44147,11 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43751
44147
|
if (e == null || e > v.length)
|
|
43752
44148
|
e = v.length;
|
|
43753
44149
|
return new u8(v.subarray(s, e));
|
|
43754
|
-
}, ec,
|
|
44150
|
+
}, ec, err2 = function(ind, msg, nt) {
|
|
43755
44151
|
var e = new Error(msg || ec[ind]);
|
|
43756
44152
|
e.code = ind;
|
|
43757
44153
|
if (Error.captureStackTrace)
|
|
43758
|
-
Error.captureStackTrace(e,
|
|
44154
|
+
Error.captureStackTrace(e, err2);
|
|
43759
44155
|
if (!nt)
|
|
43760
44156
|
throw e;
|
|
43761
44157
|
return e;
|
|
@@ -43787,7 +44183,7 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43787
44183
|
var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
|
|
43788
44184
|
if (t > sl) {
|
|
43789
44185
|
if (noSt)
|
|
43790
|
-
|
|
44186
|
+
err2(0);
|
|
43791
44187
|
break;
|
|
43792
44188
|
}
|
|
43793
44189
|
if (resize)
|
|
@@ -43833,10 +44229,10 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43833
44229
|
lm = hMap(lt, lbt, 1);
|
|
43834
44230
|
dm = hMap(dt, dbt, 1);
|
|
43835
44231
|
} else
|
|
43836
|
-
|
|
44232
|
+
err2(1);
|
|
43837
44233
|
if (pos > tbts) {
|
|
43838
44234
|
if (noSt)
|
|
43839
|
-
|
|
44235
|
+
err2(0);
|
|
43840
44236
|
break;
|
|
43841
44237
|
}
|
|
43842
44238
|
}
|
|
@@ -43849,11 +44245,11 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43849
44245
|
pos += c & 15;
|
|
43850
44246
|
if (pos > tbts) {
|
|
43851
44247
|
if (noSt)
|
|
43852
|
-
|
|
44248
|
+
err2(0);
|
|
43853
44249
|
break;
|
|
43854
44250
|
}
|
|
43855
44251
|
if (!c)
|
|
43856
|
-
|
|
44252
|
+
err2(2);
|
|
43857
44253
|
if (sym < 256)
|
|
43858
44254
|
buf[bt++] = sym;
|
|
43859
44255
|
else if (sym == 256) {
|
|
@@ -43868,7 +44264,7 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43868
44264
|
}
|
|
43869
44265
|
var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
|
|
43870
44266
|
if (!d)
|
|
43871
|
-
|
|
44267
|
+
err2(3);
|
|
43872
44268
|
pos += d & 15;
|
|
43873
44269
|
var dt = fd[dsym];
|
|
43874
44270
|
if (dsym > 3) {
|
|
@@ -43877,7 +44273,7 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43877
44273
|
}
|
|
43878
44274
|
if (pos > tbts) {
|
|
43879
44275
|
if (noSt)
|
|
43880
|
-
|
|
44276
|
+
err2(0);
|
|
43881
44277
|
break;
|
|
43882
44278
|
}
|
|
43883
44279
|
if (resize)
|
|
@@ -43886,7 +44282,7 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43886
44282
|
if (bt < dt) {
|
|
43887
44283
|
var shift = dl - dt, dend = Math.min(dt, end);
|
|
43888
44284
|
if (shift + bt < 0)
|
|
43889
|
-
|
|
44285
|
+
err2(3);
|
|
43890
44286
|
for (;bt < dend; ++bt)
|
|
43891
44287
|
buf[bt] = dict[shift + bt];
|
|
43892
44288
|
}
|
|
@@ -43941,7 +44337,7 @@ var require2, _a, Worker2, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, c
|
|
|
43941
44337
|
}
|
|
43942
44338
|
}
|
|
43943
44339
|
if (z < 2)
|
|
43944
|
-
|
|
44340
|
+
err2(13);
|
|
43945
44341
|
}
|
|
43946
44342
|
return [sc, su, off, 0];
|
|
43947
44343
|
};
|
|
@@ -44010,13 +44406,14 @@ var init_esm = __esm(() => {
|
|
|
44010
44406
|
|
|
44011
44407
|
// packages/gateway/src/skill-market.ts
|
|
44012
44408
|
import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
|
|
44013
|
-
import { mkdir as mkdir6, readdir as readdir2,
|
|
44409
|
+
import { mkdir as mkdir6, readdir as readdir2, rm as rm5, stat as stat5, writeFile as writeFile5 } from "fs/promises";
|
|
44014
44410
|
import { homedir as homedir7 } from "os";
|
|
44015
|
-
import { dirname as dirname8, join as
|
|
44411
|
+
import { dirname as dirname8, join as join13 } from "path";
|
|
44016
44412
|
function createSkillMarketClient(options) {
|
|
44017
44413
|
const home = options.home ?? homedir7();
|
|
44018
44414
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
44019
44415
|
const now = options.now ?? Date.now;
|
|
44416
|
+
const placeRename = options.renameImpl;
|
|
44020
44417
|
let base;
|
|
44021
44418
|
try {
|
|
44022
44419
|
base = new URL(options.marketUrl);
|
|
@@ -44121,6 +44518,8 @@ function createSkillMarketClient(options) {
|
|
|
44121
44518
|
throw new SkillMarketError("market_unavailable", "Project-scope install needs a workspaceRoot");
|
|
44122
44519
|
return projectSkillsDir(workspaceRoot);
|
|
44123
44520
|
};
|
|
44521
|
+
const BACKUP_PREFIX = ".bak-";
|
|
44522
|
+
const UUID_LENGTH = 36;
|
|
44124
44523
|
const sweepTempDirs = async (skillsDir) => {
|
|
44125
44524
|
const parent = dirname8(skillsDir);
|
|
44126
44525
|
let names = [];
|
|
@@ -44129,12 +44528,27 @@ function createSkillMarketClient(options) {
|
|
|
44129
44528
|
} catch {
|
|
44130
44529
|
return;
|
|
44131
44530
|
}
|
|
44132
|
-
await Promise.all(names.filter((n) => n.startsWith(".tmp-")).map((n) => rm5(
|
|
44531
|
+
await Promise.all(names.filter((n) => n.startsWith(".tmp-")).map((n) => rm5(join13(parent, n), { recursive: true, force: true }).catch(() => {})));
|
|
44532
|
+
for (const n of names.filter((n) => n.startsWith(BACKUP_PREFIX))) {
|
|
44533
|
+
const backup = join13(parent, n);
|
|
44534
|
+
const skill = n.slice(BACKUP_PREFIX.length + UUID_LENGTH + 1);
|
|
44535
|
+
if (skill === "" || skill.includes("/") || skill.includes("\\") || skill === "." || skill === "..")
|
|
44536
|
+
continue;
|
|
44537
|
+
const target = join13(skillsDir, skill);
|
|
44538
|
+
if (await stat5(target).catch(() => {
|
|
44539
|
+
return;
|
|
44540
|
+
}) === undefined) {
|
|
44541
|
+
await mkdir6(skillsDir, { recursive: true }).catch(() => {});
|
|
44542
|
+
await renameWithRetry(backup, target).catch(() => {});
|
|
44543
|
+
} else {
|
|
44544
|
+
await rm5(backup, { recursive: true, force: true }).catch(() => {});
|
|
44545
|
+
}
|
|
44546
|
+
}
|
|
44133
44547
|
};
|
|
44134
44548
|
const install = async ({ name, scope, workspaceRoot, overwrite }) => {
|
|
44135
44549
|
const skillsDir = skillsDirFor(scope, workspaceRoot);
|
|
44136
|
-
const target =
|
|
44137
|
-
const exists = await
|
|
44550
|
+
const target = join13(skillsDir, name);
|
|
44551
|
+
const exists = await stat5(target).catch(() => {
|
|
44138
44552
|
return;
|
|
44139
44553
|
}) !== undefined;
|
|
44140
44554
|
if (exists && overwrite !== true)
|
|
@@ -44142,24 +44556,34 @@ function createSkillMarketClient(options) {
|
|
|
44142
44556
|
const skill = await detail(name);
|
|
44143
44557
|
const files = unpack(skill, await download(skill));
|
|
44144
44558
|
await sweepTempDirs(skillsDir);
|
|
44145
|
-
const temp =
|
|
44559
|
+
const temp = join13(dirname8(skillsDir), `.tmp-${randomUUID4()}`);
|
|
44146
44560
|
try {
|
|
44147
44561
|
for (const [relative, bytes] of files) {
|
|
44148
|
-
const dest =
|
|
44562
|
+
const dest = join13(temp, ...relative.split("/"));
|
|
44149
44563
|
await mkdir6(dirname8(dest), { recursive: true });
|
|
44150
44564
|
await writeFile5(dest, bytes);
|
|
44151
44565
|
}
|
|
44152
|
-
if (exists)
|
|
44153
|
-
await rm5(target, { recursive: true, force: true });
|
|
44154
44566
|
await mkdir6(skillsDir, { recursive: true });
|
|
44155
|
-
|
|
44567
|
+
if (exists) {
|
|
44568
|
+
const backup = join13(dirname8(skillsDir), `${BACKUP_PREFIX}${randomUUID4()}-${name}`);
|
|
44569
|
+
await renameWithRetry(target, backup);
|
|
44570
|
+
try {
|
|
44571
|
+
await renameWithRetry(temp, target, placeRename ? { rename: placeRename } : {});
|
|
44572
|
+
} catch (error) {
|
|
44573
|
+
await renameWithRetry(backup, target).catch(() => {});
|
|
44574
|
+
throw error;
|
|
44575
|
+
}
|
|
44576
|
+
await rm5(backup, { recursive: true, force: true }).catch(() => {});
|
|
44577
|
+
} else {
|
|
44578
|
+
await renameWithRetry(temp, target, placeRename ? { rename: placeRename } : {});
|
|
44579
|
+
}
|
|
44156
44580
|
} finally {
|
|
44157
44581
|
await rm5(temp, { recursive: true, force: true }).catch(() => {});
|
|
44158
44582
|
}
|
|
44159
44583
|
let shadowedBy;
|
|
44160
44584
|
if (scope === "user" && workspaceRoot !== undefined && workspaceRoot !== "") {
|
|
44161
|
-
const projectDir =
|
|
44162
|
-
if (await
|
|
44585
|
+
const projectDir = join13(projectSkillsDir(workspaceRoot), name);
|
|
44586
|
+
if (await stat5(projectDir).catch(() => {
|
|
44163
44587
|
return;
|
|
44164
44588
|
}) !== undefined)
|
|
44165
44589
|
shadowedBy = "project";
|
|
@@ -44168,8 +44592,8 @@ function createSkillMarketClient(options) {
|
|
|
44168
44592
|
};
|
|
44169
44593
|
const uninstall = async ({ name, scope, workspaceRoot }) => {
|
|
44170
44594
|
const skillsDir = skillsDirFor(scope, workspaceRoot);
|
|
44171
|
-
const target =
|
|
44172
|
-
if (await
|
|
44595
|
+
const target = join13(skillsDir, name);
|
|
44596
|
+
if (await stat5(target).catch(() => {
|
|
44173
44597
|
return;
|
|
44174
44598
|
}) === undefined)
|
|
44175
44599
|
throw new SkillMarketError("skill_not_installed", `Skill ${name} is not installed at ${scope} level`);
|
|
@@ -44200,7 +44624,7 @@ function createSkillMarketClient(options) {
|
|
|
44200
44624
|
uninstall
|
|
44201
44625
|
};
|
|
44202
44626
|
}
|
|
44203
|
-
var SkillMarketError, CATALOG_TTL_MS = 60000, ZIP_CACHE_MAX = 16, MAX_PACKAGE_BYTES, userSkillsDir2 = (home) =>
|
|
44627
|
+
var SkillMarketError, CATALOG_TTL_MS = 60000, ZIP_CACHE_MAX = 16, MAX_PACKAGE_BYTES, userSkillsDir2 = (home) => join13(home, ".agents", "skills"), projectSkillsDir = (workspaceRoot) => join13(workspaceRoot, ".agents", "skills"), activeLevelOf = (state) => state.project ? "project" : state.user ? "user" : state.builtin ? "builtin" : null, normalizeEntry = (raw) => {
|
|
44204
44628
|
const row = raw ?? {};
|
|
44205
44629
|
const metadata = row.metadata;
|
|
44206
44630
|
const source = metadata !== null && typeof metadata === "object" ? metadata.source : undefined;
|
|
@@ -44232,8 +44656,8 @@ var init_skill_market = __esm(() => {
|
|
|
44232
44656
|
|
|
44233
44657
|
// packages/gateway/src/workspaces.ts
|
|
44234
44658
|
import { constants as constants3 } from "fs";
|
|
44235
|
-
import { access, readdir as readdir3, stat as
|
|
44236
|
-
import { dirname as dirname9, isAbsolute as isAbsolute4, join as
|
|
44659
|
+
import { access, readdir as readdir3, stat as stat6 } from "fs/promises";
|
|
44660
|
+
import { dirname as dirname9, isAbsolute as isAbsolute4, join as join14 } from "path";
|
|
44237
44661
|
async function browseWorkspaces(options) {
|
|
44238
44662
|
const requested = options.path.replace(/^\/+([A-Za-z]:[\\/])/, "$1");
|
|
44239
44663
|
if (process.platform === "win32" && (requested === "/" || requested === "\\")) {
|
|
@@ -44260,11 +44684,11 @@ async function browseWorkspaces(options) {
|
|
|
44260
44684
|
let next = offset;
|
|
44261
44685
|
for (;next < entries.length && directories.length < 100; next++) {
|
|
44262
44686
|
const entry = entries[next];
|
|
44263
|
-
const target =
|
|
44687
|
+
const target = join14(path, entry.name);
|
|
44264
44688
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
44265
44689
|
continue;
|
|
44266
44690
|
try {
|
|
44267
|
-
if (!(await
|
|
44691
|
+
if (!(await stat6(target)).isDirectory())
|
|
44268
44692
|
continue;
|
|
44269
44693
|
await access(target, constants3.R_OK | constants3.X_OK);
|
|
44270
44694
|
directories.push({ name: entry.name, path: target });
|
|
@@ -44303,12 +44727,15 @@ async function createNekoServerServices(options) {
|
|
|
44303
44727
|
const updates = new UpdateChecker(options.updates);
|
|
44304
44728
|
await updates.local();
|
|
44305
44729
|
const updateListeners = new Set;
|
|
44306
|
-
const info = (value) =>
|
|
44307
|
-
|
|
44730
|
+
const info = (value) => {
|
|
44731
|
+
const shell = runtime.shellStatus();
|
|
44732
|
+
return { ...value, serverId: options.serverId, ...shell === undefined ? {} : { shell } };
|
|
44733
|
+
};
|
|
44734
|
+
let published = JSON.stringify(info(updates.state)), closed = false;
|
|
44308
44735
|
const publishUpdates = () => {
|
|
44309
44736
|
if (closed)
|
|
44310
44737
|
return;
|
|
44311
|
-
const next = JSON.stringify(updates.state);
|
|
44738
|
+
const next = JSON.stringify(info(updates.state));
|
|
44312
44739
|
if (next === published)
|
|
44313
44740
|
return;
|
|
44314
44741
|
published = next;
|
|
@@ -44327,6 +44754,7 @@ async function createNekoServerServices(options) {
|
|
|
44327
44754
|
variant: "ready",
|
|
44328
44755
|
info: current,
|
|
44329
44756
|
check: async () => {
|
|
44757
|
+
runtime.shellStatus({ refresh: true });
|
|
44330
44758
|
await updates.check(true);
|
|
44331
44759
|
publishUpdates();
|
|
44332
44760
|
return info(updates.state);
|
|
@@ -44637,8 +45065,8 @@ var init_server_services = __esm(() => {
|
|
|
44637
45065
|
|
|
44638
45066
|
// packages/gateway/src/gateway.ts
|
|
44639
45067
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
44640
|
-
import { mkdir as mkdir7, readFile as
|
|
44641
|
-
import { basename as
|
|
45068
|
+
import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
|
|
45069
|
+
import { basename as basename7, dirname as dirname10, join as join15 } from "path";
|
|
44642
45070
|
import { createHost as createHost2 } from "@earendil-works/chord";
|
|
44643
45071
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT4 } from "@earendil-works/chord/context";
|
|
44644
45072
|
async function startGateway(options = {}) {
|
|
@@ -44651,7 +45079,7 @@ async function startGateway(options = {}) {
|
|
|
44651
45079
|
const marketUrl = options.marketUrl ?? fileConfig.skills?.marketUrl ?? DEFAULT_SKILL_MARKET_URL;
|
|
44652
45080
|
const nekoDir = dirname10(storagePath);
|
|
44653
45081
|
const serverId = options.serverId ?? await loadOrCreateServerId(nekoDir);
|
|
44654
|
-
const socketPath = options.socketPath !== undefined ? options.socketPath : fileConfig.gateway?.socketPath !== undefined ? fileConfig.gateway.socketPath : process.platform === "win32" ? null :
|
|
45082
|
+
const socketPath = options.socketPath !== undefined ? options.socketPath : fileConfig.gateway?.socketPath !== undefined ? fileConfig.gateway.socketPath : process.platform === "win32" ? null : join15(nekoDir, `${serverId}.sock`);
|
|
44655
45083
|
const runtime = await createNekoRuntime({
|
|
44656
45084
|
context,
|
|
44657
45085
|
onReport: (error) => options.onError?.(error instanceof Error ? error : new Error(String(error))),
|
|
@@ -44667,7 +45095,9 @@ async function startGateway(options = {}) {
|
|
|
44667
45095
|
...options.cwd !== undefined ? { cwd: options.cwd } : {},
|
|
44668
45096
|
...options.providers !== undefined ? { providers: options.providers } : {},
|
|
44669
45097
|
...options.extensions !== undefined ? { extensions: options.extensions } : {},
|
|
44670
|
-
...options.promptHome !== undefined ? { promptHome: options.promptHome } : {}
|
|
45098
|
+
...options.promptHome !== undefined ? { promptHome: options.promptHome } : {},
|
|
45099
|
+
...fileConfig.shell?.path !== undefined ? { shellPath: fileConfig.shell.path } : {},
|
|
45100
|
+
...options.shellResolver !== undefined ? { shellResolver: options.shellResolver } : {}
|
|
44671
45101
|
});
|
|
44672
45102
|
try {
|
|
44673
45103
|
return await bindGateway(runtime, {
|
|
@@ -44682,6 +45112,7 @@ async function startGateway(options = {}) {
|
|
|
44682
45112
|
updates: options.updates,
|
|
44683
45113
|
updateIntervalMs: options.updateIntervalMs,
|
|
44684
45114
|
mobileServerName: options.mobileServerName,
|
|
45115
|
+
mobileNetwork: options.mobileNetwork,
|
|
44685
45116
|
onError: options.onError
|
|
44686
45117
|
});
|
|
44687
45118
|
} catch (error) {
|
|
@@ -44716,12 +45147,13 @@ async function bindGateway(runtime, options) {
|
|
|
44716
45147
|
wsListener = createWsListener({ host, port, onError: options.onError });
|
|
44717
45148
|
listeners.push(wsListener);
|
|
44718
45149
|
}
|
|
45150
|
+
const acceptLocal = (link) => chord.accept(link, {
|
|
45151
|
+
principal: { kind: "local", connectionId: randomUUID6() },
|
|
45152
|
+
onProtocolMismatch: ({ peer }) => options.onError?.(new Error(`Rejected a client on Chord protocol ${peer ?? "unknown"}; restart or update it.`))
|
|
45153
|
+
});
|
|
44719
45154
|
try {
|
|
44720
45155
|
for (const listener of listeners)
|
|
44721
|
-
await listener.start(
|
|
44722
|
-
principal: { kind: "local", connectionId: randomUUID6() },
|
|
44723
|
-
onProtocolMismatch: ({ peer }) => options.onError?.(new Error(`Rejected a client on Chord protocol ${peer ?? "unknown"}; restart or update it.`))
|
|
44724
|
-
}));
|
|
45156
|
+
await listener.start(acceptLocal);
|
|
44725
45157
|
} catch (error) {
|
|
44726
45158
|
await Promise.allSettled(listeners.map((listener) => listener.close()));
|
|
44727
45159
|
await chord.close();
|
|
@@ -44737,6 +45169,7 @@ async function bindGateway(runtime, options) {
|
|
|
44737
45169
|
serverId,
|
|
44738
45170
|
serverName: options.mobileServerName,
|
|
44739
45171
|
onError: options.onError,
|
|
45172
|
+
network: options.mobileNetwork,
|
|
44740
45173
|
chord: {
|
|
44741
45174
|
accept: (link, acceptOptions) => chord.accept(link, acceptOptions),
|
|
44742
45175
|
invalidatePrincipal: (match) => chord.invalidatePrincipal(match)
|
|
@@ -44761,6 +45194,9 @@ async function bindGateway(runtime, options) {
|
|
|
44761
45194
|
port: wsListener?.port,
|
|
44762
45195
|
runtime,
|
|
44763
45196
|
mobile,
|
|
45197
|
+
accept(link) {
|
|
45198
|
+
acceptLocal(link);
|
|
45199
|
+
},
|
|
44764
45200
|
async close() {
|
|
44765
45201
|
if (closing)
|
|
44766
45202
|
return closing;
|
|
@@ -44801,16 +45237,16 @@ ${connectHint}`);
|
|
|
44801
45237
|
return error instanceof Error ? error : new Error(message);
|
|
44802
45238
|
}
|
|
44803
45239
|
function serverIdFromSocketPath(socketPath) {
|
|
44804
|
-
const name =
|
|
45240
|
+
const name = basename7(socketPath);
|
|
44805
45241
|
if (!name.endsWith(".sock"))
|
|
44806
45242
|
return;
|
|
44807
45243
|
const candidate = name.slice(0, -".sock".length);
|
|
44808
45244
|
return isServerId(candidate) ? candidate : undefined;
|
|
44809
45245
|
}
|
|
44810
45246
|
async function loadOrCreateServerId(nekoDir) {
|
|
44811
|
-
const file =
|
|
45247
|
+
const file = join15(nekoDir, SERVER_ID_FILENAME);
|
|
44812
45248
|
try {
|
|
44813
|
-
const existing = (await
|
|
45249
|
+
const existing = (await readFile9(file, "utf8")).trim();
|
|
44814
45250
|
if (existing !== "")
|
|
44815
45251
|
return existing;
|
|
44816
45252
|
} catch {}
|
|
@@ -44849,10 +45285,11 @@ function gatewayCommand(args) {
|
|
|
44849
45285
|
async function stopGatewayProcess(child, options = {}) {
|
|
44850
45286
|
if (child.pid === undefined || child.exitCode !== null || child.signalCode !== null)
|
|
44851
45287
|
return;
|
|
45288
|
+
const viaStdin = options.viaStdin === true && child.stdin != null;
|
|
44852
45289
|
await new Promise((resolve) => {
|
|
44853
45290
|
let forcedTimer;
|
|
44854
45291
|
const timer = setTimeout(() => {
|
|
44855
|
-
child.kill("SIGKILL");
|
|
45292
|
+
child.kill(viaStdin ? undefined : "SIGKILL");
|
|
44856
45293
|
forcedTimer = setTimeout(resolve, options.forcedMs ?? FORCED_STOP_MS);
|
|
44857
45294
|
}, options.gracefulMs ?? GRACEFUL_STOP_MS);
|
|
44858
45295
|
child.once("exit", () => {
|
|
@@ -44860,7 +45297,10 @@ async function stopGatewayProcess(child, options = {}) {
|
|
|
44860
45297
|
clearTimeout(forcedTimer);
|
|
44861
45298
|
resolve();
|
|
44862
45299
|
});
|
|
44863
|
-
|
|
45300
|
+
if (viaStdin)
|
|
45301
|
+
child.stdin?.end();
|
|
45302
|
+
else
|
|
45303
|
+
child.kill("SIGTERM");
|
|
44864
45304
|
});
|
|
44865
45305
|
}
|
|
44866
45306
|
var GRACEFUL_STOP_MS = 3000, FORCED_STOP_MS = 2000;
|
|
@@ -44890,14 +45330,15 @@ function installShutdown(options = {}) {
|
|
|
44890
45330
|
exit(1);
|
|
44891
45331
|
});
|
|
44892
45332
|
};
|
|
44893
|
-
const
|
|
45333
|
+
const begin = (reason) => {
|
|
44894
45334
|
if (controller.signal.aborted) {
|
|
44895
45335
|
options.log?.("\u518D\u6B21\u6536\u5230\u7EC8\u6B62\u4FE1\u53F7\uFF0C\u5F3A\u5236\u9000\u51FA\u3002");
|
|
44896
45336
|
exit(130);
|
|
44897
45337
|
return;
|
|
44898
45338
|
}
|
|
44899
|
-
options.log?.(`
|
|
44900
|
-
\u6B63\u5728\u5173\u95ED\u2026`
|
|
45339
|
+
options.log?.(reason === undefined ? `
|
|
45340
|
+
\u6B63\u5728\u5173\u95ED\u2026` : `
|
|
45341
|
+
\u6B63\u5728\u5173\u95ED\uFF08${reason}\uFF09\u2026`);
|
|
44901
45342
|
controller.abort();
|
|
44902
45343
|
timer = setTimeout(() => {
|
|
44903
45344
|
options.log?.("\u5173\u95ED\u8D85\u65F6\uFF0C\u5F3A\u5236\u9000\u51FA\u3002");
|
|
@@ -44906,6 +45347,7 @@ function installShutdown(options = {}) {
|
|
|
44906
45347
|
timer.unref?.();
|
|
44907
45348
|
runCleanup();
|
|
44908
45349
|
};
|
|
45350
|
+
const onSignal = () => begin(undefined);
|
|
44909
45351
|
const installed = [];
|
|
44910
45352
|
for (const signal of shutdownSignals(proc.platform)) {
|
|
44911
45353
|
try {
|
|
@@ -44920,6 +45362,9 @@ function installShutdown(options = {}) {
|
|
|
44920
45362
|
if (controller.signal.aborted)
|
|
44921
45363
|
runCleanup();
|
|
44922
45364
|
},
|
|
45365
|
+
trigger(reason) {
|
|
45366
|
+
begin(reason);
|
|
45367
|
+
},
|
|
44923
45368
|
dispose() {
|
|
44924
45369
|
clearTimeout(timer);
|
|
44925
45370
|
for (const signal of installed)
|
|
@@ -44945,13 +45390,13 @@ var init_src2 = __esm(() => {
|
|
|
44945
45390
|
|
|
44946
45391
|
// packages/tui/src/gateway.ts
|
|
44947
45392
|
import { spawn as spawn3 } from "child_process";
|
|
44948
|
-
import { existsSync as existsSync3, readFileSync as
|
|
44949
|
-
import { basename as
|
|
45393
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
45394
|
+
import { basename as basename8, dirname as dirname11, join as join16 } from "path";
|
|
44950
45395
|
async function connectToGateway(options = {}) {
|
|
44951
45396
|
const config = loadNekoConfig(options.configDir);
|
|
44952
45397
|
const storagePath = options.storagePath ?? config.storagePath ?? DEFAULT_STORAGE_PATH;
|
|
44953
45398
|
const nekoDir = dirname11(storagePath);
|
|
44954
|
-
const fileServerId = readServerId(
|
|
45399
|
+
const fileServerId = readServerId(join16(nekoDir, SERVER_ID_FILENAME2));
|
|
44955
45400
|
const platform = options.platform ?? process.platform;
|
|
44956
45401
|
if (platform === "win32") {
|
|
44957
45402
|
if (fileServerId !== undefined) {
|
|
@@ -44968,8 +45413,8 @@ async function connectToGateway(options = {}) {
|
|
|
44968
45413
|
}
|
|
44969
45414
|
return spawnAndConnect({ ...options, platform, storagePath, socketPath: undefined, nekoDir, fileServerId });
|
|
44970
45415
|
}
|
|
44971
|
-
const socketPath = options.socketPath ?? config.gateway?.socketPath ?? (fileServerId !== undefined ?
|
|
44972
|
-
const serverIdFor = (path) => fileServerId ?? serverIdFromSocketPath2(path) ?? readServerId(
|
|
45416
|
+
const socketPath = options.socketPath ?? config.gateway?.socketPath ?? (fileServerId !== undefined ? join16(nekoDir, `${fileServerId}.sock`) : undefined);
|
|
45417
|
+
const serverIdFor = (path) => fileServerId ?? serverIdFromSocketPath2(path) ?? readServerId(join16(nekoDir, SERVER_ID_FILENAME2));
|
|
44973
45418
|
if (socketPath !== undefined && existsSync3(socketPath)) {
|
|
44974
45419
|
const serverId = serverIdFor(socketPath);
|
|
44975
45420
|
if (serverId !== undefined) {
|
|
@@ -44986,7 +45431,9 @@ async function connectToGateway(options = {}) {
|
|
|
44986
45431
|
}
|
|
44987
45432
|
async function spawnAndConnect(plan) {
|
|
44988
45433
|
const command = gatewayCommand(gatewayArgs(plan));
|
|
44989
|
-
const child = spawn3(command.binary, command.args, {
|
|
45434
|
+
const child = spawn3(command.binary, command.args, {
|
|
45435
|
+
stdio: [plan.platform === "win32" ? "pipe" : "ignore", "pipe", "pipe"]
|
|
45436
|
+
});
|
|
44990
45437
|
const outputTail = captureOutput(child);
|
|
44991
45438
|
let spawnError;
|
|
44992
45439
|
child.on("error", (error) => {
|
|
@@ -44995,7 +45442,7 @@ async function spawnAndConnect(plan) {
|
|
|
44995
45442
|
try {
|
|
44996
45443
|
return await connectSpawned(plan, child, outputTail, () => spawnError);
|
|
44997
45444
|
} catch (error) {
|
|
44998
|
-
await stopGatewayProcess(child);
|
|
45445
|
+
await stopGatewayProcess(child, { viaStdin: plan.platform === "win32" });
|
|
44999
45446
|
throw error;
|
|
45000
45447
|
}
|
|
45001
45448
|
}
|
|
@@ -45003,7 +45450,7 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
45003
45450
|
const timeoutMs = plan.spawnTimeoutMs ?? 20000;
|
|
45004
45451
|
if (plan.platform === "win32") {
|
|
45005
45452
|
const timeoutAt = Date.now() + timeoutMs;
|
|
45006
|
-
const serverId = plan.fileServerId ?? await waitForValue(() => readServerId(
|
|
45453
|
+
const serverId = plan.fileServerId ?? await waitForValue(() => readServerId(join16(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
|
|
45007
45454
|
let lastError = new Error("Gateway port file unavailable");
|
|
45008
45455
|
while (Date.now() < timeoutAt) {
|
|
45009
45456
|
if (child.exitCode !== null || child.signalCode !== null || spawnError() !== undefined) {
|
|
@@ -45024,7 +45471,7 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
45024
45471
|
spawned: true,
|
|
45025
45472
|
async close() {
|
|
45026
45473
|
await client.close().catch(() => {});
|
|
45027
|
-
await stopGatewayProcess(child);
|
|
45474
|
+
await stopGatewayProcess(child, { viaStdin: plan.platform === "win32" });
|
|
45028
45475
|
await removeGatewayPortFile(plan.nekoDir, serverId).catch(() => {});
|
|
45029
45476
|
}
|
|
45030
45477
|
};
|
|
@@ -45040,11 +45487,11 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
45040
45487
|
}
|
|
45041
45488
|
let socketPath = plan.socketPath;
|
|
45042
45489
|
if (socketPath === undefined) {
|
|
45043
|
-
const generated = await waitForValue(() => readServerId(
|
|
45044
|
-
socketPath =
|
|
45490
|
+
const generated = await waitForValue(() => readServerId(join16(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
|
|
45491
|
+
socketPath = join16(plan.nekoDir, `${generated}.sock`);
|
|
45045
45492
|
}
|
|
45046
45493
|
await waitForValue(() => existsSync3(socketPath) ? true : undefined, `gateway ready (socket: ${socketPath})`, timeoutMs, child, outputTail, spawnError);
|
|
45047
|
-
const serverId = plan.fileServerId ?? serverIdFromSocketPath2(socketPath) ?? readServerId(
|
|
45494
|
+
const serverId = plan.fileServerId ?? serverIdFromSocketPath2(socketPath) ?? readServerId(join16(plan.nekoDir, SERVER_ID_FILENAME2));
|
|
45048
45495
|
if (serverId === undefined) {
|
|
45049
45496
|
throw new Error(`Cannot determine gateway serverId (socket: ${socketPath})`);
|
|
45050
45497
|
}
|
|
@@ -45061,7 +45508,7 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
45061
45508
|
spawned: true,
|
|
45062
45509
|
async close() {
|
|
45063
45510
|
await client.close().catch(() => {});
|
|
45064
|
-
await stopGatewayProcess(child);
|
|
45511
|
+
await stopGatewayProcess(child, { viaStdin: plan.platform === "win32" });
|
|
45065
45512
|
}
|
|
45066
45513
|
};
|
|
45067
45514
|
} catch (error) {
|
|
@@ -45078,6 +45525,7 @@ function gatewayArgs(plan) {
|
|
|
45078
45525
|
if (plan.platform === "win32") {
|
|
45079
45526
|
return [
|
|
45080
45527
|
"--no-socket",
|
|
45528
|
+
"--stop-on-stdin",
|
|
45081
45529
|
"--storage",
|
|
45082
45530
|
plan.storagePath,
|
|
45083
45531
|
...plan.credentialsPath ? ["--credentials", plan.credentialsPath] : [],
|
|
@@ -45096,14 +45544,14 @@ function gatewayArgs(plan) {
|
|
|
45096
45544
|
}
|
|
45097
45545
|
function readServerId(file) {
|
|
45098
45546
|
try {
|
|
45099
|
-
const value =
|
|
45547
|
+
const value = readFileSync4(file, "utf8").trim();
|
|
45100
45548
|
return isServerId(value) ? value : undefined;
|
|
45101
45549
|
} catch {
|
|
45102
45550
|
return;
|
|
45103
45551
|
}
|
|
45104
45552
|
}
|
|
45105
45553
|
function serverIdFromSocketPath2(socketPath) {
|
|
45106
|
-
const name =
|
|
45554
|
+
const name = basename8(socketPath);
|
|
45107
45555
|
if (!name.endsWith(".sock"))
|
|
45108
45556
|
return;
|
|
45109
45557
|
const candidate = name.slice(0, -".sock".length);
|
|
@@ -45175,8 +45623,8 @@ var init_gateway2 = __esm(() => {
|
|
|
45175
45623
|
});
|
|
45176
45624
|
|
|
45177
45625
|
// packages/tui/src/help-ui.ts
|
|
45178
|
-
import { getKeybindings, matchesKey as
|
|
45179
|
-
function helpLines(platform = process.platform) {
|
|
45626
|
+
import { getKeybindings, matchesKey as matchesKey9, TUI_KEYBINDINGS, wrapTextWithAnsi as wrapTextWithAnsi4 } from "@earendil-works/pi-tui";
|
|
45627
|
+
function helpLines(platform = process.platform, wsl = runningInWsl()) {
|
|
45180
45628
|
const rows = [];
|
|
45181
45629
|
const section = (title, hints) => {
|
|
45182
45630
|
rows.push(theme.accent(title), ...hints.map(([key, description]) => shortcutHints([key, description])), "");
|
|
@@ -45189,7 +45637,7 @@ function helpLines(platform = process.platform) {
|
|
|
45189
45637
|
["Ctrl+C", "Stop while running; exit when idle"],
|
|
45190
45638
|
["Ctrl+S", "Open sessions"],
|
|
45191
45639
|
["Ctrl+O", "Open or close the assistant's question"],
|
|
45192
|
-
[platform
|
|
45640
|
+
[keyLabel(clipboardShortcutKey(platform, wsl)), "Paste clipboard image or text"],
|
|
45193
45641
|
["/", "Complete commands"],
|
|
45194
45642
|
["@", "Complete workspace files"],
|
|
45195
45643
|
["/skill:", "Complete installed skills"]
|
|
@@ -45308,13 +45756,13 @@ class HelpDetail {
|
|
|
45308
45756
|
this.#offset = Math.max(0, Math.min(this.#max, this.#offset + delta));
|
|
45309
45757
|
}
|
|
45310
45758
|
handleKey(data) {
|
|
45311
|
-
if (
|
|
45759
|
+
if (matchesKey9(data, "up"))
|
|
45312
45760
|
this.scroll(-1);
|
|
45313
|
-
else if (
|
|
45761
|
+
else if (matchesKey9(data, "down"))
|
|
45314
45762
|
this.scroll(1);
|
|
45315
|
-
else if (
|
|
45763
|
+
else if (matchesKey9(data, "pageUp"))
|
|
45316
45764
|
this.scroll(-this.#page);
|
|
45317
|
-
else if (
|
|
45765
|
+
else if (matchesKey9(data, "pageDown"))
|
|
45318
45766
|
this.scroll(this.#page);
|
|
45319
45767
|
else
|
|
45320
45768
|
return false;
|
|
@@ -45331,117 +45779,11 @@ class HelpDetail {
|
|
|
45331
45779
|
var keyLabel = (key) => key.split("+").map((part) => ({ escape: "Esc", pageUp: "PgUp", pageDown: "PgDn" })[part] ?? part[0].toUpperCase() + part.slice(1)).join("+");
|
|
45332
45780
|
var init_help_ui = __esm(() => {
|
|
45333
45781
|
init_shortcut_hints();
|
|
45782
|
+
init_image_input();
|
|
45334
45783
|
init_labels();
|
|
45335
45784
|
init_theme();
|
|
45336
45785
|
});
|
|
45337
45786
|
|
|
45338
|
-
// packages/tui/src/image-input.ts
|
|
45339
|
-
import { execFile as execFile3 } from "child_process";
|
|
45340
|
-
import { readFile as readFile9, stat as stat5 } from "fs/promises";
|
|
45341
|
-
import { homedir as homedir8 } from "os";
|
|
45342
|
-
import { basename as basename8, resolve as resolve6 } from "path";
|
|
45343
|
-
import { promisify as promisify2 } from "util";
|
|
45344
|
-
import { isKeyRelease as isKeyRelease4, isKeyRepeat as isKeyRepeat2, matchesKey as matchesKey9 } from "@earendil-works/pi-tui";
|
|
45345
|
-
function imagePaths(text, platform = process.platform) {
|
|
45346
|
-
const paths = [];
|
|
45347
|
-
let word = "", quote = "", escaped = false;
|
|
45348
|
-
for (const c of text.trim()) {
|
|
45349
|
-
if (escaped) {
|
|
45350
|
-
word += c;
|
|
45351
|
-
escaped = false;
|
|
45352
|
-
} else if (c === "\\" && quote !== "'" && platform !== "win32") {
|
|
45353
|
-
escaped = true;
|
|
45354
|
-
} else if (quote) {
|
|
45355
|
-
if (c === quote)
|
|
45356
|
-
quote = "";
|
|
45357
|
-
else
|
|
45358
|
-
word += c;
|
|
45359
|
-
} else if (c === '"' || c === "'")
|
|
45360
|
-
quote = c;
|
|
45361
|
-
else if (/\s/.test(c)) {
|
|
45362
|
-
if (word) {
|
|
45363
|
-
paths.push(word);
|
|
45364
|
-
word = "";
|
|
45365
|
-
}
|
|
45366
|
-
} else
|
|
45367
|
-
word += c;
|
|
45368
|
-
}
|
|
45369
|
-
if (quote || escaped)
|
|
45370
|
-
return null;
|
|
45371
|
-
if (word)
|
|
45372
|
-
paths.push(word);
|
|
45373
|
-
return paths.length && paths.every((p) => /\.(png|jpe?g|webp)$/i.test(p)) ? paths : null;
|
|
45374
|
-
}
|
|
45375
|
-
async function readImageFile(path, cwd) {
|
|
45376
|
-
const full = resolve6(cwd, path.startsWith("~/") ? homedir8() + path.slice(1) : path);
|
|
45377
|
-
const info = await stat5(full);
|
|
45378
|
-
if (!info.isFile() || info.size > 20 * 1024 * 1024)
|
|
45379
|
-
throw new Error("Image must be a file no larger than 20 MiB");
|
|
45380
|
-
const bytes = await readFile9(full);
|
|
45381
|
-
const after = await stat5(full);
|
|
45382
|
-
if (bytes.length !== info.size || after.size !== info.size || after.mtimeMs !== info.mtimeMs)
|
|
45383
|
-
throw new Error("Image changed while reading");
|
|
45384
|
-
const mimeType = /\.png$/i.test(full) ? "image/png" : /\.webp$/i.test(full) ? "image/webp" : "image/jpeg";
|
|
45385
|
-
return { name: basename8(full), mimeType, bytes };
|
|
45386
|
-
}
|
|
45387
|
-
async function command(file, args, encoding = "utf8") {
|
|
45388
|
-
return (await exec(file, args, { timeout: 2000, maxBuffer: 30 * 1024 * 1024, encoding })).stdout;
|
|
45389
|
-
}
|
|
45390
|
-
async function clipboard(options = {}) {
|
|
45391
|
-
const run = options.command ?? command;
|
|
45392
|
-
const platform = options.platform ?? process.platform;
|
|
45393
|
-
if (platform === "darwin") {
|
|
45394
|
-
const files = await run("osascript", [
|
|
45395
|
-
"-e",
|
|
45396
|
-
`try
|
|
45397
|
-
set fs to the clipboard as \xABclass furl\xBB
|
|
45398
|
-
return POSIX path of fs
|
|
45399
|
-
on error
|
|
45400
|
-
return ""
|
|
45401
|
-
end try`
|
|
45402
|
-
]).catch(() => "");
|
|
45403
|
-
if (String(files).trim())
|
|
45404
|
-
return /\.(png|jpe?g|webp)$/i.test(String(files).trim()) ? { paths: [String(files).trim()] } : { text: String(files).trim() };
|
|
45405
|
-
const image = String(await run("osascript", ["-e", `try
|
|
45406
|
-
return the clipboard as \xABclass PNGf\xBB
|
|
45407
|
-
on error
|
|
45408
|
-
return ""
|
|
45409
|
-
end try`]).catch(() => ""));
|
|
45410
|
-
const hex = /\u00ABdata PNGf([0-9A-Fa-f]+)\u00BB/.exec(image)?.[1];
|
|
45411
|
-
if (hex)
|
|
45412
|
-
return { image: { name: "Clipboard.png", mimeType: "image/png", bytes: Buffer.from(hex, "hex") } };
|
|
45413
|
-
return { text: String(await run("pbpaste", [])) };
|
|
45414
|
-
}
|
|
45415
|
-
if (platform === "win32") {
|
|
45416
|
-
const value = String(await run("powershell.exe", [
|
|
45417
|
-
"-NoProfile",
|
|
45418
|
-
"-STA",
|
|
45419
|
-
"-Command",
|
|
45420
|
-
'Add-Type -AssemblyName System.Windows.Forms; $i=[Windows.Forms.Clipboard]::GetImage(); if($i){$m=New-Object IO.MemoryStream; $i.Save($m,[Drawing.Imaging.ImageFormat]::Png); "IMAGE:"+[Convert]::ToBase64String($m.ToArray());$i.Dispose();$m.Dispose()}else{Get-Clipboard -Raw}'
|
|
45421
|
-
]));
|
|
45422
|
-
if (value.startsWith("IMAGE:"))
|
|
45423
|
-
return {
|
|
45424
|
-
image: { name: "Clipboard.png", mimeType: "image/png", bytes: Buffer.from(value.slice(6).trim(), "base64") }
|
|
45425
|
-
};
|
|
45426
|
-
return { text: value };
|
|
45427
|
-
}
|
|
45428
|
-
const wayland = options.wayland ?? !!process.env.WAYLAND_DISPLAY;
|
|
45429
|
-
const tool = wayland ? "wl-paste" : "xclip";
|
|
45430
|
-
try {
|
|
45431
|
-
const bytes = await run(tool, wayland ? ["--type", "image/png"] : ["-selection", "clipboard", "-t", "image/png", "-o"], "buffer");
|
|
45432
|
-
if (bytes.length)
|
|
45433
|
-
return { image: { name: "Clipboard.png", mimeType: "image/png", bytes: Buffer.from(bytes) } };
|
|
45434
|
-
} catch {}
|
|
45435
|
-
return { text: String(await run(tool, wayland ? ["--no-newline"] : ["-selection", "clipboard", "-o"])) };
|
|
45436
|
-
}
|
|
45437
|
-
function isClipboardShortcut(data, platform = process.platform) {
|
|
45438
|
-
return !isKeyRelease4(data) && !isKeyRepeat2(data) && matchesKey9(data, platform === "win32" ? "alt+v" : "ctrl+v");
|
|
45439
|
-
}
|
|
45440
|
-
var exec;
|
|
45441
|
-
var init_image_input = __esm(() => {
|
|
45442
|
-
exec = promisify2(execFile3);
|
|
45443
|
-
});
|
|
45444
|
-
|
|
45445
45787
|
// packages/tui/src/question-dock.ts
|
|
45446
45788
|
import {
|
|
45447
45789
|
Input as Input3,
|
|
@@ -46440,6 +46782,31 @@ var init_search_ui = __esm(() => {
|
|
|
46440
46782
|
};
|
|
46441
46783
|
});
|
|
46442
46784
|
|
|
46785
|
+
// packages/tui/src/shell-notice.ts
|
|
46786
|
+
function shellNoticeText(shell) {
|
|
46787
|
+
if (shell.status !== "missing")
|
|
46788
|
+
return;
|
|
46789
|
+
if (shell.reason === "invalid_config")
|
|
46790
|
+
return `${shell.detail ?? "shell.path is invalid."} Bash and background jobs are unavailable. Fix shell.path in neko.config.json.`;
|
|
46791
|
+
return "Git Bash not found: bash and background jobs are unavailable. Install Git for Windows (https://git-scm.com/download/win) or set shell.path in neko.config.json.";
|
|
46792
|
+
}
|
|
46793
|
+
function createShellNotifier(show) {
|
|
46794
|
+
let last;
|
|
46795
|
+
return (shell) => {
|
|
46796
|
+
if (shell === undefined)
|
|
46797
|
+
return;
|
|
46798
|
+
const text = shellNoticeText(shell);
|
|
46799
|
+
if (text === undefined) {
|
|
46800
|
+
last = undefined;
|
|
46801
|
+
return;
|
|
46802
|
+
}
|
|
46803
|
+
if (text === last)
|
|
46804
|
+
return;
|
|
46805
|
+
last = text;
|
|
46806
|
+
show(text);
|
|
46807
|
+
};
|
|
46808
|
+
}
|
|
46809
|
+
|
|
46443
46810
|
// packages/tui/src/skill-market-ui.ts
|
|
46444
46811
|
import { ChordError as ChordError6 } from "@earendil-works/chord";
|
|
46445
46812
|
import { Markdown as Markdown3, matchesKey as matchesKey13, wrapTextWithAnsi as wrapTextWithAnsi6 } from "@earendil-works/pi-tui";
|
|
@@ -47222,6 +47589,7 @@ class NekoTuiApp {
|
|
|
47222
47589
|
#notices = new Container6;
|
|
47223
47590
|
#versionNotice = new Container6;
|
|
47224
47591
|
#versionStop;
|
|
47592
|
+
#shellStop;
|
|
47225
47593
|
#versionTimer;
|
|
47226
47594
|
#terminal = new ProcessTerminal;
|
|
47227
47595
|
#panel;
|
|
@@ -47391,7 +47759,7 @@ class NekoTuiApp {
|
|
|
47391
47759
|
showAgents: () => this.#showAgents(),
|
|
47392
47760
|
toggleQuestion: () => this.#toggleQuestion(),
|
|
47393
47761
|
clipboardKey: (data) => {
|
|
47394
|
-
if (!matchesKey17(data,
|
|
47762
|
+
if (!matchesKey17(data, clipboardShortcutKey()))
|
|
47395
47763
|
return false;
|
|
47396
47764
|
if (isClipboardShortcut(data))
|
|
47397
47765
|
this.pasteClipboard();
|
|
@@ -47480,15 +47848,20 @@ class NekoTuiApp {
|
|
|
47480
47848
|
this.#ui.requestRender();
|
|
47481
47849
|
};
|
|
47482
47850
|
this.#versionStop = this.#gateway.client.application.info.subscribe(() => void refreshVersion());
|
|
47851
|
+
const notifyShell = createShellNotifier((text) => this.addNotice(text, "warning", { wrap: true, ttlMs: 20000 }));
|
|
47852
|
+
const checkShell = () => notifyShell(this.#gateway.client.application.info.value?.shell);
|
|
47853
|
+
this.#shellStop = this.#gateway.client.application.info.subscribe(checkShell);
|
|
47483
47854
|
this.#versionTimer = setInterval(() => void refreshVersion(), 30000);
|
|
47484
47855
|
refreshVersion();
|
|
47485
47856
|
await this.switchSession(initialSessionId);
|
|
47486
47857
|
this.#ui.start();
|
|
47487
47858
|
this.#ui.requestRender(true);
|
|
47859
|
+
checkShell();
|
|
47488
47860
|
loadAllHighlightLanguages();
|
|
47489
47861
|
this.#startThemeDetection();
|
|
47490
47862
|
await this.#exited;
|
|
47491
47863
|
this.#versionStop?.();
|
|
47864
|
+
this.#shellStop?.();
|
|
47492
47865
|
clearInterval(this.#versionTimer);
|
|
47493
47866
|
this.#viewUnsubscribe?.();
|
|
47494
47867
|
this.#directoryUnsubscribe?.();
|
|
@@ -48679,7 +49052,7 @@ var init_open_browser = () => {};
|
|
|
48679
49052
|
import { spawn as spawn4 } from "child_process";
|
|
48680
49053
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
48681
49054
|
import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile7 } from "fs/promises";
|
|
48682
|
-
import { dirname as dirname12, join as
|
|
49055
|
+
import { dirname as dirname12, join as join17 } from "path";
|
|
48683
49056
|
async function ensureNekoGateway(options) {
|
|
48684
49057
|
const timeoutMs = options.spawnTimeoutMs ?? 30000;
|
|
48685
49058
|
const timeoutAt = Date.now() + timeoutMs;
|
|
@@ -48721,11 +49094,11 @@ async function ensureNekoGateway(options) {
|
|
|
48721
49094
|
}
|
|
48722
49095
|
throw new Error(`\u7B49\u5F85 neko gateway \u5C31\u7EEA\u8D85\u65F6\uFF08${options.spawnTimeoutMs ?? 30000}ms\uFF09\uFF1A${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
48723
49096
|
} catch (error) {
|
|
48724
|
-
await
|
|
49097
|
+
await stopSpawnedGateway(child);
|
|
48725
49098
|
throw error;
|
|
48726
49099
|
}
|
|
48727
49100
|
}
|
|
48728
|
-
const socketPath = options.socketPath ??
|
|
49101
|
+
const socketPath = options.socketPath ?? join17(nekoDir, `${serverId}.sock`);
|
|
48729
49102
|
const host = options.host ?? "127.0.0.1";
|
|
48730
49103
|
const port = options.port ?? 8321;
|
|
48731
49104
|
const wsUrl = `ws://${host.includes(":") && !host.startsWith("[") ? `[${host}]` : host}:${port}`;
|
|
@@ -48764,15 +49137,18 @@ async function ensureNekoGateway(options) {
|
|
|
48764
49137
|
}
|
|
48765
49138
|
throw new Error(`\u7B49\u5F85 neko gateway \u5C31\u7EEA\u8D85\u65F6\uFF08${options.spawnTimeoutMs ?? 30000}ms\uFF09\uFF1A${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
48766
49139
|
} catch (error) {
|
|
48767
|
-
await
|
|
49140
|
+
await stopSpawnedGateway(child);
|
|
48768
49141
|
throw error;
|
|
48769
49142
|
}
|
|
48770
49143
|
}
|
|
49144
|
+
function stopSpawnedGateway(child) {
|
|
49145
|
+
return stopGatewayProcess(child, { viaStdin: process.platform === "win32" });
|
|
49146
|
+
}
|
|
48771
49147
|
function spawnGatewayProcess(options, socketPath, serverId) {
|
|
48772
49148
|
const args = [
|
|
48773
49149
|
"--host",
|
|
48774
49150
|
options.host ?? "127.0.0.1",
|
|
48775
|
-
...process.platform === "win32" ? ["--no-socket"] : ["--port", String(options.port ?? 8321), "--socket-path", socketPath ?? ""],
|
|
49151
|
+
...process.platform === "win32" ? ["--no-socket", "--stop-on-stdin"] : ["--port", String(options.port ?? 8321), "--socket-path", socketPath ?? ""],
|
|
48776
49152
|
"--server-id",
|
|
48777
49153
|
serverId,
|
|
48778
49154
|
"--storage",
|
|
@@ -48782,7 +49158,10 @@ function spawnGatewayProcess(options, socketPath, serverId) {
|
|
|
48782
49158
|
...options.model !== undefined ? ["--model", `${options.model.provider}/${options.model.modelId}`] : []
|
|
48783
49159
|
];
|
|
48784
49160
|
const command = gatewayCommand(args);
|
|
48785
|
-
return spawn4(command.binary, command.args, {
|
|
49161
|
+
return spawn4(command.binary, command.args, {
|
|
49162
|
+
env: process.env,
|
|
49163
|
+
stdio: [process.platform === "win32" ? "pipe" : "ignore", "pipe", "pipe"]
|
|
49164
|
+
});
|
|
48786
49165
|
}
|
|
48787
49166
|
function pipeChildLogs(child, onLog) {
|
|
48788
49167
|
if (onLog === undefined)
|
|
@@ -48839,7 +49218,7 @@ function makeHandle(serverId, socketPath, wsUrl, spawned, nekoDir) {
|
|
|
48839
49218
|
spawned,
|
|
48840
49219
|
async close() {
|
|
48841
49220
|
if (spawned !== null) {
|
|
48842
|
-
await
|
|
49221
|
+
await stopSpawnedGateway(spawned);
|
|
48843
49222
|
if (process.platform === "win32")
|
|
48844
49223
|
await removeGatewayPortFile(nekoDir, serverId).catch(() => {});
|
|
48845
49224
|
}
|
|
@@ -48847,7 +49226,7 @@ function makeHandle(serverId, socketPath, wsUrl, spawned, nekoDir) {
|
|
|
48847
49226
|
};
|
|
48848
49227
|
}
|
|
48849
49228
|
async function loadOrCreateServerId2(nekoDir) {
|
|
48850
|
-
const file =
|
|
49229
|
+
const file = join17(nekoDir, SERVER_ID_FILENAME3);
|
|
48851
49230
|
try {
|
|
48852
49231
|
const existing = (await readFile10(file, "utf8")).trim();
|
|
48853
49232
|
if (existing !== "")
|
|
@@ -49172,9 +49551,9 @@ var init_mobile_admin = __esm(() => {
|
|
|
49172
49551
|
|
|
49173
49552
|
// packages/web/src/open-target.ts
|
|
49174
49553
|
import { spawn as spawn5 } from "child_process";
|
|
49175
|
-
import { existsSync as existsSync4, readFileSync as
|
|
49176
|
-
import { homedir as
|
|
49177
|
-
import { posix, win32 } from "path";
|
|
49554
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
|
|
49555
|
+
import { homedir as homedir8 } from "os";
|
|
49556
|
+
import { posix, win32 as win322 } from "path";
|
|
49178
49557
|
function isOpenTargetId(value) {
|
|
49179
49558
|
return value === "vscode" || value === "file-manager";
|
|
49180
49559
|
}
|
|
@@ -49193,9 +49572,9 @@ function systemOpenHost() {
|
|
|
49193
49572
|
const platform = process.platform;
|
|
49194
49573
|
return {
|
|
49195
49574
|
platform,
|
|
49196
|
-
wsl: detectWsl2(platform, process.env, () =>
|
|
49575
|
+
wsl: detectWsl2(platform, process.env, () => readFileSync5("/proc/version", "utf8")),
|
|
49197
49576
|
env: process.env,
|
|
49198
|
-
home:
|
|
49577
|
+
home: homedir8(),
|
|
49199
49578
|
exists: existsSync4
|
|
49200
49579
|
};
|
|
49201
49580
|
}
|
|
@@ -49208,7 +49587,7 @@ function fileManagerLabel(host) {
|
|
|
49208
49587
|
}
|
|
49209
49588
|
function findOnPath(host, names) {
|
|
49210
49589
|
const windows = host.platform === "win32";
|
|
49211
|
-
const pathApi = windows ?
|
|
49590
|
+
const pathApi = windows ? win322 : posix;
|
|
49212
49591
|
const raw = host.env[windows ? "Path" : "PATH"] ?? host.env.PATH ?? host.env.Path ?? "";
|
|
49213
49592
|
for (const entry of raw.split(windows ? ";" : ":")) {
|
|
49214
49593
|
if (entry === "")
|
|
@@ -49224,7 +49603,7 @@ function findOnPath(host, names) {
|
|
|
49224
49603
|
function windowsCodeExecutable(host) {
|
|
49225
49604
|
const shim = findOnPath(host, ["code.cmd"]);
|
|
49226
49605
|
if (shim !== undefined) {
|
|
49227
|
-
const exe =
|
|
49606
|
+
const exe = win322.join(win322.dirname(win322.dirname(shim)), "Code.exe");
|
|
49228
49607
|
if (host.exists(exe))
|
|
49229
49608
|
return exe;
|
|
49230
49609
|
}
|
|
@@ -49235,7 +49614,7 @@ function windowsCodeExecutable(host) {
|
|
|
49235
49614
|
const base = host.env[variable];
|
|
49236
49615
|
if (base === undefined || base === "")
|
|
49237
49616
|
continue;
|
|
49238
|
-
const exe =
|
|
49617
|
+
const exe = win322.join(base, suffix);
|
|
49239
49618
|
if (host.exists(exe))
|
|
49240
49619
|
return exe;
|
|
49241
49620
|
}
|
|
@@ -49280,8 +49659,7 @@ function spawnDetached(spec) {
|
|
|
49280
49659
|
const child = spawn5(spec.command, [...spec.args], {
|
|
49281
49660
|
cwd: spec.cwd,
|
|
49282
49661
|
detached: true,
|
|
49283
|
-
stdio: "ignore"
|
|
49284
|
-
windowsHide: true
|
|
49662
|
+
stdio: "ignore"
|
|
49285
49663
|
});
|
|
49286
49664
|
const timer = setTimeout(() => reject(new OpenError("launch_failed", "\u542F\u52A8\u8D85\u65F6")), LAUNCH_TIMEOUT_MS);
|
|
49287
49665
|
child.once("error", (error) => {
|
|
@@ -49322,7 +49700,7 @@ var init_open_target = __esm(() => {
|
|
|
49322
49700
|
});
|
|
49323
49701
|
|
|
49324
49702
|
// packages/web/src/open-api.ts
|
|
49325
|
-
import { realpath as realpath4, stat as
|
|
49703
|
+
import { realpath as realpath4, stat as stat7 } from "fs/promises";
|
|
49326
49704
|
import { isAbsolute as isAbsolute5 } from "path";
|
|
49327
49705
|
async function validateDirectory(value) {
|
|
49328
49706
|
if (typeof value !== "string" || value === "" || value.length > MAX_PATH_LENGTH)
|
|
@@ -49331,7 +49709,7 @@ async function validateDirectory(value) {
|
|
|
49331
49709
|
return;
|
|
49332
49710
|
try {
|
|
49333
49711
|
const resolved = await realpath4(value);
|
|
49334
|
-
return (await
|
|
49712
|
+
return (await stat7(resolved)).isDirectory() ? resolved : undefined;
|
|
49335
49713
|
} catch {
|
|
49336
49714
|
return;
|
|
49337
49715
|
}
|
|
@@ -49393,7 +49771,7 @@ var init_open_api = __esm(() => {
|
|
|
49393
49771
|
|
|
49394
49772
|
// packages/web/src/server.ts
|
|
49395
49773
|
import { createReadStream } from "fs";
|
|
49396
|
-
import { stat as
|
|
49774
|
+
import { stat as stat8 } from "fs/promises";
|
|
49397
49775
|
import { createServer as createServer3 } from "http";
|
|
49398
49776
|
import { dirname as dirname13, extname as extname2, resolve as resolve7, sep as sep3 } from "path";
|
|
49399
49777
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
@@ -49525,18 +49903,18 @@ async function serveStatic(pathname, method, response, staticDir) {
|
|
|
49525
49903
|
return;
|
|
49526
49904
|
}
|
|
49527
49905
|
let file = candidate;
|
|
49528
|
-
let stats = await
|
|
49906
|
+
let stats = await stat8(file).catch(() => {
|
|
49529
49907
|
return;
|
|
49530
49908
|
});
|
|
49531
49909
|
if (stats?.isDirectory()) {
|
|
49532
49910
|
file = resolve7(file, "index.html");
|
|
49533
|
-
stats = await
|
|
49911
|
+
stats = await stat8(file).catch(() => {
|
|
49534
49912
|
return;
|
|
49535
49913
|
});
|
|
49536
49914
|
}
|
|
49537
49915
|
if (stats === undefined && extname2(candidate) === "") {
|
|
49538
49916
|
file = resolve7(staticDir, "index.html");
|
|
49539
|
-
stats = await
|
|
49917
|
+
stats = await stat8(file).catch(() => {
|
|
49540
49918
|
return;
|
|
49541
49919
|
});
|
|
49542
49920
|
}
|
|
@@ -49823,6 +50201,9 @@ function parseGatewayArgs(argv) {
|
|
|
49823
50201
|
options.configDir = next;
|
|
49824
50202
|
i++;
|
|
49825
50203
|
break;
|
|
50204
|
+
case "--stop-on-stdin":
|
|
50205
|
+
options.stopOnStdin = true;
|
|
50206
|
+
break;
|
|
49826
50207
|
default:
|
|
49827
50208
|
throw new Error(`\u672A\u77E5\u9009\u9879: ${arg}`);
|
|
49828
50209
|
}
|
|
@@ -49844,6 +50225,7 @@ var GATEWAY_USAGE = `\u7528\u6CD5: neko serve [\u9009\u9879]
|
|
|
49844
50225
|
--storage <path> SQLite \u5B58\u50A8\u6587\u4EF6\uFF08\u9ED8\u8BA4 ~/.neko/neko.sqlite\uFF09
|
|
49845
50226
|
--credentials <path> \u7528\u6237\u51ED\u636E\u6587\u4EF6\uFF08\u9ED8\u8BA4 ~/.neko/credentials.json\uFF09
|
|
49846
50227
|
--config <dir> neko.config.json \u67E5\u627E\u8D77\u59CB\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09
|
|
50228
|
+
--stop-on-stdin \u6807\u51C6\u8F93\u5165\u7ED3\u675F\u65F6\u5173\u505C\u7F51\u5173\uFF08\u4F9B Windows \u4E0A\u62C9\u8D77\u7F51\u5173\u7684\u524D\u7AEF\u4F7F\u7528\uFF09
|
|
49847
50229
|
-h, --help \u663E\u793A\u6B64\u5E2E\u52A9
|
|
49848
50230
|
|
|
49849
50231
|
\u53EF\u901A\u8FC7\u524D\u7AEF\u8BBE\u7F6E\u6216\u73AF\u5883\u53D8\u91CF\uFF08DEEPSEEK_API_KEY / OPENROUTER_API_KEY / OPENCODE_API_KEY / TAVILY_API_KEY \u7B49\uFF09\u914D\u7F6E\u51ED\u636E\uFF1B
|
|
@@ -49876,6 +50258,18 @@ var init_main3 = __esm(async () => {
|
|
|
49876
50258
|
console.log(` WebSocket: ${gateway.wsUrl}`);
|
|
49877
50259
|
console.log("\u6309 Ctrl+C \u505C\u6B62\u3002");
|
|
49878
50260
|
shutdown2.setCleanup(() => gateway.close());
|
|
50261
|
+
if (options.stopOnStdin === true) {
|
|
50262
|
+
let triggered = false;
|
|
50263
|
+
const onStdinEnd = () => {
|
|
50264
|
+
if (triggered)
|
|
50265
|
+
return;
|
|
50266
|
+
triggered = true;
|
|
50267
|
+
shutdown2.trigger("stdin \u5DF2\u5173\u95ED");
|
|
50268
|
+
};
|
|
50269
|
+
process.stdin.once("end", onStdinEnd);
|
|
50270
|
+
process.stdin.once("close", onStdinEnd);
|
|
50271
|
+
process.stdin.resume();
|
|
50272
|
+
}
|
|
49879
50273
|
});
|
|
49880
50274
|
|
|
49881
50275
|
// packages/cli/src/main.ts
|