neko-harness 0.4.2 → 0.4.4
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 +3 -1
- package/dist/client/assets/index-BjLs6qQU.css +2 -0
- package/dist/client/assets/index-BqLuRtzq.js +391 -0
- package/dist/client/index.html +2 -2
- package/dist/gateway.js +1880 -1082
- package/dist/neko.js +1925 -551
- package/package.json +6 -6
- package/dist/client/assets/index-C4UO2YJF.js +0 -391
- package/dist/client/assets/index-DPSmVlrG.css +0 -2
package/dist/neko.js
CHANGED
|
@@ -80,7 +80,7 @@ function updateAction(input) {
|
|
|
80
80
|
return "install";
|
|
81
81
|
return "none";
|
|
82
82
|
}
|
|
83
|
-
var RUNNING_VERSION = "0.4.
|
|
83
|
+
var RUNNING_VERSION = "0.4.4", APPLICATION_PROTOCOL = 6, ApplicationProtocolError;
|
|
84
84
|
var init_application_version = __esm(() => {
|
|
85
85
|
ApplicationProtocolError = class ApplicationProtocolError extends Error {
|
|
86
86
|
constructor() {
|
|
@@ -375,6 +375,182 @@ var init_update = __esm(() => {
|
|
|
375
375
|
init_application_version();
|
|
376
376
|
});
|
|
377
377
|
|
|
378
|
+
// packages/core/src/context-limits.ts
|
|
379
|
+
var PERCENT, CONTEXT_FIELD_LIMITS, PRUNING_DEFAULTS;
|
|
380
|
+
var init_context_limits = __esm(() => {
|
|
381
|
+
PERCENT = { kind: "int", min: 1, max: 99 };
|
|
382
|
+
CONTEXT_FIELD_LIMITS = {
|
|
383
|
+
compaction: {
|
|
384
|
+
enabled: { kind: "boolean" },
|
|
385
|
+
triggerPercent: PERCENT,
|
|
386
|
+
keepRecentTokens: { kind: "int", min: 1, max: 1e8 }
|
|
387
|
+
},
|
|
388
|
+
pruning: {
|
|
389
|
+
enabled: { kind: "boolean" },
|
|
390
|
+
triggerPercent: PERCENT,
|
|
391
|
+
keepRecent: { kind: "int", min: 0, max: 1e5 },
|
|
392
|
+
minChars: { kind: "int", min: 1, max: 1e8 },
|
|
393
|
+
minSavePercent: PERCENT
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
PRUNING_DEFAULTS = {
|
|
397
|
+
enabled: false,
|
|
398
|
+
triggerPercent: 35,
|
|
399
|
+
keepRecent: 10,
|
|
400
|
+
minChars: 4000,
|
|
401
|
+
minSavePercent: 5
|
|
402
|
+
};
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// packages/core/src/context-settings.ts
|
|
406
|
+
import { DEFAULT_COMPACTION_POLICY } from "@earendil-works/pi-durable";
|
|
407
|
+
function describe(field) {
|
|
408
|
+
return field.kind === "boolean" ? "a boolean" : `an integer between ${field.min} and ${field.max}`;
|
|
409
|
+
}
|
|
410
|
+
function validateGroups(value, prefix, allowNull) {
|
|
411
|
+
if (!isObject(value))
|
|
412
|
+
throw new Error(`"${prefix}" must be an object.`);
|
|
413
|
+
const out = {};
|
|
414
|
+
for (const key of Object.keys(value)) {
|
|
415
|
+
if (!(key in SCHEMA))
|
|
416
|
+
throw new Error(`"${prefix}.${key}" is not a known setting.`);
|
|
417
|
+
}
|
|
418
|
+
for (const [group, fields] of Object.entries(SCHEMA)) {
|
|
419
|
+
const given = value[group];
|
|
420
|
+
if (given === undefined)
|
|
421
|
+
continue;
|
|
422
|
+
if (!isObject(given))
|
|
423
|
+
throw new Error(`"${prefix}.${group}" must be an object.`);
|
|
424
|
+
const target = {};
|
|
425
|
+
for (const key of Object.keys(given)) {
|
|
426
|
+
if (!(key in fields))
|
|
427
|
+
throw new Error(`"${prefix}.${group}.${key}" is not a known setting.`);
|
|
428
|
+
}
|
|
429
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
430
|
+
const item = given[name];
|
|
431
|
+
if (item === undefined)
|
|
432
|
+
continue;
|
|
433
|
+
if (item === null && allowNull) {
|
|
434
|
+
target[name] = null;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const ok = field.kind === "boolean" ? typeof item === "boolean" : typeof item === "number" && Number.isInteger(item) && item >= field.min && item <= field.max;
|
|
438
|
+
if (!ok)
|
|
439
|
+
throw new Error(`"${prefix}.${group}.${name}" must be ${describe(field)}.`);
|
|
440
|
+
target[name] = item;
|
|
441
|
+
}
|
|
442
|
+
out[group] = target;
|
|
443
|
+
}
|
|
444
|
+
return out;
|
|
445
|
+
}
|
|
446
|
+
function parseContextSettings(value, prefix = "context") {
|
|
447
|
+
return validateGroups(value, prefix, false);
|
|
448
|
+
}
|
|
449
|
+
function parseContextSettingsPatch(value) {
|
|
450
|
+
return validateGroups(value, "context", true);
|
|
451
|
+
}
|
|
452
|
+
function applyContextPatch(saved, patch) {
|
|
453
|
+
const next = {
|
|
454
|
+
compaction: { ...saved.compaction },
|
|
455
|
+
pruning: { ...saved.pruning }
|
|
456
|
+
};
|
|
457
|
+
for (const [key, value] of Object.entries(patch.compaction ?? {})) {
|
|
458
|
+
if (value !== undefined)
|
|
459
|
+
next.compaction[key] = value;
|
|
460
|
+
}
|
|
461
|
+
for (const [key, value] of Object.entries(patch.pruning ?? {})) {
|
|
462
|
+
if (value !== undefined)
|
|
463
|
+
next.pruning[key] = value;
|
|
464
|
+
}
|
|
465
|
+
return next;
|
|
466
|
+
}
|
|
467
|
+
function sanitizeSavedContextSettings(raw) {
|
|
468
|
+
const saved = emptySavedContextSettings();
|
|
469
|
+
if (!isObject(raw))
|
|
470
|
+
return saved;
|
|
471
|
+
for (const [group, fields] of Object.entries(SCHEMA)) {
|
|
472
|
+
const stored = raw[group];
|
|
473
|
+
if (!isObject(stored))
|
|
474
|
+
continue;
|
|
475
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
476
|
+
const item = stored[name];
|
|
477
|
+
const ok = field.kind === "boolean" ? typeof item === "boolean" : typeof item === "number" && Number.isInteger(item) && item >= field.min && item <= field.max;
|
|
478
|
+
if (ok)
|
|
479
|
+
saved[group][name] = item;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return saved;
|
|
483
|
+
}
|
|
484
|
+
function pick(saved, config, fallback) {
|
|
485
|
+
if (saved !== null && saved !== undefined)
|
|
486
|
+
return { value: saved, source: "saved" };
|
|
487
|
+
if (config !== undefined)
|
|
488
|
+
return { value: config, source: "config" };
|
|
489
|
+
return { value: fallback, source: "default" };
|
|
490
|
+
}
|
|
491
|
+
function resolveContextSettings(config, saved) {
|
|
492
|
+
const cEnabled = pick(saved.compaction.enabled, config?.compaction?.enabled, DEFAULT_COMPACTION_POLICY.enabled);
|
|
493
|
+
const cTrigger = pick(saved.compaction.triggerPercent, config?.compaction?.triggerPercent, null);
|
|
494
|
+
const cKeep = pick(saved.compaction.keepRecentTokens, config?.compaction?.keepRecentTokens, DEFAULT_COMPACTION_POLICY.keepRecentTokens);
|
|
495
|
+
const pEnabled = pick(saved.pruning.enabled, config?.pruning?.enabled, PRUNING_DEFAULTS.enabled);
|
|
496
|
+
const pTrigger = pick(saved.pruning.triggerPercent, config?.pruning?.triggerPercent, PRUNING_DEFAULTS.triggerPercent);
|
|
497
|
+
const pKeep = pick(saved.pruning.keepRecent, config?.pruning?.keepRecent, PRUNING_DEFAULTS.keepRecent);
|
|
498
|
+
const pMin = pick(saved.pruning.minChars, config?.pruning?.minChars, PRUNING_DEFAULTS.minChars);
|
|
499
|
+
const pSave = pick(saved.pruning.minSavePercent, config?.pruning?.minSavePercent, PRUNING_DEFAULTS.minSavePercent);
|
|
500
|
+
return {
|
|
501
|
+
effective: {
|
|
502
|
+
compaction: { enabled: cEnabled.value, triggerPercent: cTrigger.value, keepRecentTokens: cKeep.value },
|
|
503
|
+
pruning: {
|
|
504
|
+
enabled: pEnabled.value,
|
|
505
|
+
triggerPercent: pTrigger.value,
|
|
506
|
+
keepRecent: pKeep.value,
|
|
507
|
+
minChars: pMin.value,
|
|
508
|
+
minSavePercent: pSave.value
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
sources: {
|
|
512
|
+
compaction: { enabled: cEnabled.source, triggerPercent: cTrigger.source, keepRecentTokens: cKeep.source },
|
|
513
|
+
pruning: {
|
|
514
|
+
enabled: pEnabled.source,
|
|
515
|
+
triggerPercent: pTrigger.source,
|
|
516
|
+
keepRecent: pKeep.source,
|
|
517
|
+
minChars: pMin.source,
|
|
518
|
+
minSavePercent: pSave.source
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function compactionPolicy(state, windows) {
|
|
524
|
+
const { effective, sources } = state;
|
|
525
|
+
const policy = {};
|
|
526
|
+
if (sources.compaction.enabled !== "default")
|
|
527
|
+
policy.enabled = effective.compaction.enabled;
|
|
528
|
+
if (sources.compaction.keepRecentTokens !== "default")
|
|
529
|
+
policy.keepRecentTokens = effective.compaction.keepRecentTokens;
|
|
530
|
+
const percent = effective.compaction.triggerPercent;
|
|
531
|
+
const window2 = windows.length > 0 ? Math.min(...windows) : 0;
|
|
532
|
+
if (percent !== null && window2 > 0)
|
|
533
|
+
policy.reserveTokens = window2 - Math.floor(window2 * percent / 100);
|
|
534
|
+
return policy;
|
|
535
|
+
}
|
|
536
|
+
var emptySavedContextSettings = () => ({
|
|
537
|
+
compaction: { enabled: null, triggerPercent: null, keepRecentTokens: null },
|
|
538
|
+
pruning: { enabled: null, triggerPercent: null, keepRecent: null, minChars: null, minSavePercent: null }
|
|
539
|
+
}), SCHEMA, isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
540
|
+
var init_context_settings = __esm(() => {
|
|
541
|
+
init_context_limits();
|
|
542
|
+
SCHEMA = CONTEXT_FIELD_LIMITS;
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// packages/core/src/search-providers.ts
|
|
546
|
+
function isSearchProvider(value) {
|
|
547
|
+
return typeof value === "string" && SEARCH_PROVIDERS.includes(value);
|
|
548
|
+
}
|
|
549
|
+
var SEARCH_PROVIDERS, DEFAULT_SEARCH_PROVIDER = "tavily";
|
|
550
|
+
var init_search_providers = __esm(() => {
|
|
551
|
+
SEARCH_PROVIDERS = ["tavily", "deepseek"];
|
|
552
|
+
});
|
|
553
|
+
|
|
378
554
|
// packages/core/src/config.ts
|
|
379
555
|
import { existsSync, readFileSync } from "fs";
|
|
380
556
|
import { homedir as homedir2 } from "os";
|
|
@@ -489,9 +665,28 @@ function validate(config, source) {
|
|
|
489
665
|
throw new Error(`neko config: ${source}: "skills.marketUrl" must be a string.`);
|
|
490
666
|
}
|
|
491
667
|
}
|
|
668
|
+
const webSearch = config.webSearch;
|
|
669
|
+
if (webSearch !== undefined) {
|
|
670
|
+
if (typeof webSearch !== "object" || webSearch === null || Array.isArray(webSearch)) {
|
|
671
|
+
throw new Error(`neko config: ${source}: "webSearch" must be an object.`);
|
|
672
|
+
}
|
|
673
|
+
const { provider } = webSearch;
|
|
674
|
+
if (provider !== undefined && !isSearchProvider(provider)) {
|
|
675
|
+
throw new Error(`neko config: ${source}: "webSearch.provider" must be one of ${SEARCH_PROVIDERS.map((item) => `"${item}"`).join(", ")}.`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (config.context !== undefined) {
|
|
679
|
+
try {
|
|
680
|
+
parseContextSettings(config.context);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
throw new Error(`neko config: ${source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
492
685
|
}
|
|
493
686
|
var DEFAULT_SKILL_MARKET_URL = "https://skill.kirineko.tech", NEKO_CONFIG_BASENAME = "neko.config.json", ENV_REFERENCE;
|
|
494
687
|
var init_config = __esm(() => {
|
|
688
|
+
init_context_settings();
|
|
689
|
+
init_search_providers();
|
|
495
690
|
ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
496
691
|
});
|
|
497
692
|
|
|
@@ -865,6 +1060,52 @@ var init_input = __esm(() => {
|
|
|
865
1060
|
NekoInput = defineEntry("app.neko.input");
|
|
866
1061
|
});
|
|
867
1062
|
|
|
1063
|
+
// packages/core/src/queued.ts
|
|
1064
|
+
function queuedUserInput(raw) {
|
|
1065
|
+
const item = raw;
|
|
1066
|
+
if (item.mode !== "steer" && item.mode !== "followUp")
|
|
1067
|
+
return;
|
|
1068
|
+
if (item.entry?.kind !== NekoInput.kind)
|
|
1069
|
+
return;
|
|
1070
|
+
const data = item.entry.data;
|
|
1071
|
+
if (!data || data.jobReport || data.subagentRoundId)
|
|
1072
|
+
return;
|
|
1073
|
+
const defaultPrompt = Array.isArray(item.content) ? savedDefaultImagePrompt(item.content, data.defaultImagePrompt) : undefined;
|
|
1074
|
+
const content = defaultPrompt && Array.isArray(item.content) ? item.content.slice(1) : item.content;
|
|
1075
|
+
const skill = data.skill ? { name: data.skill.name, args: data.skill.args } : undefined;
|
|
1076
|
+
return {
|
|
1077
|
+
id: item.id,
|
|
1078
|
+
mode: item.mode,
|
|
1079
|
+
text: skill ? `/skill:${skill.name}${skill.args ? ` ${skill.args}` : ""}` : textOf(content),
|
|
1080
|
+
images: data.images?.length ?? 0,
|
|
1081
|
+
...skill ? { skill } : {}
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function projectQueued(view) {
|
|
1085
|
+
const items = view.docs["pi.inbox"]?.items ?? [];
|
|
1086
|
+
return items.flatMap((raw) => {
|
|
1087
|
+
const input = queuedUserInput(raw);
|
|
1088
|
+
if (!input)
|
|
1089
|
+
return [];
|
|
1090
|
+
const { text, ...rest } = input;
|
|
1091
|
+
return [{ ...rest, preview: clip(text) }];
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
var QUEUED_PREVIEW_CHARS = 300, textOf = (content) => {
|
|
1095
|
+
if (typeof content === "string")
|
|
1096
|
+
return content;
|
|
1097
|
+
if (!Array.isArray(content))
|
|
1098
|
+
return "";
|
|
1099
|
+
return content.flatMap((b) => b && b.type === "text" && typeof b.text === "string" ? [b.text] : []).join("");
|
|
1100
|
+
}, clip = (text) => {
|
|
1101
|
+
const chars = [...text.replace(/\s+/g, " ").trim()];
|
|
1102
|
+
return chars.length > QUEUED_PREVIEW_CHARS ? `${chars.slice(0, QUEUED_PREVIEW_CHARS - 1).join("")}\u2026` : chars.join("");
|
|
1103
|
+
};
|
|
1104
|
+
var init_queued = __esm(() => {
|
|
1105
|
+
init_image_prompt();
|
|
1106
|
+
init_input();
|
|
1107
|
+
});
|
|
1108
|
+
|
|
868
1109
|
// packages/core/src/workspace.ts
|
|
869
1110
|
import { accessSync, constants, realpathSync, statSync } from "fs";
|
|
870
1111
|
import { isAbsolute, resolve as resolve2 } from "path";
|
|
@@ -1184,6 +1425,7 @@ function projectChatView(view, modelLabel, options = {}) {
|
|
|
1184
1425
|
},
|
|
1185
1426
|
cwd: savedWorkspace(agent?.cwd),
|
|
1186
1427
|
busy: live.run !== undefined,
|
|
1428
|
+
queued: projectQueued(view),
|
|
1187
1429
|
status,
|
|
1188
1430
|
usage: {
|
|
1189
1431
|
input: usage.input,
|
|
@@ -1296,6 +1538,7 @@ var WRITE_DIFF_MAX_LINES = 2000, WRITE_DIFF_MAX_BYTES;
|
|
|
1296
1538
|
var init_projection = __esm(() => {
|
|
1297
1539
|
init_image_prompt();
|
|
1298
1540
|
init_input();
|
|
1541
|
+
init_queued();
|
|
1299
1542
|
init_workspace();
|
|
1300
1543
|
WRITE_DIFF_MAX_BYTES = 64 * 1024;
|
|
1301
1544
|
});
|
|
@@ -1517,6 +1760,132 @@ async function readCompaction(harness, storage, id, context) {
|
|
|
1517
1760
|
}
|
|
1518
1761
|
var init_compaction = () => {};
|
|
1519
1762
|
|
|
1763
|
+
// packages/core/src/context-pruning.ts
|
|
1764
|
+
function estimateTokens(text) {
|
|
1765
|
+
const cjk = text.match(CJK)?.length ?? 0;
|
|
1766
|
+
return cjk * TOKENS_PER_CJK_CHAR + (text.length - cjk) * TOKENS_PER_LATIN_CHAR;
|
|
1767
|
+
}
|
|
1768
|
+
function toolResultChars(message) {
|
|
1769
|
+
return resultTexts(message).reduce((total, text) => total + text.length, 0);
|
|
1770
|
+
}
|
|
1771
|
+
function toolResultTokens(message) {
|
|
1772
|
+
return resultTexts(message).reduce((total, text) => total + estimateTokens(text), 0);
|
|
1773
|
+
}
|
|
1774
|
+
function prunedPlaceholder(message, chars) {
|
|
1775
|
+
const { details: _details, ...rest } = message;
|
|
1776
|
+
return {
|
|
1777
|
+
...rest,
|
|
1778
|
+
content: [
|
|
1779
|
+
{
|
|
1780
|
+
type: "text",
|
|
1781
|
+
text: `[Output of ${message.toolName} omitted from context to save space (${chars} characters). Run the tool again if the content is needed.]`
|
|
1782
|
+
}
|
|
1783
|
+
]
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
function planPrune(entries, settings) {
|
|
1787
|
+
const replaced = new Set;
|
|
1788
|
+
for (const entry of entries)
|
|
1789
|
+
for (const edit of entry.edits ?? [])
|
|
1790
|
+
replaced.add(edit.target);
|
|
1791
|
+
const results = entries.filter(isToolResult);
|
|
1792
|
+
const candidates = results.slice(0, Math.max(0, results.length - settings.keepRecent));
|
|
1793
|
+
const edits = [];
|
|
1794
|
+
const targets = [];
|
|
1795
|
+
let savedChars = 0;
|
|
1796
|
+
let savedTokens = 0;
|
|
1797
|
+
for (const entry of candidates) {
|
|
1798
|
+
if (replaced.has(entry.id))
|
|
1799
|
+
continue;
|
|
1800
|
+
const message = entry.model[0];
|
|
1801
|
+
const chars = toolResultChars(message);
|
|
1802
|
+
if (chars < settings.minChars)
|
|
1803
|
+
continue;
|
|
1804
|
+
const replacement = prunedPlaceholder(message, chars);
|
|
1805
|
+
edits.push({ target: entry.id, action: "replace", messages: [replacement] });
|
|
1806
|
+
targets.push(entry.id);
|
|
1807
|
+
savedChars += chars;
|
|
1808
|
+
savedTokens += toolResultTokens(message);
|
|
1809
|
+
}
|
|
1810
|
+
return edits.length === 0 ? undefined : { edits, targets, savedChars, savedTokens };
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
class ContextPruner {
|
|
1814
|
+
#options;
|
|
1815
|
+
#unsubscribe;
|
|
1816
|
+
#tail = Promise.resolve();
|
|
1817
|
+
#closed = false;
|
|
1818
|
+
constructor(options) {
|
|
1819
|
+
this.#options = options;
|
|
1820
|
+
this.#unsubscribe = options.harness.subscribeCommits(({ changes }) => {
|
|
1821
|
+
if (!options.settings().enabled)
|
|
1822
|
+
return;
|
|
1823
|
+
for (const change of changes) {
|
|
1824
|
+
if (change.type !== "entry" || change.value.kind !== "pi.assistant")
|
|
1825
|
+
continue;
|
|
1826
|
+
const message = change.value.model?.[0];
|
|
1827
|
+
if (message?.role !== "assistant" || message.stopReason === "aborted" || message.stopReason === "error")
|
|
1828
|
+
continue;
|
|
1829
|
+
const usage = message.usage;
|
|
1830
|
+
const tokens = usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
1831
|
+
if (tokens <= 0)
|
|
1832
|
+
continue;
|
|
1833
|
+
const { conversationId, id } = change.value;
|
|
1834
|
+
this.#enqueue(() => this.#evaluate(conversationId, id, tokens));
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
async flush() {
|
|
1839
|
+
await this.#tail;
|
|
1840
|
+
}
|
|
1841
|
+
close() {
|
|
1842
|
+
this.#closed = true;
|
|
1843
|
+
this.#unsubscribe();
|
|
1844
|
+
}
|
|
1845
|
+
#enqueue(work) {
|
|
1846
|
+
if (this.#closed)
|
|
1847
|
+
return;
|
|
1848
|
+
this.#tail = this.#tail.then(work).catch((error) => {
|
|
1849
|
+
if (!this.#closed)
|
|
1850
|
+
this.#options.report(error);
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
async#evaluate(conversationId, entryId, tokens) {
|
|
1854
|
+
const { harness, context } = this.#options;
|
|
1855
|
+
const settings = this.#options.settings();
|
|
1856
|
+
if (!settings.enabled)
|
|
1857
|
+
return;
|
|
1858
|
+
const window2 = await this.#options.windowOf(conversationId);
|
|
1859
|
+
if (window2 <= 0 || tokens / window2 * 100 < settings.triggerPercent)
|
|
1860
|
+
return;
|
|
1861
|
+
const conversation = await harness.conversation(conversationId, context);
|
|
1862
|
+
if (!conversation)
|
|
1863
|
+
return;
|
|
1864
|
+
const view = await conversation.context(context);
|
|
1865
|
+
const plan = planPrune(view.entries, settings);
|
|
1866
|
+
if (!plan)
|
|
1867
|
+
return;
|
|
1868
|
+
if (plan.savedTokens / window2 * 100 < settings.minSavePercent)
|
|
1869
|
+
return;
|
|
1870
|
+
await conversation.submit({
|
|
1871
|
+
type: "write",
|
|
1872
|
+
requestId: `prune:${entryId}`,
|
|
1873
|
+
entry: {
|
|
1874
|
+
kind: PRUNE_ENTRY_KIND,
|
|
1875
|
+
data: { targets: [...plan.targets], savedChars: plan.savedChars },
|
|
1876
|
+
edits: plan.edits
|
|
1877
|
+
}
|
|
1878
|
+
}, context);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
var PRUNE_ENTRY_KIND = "app.neko.context-prune", TOKENS_PER_LATIN_CHAR = 0.3, TOKENS_PER_CJK_CHAR = 0.6, CJK, isToolResult = (entry) => {
|
|
1882
|
+
const message = entry.model?.[0];
|
|
1883
|
+
return entry.kind === "pi.tool-result" && message?.role === "toolResult";
|
|
1884
|
+
}, resultTexts = (message) => message.content.flatMap((block) => block.type === "text" ? [block.text] : []);
|
|
1885
|
+
var init_context_pruning = __esm(() => {
|
|
1886
|
+
CJK = /[\u3000-\u9fff\uac00-\ud7af\uf900-\ufaff\uff00-\uffef]/g;
|
|
1887
|
+
});
|
|
1888
|
+
|
|
1520
1889
|
// packages/core/src/image-metadata.ts
|
|
1521
1890
|
import { defineExtension as defineExtension2, hook, ToolTask } from "@earendil-works/pi-durable";
|
|
1522
1891
|
import sharp from "sharp";
|
|
@@ -2176,7 +2545,7 @@ class JobRegistry {
|
|
|
2176
2545
|
function reportText(job, tail) {
|
|
2177
2546
|
const body = reportTail(tail);
|
|
2178
2547
|
return [
|
|
2179
|
-
`[Background job ${job.jobId} "${job.title}" ${
|
|
2548
|
+
`[Background job ${job.jobId} "${job.title}" ${describe2(job)}]`,
|
|
2180
2549
|
`Command: ${job.command}`,
|
|
2181
2550
|
body ? `Last output (use the job tool's output action with jobId "${job.jobId}" for more):
|
|
2182
2551
|
${body}` : "It produced no output."
|
|
@@ -2355,7 +2724,7 @@ class JobManager {
|
|
|
2355
2724
|
if (kind === "stop") {
|
|
2356
2725
|
const jobId = stringField(args.jobId, "jobId");
|
|
2357
2726
|
const job = await this.stop(io, api.conversationId, jobId, "model", context);
|
|
2358
|
-
return this.#text(`Job ${job.jobId} ${
|
|
2727
|
+
return this.#text(`Job ${job.jobId} ${describe2(job)}.`);
|
|
2359
2728
|
}
|
|
2360
2729
|
if (kind !== "start")
|
|
2361
2730
|
throw new Error("Invalid job arguments: action must be start, output, stop or list.");
|
|
@@ -2383,7 +2752,7 @@ class JobManager {
|
|
|
2383
2752
|
#listText(items) {
|
|
2384
2753
|
if (!items.length)
|
|
2385
2754
|
return "No jobs.";
|
|
2386
|
-
return items.map((job) => `${job.jobId} [${
|
|
2755
|
+
return items.map((job) => `${job.jobId} [${describe2(job)}] "${job.title}" \u2014 ${job.command}`).join(`
|
|
2387
2756
|
`);
|
|
2388
2757
|
}
|
|
2389
2758
|
async start(io, conversationId, cwd, args, context) {
|
|
@@ -2435,7 +2804,7 @@ class JobManager {
|
|
|
2435
2804
|
return this.#render(job, read.text, read.offset, read.dropped);
|
|
2436
2805
|
}
|
|
2437
2806
|
#render(job, text, offset, dropped) {
|
|
2438
|
-
const lines = [`Job ${job.jobId} ${
|
|
2807
|
+
const lines = [`Job ${job.jobId} ${describe2(job)}.`, `Title: ${job.title}`, `Command: ${job.command}`];
|
|
2439
2808
|
if (dropped > 0)
|
|
2440
2809
|
lines.push(`(${dropped} bytes of earlier output were dropped)`);
|
|
2441
2810
|
lines.push(text ? `Output (next offset ${offset}):
|
|
@@ -2500,7 +2869,7 @@ ${text}` : `No new output (next offset ${offset}).`);
|
|
|
2500
2869
|
this.registry.abortAll();
|
|
2501
2870
|
}
|
|
2502
2871
|
}
|
|
2503
|
-
var plain = (value) => JSON.parse(JSON.stringify(value)), statusWord,
|
|
2872
|
+
var plain = (value) => JSON.parse(JSON.stringify(value)), statusWord, describe2 = (job) => {
|
|
2504
2873
|
const parts = [statusWord[job.status]];
|
|
2505
2874
|
if (job.exitCode !== null)
|
|
2506
2875
|
parts.push(`exit code ${job.exitCode}`);
|
|
@@ -2886,6 +3255,14 @@ var createResourceSnapshots = (load, submission) => {
|
|
|
2886
3255
|
discard(id, requestId) {
|
|
2887
3256
|
conversations.get(id)?.pending.delete(requestId);
|
|
2888
3257
|
},
|
|
3258
|
+
move(id, from, to) {
|
|
3259
|
+
const pending = conversations.get(id)?.pending;
|
|
3260
|
+
const snapshot = pending?.get(from);
|
|
3261
|
+
if (!pending || !snapshot)
|
|
3262
|
+
return;
|
|
3263
|
+
pending.delete(from);
|
|
3264
|
+
pending.set(to, snapshot);
|
|
3265
|
+
},
|
|
2889
3266
|
forget(id) {
|
|
2890
3267
|
conversations.delete(id);
|
|
2891
3268
|
}
|
|
@@ -3419,21 +3796,22 @@ var init_tool_approvals = __esm(() => {
|
|
|
3419
3796
|
});
|
|
3420
3797
|
});
|
|
3421
3798
|
|
|
3422
|
-
// packages/core/src/web-search.ts
|
|
3423
|
-
import { Type as Type4 } from "@earendil-works/pi-ai";
|
|
3424
|
-
import { defineExtension as defineExtension8, defineTool as defineTool4 } from "@earendil-works/pi-durable";
|
|
3799
|
+
// packages/core/src/web-search/http.ts
|
|
3425
3800
|
function redact(text, key) {
|
|
3426
3801
|
for (const secret of key ? [key, encodeURIComponent(key)] : [])
|
|
3427
3802
|
text = text.replaceAll(secret, "[redacted]");
|
|
3428
|
-
return text.replace(
|
|
3803
|
+
return text.replace(/\b(?:tvly|sk)-[a-zA-Z0-9_-]+/g, "[redacted]");
|
|
3429
3804
|
}
|
|
3430
3805
|
function record(value) {
|
|
3431
3806
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3432
3807
|
}
|
|
3433
|
-
|
|
3808
|
+
function truncate(text, max) {
|
|
3809
|
+
return text.length > max ? `${text.slice(0, Math.max(0, max - 1))}\u2026` : text;
|
|
3810
|
+
}
|
|
3811
|
+
async function readBounded(response, signal, label) {
|
|
3434
3812
|
if (Number(response.headers.get("content-length")) > MAX_RESPONSE_BYTES) {
|
|
3435
3813
|
await response.body?.cancel();
|
|
3436
|
-
throw new
|
|
3814
|
+
throw new WebToolError(`${label} response exceeds the 2 MiB limit.`);
|
|
3437
3815
|
}
|
|
3438
3816
|
if (!response.body)
|
|
3439
3817
|
return "";
|
|
@@ -3454,7 +3832,7 @@ async function readBounded(response, signal) {
|
|
|
3454
3832
|
return text + decoder.decode();
|
|
3455
3833
|
size += chunk.value.byteLength;
|
|
3456
3834
|
if (size > MAX_RESPONSE_BYTES)
|
|
3457
|
-
throw new
|
|
3835
|
+
throw new WebToolError(`${label} response exceeds the 2 MiB limit.`);
|
|
3458
3836
|
text += decoder.decode(chunk.value, { stream: true });
|
|
3459
3837
|
}
|
|
3460
3838
|
} finally {
|
|
@@ -3463,7 +3841,8 @@ async function readBounded(response, signal) {
|
|
|
3463
3841
|
reader.releaseLock();
|
|
3464
3842
|
}
|
|
3465
3843
|
}
|
|
3466
|
-
async function
|
|
3844
|
+
async function postJson(options) {
|
|
3845
|
+
const { label, context } = options;
|
|
3467
3846
|
let key;
|
|
3468
3847
|
const signal = AbortSignal.any([
|
|
3469
3848
|
AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -3471,68 +3850,190 @@ async function tavilyPost(url, body, resolveKey, fetchImpl, context) {
|
|
|
3471
3850
|
]);
|
|
3472
3851
|
try {
|
|
3473
3852
|
signal.throwIfAborted();
|
|
3474
|
-
key = await resolveKey();
|
|
3853
|
+
key = await options.resolveKey();
|
|
3475
3854
|
signal.throwIfAborted();
|
|
3476
|
-
const
|
|
3477
|
-
|
|
3478
|
-
headers
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
const text = redact(await readBounded(response, signal), key);
|
|
3855
|
+
const response = await options.fetchImpl(options.url, {
|
|
3856
|
+
method: "POST",
|
|
3857
|
+
headers: { "content-type": "application/json", ...options.headers(key) },
|
|
3858
|
+
body: JSON.stringify(options.body),
|
|
3859
|
+
signal
|
|
3860
|
+
});
|
|
3861
|
+
const text = redact(await readBounded(response, signal, label), key);
|
|
3483
3862
|
if (!response.ok)
|
|
3484
|
-
throw
|
|
3485
|
-
|
|
3486
|
-
if (!record(data))
|
|
3487
|
-
throw new TavilyError("Invalid Tavily response.");
|
|
3488
|
-
if (data.error)
|
|
3489
|
-
throw new TavilyError(`Tavily request failed: ${truncate(JSON.stringify(data.error), 500)}`);
|
|
3490
|
-
return data;
|
|
3863
|
+
throw options.httpError(response.status, text, key);
|
|
3864
|
+
return JSON.parse(text);
|
|
3491
3865
|
} catch (error) {
|
|
3492
3866
|
if (context.abortSignal?.aborted)
|
|
3493
|
-
throw new
|
|
3867
|
+
throw new WebToolError("Web request cancelled.");
|
|
3494
3868
|
if (signal.aborted)
|
|
3495
|
-
throw new
|
|
3496
|
-
if (error instanceof
|
|
3869
|
+
throw new WebToolError(`${label} request timed out. Please retry.`);
|
|
3870
|
+
if (error instanceof WebToolError)
|
|
3497
3871
|
throw error;
|
|
3498
|
-
throw new
|
|
3872
|
+
throw new WebToolError(`${label} request failed. Check the connection and credentials, then retry.`);
|
|
3499
3873
|
}
|
|
3500
3874
|
}
|
|
3875
|
+
var REQUEST_TIMEOUT_MS = 30000, MAX_RESPONSE_BYTES, WebToolError;
|
|
3876
|
+
var init_http = __esm(() => {
|
|
3877
|
+
MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
3878
|
+
WebToolError = class WebToolError extends Error {
|
|
3879
|
+
status;
|
|
3880
|
+
constructor(message, status) {
|
|
3881
|
+
super(message);
|
|
3882
|
+
this.status = status;
|
|
3883
|
+
}
|
|
3884
|
+
};
|
|
3885
|
+
});
|
|
3886
|
+
|
|
3887
|
+
// packages/core/src/web-search/deepseek.ts
|
|
3888
|
+
function defaultSleep(ms, signal) {
|
|
3889
|
+
return new Promise((resolve, reject) => {
|
|
3890
|
+
if (signal?.aborted)
|
|
3891
|
+
return reject(signal.reason);
|
|
3892
|
+
const timer = setTimeout(resolve, ms);
|
|
3893
|
+
signal?.addEventListener("abort", () => {
|
|
3894
|
+
clearTimeout(timer);
|
|
3895
|
+
reject(signal.reason);
|
|
3896
|
+
}, { once: true });
|
|
3897
|
+
});
|
|
3898
|
+
}
|
|
3899
|
+
function apiMessage(text) {
|
|
3900
|
+
try {
|
|
3901
|
+
const data = JSON.parse(text);
|
|
3902
|
+
if (record(data) && record(data.error) && typeof data.error.message === "string")
|
|
3903
|
+
return truncate(data.error.message, 300);
|
|
3904
|
+
} catch {}
|
|
3905
|
+
return truncate(text, 300);
|
|
3906
|
+
}
|
|
3907
|
+
function httpError(status, text) {
|
|
3908
|
+
const guidance = "Check the key in /login or the Web settings, or switch the search provider.";
|
|
3909
|
+
if (status === 401)
|
|
3910
|
+
return new WebToolError(`DeepSeek API key is missing or invalid. ${guidance}`, status);
|
|
3911
|
+
if (status === 402)
|
|
3912
|
+
return new WebToolError("DeepSeek account balance is insufficient. Recharge, or switch the search provider to Tavily.", status);
|
|
3913
|
+
if (status === 403)
|
|
3914
|
+
return new WebToolError("DeepSeek blocked this request by its security policy.", status);
|
|
3915
|
+
if (status === 404)
|
|
3916
|
+
return new WebToolError("DeepSeek could not fetch a usable page for this URL.", status);
|
|
3917
|
+
if (status === 429)
|
|
3918
|
+
return new WebToolError("DeepSeek rate limit reached. Retry shortly.", status);
|
|
3919
|
+
if (status === 408 || status >= 500)
|
|
3920
|
+
return new WebToolError(`DeepSeek service error (${status}). Retry shortly.`, status);
|
|
3921
|
+
return new WebToolError(`DeepSeek request failed (${status}): ${apiMessage(text)}`, status);
|
|
3922
|
+
}
|
|
3923
|
+
function dateOnly(value) {
|
|
3924
|
+
return typeof value === "string" && /^\d{4}-\d{2}-\d{2}/.test(value) ? value.slice(0, 10) : undefined;
|
|
3925
|
+
}
|
|
3926
|
+
function createDeepseekProvider(resolveKey, fetchImpl = fetch, options = {}) {
|
|
3927
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
3928
|
+
const requireKey = async () => {
|
|
3929
|
+
const key = await resolveKey();
|
|
3930
|
+
if (!key)
|
|
3931
|
+
throw new WebToolError(DEEPSEEK_MISSING_KEY);
|
|
3932
|
+
return key;
|
|
3933
|
+
};
|
|
3934
|
+
const post = (url, body, context) => postJson({
|
|
3935
|
+
label: "DeepSeek",
|
|
3936
|
+
url,
|
|
3937
|
+
body,
|
|
3938
|
+
fetchImpl,
|
|
3939
|
+
context,
|
|
3940
|
+
resolveKey: requireKey,
|
|
3941
|
+
headers: (key) => ({ authorization: `Bearer ${key}` }),
|
|
3942
|
+
httpError
|
|
3943
|
+
});
|
|
3944
|
+
return {
|
|
3945
|
+
id: "deepseek",
|
|
3946
|
+
async search(query, limit, context) {
|
|
3947
|
+
if (!query.trim())
|
|
3948
|
+
throw new WebToolError("Provide a non-empty query.");
|
|
3949
|
+
if ([...query].length > DEEPSEEK_QUERY_MAX_CHARS)
|
|
3950
|
+
throw new WebToolError(`DeepSeek search queries are limited to ${DEEPSEEK_QUERY_MAX_CHARS} characters. Shorten the query and retry.`);
|
|
3951
|
+
await requireKey();
|
|
3952
|
+
const data = await post(SEARCH_URL, { query, count: limit, need_content: false }, context);
|
|
3953
|
+
if (!record(data) || !Array.isArray(data.results) || !data.results.every(record))
|
|
3954
|
+
throw new WebToolError("Invalid DeepSeek response.");
|
|
3955
|
+
return data.results.slice(0, limit).map((item) => {
|
|
3956
|
+
const date = dateOnly(item.date);
|
|
3957
|
+
return {
|
|
3958
|
+
title: String(item.title ?? "(untitled)"),
|
|
3959
|
+
url: String(item.url ?? ""),
|
|
3960
|
+
text: String(item.summary || item.snippet || ""),
|
|
3961
|
+
...date ? { date } : {}
|
|
3962
|
+
};
|
|
3963
|
+
});
|
|
3964
|
+
},
|
|
3965
|
+
async extract(urls, context) {
|
|
3966
|
+
await requireKey();
|
|
3967
|
+
const outcomes = new Array(urls.length);
|
|
3968
|
+
let next = 0;
|
|
3969
|
+
let fatal;
|
|
3970
|
+
const fetchOne = async (url) => {
|
|
3971
|
+
for (let attempt = 0;; attempt++) {
|
|
3972
|
+
try {
|
|
3973
|
+
const data = await post(FETCH_URL, { url }, context);
|
|
3974
|
+
if (!record(data) || typeof data.content !== "string")
|
|
3975
|
+
throw new WebToolError("Invalid DeepSeek response.");
|
|
3976
|
+
return { url, text: data.content };
|
|
3977
|
+
} catch (error) {
|
|
3978
|
+
const failure = error instanceof WebToolError ? error : new WebToolError("DeepSeek request failed.");
|
|
3979
|
+
if (context.abortSignal?.aborted || wholeCall(failure.status))
|
|
3980
|
+
throw failure;
|
|
3981
|
+
if (!retryable(failure.status) || attempt >= MAX_RETRIES)
|
|
3982
|
+
return { url, error: failure.message };
|
|
3983
|
+
try {
|
|
3984
|
+
await sleep(BACKOFF_MS[attempt] ?? 750, context.abortSignal);
|
|
3985
|
+
} catch {
|
|
3986
|
+
throw new WebToolError("Web request cancelled.");
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
};
|
|
3991
|
+
const worker = async () => {
|
|
3992
|
+
while (!fatal) {
|
|
3993
|
+
const index = next++;
|
|
3994
|
+
if (index >= urls.length)
|
|
3995
|
+
return;
|
|
3996
|
+
try {
|
|
3997
|
+
outcomes[index] = await fetchOne(urls[index]);
|
|
3998
|
+
} catch (error) {
|
|
3999
|
+
fatal ??= error instanceof WebToolError ? error : new WebToolError("DeepSeek request failed.");
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
};
|
|
4003
|
+
await Promise.all(Array.from({ length: Math.min(FETCH_CONCURRENCY, urls.length) }, worker));
|
|
4004
|
+
if (fatal)
|
|
4005
|
+
throw fatal;
|
|
4006
|
+
return outcomes;
|
|
4007
|
+
}
|
|
4008
|
+
};
|
|
4009
|
+
}
|
|
4010
|
+
var SEARCH_URL = "https://api.deepseek.com/v1/tools/search", FETCH_URL = "https://api.deepseek.com/v1/tools/fetch", DEEPSEEK_QUERY_MAX_CHARS = 128, FETCH_CONCURRENCY = 3, MAX_RETRIES = 2, BACKOFF_MS, DEEPSEEK_MISSING_KEY = "DeepSeek API key is not configured. Save one with /login or in the Web settings, or switch the search provider to Tavily.", retryable = (status) => status !== undefined && (status === 429 || status === 408 || status >= 500), wholeCall = (status) => status === 401 || status === 402;
|
|
4011
|
+
var init_deepseek = __esm(() => {
|
|
4012
|
+
init_http();
|
|
4013
|
+
BACKOFF_MS = [250, 750];
|
|
4014
|
+
});
|
|
4015
|
+
|
|
4016
|
+
// packages/core/src/web-search/index.ts
|
|
4017
|
+
import { Type as Type4 } from "@earendil-works/pi-ai";
|
|
4018
|
+
import { defineExtension as defineExtension8, defineTool as defineTool4 } from "@earendil-works/pi-durable";
|
|
3501
4019
|
function toolError(error) {
|
|
3502
4020
|
return {
|
|
3503
4021
|
output: [
|
|
3504
|
-
{ type: "text", text: error instanceof
|
|
4022
|
+
{ type: "text", text: error instanceof WebToolError ? error.message : "Web tool failed. Please retry." }
|
|
3505
4023
|
],
|
|
3506
4024
|
isError: true
|
|
3507
4025
|
};
|
|
3508
4026
|
}
|
|
3509
|
-
function truncate(text, max) {
|
|
3510
|
-
return text.length > max ? `${text.slice(0, Math.max(0, max - 1))}\u2026` : text;
|
|
3511
|
-
}
|
|
3512
|
-
function rows(data, field) {
|
|
3513
|
-
const value = data[field];
|
|
3514
|
-
if (!Array.isArray(value) || !value.every(record))
|
|
3515
|
-
throw new TavilyError("Invalid Tavily response.");
|
|
3516
|
-
return value;
|
|
3517
|
-
}
|
|
3518
4027
|
function content(text) {
|
|
3519
4028
|
return { output: [{ type: "text", text }] };
|
|
3520
4029
|
}
|
|
3521
|
-
function
|
|
3522
|
-
const text = String(value);
|
|
3523
|
-
try {
|
|
3524
|
-
return new URL(text).href;
|
|
3525
|
-
} catch {
|
|
3526
|
-
return text;
|
|
3527
|
-
}
|
|
3528
|
-
}
|
|
3529
|
-
function createWebSearchTools(resolveKey, fetchImpl = fetch) {
|
|
4030
|
+
function createWebSearchTools(resolveProvider) {
|
|
3530
4031
|
return defineExtension8({
|
|
3531
4032
|
name: "neko-web-search",
|
|
3532
4033
|
tools: [
|
|
3533
4034
|
defineTool4({
|
|
3534
4035
|
name: "web_search",
|
|
3535
|
-
description: "Search the live web for current or unknown information. Returns ranked sources (title, URL, bounded excerpts). Use only when up-to-date or external information is needed; use web_extract to read a known URL.",
|
|
4036
|
+
description: "Search the live web for current or unknown information. Returns ranked sources (title, URL, bounded excerpts). Keep the query short and focused (ideally under 128 characters). Use only when up-to-date or external information is needed; use web_extract to read a known URL.",
|
|
3536
4037
|
parameters: Type4.Object({
|
|
3537
4038
|
query: Type4.String({ minLength: 1, maxLength: 4096, description: "The search query" }),
|
|
3538
4039
|
maxResults: Type4.Optional(Type4.Number({ description: `Maximum number of results (1-${MAX_RESULTS}, default ${DEFAULT_RESULTS})` }))
|
|
@@ -3540,18 +4041,19 @@ function createWebSearchTools(resolveKey, fetchImpl = fetch) {
|
|
|
3540
4041
|
async execute({ query, maxResults }, _api, context) {
|
|
3541
4042
|
try {
|
|
3542
4043
|
if (!query.trim() || query.length > 4096)
|
|
3543
|
-
throw new
|
|
4044
|
+
throw new WebToolError("Provide a non-empty query of at most 4096 characters.");
|
|
3544
4045
|
if (maxResults !== undefined && !Number.isFinite(maxResults))
|
|
3545
|
-
throw new
|
|
4046
|
+
throw new WebToolError("maxResults must be a finite number.");
|
|
3546
4047
|
const limit = Math.max(1, Math.min(MAX_RESULTS, Math.trunc(maxResults ?? DEFAULT_RESULTS)));
|
|
3547
|
-
const
|
|
3548
|
-
const results =
|
|
4048
|
+
const provider = await resolveProvider(context);
|
|
4049
|
+
const results = (await provider.search(query, limit, context)).slice(0, limit);
|
|
3549
4050
|
const budget = Math.floor(MAX_TOTAL_CHARS / Math.max(1, results.length)) - 2;
|
|
3550
4051
|
return content(results.map((item, index) => {
|
|
3551
|
-
const header = `${index + 1}. ${truncate(
|
|
3552
|
-
${truncate(
|
|
3553
|
-
|
|
3554
|
-
|
|
4052
|
+
const header = `${index + 1}. ${truncate(item.title, 200)}
|
|
4053
|
+
${truncate(item.url, 300)}
|
|
4054
|
+
${item.date ? `Published: ${item.date}
|
|
4055
|
+
` : ""}`;
|
|
4056
|
+
return header + truncate(item.text, Math.min(MAX_SOURCE_CHARS, budget - header.length));
|
|
3555
4057
|
}).join(`
|
|
3556
4058
|
|
|
3557
4059
|
`) || "No results.");
|
|
@@ -3573,28 +4075,25 @@ ${truncate(String(item.url ?? ""), 300)}
|
|
|
3573
4075
|
async execute({ urls }, _api, context) {
|
|
3574
4076
|
try {
|
|
3575
4077
|
if (!urls.length || urls.length > MAX_URLS)
|
|
3576
|
-
throw new
|
|
4078
|
+
throw new WebToolError(`Provide between 1 and ${MAX_URLS} URLs.`);
|
|
3577
4079
|
for (const url of urls) {
|
|
3578
4080
|
let parsed;
|
|
3579
4081
|
try {
|
|
3580
4082
|
parsed = new URL(url);
|
|
3581
4083
|
} catch {
|
|
3582
|
-
throw new
|
|
4084
|
+
throw new WebToolError("Provide valid http(s) URLs.");
|
|
3583
4085
|
}
|
|
3584
4086
|
if (url.length > 2048 || !["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password)
|
|
3585
|
-
throw new
|
|
4087
|
+
throw new WebToolError("Provide http(s) URLs of at most 2048 characters without embedded credentials.");
|
|
3586
4088
|
}
|
|
3587
|
-
const
|
|
3588
|
-
const
|
|
3589
|
-
const failed = data.failed_results === undefined ? [] : rows(data, "failed_results");
|
|
3590
|
-
const byUrl = (items) => new Map(items.map((item) => [urlKey(item.url), item]));
|
|
3591
|
-
const successes = byUrl(results), failures = byUrl(failed);
|
|
4089
|
+
const provider = await resolveProvider(context);
|
|
4090
|
+
const outcomes = await provider.extract(urls, context);
|
|
3592
4091
|
const budget = Math.floor(MAX_TOTAL_CHARS / urls.length) - 2;
|
|
3593
4092
|
return content(urls.map((url, index) => {
|
|
3594
4093
|
const header = `## ${index + 1}. ${truncate(url, 300)}
|
|
3595
4094
|
`;
|
|
3596
|
-
const
|
|
3597
|
-
const text =
|
|
4095
|
+
const outcome = outcomes[index];
|
|
4096
|
+
const text = outcome && "text" in outcome ? outcome.text : `Extraction failed: ${outcome?.error ?? "No result returned for this URL"}`;
|
|
3598
4097
|
return header + truncate(text, Math.min(MAX_SOURCE_CHARS, budget - header.length));
|
|
3599
4098
|
}).join(`
|
|
3600
4099
|
|
|
@@ -3607,11 +4106,75 @@ ${truncate(String(item.url ?? ""), 300)}
|
|
|
3607
4106
|
]
|
|
3608
4107
|
});
|
|
3609
4108
|
}
|
|
3610
|
-
var
|
|
4109
|
+
var MAX_URLS = 20, MAX_RESULTS = 10, DEFAULT_RESULTS = 5, MAX_SOURCE_CHARS = 2000, MAX_TOTAL_CHARS = 12000;
|
|
3611
4110
|
var init_web_search = __esm(() => {
|
|
3612
|
-
|
|
3613
|
-
|
|
4111
|
+
init_http();
|
|
4112
|
+
init_http();
|
|
4113
|
+
});
|
|
4114
|
+
|
|
4115
|
+
// packages/core/src/web-search/tavily.ts
|
|
4116
|
+
function rows(data, field) {
|
|
4117
|
+
const value = data[field];
|
|
4118
|
+
if (!Array.isArray(value) || !value.every(record))
|
|
4119
|
+
throw new WebToolError("Invalid Tavily response.");
|
|
4120
|
+
return value;
|
|
4121
|
+
}
|
|
4122
|
+
function urlKey(value) {
|
|
4123
|
+
const text = String(value);
|
|
4124
|
+
try {
|
|
4125
|
+
return new URL(text).href;
|
|
4126
|
+
} catch {
|
|
4127
|
+
return text;
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
4130
|
+
function createTavilyProvider(resolveKey, fetchImpl = fetch) {
|
|
4131
|
+
const post = async (url, body, context) => {
|
|
4132
|
+
const data = await postJson({
|
|
4133
|
+
label: "Tavily",
|
|
4134
|
+
url,
|
|
4135
|
+
body,
|
|
4136
|
+
fetchImpl,
|
|
4137
|
+
context,
|
|
4138
|
+
resolveKey,
|
|
4139
|
+
headers: (key) => key ? { authorization: `Bearer ${key}` } : { "x-tavily-access-mode": "keyless" },
|
|
4140
|
+
httpError: (status, text, key) => new WebToolError(`Tavily request failed (${status}): ${truncate(text, 500)}${key ? "" : " (keyless rate limits apply; save a Tavily API key in settings for higher limits)"}`, status)
|
|
4141
|
+
});
|
|
4142
|
+
if (!record(data))
|
|
4143
|
+
throw new WebToolError("Invalid Tavily response.");
|
|
4144
|
+
if (data.error)
|
|
4145
|
+
throw new WebToolError(`Tavily request failed: ${truncate(JSON.stringify(data.error), 500)}`);
|
|
4146
|
+
return data;
|
|
3614
4147
|
};
|
|
4148
|
+
return {
|
|
4149
|
+
id: "tavily",
|
|
4150
|
+
async search(query, limit, context) {
|
|
4151
|
+
const data = await post(SEARCH_URL2, { query, max_results: limit, search_depth: "basic" }, context);
|
|
4152
|
+
return rows(data, "results").slice(0, limit).map((item) => ({
|
|
4153
|
+
title: String(item.title ?? "(untitled)"),
|
|
4154
|
+
url: String(item.url ?? ""),
|
|
4155
|
+
text: String(item.content ?? "")
|
|
4156
|
+
}));
|
|
4157
|
+
},
|
|
4158
|
+
async extract(urls, context) {
|
|
4159
|
+
const data = await post(EXTRACT_URL, { urls, format: "markdown" }, context);
|
|
4160
|
+
const results = rows(data, "results");
|
|
4161
|
+
const failed = data.failed_results === undefined ? [] : rows(data, "failed_results");
|
|
4162
|
+
const byUrl = (items) => new Map(items.map((item) => [urlKey(item.url), item]));
|
|
4163
|
+
const successes = byUrl(results);
|
|
4164
|
+
const failures = byUrl(failed);
|
|
4165
|
+
return urls.map((url) => {
|
|
4166
|
+
const result = successes.get(urlKey(url));
|
|
4167
|
+
if (result)
|
|
4168
|
+
return { url, text: String(result.raw_content ?? "") };
|
|
4169
|
+
const failure = failures.get(urlKey(url));
|
|
4170
|
+
return { url, error: String(failure?.error ?? "No result returned for this URL") };
|
|
4171
|
+
});
|
|
4172
|
+
}
|
|
4173
|
+
};
|
|
4174
|
+
}
|
|
4175
|
+
var SEARCH_URL2 = "https://api.tavily.com/search", EXTRACT_URL = "https://api.tavily.com/extract";
|
|
4176
|
+
var init_tavily = __esm(() => {
|
|
4177
|
+
init_http();
|
|
3615
4178
|
});
|
|
3616
4179
|
|
|
3617
4180
|
// packages/core/src/workspace-files.ts
|
|
@@ -3988,6 +4551,7 @@ import {
|
|
|
3988
4551
|
createRegistry,
|
|
3989
4552
|
defineDoc as defineDoc5,
|
|
3990
4553
|
Harness,
|
|
4554
|
+
InboxDoc as InboxDoc2,
|
|
3991
4555
|
LiveDoc as importLiveDoc,
|
|
3992
4556
|
MemoryStorage,
|
|
3993
4557
|
UsageDoc as UsageDoc2,
|
|
@@ -4034,12 +4598,21 @@ async function createNekoRuntime(options = {}) {
|
|
|
4034
4598
|
registry.install(createCodingTools({ images: await createNodePhotonImages() }));
|
|
4035
4599
|
registry.install(ImageDetails);
|
|
4036
4600
|
registry.install(AgentFileSearch);
|
|
4037
|
-
|
|
4601
|
+
const tavily = createTavilyProvider(async () => {
|
|
4038
4602
|
const saved = await effectiveCredentials.read("tavily");
|
|
4039
4603
|
if (saved?.type === "api_key" && saved.key)
|
|
4040
4604
|
return saved.key;
|
|
4041
4605
|
return process.env.TAVILY_API_KEY || undefined;
|
|
4042
|
-
})
|
|
4606
|
+
});
|
|
4607
|
+
const deepseekApiKey = async () => {
|
|
4608
|
+
const saved = await effectiveCredentials.read("deepseek");
|
|
4609
|
+
if (saved?.type === "api_key" && saved.key)
|
|
4610
|
+
return saved.key;
|
|
4611
|
+
return process.env.DEEPSEEK_API_KEY || undefined;
|
|
4612
|
+
};
|
|
4613
|
+
const deepseek = createDeepseekProvider(deepseekApiKey);
|
|
4614
|
+
let searchProvider = options.searchProvider ?? DEFAULT_SEARCH_PROVIDER;
|
|
4615
|
+
registry.install(createWebSearchTools(async () => searchProvider === "deepseek" ? deepseek : tavily));
|
|
4043
4616
|
const loadResources = createResourceLoader(options.promptHome);
|
|
4044
4617
|
const resourceSnapshots = createResourceSnapshots(loadResources, (id, requestId) => storage.submissionByRequest(id, requestId, context));
|
|
4045
4618
|
registry.install(createNekoPrompt(resourceSnapshots.current));
|
|
@@ -4053,9 +4626,16 @@ async function createNekoRuntime(options = {}) {
|
|
|
4053
4626
|
let approvals;
|
|
4054
4627
|
const approvalExtension = createApprovalExtension(() => approvals);
|
|
4055
4628
|
registry.install(approvalExtension);
|
|
4629
|
+
let savedContext = emptySavedContextSettings();
|
|
4630
|
+
let contextState = resolveContextSettings(options.contextSettings, savedContext);
|
|
4056
4631
|
const harness = await Harness.open(storage, {
|
|
4057
4632
|
models,
|
|
4058
4633
|
registry,
|
|
4634
|
+
settings: {
|
|
4635
|
+
get compaction() {
|
|
4636
|
+
return compactionPolicy(contextState, models.getModels().map((model) => model.contextWindow));
|
|
4637
|
+
}
|
|
4638
|
+
},
|
|
4059
4639
|
onReport: options.onReport,
|
|
4060
4640
|
conversationCreated: async (tx, record) => {
|
|
4061
4641
|
(await tx.doc(SessionMetadataDoc, record.id)).createdAt = Date.now();
|
|
@@ -4097,6 +4677,21 @@ async function createNekoRuntime(options = {}) {
|
|
|
4097
4677
|
cwd: defaultCwd
|
|
4098
4678
|
}
|
|
4099
4679
|
});
|
|
4680
|
+
const savedSearch = (await harness.snapshot(SearchSettingsDoc, conversation.id, context))?.provider;
|
|
4681
|
+
if (isSearchProvider(savedSearch))
|
|
4682
|
+
searchProvider = savedSearch;
|
|
4683
|
+
savedContext = sanitizeSavedContextSettings(await harness.snapshot(ContextSettingsDoc, conversation.id, context));
|
|
4684
|
+
contextState = resolveContextSettings(options.contextSettings, savedContext);
|
|
4685
|
+
const persistContext = async (next) => {
|
|
4686
|
+
await harness.commit(async (tx) => {
|
|
4687
|
+
const doc = await tx.doc(ContextSettingsDoc, conversation.id);
|
|
4688
|
+
doc.compaction = next.compaction;
|
|
4689
|
+
doc.pruning = next.pruning;
|
|
4690
|
+
}, context);
|
|
4691
|
+
savedContext = next;
|
|
4692
|
+
contextState = resolveContextSettings(options.contextSettings, savedContext);
|
|
4693
|
+
return contextState;
|
|
4694
|
+
};
|
|
4100
4695
|
const registerWorkspace = async (path) => {
|
|
4101
4696
|
await harness.commit(async (tx) => {
|
|
4102
4697
|
const doc = await tx.doc(WorkspacesDoc, conversation.id);
|
|
@@ -4133,10 +4728,22 @@ async function createNekoRuntime(options = {}) {
|
|
|
4133
4728
|
for (const record of existing)
|
|
4134
4729
|
if (!record.owner && !(await harness.snapshot(ArchivedDoc, record.id, context))?.archived)
|
|
4135
4730
|
await metadata.restore(record.id);
|
|
4731
|
+
const pruner = new ContextPruner({
|
|
4732
|
+
harness,
|
|
4733
|
+
context,
|
|
4734
|
+
settings: () => contextState.effective.pruning,
|
|
4735
|
+
windowOf: async (id) => {
|
|
4736
|
+
const target = await harness.conversation(id, context);
|
|
4737
|
+
const model = (await target?.agent(context))?.model;
|
|
4738
|
+
return model && models.getModel(model.provider, model.modelId)?.contextWindow || 0;
|
|
4739
|
+
},
|
|
4740
|
+
report: (error) => options.onReport?.(error)
|
|
4741
|
+
});
|
|
4136
4742
|
let closing;
|
|
4137
4743
|
const cleanup = () => {
|
|
4138
4744
|
if (!closing)
|
|
4139
4745
|
closing = (async () => {
|
|
4746
|
+
pruner.close();
|
|
4140
4747
|
jobs.shutdown();
|
|
4141
4748
|
await workspaceFiles.close();
|
|
4142
4749
|
await metadata.close();
|
|
@@ -4293,6 +4900,57 @@ async function createNekoRuntime(options = {}) {
|
|
|
4293
4900
|
},
|
|
4294
4901
|
abort() {
|
|
4295
4902
|
return target.abort(context);
|
|
4903
|
+
},
|
|
4904
|
+
async withdraw(submissionId) {
|
|
4905
|
+
const id = submissionId;
|
|
4906
|
+
const inbox = await harness.snapshot(InboxDoc2, target.id, context);
|
|
4907
|
+
const item = inbox?.items.find((i) => i.id === id);
|
|
4908
|
+
const input = item ? queuedUserInput(item) : undefined;
|
|
4909
|
+
if (item && !input)
|
|
4910
|
+
return { result: "unknown" };
|
|
4911
|
+
const requestId = (await (await harness.submission(id, context))?.status(context))?.requestId;
|
|
4912
|
+
const outcome = await harness.abortSubmission(id, context, target.id);
|
|
4913
|
+
if (outcome !== "aborted") {
|
|
4914
|
+
return { result: outcome === "already_placed" ? "placed" : outcome === "settled" ? "settled" : "unknown" };
|
|
4915
|
+
}
|
|
4916
|
+
if (requestId !== undefined)
|
|
4917
|
+
resourceSnapshots.discard(target.id, requestId);
|
|
4918
|
+
await metadata.reconcile(target.id);
|
|
4919
|
+
return { result: "withdrawn", ...input ? { text: input.text, images: input.images } : {} };
|
|
4920
|
+
},
|
|
4921
|
+
async steer(submissionId) {
|
|
4922
|
+
const id = submissionId;
|
|
4923
|
+
const inbox = await harness.snapshot(InboxDoc2, target.id, context);
|
|
4924
|
+
const item = inbox?.items.find((i) => i.id === id);
|
|
4925
|
+
const input = item ? queuedUserInput(item) : undefined;
|
|
4926
|
+
if (item && (!input || item.mode === "write"))
|
|
4927
|
+
return { result: "unknown" };
|
|
4928
|
+
if (item && input?.mode === "steer")
|
|
4929
|
+
return { result: "steered", id: submissionId };
|
|
4930
|
+
const requestId = (await (await harness.submission(id, context))?.status(context))?.requestId;
|
|
4931
|
+
const outcome = await harness.abortSubmission(id, context, target.id);
|
|
4932
|
+
if (outcome !== "aborted" || !item || !input) {
|
|
4933
|
+
return { result: outcome === "already_placed" ? "placed" : outcome === "settled" ? "settled" : "unknown" };
|
|
4934
|
+
}
|
|
4935
|
+
const steerRequestId = requestId !== undefined ? `steer:${requestId}` : crypto.randomUUID();
|
|
4936
|
+
try {
|
|
4937
|
+
const next = await target.submit({
|
|
4938
|
+
type: "input",
|
|
4939
|
+
content: item.content,
|
|
4940
|
+
...item.entry ? { entry: item.entry } : {},
|
|
4941
|
+
requestId: steerRequestId,
|
|
4942
|
+
whenBusy: "steer"
|
|
4943
|
+
}, context);
|
|
4944
|
+
if (requestId !== undefined)
|
|
4945
|
+
resourceSnapshots.move(target.id, requestId, steerRequestId);
|
|
4946
|
+
await metadata.reconcile(target.id);
|
|
4947
|
+
return { result: "steered", id: next.id };
|
|
4948
|
+
} catch {
|
|
4949
|
+
if (requestId !== undefined)
|
|
4950
|
+
resourceSnapshots.discard(target.id, requestId);
|
|
4951
|
+
await metadata.reconcile(target.id);
|
|
4952
|
+
return { result: "withdrawn", text: input.text, images: input.images };
|
|
4953
|
+
}
|
|
4296
4954
|
}
|
|
4297
4955
|
});
|
|
4298
4956
|
const rootSession = makeSession(conversation);
|
|
@@ -4503,6 +5161,26 @@ async function createNekoRuntime(options = {}) {
|
|
|
4503
5161
|
source: runtimeKeys.has(spec.id) ? "runtime" : savedConfigured ? "saved" : environmentConfigured ? "environment" : spec.keyless ? "keyless" : "none"
|
|
4504
5162
|
};
|
|
4505
5163
|
},
|
|
5164
|
+
async contextSettings() {
|
|
5165
|
+
return contextState;
|
|
5166
|
+
},
|
|
5167
|
+
async setContextSettings(patch) {
|
|
5168
|
+
return persistContext(applyContextPatch(savedContext, parseContextSettingsPatch(patch)));
|
|
5169
|
+
},
|
|
5170
|
+
async resetContextSettings() {
|
|
5171
|
+
return persistContext(emptySavedContextSettings());
|
|
5172
|
+
},
|
|
5173
|
+
async searchSettings() {
|
|
5174
|
+
return { selected: searchProvider };
|
|
5175
|
+
},
|
|
5176
|
+
async setSearchProvider(provider) {
|
|
5177
|
+
if (!isSearchProvider(provider))
|
|
5178
|
+
throw new Error("Unknown search provider.");
|
|
5179
|
+
await harness.commit(async (tx) => {
|
|
5180
|
+
(await tx.doc(SearchSettingsDoc, conversation.id)).provider = provider;
|
|
5181
|
+
}, context);
|
|
5182
|
+
searchProvider = provider;
|
|
5183
|
+
},
|
|
4506
5184
|
async saveCredential(provider, key) {
|
|
4507
5185
|
const spec = credentialProvider(provider);
|
|
4508
5186
|
if (typeof key !== "string" || !key.trim() || key.length > 4096 || Array.from(key).some((char) => char.charCodeAt(0) < 32))
|
|
@@ -4576,10 +5254,12 @@ async function createNekoRuntime(options = {}) {
|
|
|
4576
5254
|
}
|
|
4577
5255
|
};
|
|
4578
5256
|
}
|
|
4579
|
-
var DEFAULT_MODEL, DEFAULT_STORAGE_PATH, ArchivedDoc, WorkspacesDoc, ActivityDoc;
|
|
5257
|
+
var DEFAULT_MODEL, DEFAULT_STORAGE_PATH, ArchivedDoc, WorkspacesDoc, SearchSettingsDoc, ContextSettingsDoc, ActivityDoc;
|
|
4580
5258
|
var init_runtime = __esm(() => {
|
|
4581
5259
|
init_agent_file_search();
|
|
4582
5260
|
init_compaction();
|
|
5261
|
+
init_context_pruning();
|
|
5262
|
+
init_context_settings();
|
|
4583
5263
|
init_credentials();
|
|
4584
5264
|
init_image_metadata();
|
|
4585
5265
|
init_image_prompt();
|
|
@@ -4591,11 +5271,15 @@ var init_runtime = __esm(() => {
|
|
|
4591
5271
|
init_invoke();
|
|
4592
5272
|
init_prompt();
|
|
4593
5273
|
init_resources();
|
|
5274
|
+
init_queued();
|
|
5275
|
+
init_search_providers();
|
|
4594
5276
|
init_session_metadata();
|
|
4595
5277
|
init_subagent_usage();
|
|
4596
5278
|
init_subagents();
|
|
4597
5279
|
init_tool_approvals();
|
|
5280
|
+
init_deepseek();
|
|
4598
5281
|
init_web_search();
|
|
5282
|
+
init_tavily();
|
|
4599
5283
|
init_workspace();
|
|
4600
5284
|
init_workspace_files();
|
|
4601
5285
|
DEFAULT_MODEL = { provider: "deepseek", modelId: "deepseek-flash" };
|
|
@@ -4616,6 +5300,22 @@ var init_runtime = __esm(() => {
|
|
|
4616
5300
|
fork: "initial",
|
|
4617
5301
|
initial: () => ({ items: [] })
|
|
4618
5302
|
});
|
|
5303
|
+
SearchSettingsDoc = defineDoc5({
|
|
5304
|
+
kind: "app.neko.search-settings",
|
|
5305
|
+
version: 1,
|
|
5306
|
+
scope: "conversation",
|
|
5307
|
+
history: "latest",
|
|
5308
|
+
fork: "initial",
|
|
5309
|
+
initial: () => ({ provider: null })
|
|
5310
|
+
});
|
|
5311
|
+
ContextSettingsDoc = defineDoc5({
|
|
5312
|
+
kind: "app.neko.context-settings",
|
|
5313
|
+
version: 1,
|
|
5314
|
+
scope: "conversation",
|
|
5315
|
+
history: "latest",
|
|
5316
|
+
fork: "initial",
|
|
5317
|
+
initial: emptySavedContextSettings
|
|
5318
|
+
});
|
|
4619
5319
|
ActivityDoc = defineDoc5({
|
|
4620
5320
|
kind: "app.neko.activity",
|
|
4621
5321
|
version: 1,
|
|
@@ -4641,12 +5341,15 @@ function mergeTranscriptItems(previous, incoming) {
|
|
|
4641
5341
|
// packages/core/src/index.ts
|
|
4642
5342
|
var init_src = __esm(() => {
|
|
4643
5343
|
init_config();
|
|
5344
|
+
init_context_settings();
|
|
4644
5345
|
init_credentials();
|
|
4645
5346
|
init_images();
|
|
4646
5347
|
init_jobs();
|
|
4647
5348
|
init_projection();
|
|
4648
5349
|
init_frontmatter();
|
|
5350
|
+
init_queued();
|
|
4649
5351
|
init_runtime();
|
|
5352
|
+
init_search_providers();
|
|
4650
5353
|
init_session_metadata();
|
|
4651
5354
|
init_subagent_usage();
|
|
4652
5355
|
init_subagents();
|
|
@@ -4763,7 +5466,7 @@ function relativeTime(at, now, language = "en") {
|
|
|
4763
5466
|
var sessionName = (session, empty = "New session") => session.title || session.firstPromptPreview || empty, messageTime = (session) => session.lastMessageAt ?? session.createdAt, byMessage = (a, b) => messageTime(b) - messageTime(a) || Number(b.id) - Number(a.id), byVisited = (a, b) => b.lastUsedAt - a.lastUsedAt || Number(b.id) - Number(a.id);
|
|
4764
5467
|
|
|
4765
5468
|
// packages/gateway/src/contract.ts
|
|
4766
|
-
import { method, remote } from "@earendil-works/chord";
|
|
5469
|
+
import { hostNamed, method, remote } from "@earendil-works/chord";
|
|
4767
5470
|
import { Type as Type5 } from "typebox";
|
|
4768
5471
|
function contract(name, def) {
|
|
4769
5472
|
const variants = Object.fromEntries(Object.entries(def.variants).map(([key, value]) => [
|
|
@@ -4773,9 +5476,9 @@ function contract(name, def) {
|
|
|
4773
5476
|
methods: Object.fromEntries(Object.entries(value.methods ?? {}).map(([member, [args, result]]) => [member, method(args, result)]))
|
|
4774
5477
|
}
|
|
4775
5478
|
]));
|
|
4776
|
-
return remote(name, { ...def, variants });
|
|
5479
|
+
return remote(name, { ...def, host: hostNamed(GATEWAY_HOST), variants });
|
|
4777
5480
|
}
|
|
4778
|
-
var rpc = (args, result) => [args, result], str, num, bool, nil, integer, nullableString, phase, level, thinking, model, change, usage, image, skill, call, DelegationSchema, SubagentSummarySchema, jobStatus, JobSummarySchema, JobOutputSchema, ChatViewSchema, SessionSummarySchema, DirectoryStateSchema, WorkspaceSummarySchema, WorkspaceStateSchema, credentialStatus, CredentialStateSchema, AttachmentSchema, chatInput, NekoSessionDirectory, NekoSessionManagement, resize, inputLimits, modelInfo, NekoModels, imageChunk, NekoSubagent, NekoChat, browse, page, NekoWorkspaces, provider, NekoCredentials, FileCandidatesSchema, NekoWorkspaceFiles, marketSkill, preview, scope, installResult, NekoSkillMarket, ApplicationInfoSchema, NekoApplicationInfo;
|
|
5481
|
+
var GATEWAY_HOST = "neko", rpc = (args, result) => [args, result], str, num, bool, nil, integer, nullableString, phase, level, thinking, model, change, usage, image, skill, call, DelegationSchema, SubagentSummarySchema, jobStatus, JobSummarySchema, JobOutputSchema, QueuedInputSchema, WithdrawResultSchema, SteerResultSchema, 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) => Type5.Union([schema, nil]), ContextSettingsPatchSchema, NekoContextSettings, FileCandidatesSchema, NekoWorkspaceFiles, marketSkill, preview, scope, installResult, NekoSkillMarket, ApplicationInfoSchema, NekoApplicationInfo;
|
|
4779
5482
|
var init_contract = __esm(() => {
|
|
4780
5483
|
str = Type5.String();
|
|
4781
5484
|
num = Type5.Number();
|
|
@@ -4871,10 +5574,29 @@ var init_contract = __esm(() => {
|
|
|
4871
5574
|
truncated: bool,
|
|
4872
5575
|
dropped: integer
|
|
4873
5576
|
});
|
|
5577
|
+
QueuedInputSchema = Type5.Object({
|
|
5578
|
+
id: integer,
|
|
5579
|
+
mode: Type5.Enum(["steer", "followUp"]),
|
|
5580
|
+
preview: str,
|
|
5581
|
+
images: integer,
|
|
5582
|
+
skill: Type5.Optional(Type5.Object({ name: str, args: str }))
|
|
5583
|
+
});
|
|
5584
|
+
WithdrawResultSchema = Type5.Object({
|
|
5585
|
+
result: Type5.Enum(["withdrawn", "placed", "settled", "unknown"]),
|
|
5586
|
+
text: Type5.Optional(str),
|
|
5587
|
+
images: Type5.Optional(integer)
|
|
5588
|
+
});
|
|
5589
|
+
SteerResultSchema = Type5.Object({
|
|
5590
|
+
result: Type5.Enum(["steered", "withdrawn", "placed", "settled", "unknown"]),
|
|
5591
|
+
id: Type5.Optional(integer),
|
|
5592
|
+
text: Type5.Optional(str),
|
|
5593
|
+
images: Type5.Optional(integer)
|
|
5594
|
+
});
|
|
4874
5595
|
ChatViewSchema = Type5.Object({
|
|
4875
5596
|
historyBefore: Type5.Optional(Type5.Union([integer, nil])),
|
|
4876
5597
|
agents: Type5.Optional(Type5.Array(SubagentSummarySchema)),
|
|
4877
5598
|
jobs: Type5.Optional(Type5.Array(JobSummarySchema)),
|
|
5599
|
+
queued: Type5.Optional(Type5.Array(QueuedInputSchema)),
|
|
4878
5600
|
rounds: Type5.Optional(Type5.Array(DelegationSchema)),
|
|
4879
5601
|
subagents: Type5.Optional(Type5.Array(DelegationSchema)),
|
|
4880
5602
|
taskUsage: Type5.Optional(usage),
|
|
@@ -5079,6 +5801,8 @@ var init_contract = __esm(() => {
|
|
|
5079
5801
|
whenBusy: Type5.Optional(Type5.Enum(["steer", "followUp"]))
|
|
5080
5802
|
}, { additionalProperties: false }), nil),
|
|
5081
5803
|
abort: rpc(nil, nil),
|
|
5804
|
+
withdraw: rpc(Type5.Object({ id: integer }, { additionalProperties: false }), WithdrawResultSchema),
|
|
5805
|
+
steer: rpc(Type5.Object({ id: integer }, { additionalProperties: false }), SteerResultSchema),
|
|
5082
5806
|
usage: rpc(nil, usage),
|
|
5083
5807
|
skills: rpc(nil, Type5.Array(Type5.Object({ name: str, description: str, location: str, level }))),
|
|
5084
5808
|
jobOutput: rpc(Type5.Object({ jobId: str, since: Type5.Optional(integer) }, { additionalProperties: false }), JobOutputSchema),
|
|
@@ -5130,6 +5854,77 @@ var init_contract = __esm(() => {
|
|
|
5130
5854
|
}
|
|
5131
5855
|
}
|
|
5132
5856
|
});
|
|
5857
|
+
SearchSettingsStateSchema = Type5.Object({
|
|
5858
|
+
revision: integer,
|
|
5859
|
+
selected: Type5.Enum(["tavily", "deepseek"])
|
|
5860
|
+
});
|
|
5861
|
+
NekoSearchSettings = contract("neko.search-settings", {
|
|
5862
|
+
version: 1,
|
|
5863
|
+
input: Type5.Object({}, { additionalProperties: false }),
|
|
5864
|
+
variants: {
|
|
5865
|
+
ready: {
|
|
5866
|
+
state: { state: SearchSettingsStateSchema },
|
|
5867
|
+
methods: {
|
|
5868
|
+
set: rpc(Type5.Object({ provider: Type5.Enum(["tavily", "deepseek"]) }, { additionalProperties: false }), nil)
|
|
5869
|
+
}
|
|
5870
|
+
}
|
|
5871
|
+
}
|
|
5872
|
+
});
|
|
5873
|
+
contextSource = Type5.Enum(["default", "config", "saved"]);
|
|
5874
|
+
ContextSettingsStateSchema = Type5.Object({
|
|
5875
|
+
revision: integer,
|
|
5876
|
+
effective: Type5.Object({
|
|
5877
|
+
compaction: Type5.Object({
|
|
5878
|
+
enabled: bool,
|
|
5879
|
+
triggerPercent: Type5.Union([integer, nil]),
|
|
5880
|
+
keepRecentTokens: integer
|
|
5881
|
+
}),
|
|
5882
|
+
pruning: Type5.Object({
|
|
5883
|
+
enabled: bool,
|
|
5884
|
+
triggerPercent: integer,
|
|
5885
|
+
keepRecent: integer,
|
|
5886
|
+
minChars: integer,
|
|
5887
|
+
minSavePercent: integer
|
|
5888
|
+
})
|
|
5889
|
+
}),
|
|
5890
|
+
sources: Type5.Object({
|
|
5891
|
+
compaction: Type5.Object({ enabled: contextSource, triggerPercent: contextSource, keepRecentTokens: contextSource }),
|
|
5892
|
+
pruning: Type5.Object({
|
|
5893
|
+
enabled: contextSource,
|
|
5894
|
+
triggerPercent: contextSource,
|
|
5895
|
+
keepRecent: contextSource,
|
|
5896
|
+
minChars: contextSource,
|
|
5897
|
+
minSavePercent: contextSource
|
|
5898
|
+
})
|
|
5899
|
+
})
|
|
5900
|
+
});
|
|
5901
|
+
ContextSettingsPatchSchema = Type5.Object({
|
|
5902
|
+
compaction: Type5.Optional(Type5.Object({
|
|
5903
|
+
enabled: Type5.Optional(nullable(bool)),
|
|
5904
|
+
triggerPercent: Type5.Optional(nullable(integer)),
|
|
5905
|
+
keepRecentTokens: Type5.Optional(nullable(integer))
|
|
5906
|
+
}, { additionalProperties: false })),
|
|
5907
|
+
pruning: Type5.Optional(Type5.Object({
|
|
5908
|
+
enabled: Type5.Optional(nullable(bool)),
|
|
5909
|
+
triggerPercent: Type5.Optional(nullable(integer)),
|
|
5910
|
+
keepRecent: Type5.Optional(nullable(integer)),
|
|
5911
|
+
minChars: Type5.Optional(nullable(integer)),
|
|
5912
|
+
minSavePercent: Type5.Optional(nullable(integer))
|
|
5913
|
+
}, { additionalProperties: false }))
|
|
5914
|
+
}, { additionalProperties: false });
|
|
5915
|
+
NekoContextSettings = contract("neko.context-settings", {
|
|
5916
|
+
version: 1,
|
|
5917
|
+
input: Type5.Object({}, { additionalProperties: false }),
|
|
5918
|
+
variants: {
|
|
5919
|
+
ready: {
|
|
5920
|
+
state: { state: ContextSettingsStateSchema },
|
|
5921
|
+
methods: {
|
|
5922
|
+
set: rpc(ContextSettingsPatchSchema, nil),
|
|
5923
|
+
reset: rpc(Type5.Object({}, { additionalProperties: false }), nil)
|
|
5924
|
+
}
|
|
5925
|
+
}
|
|
5926
|
+
}
|
|
5927
|
+
});
|
|
5133
5928
|
FileCandidatesSchema = Type5.Object({
|
|
5134
5929
|
entries: Type5.Array(Type5.Object({ name: str, path: str, kind: Type5.Enum(["file", "directory"]) }), { maxItems: 100 }),
|
|
5135
5930
|
limited: bool,
|
|
@@ -5183,18 +5978,24 @@ var init_contract = __esm(() => {
|
|
|
5183
5978
|
protocolVersion: Type5.Integer({ minimum: 1 }),
|
|
5184
5979
|
latestVersion: nullableString,
|
|
5185
5980
|
checkedAt: Type5.Union([integer, nil]),
|
|
5186
|
-
checkState: Type5.Enum(["unknown", "checking", "ok", "failed", "development"])
|
|
5981
|
+
checkState: Type5.Enum(["unknown", "checking", "ok", "failed", "development"]),
|
|
5982
|
+
serverId: str
|
|
5187
5983
|
});
|
|
5188
5984
|
NekoApplicationInfo = contract("neko.application-info", {
|
|
5189
5985
|
version: 1,
|
|
5190
5986
|
input: Type5.Object({}),
|
|
5191
|
-
variants: {
|
|
5987
|
+
variants: {
|
|
5988
|
+
ready: { state: { info: ApplicationInfoSchema }, methods: { check: rpc(nil, ApplicationInfoSchema) } }
|
|
5989
|
+
}
|
|
5192
5990
|
});
|
|
5193
5991
|
});
|
|
5194
5992
|
|
|
5195
5993
|
// packages/gateway/src/link.ts
|
|
5196
|
-
import {
|
|
5197
|
-
|
|
5994
|
+
import {
|
|
5995
|
+
createHost,
|
|
5996
|
+
webSocketLink
|
|
5997
|
+
} from "@earendil-works/chord";
|
|
5998
|
+
function boundedWebSocketLink(socket, max = MAX_LINK_BYTES, options) {
|
|
5198
5999
|
let queued = 0, incoming = 0, receiving = false;
|
|
5199
6000
|
let native;
|
|
5200
6001
|
const bridge = {
|
|
@@ -5210,18 +6011,22 @@ function boundedWebSocketLink(socket, max = MAX_LINK_BYTES) {
|
|
|
5210
6011
|
},
|
|
5211
6012
|
close: (code, reason) => socket.close(code, reason),
|
|
5212
6013
|
addEventListener(type, handler) {
|
|
5213
|
-
if (type === "message")
|
|
5214
|
-
|
|
5215
|
-
const bytes = size(
|
|
6014
|
+
if (type === "message") {
|
|
6015
|
+
const deliver = (data) => {
|
|
6016
|
+
const bytes = size(data);
|
|
5216
6017
|
if (!receiving)
|
|
5217
6018
|
incoming += bytes;
|
|
5218
6019
|
if (bytes > max || incoming > max) {
|
|
5219
6020
|
native.close("frame limit exceeded");
|
|
5220
6021
|
return;
|
|
5221
6022
|
}
|
|
5222
|
-
handler(
|
|
5223
|
-
}
|
|
5224
|
-
|
|
6023
|
+
handler({ data });
|
|
6024
|
+
};
|
|
6025
|
+
if (socket.on)
|
|
6026
|
+
socket.on("message", (data, isBinary) => deliver(isBinary ? asBytes(data) : new TextDecoder().decode(asBytes(data))));
|
|
6027
|
+
else
|
|
6028
|
+
socket.addEventListener("message", (event) => deliver(typeof event.data === "string" || event.data instanceof Uint8Array ? event.data : String(event.data)));
|
|
6029
|
+
} else
|
|
5225
6030
|
socket.addEventListener(type, () => {
|
|
5226
6031
|
if (type === "open")
|
|
5227
6032
|
queued = 0;
|
|
@@ -5233,7 +6038,7 @@ function boundedWebSocketLink(socket, max = MAX_LINK_BYTES) {
|
|
|
5233
6038
|
});
|
|
5234
6039
|
}
|
|
5235
6040
|
};
|
|
5236
|
-
native = webSocketLink(bridge);
|
|
6041
|
+
native = webSocketLink(bridge, options);
|
|
5237
6042
|
socket.addEventListener("error", () => native.close("socket error"));
|
|
5238
6043
|
return {
|
|
5239
6044
|
get closed() {
|
|
@@ -5253,7 +6058,7 @@ function boundedWebSocketLink(socket, max = MAX_LINK_BYTES) {
|
|
|
5253
6058
|
}
|
|
5254
6059
|
if (socket.readyState === 0)
|
|
5255
6060
|
queued += bytes;
|
|
5256
|
-
native.send(frame);
|
|
6061
|
+
return native.send(frame);
|
|
5257
6062
|
},
|
|
5258
6063
|
receive(handler) {
|
|
5259
6064
|
receiving = true;
|
|
@@ -5264,49 +6069,19 @@ function boundedWebSocketLink(socket, max = MAX_LINK_BYTES) {
|
|
|
5264
6069
|
close: (reason) => native.close(reason)
|
|
5265
6070
|
};
|
|
5266
6071
|
}
|
|
5267
|
-
function
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
return;
|
|
5276
|
-
}
|
|
5277
|
-
seen = true;
|
|
5278
|
-
}
|
|
5279
|
-
if (receiver)
|
|
5280
|
-
receiver(frame);
|
|
5281
|
-
else {
|
|
5282
|
-
pendingBytes += size(JSON.stringify(frame));
|
|
5283
|
-
if (pendingBytes > MAX_LINK_BYTES) {
|
|
5284
|
-
link.close("receive queue limit exceeded");
|
|
5285
|
-
pending.length = 0;
|
|
5286
|
-
return;
|
|
5287
|
-
}
|
|
5288
|
-
pending.push(frame);
|
|
5289
|
-
}
|
|
5290
|
-
});
|
|
5291
|
-
return {
|
|
5292
|
-
get closed() {
|
|
5293
|
-
return link.closed;
|
|
5294
|
-
},
|
|
5295
|
-
send: (frame) => link.send(frame),
|
|
5296
|
-
receive(handler) {
|
|
5297
|
-
receiver = handler;
|
|
5298
|
-
pendingBytes = 0;
|
|
5299
|
-
for (const frame of pending.splice(0)) {
|
|
5300
|
-
if (link.closed)
|
|
5301
|
-
break;
|
|
5302
|
-
handler(frame);
|
|
5303
|
-
}
|
|
6072
|
+
function createClientHost(link, options = {}) {
|
|
6073
|
+
return createHost({
|
|
6074
|
+
kind: "client",
|
|
6075
|
+
self: { kind: "client" },
|
|
6076
|
+
upstream: {
|
|
6077
|
+
link,
|
|
6078
|
+
principal: { kind: "server" },
|
|
6079
|
+
...options.onProtocolMismatch ? { onProtocolMismatch: options.onProtocolMismatch } : {}
|
|
5304
6080
|
},
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
};
|
|
6081
|
+
onError: () => {}
|
|
6082
|
+
});
|
|
5308
6083
|
}
|
|
5309
|
-
var MAX_LINK_BYTES, size = (
|
|
6084
|
+
var MAX_LINK_BYTES, asBytes = (data) => data instanceof Uint8Array ? data : new Uint8Array(data), size = (data) => typeof data === "string" ? new TextEncoder().encode(data).byteLength : data.byteLength;
|
|
5310
6085
|
var init_link = __esm(() => {
|
|
5311
6086
|
MAX_LINK_BYTES = 16 * 1024 * 1024;
|
|
5312
6087
|
});
|
|
@@ -5494,35 +6269,14 @@ class ApprovalQueue {
|
|
|
5494
6269
|
}
|
|
5495
6270
|
|
|
5496
6271
|
// packages/gateway/src/client-session.ts
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
settled = true;
|
|
5506
|
-
clearTimeout(timer);
|
|
5507
|
-
stop();
|
|
5508
|
-
signal?.removeEventListener("abort", abort);
|
|
5509
|
-
if (error)
|
|
5510
|
-
reject(error);
|
|
5511
|
-
else
|
|
5512
|
-
resolve(value);
|
|
5513
|
-
};
|
|
5514
|
-
const abort = () => finish(signal?.reason ?? new Error("Cancelled"));
|
|
5515
|
-
const timer = setTimeout(() => finish(new Error("Timed out waiting for first snapshot")), 5000);
|
|
5516
|
-
stop = state.subscribe((value) => {
|
|
5517
|
-
if (value !== undefined)
|
|
5518
|
-
finish(undefined, value);
|
|
5519
|
-
});
|
|
5520
|
-
if (settled)
|
|
5521
|
-
stop();
|
|
5522
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
5523
|
-
if (signal?.aborted)
|
|
5524
|
-
abort();
|
|
5525
|
-
});
|
|
6272
|
+
async function assertGatewayIdentity(host, serverId, signal) {
|
|
6273
|
+
const application = await host.get(NekoApplicationInfo, {}, signal ? { signal } : {});
|
|
6274
|
+
try {
|
|
6275
|
+
if (application.info.value.serverId !== serverId)
|
|
6276
|
+
throw new GatewayIdentityError;
|
|
6277
|
+
} finally {
|
|
6278
|
+
application.release();
|
|
6279
|
+
}
|
|
5526
6280
|
}
|
|
5527
6281
|
function jobsClient(chat) {
|
|
5528
6282
|
const supported = () => chat.view.value?.jobs !== undefined;
|
|
@@ -5545,17 +6299,57 @@ function jobsClient(chat) {
|
|
|
5545
6299
|
}
|
|
5546
6300
|
};
|
|
5547
6301
|
}
|
|
6302
|
+
function queueClient(chat) {
|
|
6303
|
+
const supported = () => chat.view.value?.queued !== undefined;
|
|
6304
|
+
return {
|
|
6305
|
+
get supported() {
|
|
6306
|
+
return supported();
|
|
6307
|
+
},
|
|
6308
|
+
list: () => chat.view.value?.queued ?? [],
|
|
6309
|
+
async steer(id) {
|
|
6310
|
+
if (!supported())
|
|
6311
|
+
throw new Error(QUEUE_UNSUPPORTED);
|
|
6312
|
+
return chat.steer({ id });
|
|
6313
|
+
},
|
|
6314
|
+
async withdraw(id) {
|
|
6315
|
+
if (!supported())
|
|
6316
|
+
throw new Error(QUEUE_UNSUPPORTED);
|
|
6317
|
+
return chat.withdraw({ id });
|
|
6318
|
+
}
|
|
6319
|
+
};
|
|
6320
|
+
}
|
|
5548
6321
|
function createGatewayConnection(link, serverId) {
|
|
5549
|
-
|
|
5550
|
-
const
|
|
6322
|
+
let protocolMismatch = false;
|
|
6323
|
+
const linkClosed = new AbortController;
|
|
6324
|
+
let closeReason = "";
|
|
6325
|
+
link.onClose((reason) => {
|
|
6326
|
+
closeReason = reason;
|
|
6327
|
+
linkClosed.abort(new Error(`Gateway connection closed: ${reason}`));
|
|
6328
|
+
});
|
|
6329
|
+
const host = createClientHost(link, {
|
|
6330
|
+
onProtocolMismatch: () => {
|
|
6331
|
+
protocolMismatch = true;
|
|
6332
|
+
}
|
|
6333
|
+
});
|
|
6334
|
+
const get = (type) => host.hold(type, {});
|
|
5551
6335
|
const application = get(NekoApplicationInfo);
|
|
5552
6336
|
const keep = [application.info.subscribe(() => {})];
|
|
5553
6337
|
let business;
|
|
5554
6338
|
let protocolChecked = false;
|
|
5555
6339
|
function bindBusiness() {
|
|
5556
|
-
const directory = get(NekoSessionDirectory), management = get(NekoSessionManagement), models = get(NekoModels), workspaces = get(NekoWorkspaces), workspaceFiles = get(NekoWorkspaceFiles), credentials = get(NekoCredentials), skillMarket = get(NekoSkillMarket);
|
|
5557
|
-
keep.push(directory.state.subscribe(() => {}), workspaces.state.subscribe(() => {}), credentials.state.subscribe(() => {}));
|
|
5558
|
-
return {
|
|
6340
|
+
const directory = get(NekoSessionDirectory), management = get(NekoSessionManagement), models = get(NekoModels), workspaces = get(NekoWorkspaces), workspaceFiles = get(NekoWorkspaceFiles), credentials = get(NekoCredentials), searchSettings = get(NekoSearchSettings), contextSettings = get(NekoContextSettings), skillMarket = get(NekoSkillMarket);
|
|
6341
|
+
keep.push(directory.state.subscribe(() => {}), workspaces.state.subscribe(() => {}), credentials.state.subscribe(() => {}), searchSettings.state.subscribe(() => {}), contextSettings.state.subscribe(() => {}));
|
|
6342
|
+
return {
|
|
6343
|
+
directory,
|
|
6344
|
+
management,
|
|
6345
|
+
models,
|
|
6346
|
+
workspaces,
|
|
6347
|
+
workspaceFiles,
|
|
6348
|
+
credentials,
|
|
6349
|
+
searchSettings,
|
|
6350
|
+
contextSettings,
|
|
6351
|
+
skillMarket
|
|
6352
|
+
};
|
|
5559
6353
|
}
|
|
5560
6354
|
const services = () => {
|
|
5561
6355
|
if (!protocolChecked)
|
|
@@ -5564,6 +6358,37 @@ function createGatewayConnection(link, serverId) {
|
|
|
5564
6358
|
business = bindBusiness();
|
|
5565
6359
|
return business;
|
|
5566
6360
|
};
|
|
6361
|
+
const checkReady = async (signal) => {
|
|
6362
|
+
await deadline(application.ready, signal);
|
|
6363
|
+
const info = application.info.value;
|
|
6364
|
+
if (info.serverId !== serverId)
|
|
6365
|
+
throw new GatewayIdentityError;
|
|
6366
|
+
if (info.protocolVersion !== APPLICATION_PROTOCOL)
|
|
6367
|
+
throw new ApplicationProtocolError;
|
|
6368
|
+
protocolChecked = true;
|
|
6369
|
+
const {
|
|
6370
|
+
directory,
|
|
6371
|
+
management,
|
|
6372
|
+
models,
|
|
6373
|
+
workspaces,
|
|
6374
|
+
credentials,
|
|
6375
|
+
searchSettings,
|
|
6376
|
+
contextSettings,
|
|
6377
|
+
workspaceFiles,
|
|
6378
|
+
skillMarket
|
|
6379
|
+
} = services();
|
|
6380
|
+
await deadline(Promise.all([
|
|
6381
|
+
directory.ready,
|
|
6382
|
+
management.ready,
|
|
6383
|
+
models.ready,
|
|
6384
|
+
workspaces.ready,
|
|
6385
|
+
credentials.ready,
|
|
6386
|
+
searchSettings.ready,
|
|
6387
|
+
contextSettings.ready,
|
|
6388
|
+
workspaceFiles.ready,
|
|
6389
|
+
skillMarket.ready
|
|
6390
|
+
]), signal);
|
|
6391
|
+
};
|
|
5567
6392
|
let chat;
|
|
5568
6393
|
let attachment;
|
|
5569
6394
|
let stopView;
|
|
@@ -5575,7 +6400,7 @@ function createGatewayConnection(link, serverId) {
|
|
|
5575
6400
|
if (!attachment)
|
|
5576
6401
|
throw new Error("No active session");
|
|
5577
6402
|
const input = { ...attachment, ...typeof target === "string" ? { agentId: target } : { taskId: target } };
|
|
5578
|
-
return subagentClient(() => host.
|
|
6403
|
+
return subagentClient(() => host.hold(NekoSubagent, input));
|
|
5579
6404
|
},
|
|
5580
6405
|
host,
|
|
5581
6406
|
link,
|
|
@@ -5597,6 +6422,12 @@ function createGatewayConnection(link, serverId) {
|
|
|
5597
6422
|
get credentials() {
|
|
5598
6423
|
return services().credentials;
|
|
5599
6424
|
},
|
|
6425
|
+
get searchSettings() {
|
|
6426
|
+
return services().searchSettings;
|
|
6427
|
+
},
|
|
6428
|
+
get contextSettings() {
|
|
6429
|
+
return services().contextSettings;
|
|
6430
|
+
},
|
|
5600
6431
|
get skillMarket() {
|
|
5601
6432
|
return services().skillMarket;
|
|
5602
6433
|
},
|
|
@@ -5604,26 +6435,13 @@ function createGatewayConnection(link, serverId) {
|
|
|
5604
6435
|
return attachment;
|
|
5605
6436
|
},
|
|
5606
6437
|
async ready(signal) {
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
directory.ready,
|
|
5615
|
-
management.ready,
|
|
5616
|
-
models.ready,
|
|
5617
|
-
workspaces.ready,
|
|
5618
|
-
credentials.ready,
|
|
5619
|
-
workspaceFiles.ready,
|
|
5620
|
-
skillMarket.ready
|
|
5621
|
-
]), signal);
|
|
5622
|
-
await Promise.all([
|
|
5623
|
-
waitForValue(directory.state, signal),
|
|
5624
|
-
waitForValue(workspaces.state, signal),
|
|
5625
|
-
waitForValue(credentials.state, signal)
|
|
5626
|
-
]);
|
|
6438
|
+
try {
|
|
6439
|
+
await checkReady(signal ? AbortSignal.any([signal, linkClosed.signal]) : linkClosed.signal);
|
|
6440
|
+
} catch (error) {
|
|
6441
|
+
if (protocolMismatch || closeReason.startsWith("protocol"))
|
|
6442
|
+
throw new ApplicationProtocolError;
|
|
6443
|
+
throw error;
|
|
6444
|
+
}
|
|
5627
6445
|
},
|
|
5628
6446
|
async attach(sessionId, ctx) {
|
|
5629
6447
|
const next = await connection.management.attach(sessionId, ctx);
|
|
@@ -5632,12 +6450,11 @@ function createGatewayConnection(link, serverId) {
|
|
|
5632
6450
|
stopView?.();
|
|
5633
6451
|
chat?.release();
|
|
5634
6452
|
attachment = next;
|
|
5635
|
-
const current = host.
|
|
6453
|
+
const current = host.hold(NekoChat, next);
|
|
5636
6454
|
chat = current;
|
|
5637
6455
|
stopView = current.view.subscribe(() => {});
|
|
5638
6456
|
try {
|
|
5639
6457
|
await deadline(current.ready, ctx?.signal);
|
|
5640
|
-
await waitForValue(current.view, ctx?.signal);
|
|
5641
6458
|
if (current !== chat)
|
|
5642
6459
|
throw new Error("Attachment changed");
|
|
5643
6460
|
return current;
|
|
@@ -5697,11 +6514,17 @@ function deadline(work, signal) {
|
|
|
5697
6514
|
signal?.removeEventListener("abort", abort);
|
|
5698
6515
|
});
|
|
5699
6516
|
}
|
|
5700
|
-
var JOBS_UNSUPPORTED = "This gateway does not support background jobs. Restart the gateway to enable them.";
|
|
6517
|
+
var GatewayIdentityError, JOBS_UNSUPPORTED = "This gateway does not support background jobs. Restart the gateway to enable them.", QUEUE_UNSUPPORTED = "This gateway does not support queued message controls. Restart the gateway to enable them.";
|
|
5701
6518
|
var init_client_session = __esm(() => {
|
|
5702
6519
|
init_application_version();
|
|
5703
6520
|
init_contract();
|
|
5704
6521
|
init_link();
|
|
6522
|
+
GatewayIdentityError = class GatewayIdentityError extends Error {
|
|
6523
|
+
constructor() {
|
|
6524
|
+
super("Gateway identity mismatch");
|
|
6525
|
+
this.name = "GatewayIdentityError";
|
|
6526
|
+
}
|
|
6527
|
+
};
|
|
5705
6528
|
});
|
|
5706
6529
|
|
|
5707
6530
|
// packages/tui/src/theme/palette.ts
|
|
@@ -17881,6 +18704,8 @@ var init_labels = __esm(() => {
|
|
|
17881
18704
|
{ name: "model", description: "Choose a model" },
|
|
17882
18705
|
{ name: "agents", description: "View agent tasks and activity" },
|
|
17883
18706
|
{ name: "jobs", description: "View background jobs" },
|
|
18707
|
+
{ name: "search", description: "Choose the web search provider (Tavily/DeepSeek)" },
|
|
18708
|
+
{ name: "context", description: "Tune context compaction and tool result pruning" },
|
|
17884
18709
|
{ name: "theme", description: "Choose color theme (auto/light/dark)" },
|
|
17885
18710
|
{ name: "think", description: "Set thinking level (/think high)" },
|
|
17886
18711
|
{ name: "fork", description: "Fork this session" },
|
|
@@ -17903,9 +18728,9 @@ var init_labels = __esm(() => {
|
|
|
17903
18728
|
return shortcutHints(["\u2191\u2193", "Select"], ["Enter", "Open"], ["Esc", "Close"]);
|
|
17904
18729
|
},
|
|
17905
18730
|
idleHints: "Enter Send \xB7 /help Keys",
|
|
17906
|
-
busyHints: "Enter
|
|
18731
|
+
busyHints: "Enter Queue \xB7 Esc Stop",
|
|
17907
18732
|
idleCompact: "Enter Send \xB7 /help Keys",
|
|
17908
|
-
busyCompact: "Enter
|
|
18733
|
+
busyCompact: "Enter Queue \xB7 Esc Stop"
|
|
17909
18734
|
};
|
|
17910
18735
|
});
|
|
17911
18736
|
|
|
@@ -19390,6 +20215,227 @@ var init_welcome = __esm(() => {
|
|
|
19390
20215
|
CAT = ["|\\___/|", ") (", "=\\^_^ /=", ")---("];
|
|
19391
20216
|
});
|
|
19392
20217
|
|
|
20218
|
+
// packages/tui/src/context-ui.ts
|
|
20219
|
+
import { matchesKey as matchesKey7 } from "@earendil-works/pi-tui";
|
|
20220
|
+
function format(kind, value) {
|
|
20221
|
+
if (value === null)
|
|
20222
|
+
return "Default";
|
|
20223
|
+
if (typeof value === "boolean")
|
|
20224
|
+
return value ? "On" : "Off";
|
|
20225
|
+
switch (kind) {
|
|
20226
|
+
case "percent":
|
|
20227
|
+
return `${value}%`;
|
|
20228
|
+
case "tokens":
|
|
20229
|
+
return value >= 1000 ? `${Math.round(value / 1000)}k tokens` : `${value} tokens`;
|
|
20230
|
+
case "chars":
|
|
20231
|
+
return value >= 1000 ? `${Math.round(value / 1000)}k chars` : `${value} chars`;
|
|
20232
|
+
default:
|
|
20233
|
+
return String(value);
|
|
20234
|
+
}
|
|
20235
|
+
}
|
|
20236
|
+
function read(state, row) {
|
|
20237
|
+
const effective = state.effective[row.group];
|
|
20238
|
+
const sources = state.sources[row.group];
|
|
20239
|
+
return { value: effective[row.field] ?? null, source: sources[row.field] ?? "default" };
|
|
20240
|
+
}
|
|
20241
|
+
function contextItems(state) {
|
|
20242
|
+
if (!state)
|
|
20243
|
+
return [];
|
|
20244
|
+
const items = [];
|
|
20245
|
+
let group = "";
|
|
20246
|
+
for (const row of CONTEXT_ROWS) {
|
|
20247
|
+
if (row.group !== group) {
|
|
20248
|
+
group = row.group;
|
|
20249
|
+
items.push({
|
|
20250
|
+
value: `header:${group}`,
|
|
20251
|
+
label: group === "compaction" ? "Compaction" : "Tool result pruning",
|
|
20252
|
+
header: true
|
|
20253
|
+
});
|
|
20254
|
+
}
|
|
20255
|
+
const { value, source } = read(state, row);
|
|
20256
|
+
const custom = source !== "default" && value !== null && !row.presets.includes(value);
|
|
20257
|
+
items.push({
|
|
20258
|
+
value: row.key,
|
|
20259
|
+
label: row.label,
|
|
20260
|
+
rightLabel: `${format(row.kind, value)} ${SOURCES[source]}${custom ? " \xB7 Custom" : ""}`,
|
|
20261
|
+
description: row.description
|
|
20262
|
+
});
|
|
20263
|
+
}
|
|
20264
|
+
items.push({
|
|
20265
|
+
value: RESET_VALUE,
|
|
20266
|
+
label: "Reset to defaults",
|
|
20267
|
+
description: "Clear every saved value. Config file values still apply."
|
|
20268
|
+
});
|
|
20269
|
+
return items;
|
|
20270
|
+
}
|
|
20271
|
+
|
|
20272
|
+
class PresetDetail {
|
|
20273
|
+
help = shortcutHints(["\u2191\u2193", "Select"], ["Enter", "Save"], ["Esc", "Back"]);
|
|
20274
|
+
#index = 0;
|
|
20275
|
+
#error = "";
|
|
20276
|
+
#saving = false;
|
|
20277
|
+
#options;
|
|
20278
|
+
#row;
|
|
20279
|
+
#client;
|
|
20280
|
+
#panel;
|
|
20281
|
+
#notify;
|
|
20282
|
+
#changed;
|
|
20283
|
+
constructor(row, client, panel, notify, changed) {
|
|
20284
|
+
this.#row = row;
|
|
20285
|
+
this.#client = client;
|
|
20286
|
+
this.#panel = panel;
|
|
20287
|
+
this.#notify = notify;
|
|
20288
|
+
this.#changed = changed;
|
|
20289
|
+
const state = client.contextSettings.state.value;
|
|
20290
|
+
const current = state ? read(state, row) : { value: null, source: "default" };
|
|
20291
|
+
const options = [null, ...row.presets];
|
|
20292
|
+
const custom = current.source !== "default" && current.value !== null && !row.presets.includes(current.value);
|
|
20293
|
+
if (custom)
|
|
20294
|
+
options.push(current.value);
|
|
20295
|
+
this.#options = options;
|
|
20296
|
+
this.#index = current.source === "saved" || custom ? Math.max(0, options.indexOf(current.value)) : 0;
|
|
20297
|
+
}
|
|
20298
|
+
lines(_width, height) {
|
|
20299
|
+
const state = this.#client.contextSettings.state.value;
|
|
20300
|
+
const current = state ? read(state, this.#row) : undefined;
|
|
20301
|
+
const head = [theme.accent(this.#row.label), theme.dim(this.#row.description)];
|
|
20302
|
+
if (this.#saving)
|
|
20303
|
+
head.push("Saving...");
|
|
20304
|
+
if (this.#error)
|
|
20305
|
+
head.push(theme.error(`Error: ${this.#error}`));
|
|
20306
|
+
const options = this.#options.map((option, i) => {
|
|
20307
|
+
const custom = option !== null && !this.#row.presets.includes(option);
|
|
20308
|
+
const text = `${format(this.#row.kind, option)}${custom ? " (Custom)" : ""}${option === null && current ? ` - now ${SOURCES[current.source]}` : ""}`;
|
|
20309
|
+
return i === this.#index ? theme.selection(`\u276F ${text}`) : ` ${text}`;
|
|
20310
|
+
});
|
|
20311
|
+
const count = Math.max(1, height - head.length);
|
|
20312
|
+
const start = Math.max(0, Math.min(this.#index - Math.floor(count / 2), options.length - count));
|
|
20313
|
+
return [...head, ...options.slice(start, start + count)];
|
|
20314
|
+
}
|
|
20315
|
+
handleKey(data) {
|
|
20316
|
+
if (this.#saving)
|
|
20317
|
+
return true;
|
|
20318
|
+
if (matchesKey7(data, "up"))
|
|
20319
|
+
this.#index = Math.max(0, this.#index - 1);
|
|
20320
|
+
else if (matchesKey7(data, "down"))
|
|
20321
|
+
this.#index = Math.min(this.#options.length - 1, this.#index + 1);
|
|
20322
|
+
else if (matchesKey7(data, "enter")) {
|
|
20323
|
+
const value = this.#options[this.#index] ?? null;
|
|
20324
|
+
this.#saving = true;
|
|
20325
|
+
this.#error = "";
|
|
20326
|
+
const patch = { [this.#row.group]: { [this.#row.field]: value } };
|
|
20327
|
+
this.#client.contextSettings.set(patch).then(() => {
|
|
20328
|
+
this.#saving = false;
|
|
20329
|
+
this.#notify(`${this.#row.label} set to ${format(this.#row.kind, value)}.`);
|
|
20330
|
+
this.#panel.popDetail();
|
|
20331
|
+
}, (error) => {
|
|
20332
|
+
this.#saving = false;
|
|
20333
|
+
this.#error = message(error);
|
|
20334
|
+
this.#changed();
|
|
20335
|
+
});
|
|
20336
|
+
} else
|
|
20337
|
+
return false;
|
|
20338
|
+
return true;
|
|
20339
|
+
}
|
|
20340
|
+
}
|
|
20341
|
+
function openContextPanel(panel, client, notify) {
|
|
20342
|
+
const items = () => contextItems(client.contextSettings.state.value);
|
|
20343
|
+
const stop = client.contextSettings.state.subscribe(() => panel.update(items()));
|
|
20344
|
+
panel.emptyText = CONTEXT_EMPTY;
|
|
20345
|
+
panel.show("Context settings", async () => items(), async (value) => {
|
|
20346
|
+
if (value === RESET_VALUE) {
|
|
20347
|
+
await client.contextSettings.reset({});
|
|
20348
|
+
notify("Context settings reset to defaults.");
|
|
20349
|
+
return;
|
|
20350
|
+
}
|
|
20351
|
+
const row = CONTEXT_ROWS.find((item) => item.key === value);
|
|
20352
|
+
if (!row)
|
|
20353
|
+
return;
|
|
20354
|
+
return new PresetDetail(row, client, panel, notify, () => panel.changed());
|
|
20355
|
+
});
|
|
20356
|
+
return stop;
|
|
20357
|
+
}
|
|
20358
|
+
var CONTEXT_EMPTY = "Context settings are unavailable.", CONTEXT_ROWS, SOURCES, RESET_VALUE = "reset", message = (error) => error instanceof Error ? error.message : String(error);
|
|
20359
|
+
var init_context_ui = __esm(() => {
|
|
20360
|
+
init_shortcut_hints();
|
|
20361
|
+
init_theme();
|
|
20362
|
+
CONTEXT_ROWS = [
|
|
20363
|
+
{
|
|
20364
|
+
key: "compaction.enabled",
|
|
20365
|
+
group: "compaction",
|
|
20366
|
+
field: "enabled",
|
|
20367
|
+
label: "Auto compaction",
|
|
20368
|
+
kind: "bool",
|
|
20369
|
+
description: "Compact automatically near the limit. /compact always works.",
|
|
20370
|
+
presets: [true, false]
|
|
20371
|
+
},
|
|
20372
|
+
{
|
|
20373
|
+
key: "compaction.triggerPercent",
|
|
20374
|
+
group: "compaction",
|
|
20375
|
+
field: "triggerPercent",
|
|
20376
|
+
label: "Compact at",
|
|
20377
|
+
kind: "percent",
|
|
20378
|
+
description: "Compact when context reaches this share of the smallest model window. Default is near the limit.",
|
|
20379
|
+
presets: [30, 40, 50, 60, 70, 80]
|
|
20380
|
+
},
|
|
20381
|
+
{
|
|
20382
|
+
key: "compaction.keepRecentTokens",
|
|
20383
|
+
group: "compaction",
|
|
20384
|
+
field: "keepRecentTokens",
|
|
20385
|
+
label: "Keep recent",
|
|
20386
|
+
kind: "tokens",
|
|
20387
|
+
description: "Recent context a compaction keeps verbatim.",
|
|
20388
|
+
presets: [1e4, 20000, 40000, 80000]
|
|
20389
|
+
},
|
|
20390
|
+
{
|
|
20391
|
+
key: "pruning.enabled",
|
|
20392
|
+
group: "pruning",
|
|
20393
|
+
field: "enabled",
|
|
20394
|
+
label: "Prune tool results",
|
|
20395
|
+
kind: "bool",
|
|
20396
|
+
description: "Replace old, large tool results with a short note in what the model sees. History is kept.",
|
|
20397
|
+
presets: [true, false]
|
|
20398
|
+
},
|
|
20399
|
+
{
|
|
20400
|
+
key: "pruning.triggerPercent",
|
|
20401
|
+
group: "pruning",
|
|
20402
|
+
field: "triggerPercent",
|
|
20403
|
+
label: "Prune at",
|
|
20404
|
+
kind: "percent",
|
|
20405
|
+
description: "Prune when context reaches this share of the model window. Each prune invalidates the prompt cache.",
|
|
20406
|
+
presets: [20, 30, 35, 45, 55]
|
|
20407
|
+
},
|
|
20408
|
+
{
|
|
20409
|
+
key: "pruning.keepRecent",
|
|
20410
|
+
group: "pruning",
|
|
20411
|
+
field: "keepRecent",
|
|
20412
|
+
label: "Keep recent results",
|
|
20413
|
+
kind: "count",
|
|
20414
|
+
description: "Newest tool results that are never pruned.",
|
|
20415
|
+
presets: [5, 10, 20, 40]
|
|
20416
|
+
},
|
|
20417
|
+
{
|
|
20418
|
+
key: "pruning.minChars",
|
|
20419
|
+
group: "pruning",
|
|
20420
|
+
field: "minChars",
|
|
20421
|
+
label: "Min result size",
|
|
20422
|
+
kind: "chars",
|
|
20423
|
+
description: "Results shorter than this are never pruned.",
|
|
20424
|
+
presets: [2000, 4000, 8000, 16000]
|
|
20425
|
+
},
|
|
20426
|
+
{
|
|
20427
|
+
key: "pruning.minSavePercent",
|
|
20428
|
+
group: "pruning",
|
|
20429
|
+
field: "minSavePercent",
|
|
20430
|
+
label: "Min release",
|
|
20431
|
+
kind: "percent",
|
|
20432
|
+
description: "A prune runs only if it frees at least this share of the model window. Each prune invalidates the prompt cache, so a larger share means fewer prunes.",
|
|
20433
|
+
presets: [2, 5, 10, 15]
|
|
20434
|
+
}
|
|
20435
|
+
];
|
|
20436
|
+
SOURCES = { default: "Default", config: "Config", saved: "Saved" };
|
|
20437
|
+
});
|
|
20438
|
+
|
|
19393
20439
|
// packages/tui/src/detect-theme.ts
|
|
19394
20440
|
function detectColorFgBgMode(env = process.env) {
|
|
19395
20441
|
const bg = env.COLORFGBG?.split(";").at(-1)?.trim();
|
|
@@ -19695,7 +20741,7 @@ class UnixLink {
|
|
|
19695
20741
|
}
|
|
19696
20742
|
send(frame) {
|
|
19697
20743
|
try {
|
|
19698
|
-
this.#native.send(frame);
|
|
20744
|
+
return this.#native.send(frame);
|
|
19699
20745
|
} catch (error) {
|
|
19700
20746
|
this.#native.close("pending byte limit exceeded");
|
|
19701
20747
|
throw error;
|
|
@@ -19829,7 +20875,7 @@ function createWsListener(options) {
|
|
|
19829
20875
|
server.on("error", (error) => options.onError?.(error));
|
|
19830
20876
|
server.on("connection", (socket) => {
|
|
19831
20877
|
socket.on("error", (error) => options.onError?.(error));
|
|
19832
|
-
accept(boundedWebSocketLink(socket, options.maxPendingBytes));
|
|
20878
|
+
accept(boundedWebSocketLink(socket, options.maxPendingBytes, { acceptPacked: false }));
|
|
19833
20879
|
});
|
|
19834
20880
|
},
|
|
19835
20881
|
async close() {
|
|
@@ -19879,6 +20925,25 @@ function isServerId(value) {
|
|
|
19879
20925
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
19880
20926
|
}
|
|
19881
20927
|
|
|
20928
|
+
// packages/gateway/src/principal.ts
|
|
20929
|
+
function gatewayPrincipalKey(principal) {
|
|
20930
|
+
switch (principal.kind) {
|
|
20931
|
+
case "mobile":
|
|
20932
|
+
return `mobile:${principal.deviceId}:${principal.grantRevision}:${principal.connectionId}`;
|
|
20933
|
+
case "local":
|
|
20934
|
+
return `local:${principal.connectionId}`;
|
|
20935
|
+
case "host":
|
|
20936
|
+
return "host";
|
|
20937
|
+
}
|
|
20938
|
+
}
|
|
20939
|
+
function isMobilePrincipal(principal) {
|
|
20940
|
+
return typeof principal === "object" && principal !== null && principal.kind === "mobile";
|
|
20941
|
+
}
|
|
20942
|
+
var HOST_PRINCIPAL;
|
|
20943
|
+
var init_principal = __esm(() => {
|
|
20944
|
+
HOST_PRINCIPAL = { kind: "host" };
|
|
20945
|
+
});
|
|
20946
|
+
|
|
19882
20947
|
// packages/gateway/src/mobile/store.ts
|
|
19883
20948
|
import { randomBytes } from "crypto";
|
|
19884
20949
|
import { chmod as chmod2, mkdir as mkdir5, readFile as readFile6, rename as rename3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -20189,20 +21254,8 @@ var init_store = __esm(() => {
|
|
|
20189
21254
|
|
|
20190
21255
|
// packages/gateway/src/mobile/admin.ts
|
|
20191
21256
|
import { createHash, timingSafeEqual } from "crypto";
|
|
20192
|
-
import { ChordError
|
|
21257
|
+
import { ChordError } from "@earendil-works/chord";
|
|
20193
21258
|
import { Type as Type6 } from "typebox";
|
|
20194
|
-
function contract2(name, def) {
|
|
20195
|
-
const variants = Object.fromEntries(Object.entries(def.variants).map(([key, value]) => [
|
|
20196
|
-
key,
|
|
20197
|
-
{
|
|
20198
|
-
methods: Object.fromEntries(Object.entries(value.methods ?? {}).map(([member, [args, result]]) => [member, method2(args, result)]))
|
|
20199
|
-
}
|
|
20200
|
-
]));
|
|
20201
|
-
return remote2(name, { ...def, variants });
|
|
20202
|
-
}
|
|
20203
|
-
function isMobilePrincipal(principal) {
|
|
20204
|
-
return typeof principal === "object" && principal !== null && principal.kind === "mobile";
|
|
20205
|
-
}
|
|
20206
21259
|
function digest(value) {
|
|
20207
21260
|
return createHash("sha256").update(value, "utf8").digest();
|
|
20208
21261
|
}
|
|
@@ -20231,7 +21284,7 @@ function createMobileAdminFactory(deps) {
|
|
|
20231
21284
|
throw new ChordError("forbidden", "forbidden");
|
|
20232
21285
|
};
|
|
20233
21286
|
return {
|
|
20234
|
-
|
|
21287
|
+
variant: "admin",
|
|
20235
21288
|
async state() {
|
|
20236
21289
|
await guard();
|
|
20237
21290
|
return deps.mobile.adminState();
|
|
@@ -20269,6 +21322,8 @@ function createMobileAdminFactory(deps) {
|
|
|
20269
21322
|
}
|
|
20270
21323
|
var listenerStateSchema, adminDeviceSchema, NekoMobileAdmin;
|
|
20271
21324
|
var init_admin = __esm(() => {
|
|
21325
|
+
init_contract();
|
|
21326
|
+
init_principal();
|
|
20272
21327
|
init_store();
|
|
20273
21328
|
listenerStateSchema = Type6.Union([
|
|
20274
21329
|
Type6.Object({ status: Type6.Literal("disabled") }),
|
|
@@ -20290,54 +21345,42 @@ var init_admin = __esm(() => {
|
|
|
20290
21345
|
lastSeenAt: Type6.Integer(),
|
|
20291
21346
|
online: Type6.Boolean()
|
|
20292
21347
|
});
|
|
20293
|
-
NekoMobileAdmin =
|
|
21348
|
+
NekoMobileAdmin = contract("neko.mobile-admin", {
|
|
20294
21349
|
version: 1,
|
|
20295
21350
|
input: Type6.Object({ token: Type6.String() }),
|
|
20296
21351
|
variants: {
|
|
20297
21352
|
admin: {
|
|
20298
21353
|
methods: {
|
|
20299
|
-
state:
|
|
20300
|
-
|
|
20301
|
-
Type6.
|
|
20302
|
-
|
|
20303
|
-
|
|
20304
|
-
|
|
20305
|
-
|
|
20306
|
-
|
|
20307
|
-
|
|
20308
|
-
|
|
20309
|
-
|
|
20310
|
-
|
|
20311
|
-
|
|
20312
|
-
|
|
20313
|
-
|
|
20314
|
-
|
|
20315
|
-
|
|
20316
|
-
|
|
20317
|
-
|
|
20318
|
-
Type6.
|
|
20319
|
-
|
|
20320
|
-
|
|
20321
|
-
|
|
20322
|
-
Type6.
|
|
20323
|
-
|
|
20324
|
-
|
|
20325
|
-
|
|
20326
|
-
|
|
20327
|
-
|
|
20328
|
-
|
|
20329
|
-
],
|
|
20330
|
-
disable: [Type6.Null(), listenerStateSchema],
|
|
20331
|
-
createPairing: [Type6.Null(), Type6.Object({ pairingText: Type6.String(), expiresAt: Type6.Integer() })],
|
|
20332
|
-
cancelPairing: [Type6.Null(), Type6.Null()],
|
|
20333
|
-
revoke: [
|
|
20334
|
-
Type6.Object({ deviceId: Type6.String({ minLength: 1 }) }),
|
|
20335
|
-
Type6.Object({ revoked: Type6.Boolean() })
|
|
20336
|
-
],
|
|
20337
|
-
regenerateCertificate: [
|
|
20338
|
-
Type6.Null(),
|
|
20339
|
-
Type6.Object({ fingerprint: Type6.String(), expiresAt: Type6.Integer() })
|
|
20340
|
-
]
|
|
21354
|
+
state: rpc(Type6.Null(), Type6.Object({
|
|
21355
|
+
state: listenerStateSchema,
|
|
21356
|
+
candidates: Type6.Array(Type6.String()),
|
|
21357
|
+
port: Type6.Integer({ minimum: 1, maximum: 65535 }),
|
|
21358
|
+
certificate: Type6.Union([
|
|
21359
|
+
Type6.Object({ fingerprint: Type6.String(), expiresAt: Type6.Integer() }),
|
|
21360
|
+
Type6.Null()
|
|
21361
|
+
]),
|
|
21362
|
+
devices: Type6.Array(adminDeviceSchema),
|
|
21363
|
+
lastConnectedAddress: Type6.Union([Type6.String(), Type6.Null()]),
|
|
21364
|
+
wsl: Type6.Optional(Type6.Union([
|
|
21365
|
+
Type6.Object({ interop: Type6.Boolean(), error: Type6.Union([Type6.String(), Type6.Null()]) }),
|
|
21366
|
+
Type6.Null()
|
|
21367
|
+
]))
|
|
21368
|
+
})),
|
|
21369
|
+
enable: rpc(Type6.Object({
|
|
21370
|
+
address: Type6.String(),
|
|
21371
|
+
port: Type6.Optional(Type6.Integer({ minimum: 1, maximum: 65535 }))
|
|
21372
|
+
}), Type6.Object({
|
|
21373
|
+
status: Type6.Literal("listening"),
|
|
21374
|
+
address: Type6.String(),
|
|
21375
|
+
port: Type6.Integer({ minimum: 1, maximum: 65535 }),
|
|
21376
|
+
bindAddress: Type6.Optional(Type6.String()),
|
|
21377
|
+
mode: Type6.Optional(Type6.Union([Type6.Literal("direct"), Type6.Literal("wsl-relay")]))
|
|
21378
|
+
})),
|
|
21379
|
+
disable: rpc(Type6.Null(), listenerStateSchema),
|
|
21380
|
+
createPairing: rpc(Type6.Null(), Type6.Object({ pairingText: Type6.String(), expiresAt: Type6.Integer() })),
|
|
21381
|
+
cancelPairing: rpc(Type6.Null(), Type6.Null()),
|
|
21382
|
+
revoke: rpc(Type6.Object({ deviceId: Type6.String({ minLength: 1 }) }), Type6.Object({ revoked: Type6.Boolean() })),
|
|
21383
|
+
regenerateCertificate: rpc(Type6.Null(), Type6.Object({ fingerprint: Type6.String(), expiresAt: Type6.Integer() }))
|
|
20341
21384
|
}
|
|
20342
21385
|
}
|
|
20343
21386
|
}
|
|
@@ -20401,7 +21444,7 @@ function buildHelloResultSchema() {
|
|
|
20401
21444
|
})
|
|
20402
21445
|
});
|
|
20403
21446
|
}
|
|
20404
|
-
var MOBILE_LIMITS, MOBILE_API_VERSIONS, MOBILE_CAPABILITIES, MOBILE_CLOSE_REVOKED = 4401, MOBILE_CLOSE_INCOMPATIBLE = 4402, MOBILE_CLOSE_SLOW_CONSUMER = 4408, MobileHelloParams, opt, str2, int,
|
|
21447
|
+
var MOBILE_LIMITS, MOBILE_API_VERSIONS, MOBILE_CAPABILITIES, MOBILE_CLOSE_REVOKED = 4401, MOBILE_CLOSE_INCOMPATIBLE = 4402, MOBILE_CLOSE_SLOW_CONSUMER = 4408, MobileHelloParams, opt, str2, int, nullable2 = (schema) => Type7.Union([schema, Type7.Null()]), MobileContentRefSchema, MobileImageRefSchema, MobileToolItemSchema, MobileChatItemSchema, MobileStreamingSchema, MobileUsageSchema, MobileSubagentRoundSchema, MobileAgentSchema, MobileQueuedSchema, MobileChatStatusSchema, MobileChatSnapshotSchema, MobileWorkspaceSchema, MobileSessionSummarySchema, MobileApprovalSchema, sessionListParams, sessionListResult, subscription, pageResult, pageParams, chunkResult, MOBILE_API_V1_METHODS, patchBody, MOBILE_API_V1_NOTIFICATIONS;
|
|
20405
21448
|
var init_protocol = __esm(() => {
|
|
20406
21449
|
init_application_version();
|
|
20407
21450
|
MOBILE_LIMITS = {
|
|
@@ -20520,7 +21563,7 @@ var init_protocol = __esm(() => {
|
|
|
20520
21563
|
approvalCount: int,
|
|
20521
21564
|
readonly: Type7.Boolean(),
|
|
20522
21565
|
createdAt: Type7.Number(),
|
|
20523
|
-
endedAt:
|
|
21566
|
+
endedAt: nullable2(Type7.Number()),
|
|
20524
21567
|
usage: opt(MobileUsageSchema)
|
|
20525
21568
|
});
|
|
20526
21569
|
MobileAgentSchema = Type7.Object({
|
|
@@ -20528,18 +21571,25 @@ var init_protocol = __esm(() => {
|
|
|
20528
21571
|
role: str2,
|
|
20529
21572
|
title: str2,
|
|
20530
21573
|
status: Type7.Union([Type7.Literal("idle"), Type7.Literal("queued"), Type7.Literal("running")]),
|
|
20531
|
-
activeRoundId:
|
|
21574
|
+
activeRoundId: nullable2(int),
|
|
20532
21575
|
lastRoundId: int,
|
|
20533
21576
|
roundCount: int,
|
|
20534
21577
|
approvalCount: int
|
|
20535
21578
|
});
|
|
21579
|
+
MobileQueuedSchema = Type7.Object({
|
|
21580
|
+
id: int,
|
|
21581
|
+
mode: Type7.Union([Type7.Literal("steer"), Type7.Literal("followUp")]),
|
|
21582
|
+
preview: Type7.String(),
|
|
21583
|
+
images: int
|
|
21584
|
+
});
|
|
20536
21585
|
MobileChatStatusSchema = Type7.Object({
|
|
20537
21586
|
busy: Type7.Boolean(),
|
|
20538
|
-
|
|
21587
|
+
queued: opt(Type7.Array(MobileQueuedSchema)),
|
|
21588
|
+
status: nullable2(str2),
|
|
20539
21589
|
usage: MobileUsageSchema,
|
|
20540
21590
|
taskUsage: opt(MobileUsageSchema),
|
|
20541
|
-
contextUsage: opt(Type7.Object({ tokens:
|
|
20542
|
-
agent: Type7.Object({ model:
|
|
21591
|
+
contextUsage: opt(Type7.Object({ tokens: nullable2(Type7.Number()), window: Type7.Number(), percent: nullable2(Type7.Number()) })),
|
|
21592
|
+
agent: Type7.Object({ model: nullable2(str2), thinkingLevel: nullable2(str2) }),
|
|
20543
21593
|
subagents: Type7.Array(MobileSubagentRoundSchema),
|
|
20544
21594
|
agents: Type7.Array(MobileAgentSchema),
|
|
20545
21595
|
approvalCount: int,
|
|
@@ -20549,8 +21599,8 @@ var init_protocol = __esm(() => {
|
|
|
20549
21599
|
MobileChatSnapshotSchema = Type7.Object({
|
|
20550
21600
|
...MobileChatStatusSchema.properties,
|
|
20551
21601
|
items: Type7.Array(MobileChatItemSchema),
|
|
20552
|
-
historyBefore:
|
|
20553
|
-
streaming:
|
|
21602
|
+
historyBefore: nullable2(str2),
|
|
21603
|
+
streaming: nullable2(MobileStreamingSchema)
|
|
20554
21604
|
});
|
|
20555
21605
|
MobileWorkspaceSchema = Type7.Object({ root: str2, name: str2 });
|
|
20556
21606
|
MobileSessionSummarySchema = Type7.Object({
|
|
@@ -20583,7 +21633,7 @@ var init_protocol = __esm(() => {
|
|
|
20583
21633
|
nextCursor: opt(str2)
|
|
20584
21634
|
});
|
|
20585
21635
|
subscription = { subscriptionId: Type7.String({ minLength: 1 }) };
|
|
20586
|
-
pageResult = Type7.Object({ items: Type7.Array(MobileChatItemSchema), historyBefore:
|
|
21636
|
+
pageResult = Type7.Object({ items: Type7.Array(MobileChatItemSchema), historyBefore: nullable2(str2) });
|
|
20587
21637
|
pageParams = Type7.Object({
|
|
20588
21638
|
...subscription,
|
|
20589
21639
|
before: Type7.String({ minLength: 1 }),
|
|
@@ -20618,6 +21668,34 @@ var init_protocol = __esm(() => {
|
|
|
20618
21668
|
params: Type7.Object({ ...subscription, requestId: Type7.String({ minLength: 1, maxLength: 128 }) }),
|
|
20619
21669
|
result: Type7.Object({ status: Type7.Union([Type7.Literal("accepted"), Type7.Literal("unknown")]) })
|
|
20620
21670
|
},
|
|
21671
|
+
"chat.steer": {
|
|
21672
|
+
params: Type7.Object({ ...subscription, id: int }),
|
|
21673
|
+
result: Type7.Object({
|
|
21674
|
+
result: Type7.Union([
|
|
21675
|
+
Type7.Literal("steered"),
|
|
21676
|
+
Type7.Literal("withdrawn"),
|
|
21677
|
+
Type7.Literal("placed"),
|
|
21678
|
+
Type7.Literal("settled"),
|
|
21679
|
+
Type7.Literal("unknown")
|
|
21680
|
+
]),
|
|
21681
|
+
id: opt(int),
|
|
21682
|
+
text: opt(Type7.String()),
|
|
21683
|
+
images: opt(int)
|
|
21684
|
+
})
|
|
21685
|
+
},
|
|
21686
|
+
"chat.withdraw": {
|
|
21687
|
+
params: Type7.Object({ ...subscription, id: int }),
|
|
21688
|
+
result: Type7.Object({
|
|
21689
|
+
result: Type7.Union([
|
|
21690
|
+
Type7.Literal("withdrawn"),
|
|
21691
|
+
Type7.Literal("placed"),
|
|
21692
|
+
Type7.Literal("settled"),
|
|
21693
|
+
Type7.Literal("unknown")
|
|
21694
|
+
]),
|
|
21695
|
+
text: opt(Type7.String()),
|
|
21696
|
+
images: opt(int)
|
|
21697
|
+
})
|
|
21698
|
+
},
|
|
20621
21699
|
"chat.abort": { params: Type7.Object(subscription), result: Type7.Object({ accepted: Type7.Boolean() }) },
|
|
20622
21700
|
"subagents.open": {
|
|
20623
21701
|
params: Type7.Object({ ...subscription, agentId: opt(Type7.String({ minLength: 1 })), taskId: opt(int) }),
|
|
@@ -20680,7 +21758,7 @@ var init_protocol = __esm(() => {
|
|
|
20680
21758
|
seq: Type7.Integer({ minimum: 1 }),
|
|
20681
21759
|
upserts: Type7.Array(MobileChatItemSchema),
|
|
20682
21760
|
status: opt(MobileChatStatusSchema),
|
|
20683
|
-
streaming: opt(
|
|
21761
|
+
streaming: opt(nullable2(MobileStreamingSchema)),
|
|
20684
21762
|
streamingAppend: opt(Type7.Object({ text: opt(str2), thinking: opt(str2) }))
|
|
20685
21763
|
};
|
|
20686
21764
|
MOBILE_API_V1_NOTIFICATIONS = {
|
|
@@ -20877,6 +21955,9 @@ function mapStatus(view, options = {}) {
|
|
|
20877
21955
|
const latest = view.compaction?.latest;
|
|
20878
21956
|
return {
|
|
20879
21957
|
busy: view.busy,
|
|
21958
|
+
...view.queued === undefined ? {} : {
|
|
21959
|
+
queued: view.queued.map(({ id, mode, preview, images }) => ({ id, mode, preview, images }))
|
|
21960
|
+
},
|
|
20880
21961
|
status: view.status,
|
|
20881
21962
|
usage: mapUsage(view.usage),
|
|
20882
21963
|
...view.taskUsage === undefined ? {} : { taskUsage: mapUsage(view.taskUsage) },
|
|
@@ -21183,7 +22264,7 @@ function chordToMobileCode(error) {
|
|
|
21183
22264
|
return "internal";
|
|
21184
22265
|
}
|
|
21185
22266
|
}
|
|
21186
|
-
async function
|
|
22267
|
+
async function remote2(work, invalidAs = "invalid_params") {
|
|
21187
22268
|
try {
|
|
21188
22269
|
return await work();
|
|
21189
22270
|
} catch (error) {
|
|
@@ -21365,6 +22446,10 @@ class MobileAdapter {
|
|
|
21365
22446
|
return this.chatSubmit(params);
|
|
21366
22447
|
case "chat.submissionStatus":
|
|
21367
22448
|
return this.chatSubmissionStatus(params);
|
|
22449
|
+
case "chat.steer":
|
|
22450
|
+
return this.chatSteer(params);
|
|
22451
|
+
case "chat.withdraw":
|
|
22452
|
+
return this.chatWithdraw(params);
|
|
21368
22453
|
case "chat.abort":
|
|
21369
22454
|
return this.chatAbort(params);
|
|
21370
22455
|
case "subagents.open":
|
|
@@ -21397,14 +22482,17 @@ class MobileAdapter {
|
|
|
21397
22482
|
}
|
|
21398
22483
|
}
|
|
21399
22484
|
async workspacesList() {
|
|
21400
|
-
const
|
|
22485
|
+
const { workspaces } = this.services();
|
|
22486
|
+
await workspaces.ready;
|
|
22487
|
+
const state = workspaces.state.value;
|
|
21401
22488
|
return {
|
|
21402
22489
|
workspaces: state.workspaces.map((workspace) => ({ root: workspace.path, name: baseName(workspace.path) }))
|
|
21403
22490
|
};
|
|
21404
22491
|
}
|
|
21405
22492
|
async registeredWorkspaceRoots() {
|
|
21406
|
-
const
|
|
21407
|
-
|
|
22493
|
+
const { workspaces } = this.services();
|
|
22494
|
+
await workspaces.ready;
|
|
22495
|
+
return workspaces.state.value.workspaces.map((workspace) => workspace.path);
|
|
21408
22496
|
}
|
|
21409
22497
|
async sessionsList(params) {
|
|
21410
22498
|
const roots = await this.registeredWorkspaceRoots();
|
|
@@ -21413,7 +22501,9 @@ class MobileAdapter {
|
|
|
21413
22501
|
const offset = params.cursor === undefined ? 0 : Number.parseInt(params.cursor, 10);
|
|
21414
22502
|
if (!Number.isInteger(offset) || offset < 0)
|
|
21415
22503
|
throw new MobileMethodError("invalid_params", "invalid cursor");
|
|
21416
|
-
const directory =
|
|
22504
|
+
const { directory: directoryRef } = this.services();
|
|
22505
|
+
await directoryRef.ready;
|
|
22506
|
+
const directory = directoryRef.state.value;
|
|
21417
22507
|
let sessions = directory.sessions.filter((session) => session.workspaceRoot === params.workspace);
|
|
21418
22508
|
if (params.query !== undefined) {
|
|
21419
22509
|
const query = params.query.toLowerCase();
|
|
@@ -21439,7 +22529,7 @@ class MobileAdapter {
|
|
|
21439
22529
|
if (!roots.includes(params.workspace))
|
|
21440
22530
|
throw new MobileMethodError("invalid_params", `unknown workspace: ${params.workspace}`);
|
|
21441
22531
|
this.assertAuthorized();
|
|
21442
|
-
const created = await
|
|
22532
|
+
const created = await remote2(() => this.services().management.create({ cwd: params.workspace }));
|
|
21443
22533
|
return {
|
|
21444
22534
|
id: created.id,
|
|
21445
22535
|
workspace: created.workspaceRoot,
|
|
@@ -21476,16 +22566,13 @@ class MobileAdapter {
|
|
|
21476
22566
|
const generation = ++this.attachGeneration;
|
|
21477
22567
|
this.closeSubagent();
|
|
21478
22568
|
this.releaseChat();
|
|
21479
|
-
const attachment = await
|
|
22569
|
+
const attachment = await remote2(() => services.management.attach(params.sessionId));
|
|
21480
22570
|
if (generation !== this.attachGeneration || this.disposed)
|
|
21481
22571
|
throw new MobileMethodError("gone", "superseded by a newer attach");
|
|
21482
22572
|
const chat = services.openChat(attachment);
|
|
21483
22573
|
const stopView = chat.view.subscribe(() => {});
|
|
21484
22574
|
try {
|
|
21485
|
-
await
|
|
21486
|
-
await chat.ready;
|
|
21487
|
-
await waitForValue(chat.view);
|
|
21488
|
-
});
|
|
22575
|
+
await remote2(() => chat.ready);
|
|
21489
22576
|
} catch (error) {
|
|
21490
22577
|
stopView();
|
|
21491
22578
|
chat.release();
|
|
@@ -21547,7 +22634,7 @@ class MobileAdapter {
|
|
|
21547
22634
|
this.attachGeneration += 1;
|
|
21548
22635
|
this.closeSubagent();
|
|
21549
22636
|
this.releaseChat();
|
|
21550
|
-
await
|
|
22637
|
+
await remote2(() => this.services().management.detach(null)).catch(() => {
|
|
21551
22638
|
return;
|
|
21552
22639
|
});
|
|
21553
22640
|
return null;
|
|
@@ -21578,7 +22665,7 @@ class MobileAdapter {
|
|
|
21578
22665
|
const state = this.requireChat(params);
|
|
21579
22666
|
const busy = state.chat.view.value?.busy ?? false;
|
|
21580
22667
|
this.assertAuthorized();
|
|
21581
|
-
await
|
|
22668
|
+
await remote2(() => state.chat.submit({
|
|
21582
22669
|
content: params.text,
|
|
21583
22670
|
requestId: params.requestId,
|
|
21584
22671
|
...params.mode === "steer" ? { whenBusy: "steer" } : {}
|
|
@@ -21592,10 +22679,20 @@ class MobileAdapter {
|
|
|
21592
22679
|
const accepted = await this.runtime.submissionAccepted(state.sessionId, params.requestId);
|
|
21593
22680
|
return { status: accepted ? "accepted" : "unknown" };
|
|
21594
22681
|
}
|
|
22682
|
+
async chatSteer(params) {
|
|
22683
|
+
const state = this.requireChat(params);
|
|
22684
|
+
this.assertAuthorized();
|
|
22685
|
+
return remote2(() => state.chat.steer({ id: params.id }), "rejected");
|
|
22686
|
+
}
|
|
22687
|
+
async chatWithdraw(params) {
|
|
22688
|
+
const state = this.requireChat(params);
|
|
22689
|
+
this.assertAuthorized();
|
|
22690
|
+
return remote2(() => state.chat.withdraw({ id: params.id }), "rejected");
|
|
22691
|
+
}
|
|
21595
22692
|
async chatAbort(params) {
|
|
21596
22693
|
const state = this.requireChat(params);
|
|
21597
22694
|
this.assertAuthorized();
|
|
21598
|
-
await
|
|
22695
|
+
await remote2(() => state.chat.abort(null));
|
|
21599
22696
|
return { accepted: true };
|
|
21600
22697
|
}
|
|
21601
22698
|
async approvalRecords(state) {
|
|
@@ -21674,7 +22771,7 @@ class MobileAdapter {
|
|
|
21674
22771
|
this.assertAuthorized();
|
|
21675
22772
|
const handle = openSubagent({ ...state.attachment, taskId: params.taskId });
|
|
21676
22773
|
try {
|
|
21677
|
-
const status = await
|
|
22774
|
+
const status = await remote2(() => handle.decideApproval({
|
|
21678
22775
|
id: params.id,
|
|
21679
22776
|
version: params.version,
|
|
21680
22777
|
decision: params.decision === "allow" ? "allowed" : "denied"
|
|
@@ -21754,7 +22851,7 @@ class MobileAdapter {
|
|
|
21754
22851
|
return () => listeners.delete(listener);
|
|
21755
22852
|
},
|
|
21756
22853
|
older: async (before) => {
|
|
21757
|
-
const page = await
|
|
22854
|
+
const page = await remote2(() => client.history({ before }));
|
|
21758
22855
|
return { items: page.items, next: page.next };
|
|
21759
22856
|
}
|
|
21760
22857
|
};
|
|
@@ -21798,7 +22895,7 @@ class MobileAdapter {
|
|
|
21798
22895
|
this.assertAuthorized();
|
|
21799
22896
|
const handle = openSubagent({ ...chat.attachment, taskId: params.taskId });
|
|
21800
22897
|
try {
|
|
21801
|
-
await
|
|
22898
|
+
await remote2(() => handle.stop({ taskId: params.taskId }));
|
|
21802
22899
|
} finally {
|
|
21803
22900
|
handle.release();
|
|
21804
22901
|
}
|
|
@@ -21812,9 +22909,9 @@ class MobileAdapter {
|
|
|
21812
22909
|
const state = this.subagentState;
|
|
21813
22910
|
if (state === undefined || state.agentId !== params.agentId || state.chatSubscriptionId !== chat.subscriptionId)
|
|
21814
22911
|
throw new MobileMethodError("gone", "subagent view is no longer open");
|
|
21815
|
-
chunk = await
|
|
22912
|
+
chunk = await remote2(() => state.client.getImage(input));
|
|
21816
22913
|
} else {
|
|
21817
|
-
chunk = await
|
|
22914
|
+
chunk = await remote2(() => chat.chat.getImage(input));
|
|
21818
22915
|
}
|
|
21819
22916
|
const bytes = Buffer.from(chunk.data, "base64").subarray(0, MOBILE_LIMITS.chunkBytes);
|
|
21820
22917
|
const nextOffset = params.offset + bytes.length;
|
|
@@ -21864,7 +22961,6 @@ class MobileAdapter {
|
|
|
21864
22961
|
var CLOSE_INVALID_JSON = 1003, CLOSE_PROTOCOL_ERROR = 1002, ACTIVE_ROUND, MobileMethodError;
|
|
21865
22962
|
var init_adapter = __esm(() => {
|
|
21866
22963
|
init_src();
|
|
21867
|
-
init_client_session();
|
|
21868
22964
|
init_projection2();
|
|
21869
22965
|
init_protocol();
|
|
21870
22966
|
ACTIVE_ROUND = new Set(["queued", "running"]);
|
|
@@ -39188,7 +40284,7 @@ var init_wsl = __esm(() => {
|
|
|
39188
40284
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
39189
40285
|
import { createServer as createServer2 } from "https";
|
|
39190
40286
|
import { hostname } from "os";
|
|
39191
|
-
import {
|
|
40287
|
+
import { memoryLinkPair } from "@earendil-works/chord";
|
|
39192
40288
|
import { WebSocketServer as WebSocketServer2 } from "ws";
|
|
39193
40289
|
function defaultServerName() {
|
|
39194
40290
|
const name = hostname().trim();
|
|
@@ -39220,9 +40316,6 @@ function describeRelayError(error, address, port, bindAddress) {
|
|
|
39220
40316
|
return `Windows \u4E2D\u7EE7\u542F\u52A8\u5931\u8D25\uFF1A${error.message}`;
|
|
39221
40317
|
}
|
|
39222
40318
|
}
|
|
39223
|
-
function isMobilePrincipal2(principal) {
|
|
39224
|
-
return typeof principal === "object" && principal !== null && principal.kind === "mobile";
|
|
39225
|
-
}
|
|
39226
40319
|
function toMessage(error) {
|
|
39227
40320
|
if (error instanceof Error) {
|
|
39228
40321
|
const code = error.code;
|
|
@@ -39401,7 +40494,7 @@ class MobileGateway {
|
|
|
39401
40494
|
await store.clearMobileAuthorizations();
|
|
39402
40495
|
this.certificate = certificate;
|
|
39403
40496
|
this.pairingValue?.invalidate();
|
|
39404
|
-
await this.chord.invalidatePrincipal(
|
|
40497
|
+
await this.chord.invalidatePrincipal(isMobilePrincipal);
|
|
39405
40498
|
for (const connection of [...this.connections.values()])
|
|
39406
40499
|
connection.close(MOBILE_CLOSE_REVOKED, "revoked");
|
|
39407
40500
|
const current = this.stateValue;
|
|
@@ -39477,11 +40570,7 @@ class MobileGateway {
|
|
|
39477
40570
|
store.rememberConnectedAddress(learned).catch((error) => this.onError?.(toError(error)));
|
|
39478
40571
|
}
|
|
39479
40572
|
}
|
|
39480
|
-
const clientHost =
|
|
39481
|
-
kind: "client",
|
|
39482
|
-
upstream: identifiedLink(clientEnd, this.serverId),
|
|
39483
|
-
onError: () => {}
|
|
39484
|
-
});
|
|
40573
|
+
const clientHost = createClientHost(clientEnd);
|
|
39485
40574
|
let servicesValue;
|
|
39486
40575
|
const adapter = new MobileAdapter({
|
|
39487
40576
|
socket,
|
|
@@ -39493,11 +40582,11 @@ class MobileGateway {
|
|
|
39493
40582
|
services: () => {
|
|
39494
40583
|
if (servicesValue === undefined) {
|
|
39495
40584
|
servicesValue = {
|
|
39496
|
-
directory: clientHost.
|
|
39497
|
-
management: clientHost.
|
|
39498
|
-
workspaces: clientHost.
|
|
39499
|
-
openChat: (attachment) => clientHost.
|
|
39500
|
-
openSubagent: (input) => clientHost.
|
|
40585
|
+
directory: clientHost.hold(NekoSessionDirectory, {}),
|
|
40586
|
+
management: clientHost.hold(NekoSessionManagement, {}),
|
|
40587
|
+
workspaces: clientHost.hold(NekoWorkspaces, {}),
|
|
40588
|
+
openChat: (attachment) => clientHost.hold(NekoChat, attachment),
|
|
40589
|
+
openSubagent: (input) => clientHost.hold(NekoSubagent, input)
|
|
39501
40590
|
};
|
|
39502
40591
|
}
|
|
39503
40592
|
return servicesValue;
|
|
@@ -39521,10 +40610,13 @@ class MobileGateway {
|
|
|
39521
40610
|
if (this.notificationCleanup !== undefined)
|
|
39522
40611
|
return;
|
|
39523
40612
|
const [serverEnd, clientEnd] = memoryLinkPair();
|
|
39524
|
-
this.chord.accept(serverEnd, {
|
|
39525
|
-
|
|
39526
|
-
|
|
39527
|
-
|
|
40613
|
+
this.chord.accept(serverEnd, {
|
|
40614
|
+
principal: { kind: "local", connectionId: "mobile-notification-hub" },
|
|
40615
|
+
kind: "client"
|
|
40616
|
+
});
|
|
40617
|
+
const host = createClientHost(clientEnd);
|
|
40618
|
+
const directory = host.hold(NekoSessionDirectory, {});
|
|
40619
|
+
const workspaces = host.hold(NekoWorkspaces, {});
|
|
39528
40620
|
const last = { directory: -1, workspaces: -1 };
|
|
39529
40621
|
const unsubscribe = [
|
|
39530
40622
|
directory.state.subscribe(() => {
|
|
@@ -39808,6 +40900,7 @@ var RELAY_PORT_RETRY_MS = 1e4, RELAY_PORT_RETRY_INTERVAL_MS = 1000, WSL_INTEROP_
|
|
|
39808
40900
|
var init_listener = __esm(() => {
|
|
39809
40901
|
init_contract();
|
|
39810
40902
|
init_link();
|
|
40903
|
+
init_principal();
|
|
39811
40904
|
init_adapter();
|
|
39812
40905
|
init_auth();
|
|
39813
40906
|
init_certificate();
|
|
@@ -39942,15 +41035,7 @@ var init_image_uploads = __esm(() => {
|
|
|
39942
41035
|
});
|
|
39943
41036
|
|
|
39944
41037
|
// packages/gateway/src/subagent-host.ts
|
|
39945
|
-
import {
|
|
39946
|
-
ChordError as ChordError3,
|
|
39947
|
-
cell,
|
|
39948
|
-
hostPrincipal,
|
|
39949
|
-
listener,
|
|
39950
|
-
local,
|
|
39951
|
-
opaque,
|
|
39952
|
-
resource
|
|
39953
|
-
} from "@earendil-works/chord";
|
|
41038
|
+
import { ChordError as ChordError3, local, opaque } from "@earendil-works/chord";
|
|
39954
41039
|
import { UsageDoc as UsageDoc3 } from "@earendil-works/pi-durable";
|
|
39955
41040
|
import { Type as Type8 } from "typebox";
|
|
39956
41041
|
async function openReader(runtime, childId, parentId, context, through, historyUsage) {
|
|
@@ -40077,31 +41162,27 @@ async function openReader(runtime, childId, parentId, context, through, historyU
|
|
|
40077
41162
|
}
|
|
40078
41163
|
function installSubagentHost(host, runtime, context) {
|
|
40079
41164
|
host.serve(ChildProjection, (input, _principal, self2) => {
|
|
40080
|
-
const
|
|
40081
|
-
const
|
|
40082
|
-
|
|
40083
|
-
|
|
40084
|
-
|
|
40085
|
-
|
|
40086
|
-
|
|
40087
|
-
|
|
40088
|
-
|
|
40089
|
-
|
|
40090
|
-
|
|
40091
|
-
|
|
40092
|
-
|
|
40093
|
-
|
|
40094
|
-
|
|
40095
|
-
|
|
40096
|
-
|
|
40097
|
-
|
|
40098
|
-
close: (r) => r.close()
|
|
40099
|
-
})
|
|
41165
|
+
const revision = self2.state("revision", Type8.Integer(), 0);
|
|
41166
|
+
const reader = self2.resource("reader", {
|
|
41167
|
+
version: 1,
|
|
41168
|
+
async open() {
|
|
41169
|
+
const entry = (await listDelegations(runtime.harness, input.parentId, context)).find((d) => input.agentId.startsWith("round-") ? d.taskId === Number(input.agentId.slice(6)) : (d.agentId ?? agentIdFor(d.childId)) === input.agentId);
|
|
41170
|
+
if (!entry)
|
|
41171
|
+
throw new ChordError3("gone", "Subagent unavailable");
|
|
41172
|
+
const r = await openReader(runtime, entry.childId, input.parentId, context, entry.historyThrough, entry.historyUsage);
|
|
41173
|
+
try {
|
|
41174
|
+
const initial = await r.refresh();
|
|
41175
|
+
await host.root((ctx) => revision.replace(ctx, initial));
|
|
41176
|
+
return r;
|
|
41177
|
+
} catch (error) {
|
|
41178
|
+
r.close();
|
|
41179
|
+
throw error;
|
|
41180
|
+
}
|
|
41181
|
+
},
|
|
41182
|
+
close: (r) => r.close()
|
|
40100
41183
|
});
|
|
40101
|
-
self2.
|
|
40102
|
-
|
|
40103
|
-
revision.replace(ctx, await reader.value.refresh());
|
|
40104
|
-
})
|
|
41184
|
+
self2.on("view", reader, (r) => r, async (_event, ctx) => {
|
|
41185
|
+
revision.replace(ctx, await reader.value.refresh());
|
|
40105
41186
|
});
|
|
40106
41187
|
return { revision, reader: self2.expose(reader) };
|
|
40107
41188
|
});
|
|
@@ -40117,23 +41198,24 @@ function installSubagentHost(host, runtime, context) {
|
|
|
40117
41198
|
throw new ChordError3("forbidden", "Subagent does not belong to this session");
|
|
40118
41199
|
return entry;
|
|
40119
41200
|
};
|
|
40120
|
-
const
|
|
40121
|
-
const shared = self2.get(ChildProjection, {
|
|
40122
|
-
|
|
40123
|
-
|
|
40124
|
-
|
|
40125
|
-
|
|
40126
|
-
|
|
40127
|
-
|
|
40128
|
-
|
|
40129
|
-
|
|
40130
|
-
|
|
40131
|
-
authorizedRevision.replace(ctx,
|
|
40132
|
-
|
|
40133
|
-
|
|
41201
|
+
const target = self2.resource("target", { version: 1, deps: [owner], open: check });
|
|
41202
|
+
const shared = self2.get(ChildProjection, {
|
|
41203
|
+
parentId: Number(input.sessionId),
|
|
41204
|
+
agentId: input.agentId ?? `round-${input.taskId}`
|
|
41205
|
+
});
|
|
41206
|
+
const authorizedRevision = self2.state("authorizedRevision", Type8.Integer(), 0);
|
|
41207
|
+
self2.on("authorizedView", shared.revision, async (value, ctx) => {
|
|
41208
|
+
try {
|
|
41209
|
+
target.value;
|
|
41210
|
+
await check();
|
|
41211
|
+
if (value)
|
|
41212
|
+
authorizedRevision.replace(ctx, value);
|
|
41213
|
+
} catch {
|
|
41214
|
+
authorizedRevision.replace(ctx, 0);
|
|
41215
|
+
}
|
|
40134
41216
|
});
|
|
40135
41217
|
return {
|
|
40136
|
-
|
|
41218
|
+
variant: "ready",
|
|
40137
41219
|
revision: authorizedRevision,
|
|
40138
41220
|
chunk: async (request) => {
|
|
40139
41221
|
await check();
|
|
@@ -40193,12 +41275,8 @@ import { open as open3 } from "fs/promises";
|
|
|
40193
41275
|
import { basename as basename5, extname } from "path";
|
|
40194
41276
|
import {
|
|
40195
41277
|
ChordError as ChordError4,
|
|
40196
|
-
cell as cell2,
|
|
40197
|
-
hostPrincipal as hostPrincipal2,
|
|
40198
|
-
listener as listener2,
|
|
40199
41278
|
local as local2,
|
|
40200
|
-
opaque as opaque2
|
|
40201
|
-
resource as resource2
|
|
41279
|
+
opaque as opaque2
|
|
40202
41280
|
} from "@earendil-works/chord";
|
|
40203
41281
|
import { withAbortSignal as withAbortSignal4 } from "@earendil-works/chord/context";
|
|
40204
41282
|
import { UsageDoc as UsageDoc4 } from "@earendil-works/pi-durable";
|
|
@@ -40292,65 +41370,61 @@ async function openProjection(runtime, session, context) {
|
|
|
40292
41370
|
function serveSessions(host, runtime, context) {
|
|
40293
41371
|
installSubagentHost(host, runtime, context);
|
|
40294
41372
|
host.serve(Owner, (_input, _principal, self2) => {
|
|
40295
|
-
const
|
|
40296
|
-
|
|
40297
|
-
|
|
40298
|
-
|
|
40299
|
-
|
|
40300
|
-
|
|
40301
|
-
}
|
|
40302
|
-
})
|
|
41373
|
+
const owner = self2.resource("owner", {
|
|
41374
|
+
version: 1,
|
|
41375
|
+
open: () => ({ active: null }),
|
|
41376
|
+
close: (value) => {
|
|
41377
|
+
value.active = null;
|
|
41378
|
+
}
|
|
40303
41379
|
});
|
|
40304
41380
|
return { owner: self2.expose(owner) };
|
|
40305
41381
|
});
|
|
40306
41382
|
host.serve(Projection, (input, _principal, self2) => {
|
|
40307
|
-
const
|
|
40308
|
-
const
|
|
40309
|
-
|
|
40310
|
-
|
|
40311
|
-
|
|
40312
|
-
|
|
40313
|
-
|
|
40314
|
-
|
|
40315
|
-
|
|
40316
|
-
|
|
40317
|
-
|
|
40318
|
-
|
|
40319
|
-
|
|
40320
|
-
|
|
40321
|
-
|
|
40322
|
-
|
|
40323
|
-
|
|
40324
|
-
|
|
40325
|
-
close: (value) => value.close()
|
|
40326
|
-
})
|
|
41383
|
+
const view = self2.state("view", ChatViewSchema, emptyView);
|
|
41384
|
+
const projection = self2.resource("projection", {
|
|
41385
|
+
version: 1,
|
|
41386
|
+
async open({ signal }) {
|
|
41387
|
+
const session = await runtime.getConversation(Number(input.id));
|
|
41388
|
+
if (!session)
|
|
41389
|
+
throw new ChordError4("gone", "Session unavailable");
|
|
41390
|
+
const opened = await openProjection(runtime, session, context);
|
|
41391
|
+
try {
|
|
41392
|
+
const initial = await opened.project();
|
|
41393
|
+
await host.root((ctx) => view.replace(ctx, initial), { signal });
|
|
41394
|
+
return opened;
|
|
41395
|
+
} catch (error) {
|
|
41396
|
+
opened.close();
|
|
41397
|
+
throw error;
|
|
41398
|
+
}
|
|
41399
|
+
},
|
|
41400
|
+
close: (value) => value.close()
|
|
40327
41401
|
});
|
|
40328
|
-
self2.
|
|
40329
|
-
view
|
|
40330
|
-
view.replace(ctx, await projection.value.project());
|
|
40331
|
-
})
|
|
41402
|
+
self2.on("view", projection, (value) => value, async (_event, ctx) => {
|
|
41403
|
+
view.replace(ctx, await projection.value.project());
|
|
40332
41404
|
});
|
|
40333
41405
|
return { view, reader: self2.expose(projection) };
|
|
40334
41406
|
});
|
|
40335
41407
|
host.serve(NekoChat, (input, principal, self2) => {
|
|
40336
41408
|
const owner = self2.get(Owner, {}, { principal });
|
|
40337
|
-
const shared = self2.get(Projection, { id: input.sessionId }
|
|
40338
|
-
const
|
|
40339
|
-
|
|
40340
|
-
|
|
40341
|
-
|
|
40342
|
-
|
|
40343
|
-
|
|
40344
|
-
|
|
40345
|
-
|
|
40346
|
-
|
|
40347
|
-
|
|
40348
|
-
|
|
40349
|
-
|
|
40350
|
-
|
|
40351
|
-
|
|
40352
|
-
|
|
40353
|
-
|
|
41409
|
+
const shared = self2.get(Projection, { id: input.sessionId });
|
|
41410
|
+
const uploads = self2.resource("uploads", {
|
|
41411
|
+
version: 1,
|
|
41412
|
+
open: () => new ImageUploads,
|
|
41413
|
+
close: (value) => value.close()
|
|
41414
|
+
});
|
|
41415
|
+
const projection = self2.resource("projection", {
|
|
41416
|
+
version: 1,
|
|
41417
|
+
deps: [owner],
|
|
41418
|
+
async open() {
|
|
41419
|
+
if (owner.owner?.active?.attachmentId !== input.attachmentId || owner.owner.active.sessionId !== input.sessionId)
|
|
41420
|
+
throw new ChordError4("forbidden", "Attachment does not belong to this connection");
|
|
41421
|
+
const session = await runtime.getConversation(Number(input.sessionId));
|
|
41422
|
+
if (!session)
|
|
41423
|
+
throw new ChordError4("gone", "Session unavailable");
|
|
41424
|
+
return {
|
|
41425
|
+
session
|
|
41426
|
+
};
|
|
41427
|
+
}
|
|
40354
41428
|
});
|
|
40355
41429
|
const safeUpload = (action) => {
|
|
40356
41430
|
try {
|
|
@@ -40360,7 +41434,7 @@ function serveSessions(host, runtime, context) {
|
|
|
40360
41434
|
}
|
|
40361
41435
|
};
|
|
40362
41436
|
const behavior = {
|
|
40363
|
-
|
|
41437
|
+
variant: "ready",
|
|
40364
41438
|
view: shared.view,
|
|
40365
41439
|
draftImage: async ({ id, offset }) => safeUpload(() => uploads.value.read(id, offset)),
|
|
40366
41440
|
importImage: async (path, ctx) => {
|
|
@@ -40467,6 +41541,8 @@ function serveSessions(host, runtime, context) {
|
|
|
40467
41541
|
await projection.value.session.abort();
|
|
40468
41542
|
return null;
|
|
40469
41543
|
},
|
|
41544
|
+
withdraw: ({ id }) => projection.value.session.withdraw(id),
|
|
41545
|
+
steer: ({ id }) => projection.value.session.steer(id),
|
|
40470
41546
|
skills: () => runtime.listSkills(projection.value.session.id),
|
|
40471
41547
|
jobOutput: async ({ jobId, since }) => {
|
|
40472
41548
|
try {
|
|
@@ -40544,6 +41620,7 @@ var init_session_host = __esm(() => {
|
|
|
40544
41620
|
agent: { model: null, thinkingLevel: null },
|
|
40545
41621
|
cwd: "",
|
|
40546
41622
|
busy: false,
|
|
41623
|
+
queued: [],
|
|
40547
41624
|
status: null,
|
|
40548
41625
|
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 },
|
|
40549
41626
|
items: [],
|
|
@@ -41224,20 +42301,14 @@ var init_workspaces = __esm(() => {
|
|
|
41224
42301
|
|
|
41225
42302
|
// packages/gateway/src/server-services.ts
|
|
41226
42303
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
41227
|
-
import {
|
|
41228
|
-
ChordError as ChordError5,
|
|
41229
|
-
cell as cell3,
|
|
41230
|
-
hostPrincipal as hostPrincipal3,
|
|
41231
|
-
listener as listener3,
|
|
41232
|
-
local as local3,
|
|
41233
|
-
method as method3
|
|
41234
|
-
} from "@earendil-works/chord";
|
|
42304
|
+
import { ChordError as ChordError5, local as local3, method as method2 } from "@earendil-works/chord";
|
|
41235
42305
|
import { Type as Type10 } from "typebox";
|
|
41236
42306
|
async function createNekoServerServices(options) {
|
|
41237
42307
|
const { runtime, context, host } = options;
|
|
41238
42308
|
const updates = new UpdateChecker(options.updates);
|
|
41239
42309
|
await updates.local();
|
|
41240
42310
|
const updateListeners = new Set;
|
|
42311
|
+
const info = (value) => ({ ...value, serverId: options.serverId });
|
|
41241
42312
|
let published = JSON.stringify(updates.state), closed = false;
|
|
41242
42313
|
const publishUpdates = () => {
|
|
41243
42314
|
if (closed)
|
|
@@ -41250,22 +42321,20 @@ async function createNekoServerServices(options) {
|
|
|
41250
42321
|
listener(updates.state);
|
|
41251
42322
|
};
|
|
41252
42323
|
host.serve(NekoApplicationInfo, (_input, _principal, self2) => {
|
|
41253
|
-
const
|
|
41254
|
-
self2.
|
|
41255
|
-
|
|
41256
|
-
|
|
41257
|
-
|
|
41258
|
-
|
|
41259
|
-
|
|
41260
|
-
}, (value, ctx) => state.info.replace(ctx, value))
|
|
41261
|
-
});
|
|
42324
|
+
const current = self2.state("info", ApplicationInfoSchema, info(updates.state));
|
|
42325
|
+
self2.on("updates", {
|
|
42326
|
+
subscribe: (fn) => {
|
|
42327
|
+
updateListeners.add(fn);
|
|
42328
|
+
return () => updateListeners.delete(fn);
|
|
42329
|
+
}
|
|
42330
|
+
}, (value, ctx) => current.replace(ctx, info(value)));
|
|
41262
42331
|
return {
|
|
41263
|
-
|
|
41264
|
-
info:
|
|
42332
|
+
variant: "ready",
|
|
42333
|
+
info: current,
|
|
41265
42334
|
check: async () => {
|
|
41266
42335
|
await updates.check(true);
|
|
41267
42336
|
publishUpdates();
|
|
41268
|
-
return updates.state;
|
|
42337
|
+
return info(updates.state);
|
|
41269
42338
|
}
|
|
41270
42339
|
};
|
|
41271
42340
|
});
|
|
@@ -41309,7 +42378,13 @@ async function createNekoServerServices(options) {
|
|
|
41309
42378
|
status: info.status
|
|
41310
42379
|
}));
|
|
41311
42380
|
const readStatuses = async () => Object.fromEntries(await Promise.all(CREDENTIAL_PROVIDERS.map(async (spec) => [spec.id, await runtime.credentialStatus(spec.id)])));
|
|
41312
|
-
const [sessions, workspaces, statuses] = await Promise.all([
|
|
42381
|
+
const [sessions, workspaces, statuses, search, contextSettings0] = await Promise.all([
|
|
42382
|
+
list(),
|
|
42383
|
+
runtime.listWorkspaces(),
|
|
42384
|
+
readStatuses(),
|
|
42385
|
+
runtime.searchSettings(),
|
|
42386
|
+
runtime.contextSettings()
|
|
42387
|
+
]);
|
|
41313
42388
|
let mutationTail = Promise.resolve();
|
|
41314
42389
|
const serialize = (fn) => {
|
|
41315
42390
|
const result = mutationTail.catch(() => {}).then(fn);
|
|
@@ -41317,43 +42392,64 @@ async function createNekoServerServices(options) {
|
|
|
41317
42392
|
return result;
|
|
41318
42393
|
};
|
|
41319
42394
|
host.serve(ServerState, (_input, _principal, self2) => {
|
|
41320
|
-
const
|
|
41321
|
-
|
|
41322
|
-
|
|
41323
|
-
|
|
42395
|
+
const directory = self2.state("directory", DirectoryStateSchema, { revision: 1, sessions });
|
|
42396
|
+
const workspaceState = self2.state("workspaces", WorkspaceStateSchema, { revision: 1, workspaces });
|
|
42397
|
+
const credentials = self2.state("credentials", CredentialStateSchema, { revision: 1, statuses });
|
|
42398
|
+
const searchSettings = self2.state("searchSettings", SearchSettingsStateSchema, { revision: 1, ...search });
|
|
42399
|
+
const contextSettings = self2.state("contextSettings", ContextSettingsStateSchema, {
|
|
42400
|
+
revision: 1,
|
|
42401
|
+
...contextSettings0
|
|
41324
42402
|
});
|
|
41325
|
-
|
|
41326
|
-
|
|
41327
|
-
|
|
41328
|
-
|
|
41329
|
-
|
|
41330
|
-
|
|
41331
|
-
|
|
41332
|
-
|
|
41333
|
-
|
|
41334
|
-
|
|
41335
|
-
|
|
41336
|
-
|
|
41337
|
-
|
|
41338
|
-
|
|
41339
|
-
|
|
41340
|
-
|
|
42403
|
+
const refreshContextSettings = async (_input, ctx) => {
|
|
42404
|
+
const next = await runtime.contextSettings();
|
|
42405
|
+
const { revision, ...current } = contextSettings.value;
|
|
42406
|
+
if (JSON.stringify(next) !== JSON.stringify(current))
|
|
42407
|
+
contextSettings.replace(ctx, { revision: revision + 1, ...next });
|
|
42408
|
+
return null;
|
|
42409
|
+
};
|
|
42410
|
+
const refreshSearchSettings = async (_input, ctx) => {
|
|
42411
|
+
const next = await runtime.searchSettings();
|
|
42412
|
+
const current = searchSettings.value;
|
|
42413
|
+
if (next.selected !== current.selected)
|
|
42414
|
+
searchSettings.replace(ctx, { revision: current.revision + 1, ...next });
|
|
42415
|
+
return null;
|
|
42416
|
+
};
|
|
42417
|
+
self2.on("directory", { subscribe: runtime.harness.subscribeCommits.bind(runtime.harness) }, async (event, ctx) => {
|
|
42418
|
+
if (!event.changes.some((change) => change.type === "conversation" || change.type === "document" && [
|
|
42419
|
+
"app.neko.session-metadata",
|
|
42420
|
+
"app.neko.activity",
|
|
42421
|
+
"app.neko.conversation",
|
|
42422
|
+
"app.neko.workspaces",
|
|
42423
|
+
"pi.agent"
|
|
42424
|
+
].includes(change.record.kind)))
|
|
42425
|
+
return;
|
|
42426
|
+
await serialize(async () => {
|
|
42427
|
+
const [items, roots] = await Promise.all([list(), runtime.listWorkspaces()]);
|
|
42428
|
+
directory.replace(ctx, { revision: directory.value.revision + 1, sessions: items });
|
|
42429
|
+
workspaceState.replace(ctx, { revision: workspaceState.value.revision + 1, workspaces: roots });
|
|
42430
|
+
});
|
|
41341
42431
|
});
|
|
41342
42432
|
return {
|
|
41343
|
-
|
|
42433
|
+
directory,
|
|
42434
|
+
workspaces: workspaceState,
|
|
42435
|
+
credentials,
|
|
42436
|
+
searchSettings,
|
|
42437
|
+
contextSettings,
|
|
41344
42438
|
async refreshCredentials(_input, ctx) {
|
|
41345
|
-
|
|
41346
|
-
revision:
|
|
42439
|
+
credentials.replace(ctx, {
|
|
42440
|
+
revision: credentials.value.revision + 1,
|
|
41347
42441
|
statuses: await readStatuses()
|
|
41348
42442
|
});
|
|
41349
42443
|
return null;
|
|
41350
|
-
}
|
|
42444
|
+
},
|
|
42445
|
+
refreshSearchSettings,
|
|
42446
|
+
refreshContextSettings
|
|
41351
42447
|
};
|
|
41352
|
-
}, {
|
|
41353
|
-
const shared = (self2) => self2.get(ServerState, {}
|
|
41354
|
-
host.serve(NekoSessionDirectory, (_input, _principal, self2) => ({
|
|
42448
|
+
}, { lifetime: "host" });
|
|
42449
|
+
const shared = (self2) => self2.get(ServerState, {});
|
|
42450
|
+
host.serve(NekoSessionDirectory, (_input, _principal, self2) => ({ variant: "ready", state: shared(self2).directory }));
|
|
41355
42451
|
host.serve(NekoWorkspaces, (_input, _principal, self2) => ({
|
|
41356
|
-
|
|
42452
|
+
variant: "ready",
|
|
41357
42453
|
state: shared(self2).workspaces,
|
|
41358
42454
|
browse: (input) => browseWorkspaces(input),
|
|
41359
42455
|
add: (input) => serialize(() => runtime.addWorkspace(input.path))
|
|
@@ -41361,7 +42457,7 @@ async function createNekoServerServices(options) {
|
|
|
41361
42457
|
host.serve(NekoCredentials, (_input, _principal, self2) => {
|
|
41362
42458
|
const hub = shared(self2);
|
|
41363
42459
|
return {
|
|
41364
|
-
|
|
42460
|
+
variant: "ready",
|
|
41365
42461
|
state: hub.credentials,
|
|
41366
42462
|
save: async (input, ctx) => {
|
|
41367
42463
|
await runtime.saveCredential(input.provider, input.key);
|
|
@@ -41373,8 +42469,34 @@ async function createNekoServerServices(options) {
|
|
|
41373
42469
|
}
|
|
41374
42470
|
};
|
|
41375
42471
|
});
|
|
42472
|
+
host.serve(NekoSearchSettings, (_input, _principal, self2) => {
|
|
42473
|
+
const hub = shared(self2);
|
|
42474
|
+
return {
|
|
42475
|
+
variant: "ready",
|
|
42476
|
+
state: hub.searchSettings,
|
|
42477
|
+
set: (input, ctx) => serialize(async () => {
|
|
42478
|
+
await runtime.setSearchProvider(input.provider);
|
|
42479
|
+
return hub.refreshSearchSettings(null, ctx);
|
|
42480
|
+
})
|
|
42481
|
+
};
|
|
42482
|
+
});
|
|
42483
|
+
host.serve(NekoContextSettings, (_input, _principal, self2) => {
|
|
42484
|
+
const hub = shared(self2);
|
|
42485
|
+
return {
|
|
42486
|
+
variant: "ready",
|
|
42487
|
+
state: hub.contextSettings,
|
|
42488
|
+
set: (input, ctx) => serialize(async () => {
|
|
42489
|
+
await runtime.setContextSettings(input);
|
|
42490
|
+
return hub.refreshContextSettings(null, ctx);
|
|
42491
|
+
}),
|
|
42492
|
+
reset: (_input, ctx) => serialize(async () => {
|
|
42493
|
+
await runtime.resetContextSettings();
|
|
42494
|
+
return hub.refreshContextSettings(null, ctx);
|
|
42495
|
+
})
|
|
42496
|
+
};
|
|
42497
|
+
});
|
|
41376
42498
|
host.serve(NekoModels, () => ({
|
|
41377
|
-
|
|
42499
|
+
variant: "ready",
|
|
41378
42500
|
list: async () => [...runtime.listModels()].map((value) => ({
|
|
41379
42501
|
...value,
|
|
41380
42502
|
input: [...value.input],
|
|
@@ -41382,7 +42504,7 @@ async function createNekoServerServices(options) {
|
|
|
41382
42504
|
}))
|
|
41383
42505
|
}));
|
|
41384
42506
|
host.serve(NekoWorkspaceFiles, () => ({
|
|
41385
|
-
|
|
42507
|
+
variant: "ready",
|
|
41386
42508
|
search: async (input, ctx) => {
|
|
41387
42509
|
const roots = await runtime.listWorkspaces();
|
|
41388
42510
|
if (!roots.some((root) => root.path === input.workspaceRoot))
|
|
@@ -41400,7 +42522,7 @@ async function createNekoServerServices(options) {
|
|
|
41400
42522
|
return found;
|
|
41401
42523
|
};
|
|
41402
42524
|
return {
|
|
41403
|
-
|
|
42525
|
+
variant: "ready",
|
|
41404
42526
|
create: (input) => serialize(async () => summary((await runtime.createConversation(input)).id)),
|
|
41405
42527
|
fork: (id) => serialize(async () => summary((await runtime.forkConversation(toConversationId(id))).id)),
|
|
41406
42528
|
archive: async (id) => {
|
|
@@ -41423,7 +42545,7 @@ async function createNekoServerServices(options) {
|
|
|
41423
42545
|
};
|
|
41424
42546
|
});
|
|
41425
42547
|
host.serve(NekoSkillMarket, () => ({
|
|
41426
|
-
|
|
42548
|
+
variant: "ready",
|
|
41427
42549
|
list: (input) => market.list(input.query).then((skills) => Promise.all(skills.map((skill) => withInstallState(skill, input.workspaceRoot)))).catch(toRemote),
|
|
41428
42550
|
detail: (input) => market.detail(input.name).then((skill) => withInstallState(skill, input.workspaceRoot)).catch(toRemote),
|
|
41429
42551
|
preview: (input) => market.preview(input.name).then((value) => ({ ...value, files: [...value.files] })).catch(toRemote),
|
|
@@ -41465,7 +42587,11 @@ var init_server_services = __esm(() => {
|
|
|
41465
42587
|
directory: DirectoryStateSchema,
|
|
41466
42588
|
workspaces: WorkspaceStateSchema,
|
|
41467
42589
|
credentials: CredentialStateSchema,
|
|
41468
|
-
|
|
42590
|
+
searchSettings: SearchSettingsStateSchema,
|
|
42591
|
+
contextSettings: ContextSettingsStateSchema,
|
|
42592
|
+
refreshCredentials: method2(Type10.Null(), Type10.Null()),
|
|
42593
|
+
refreshSearchSettings: method2(Type10.Null(), Type10.Null()),
|
|
42594
|
+
refreshContextSettings: method2(Type10.Null(), Type10.Null())
|
|
41469
42595
|
}
|
|
41470
42596
|
});
|
|
41471
42597
|
});
|
|
@@ -41474,7 +42600,7 @@ var init_server_services = __esm(() => {
|
|
|
41474
42600
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
41475
42601
|
import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
|
|
41476
42602
|
import { basename as basename6, dirname as dirname10, join as join14 } from "path";
|
|
41477
|
-
import { createHost as
|
|
42603
|
+
import { createHost as createHost2 } from "@earendil-works/chord";
|
|
41478
42604
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT4 } from "@earendil-works/chord/context";
|
|
41479
42605
|
async function startGateway(options = {}) {
|
|
41480
42606
|
const context = options.context ?? BACKGROUND_CONTEXT4;
|
|
@@ -41483,6 +42609,7 @@ async function startGateway(options = {}) {
|
|
|
41483
42609
|
const storagePath = options.storagePath ?? fileConfig.storagePath ?? DEFAULT_STORAGE_PATH;
|
|
41484
42610
|
const host = options.host ?? fileConfig.gateway?.host ?? DEFAULT_HOST;
|
|
41485
42611
|
const port = options.port !== undefined ? options.port : fileConfig.gateway?.port ?? (process.platform === "win32" ? 0 : DEFAULT_PORT);
|
|
42612
|
+
const searchProvider = options.searchProvider ?? fileConfig.webSearch?.provider;
|
|
41486
42613
|
const marketUrl = options.marketUrl ?? fileConfig.skills?.marketUrl ?? DEFAULT_SKILL_MARKET_URL;
|
|
41487
42614
|
const nekoDir = dirname10(storagePath);
|
|
41488
42615
|
const serverId = options.serverId ?? await loadOrCreateServerId(nekoDir);
|
|
@@ -41494,6 +42621,8 @@ async function startGateway(options = {}) {
|
|
|
41494
42621
|
...options.storage !== "memory" ? { storagePath } : {},
|
|
41495
42622
|
...options.credentialsPath !== undefined ? { credentialsPath: options.credentialsPath } : {},
|
|
41496
42623
|
...options.apiKey !== undefined ? { apiKey: options.apiKey } : {},
|
|
42624
|
+
...searchProvider !== undefined ? { searchProvider } : {},
|
|
42625
|
+
...fileConfig.context !== undefined ? { contextSettings: fileConfig.context } : {},
|
|
41497
42626
|
model,
|
|
41498
42627
|
thinkingLevel: options.thinkingLevel ?? fileConfig.thinkingLevel ?? "high",
|
|
41499
42628
|
...options.cwd !== undefined ? { cwd: options.cwd } : {},
|
|
@@ -41524,8 +42653,9 @@ async function startGateway(options = {}) {
|
|
|
41524
42653
|
}
|
|
41525
42654
|
async function bindGateway(runtime, options) {
|
|
41526
42655
|
const { context, host, port, socketPath, serverId, nekoDir } = options;
|
|
41527
|
-
const chord =
|
|
41528
|
-
name:
|
|
42656
|
+
const chord = createHost2({
|
|
42657
|
+
name: GATEWAY_HOST,
|
|
42658
|
+
self: HOST_PRINCIPAL,
|
|
41529
42659
|
dev: true,
|
|
41530
42660
|
onError: (error) => options.onError?.(error instanceof Error ? error : new Error(String(error))),
|
|
41531
42661
|
principalKey: gatewayPrincipalKey
|
|
@@ -41534,6 +42664,7 @@ async function bindGateway(runtime, options) {
|
|
|
41534
42664
|
host: chord,
|
|
41535
42665
|
runtime,
|
|
41536
42666
|
context,
|
|
42667
|
+
serverId,
|
|
41537
42668
|
marketUrl: options.marketUrl,
|
|
41538
42669
|
...options.updates ? { updates: options.updates } : {},
|
|
41539
42670
|
...options.updateIntervalMs ? { updateIntervalMs: options.updateIntervalMs } : {},
|
|
@@ -41549,7 +42680,10 @@ async function bindGateway(runtime, options) {
|
|
|
41549
42680
|
}
|
|
41550
42681
|
try {
|
|
41551
42682
|
for (const listener of listeners)
|
|
41552
|
-
await listener.start((link) => chord.accept(link, {
|
|
42683
|
+
await listener.start((link) => chord.accept(link, {
|
|
42684
|
+
principal: { kind: "local", connectionId: randomUUID5() },
|
|
42685
|
+
onProtocolMismatch: ({ peer }) => options.onError?.(new Error(`Rejected a client on Chord protocol ${peer ?? "unknown"}; restart or update it.`))
|
|
42686
|
+
}));
|
|
41553
42687
|
} catch (error) {
|
|
41554
42688
|
await Promise.allSettled(listeners.map((listener) => listener.close()));
|
|
41555
42689
|
await chord.close();
|
|
@@ -41612,16 +42746,6 @@ async function bindGateway(runtime, options) {
|
|
|
41612
42746
|
}).catch((error) => options.onError?.(error instanceof Error ? error : new Error(String(error))));
|
|
41613
42747
|
return gateway;
|
|
41614
42748
|
}
|
|
41615
|
-
function gatewayPrincipalKey(principal) {
|
|
41616
|
-
if (typeof principal === "object" && principal !== null) {
|
|
41617
|
-
const record = principal;
|
|
41618
|
-
if (record.kind === "mobile" && typeof record.deviceId === "string" && typeof record.grantRevision === "number") {
|
|
41619
|
-
const connectionId = typeof record.connectionId === "string" ? record.connectionId : "";
|
|
41620
|
-
return `mobile:${record.deviceId}:${record.grantRevision}:${connectionId}`;
|
|
41621
|
-
}
|
|
41622
|
-
}
|
|
41623
|
-
return JSON.stringify(principal) ?? "unknown";
|
|
41624
|
-
}
|
|
41625
42749
|
function toStartupError(error, info) {
|
|
41626
42750
|
const message = error instanceof Error ? error.message : String(error);
|
|
41627
42751
|
const existingServerId = info.socketPath === null ? undefined : serverIdFromSocketPath(info.socketPath);
|
|
@@ -41661,9 +42785,11 @@ async function loadOrCreateServerId(nekoDir) {
|
|
|
41661
42785
|
var DEFAULT_HOST = "127.0.0.1", DEFAULT_PORT = 8321, SERVER_ID_FILENAME = "gateway-server-id";
|
|
41662
42786
|
var init_gateway = __esm(() => {
|
|
41663
42787
|
init_src();
|
|
42788
|
+
init_contract();
|
|
41664
42789
|
init_admin();
|
|
41665
42790
|
init_listener();
|
|
41666
42791
|
init_port_file();
|
|
42792
|
+
init_principal();
|
|
41667
42793
|
init_server_services();
|
|
41668
42794
|
init_unix_link();
|
|
41669
42795
|
init_ws_listener();
|
|
@@ -41768,6 +42894,7 @@ var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5000;
|
|
|
41768
42894
|
// packages/gateway/src/index.ts
|
|
41769
42895
|
var init_src2 = __esm(() => {
|
|
41770
42896
|
init_client();
|
|
42897
|
+
init_client_session();
|
|
41771
42898
|
init_contract();
|
|
41772
42899
|
init_gateway();
|
|
41773
42900
|
init_link();
|
|
@@ -41838,7 +42965,7 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
41838
42965
|
const timeoutMs = plan.spawnTimeoutMs ?? 20000;
|
|
41839
42966
|
if (plan.platform === "win32") {
|
|
41840
42967
|
const timeoutAt = Date.now() + timeoutMs;
|
|
41841
|
-
const serverId = plan.fileServerId ?? await
|
|
42968
|
+
const serverId = plan.fileServerId ?? await waitForValue(() => readServerId(join15(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
|
|
41842
42969
|
let lastError = new Error("Gateway port file unavailable");
|
|
41843
42970
|
while (Date.now() < timeoutAt) {
|
|
41844
42971
|
if (child.exitCode !== null || child.signalCode !== null || spawnError() !== undefined) {
|
|
@@ -41875,10 +43002,10 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
41875
43002
|
}
|
|
41876
43003
|
let socketPath = plan.socketPath;
|
|
41877
43004
|
if (socketPath === undefined) {
|
|
41878
|
-
const generated = await
|
|
43005
|
+
const generated = await waitForValue(() => readServerId(join15(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
|
|
41879
43006
|
socketPath = join15(plan.nekoDir, `${generated}.sock`);
|
|
41880
43007
|
}
|
|
41881
|
-
await
|
|
43008
|
+
await waitForValue(() => existsSync3(socketPath) ? true : undefined, `gateway ready (socket: ${socketPath})`, timeoutMs, child, outputTail, spawnError);
|
|
41882
43009
|
const serverId = plan.fileServerId ?? serverIdFromSocketPath2(socketPath) ?? readServerId(join15(plan.nekoDir, SERVER_ID_FILENAME2));
|
|
41883
43010
|
if (serverId === undefined) {
|
|
41884
43011
|
throw new Error(`Cannot determine gateway serverId (socket: ${socketPath})`);
|
|
@@ -41975,7 +43102,7 @@ function captureOutput(child) {
|
|
|
41975
43102
|
return () => [stdout.trimEnd(), stderr.trimEnd()].filter((part) => part !== "").join(`
|
|
41976
43103
|
`);
|
|
41977
43104
|
}
|
|
41978
|
-
async function
|
|
43105
|
+
async function waitForValue(poll, what, timeoutMs, child, outputTail, spawnError) {
|
|
41979
43106
|
const start = Date.now();
|
|
41980
43107
|
for (;; ) {
|
|
41981
43108
|
const error = spawnError();
|
|
@@ -42010,15 +43137,14 @@ var init_gateway2 = __esm(() => {
|
|
|
42010
43137
|
});
|
|
42011
43138
|
|
|
42012
43139
|
// packages/tui/src/help-ui.ts
|
|
42013
|
-
import { getKeybindings, matchesKey as
|
|
43140
|
+
import { getKeybindings, matchesKey as matchesKey8, TUI_KEYBINDINGS, wrapTextWithAnsi as wrapTextWithAnsi4 } from "@earendil-works/pi-tui";
|
|
42014
43141
|
function helpLines(platform = process.platform) {
|
|
42015
43142
|
const rows = [];
|
|
42016
43143
|
const section = (title, hints) => {
|
|
42017
43144
|
rows.push(theme.accent(title), ...hints.map(([key, description]) => shortcutHints([key, description])), "");
|
|
42018
43145
|
};
|
|
42019
43146
|
section("Chat", [
|
|
42020
|
-
["Enter", "Send;
|
|
42021
|
-
["Alt+Enter", "Queue while running"],
|
|
43147
|
+
["Enter", "Send; queue while running"],
|
|
42022
43148
|
["Esc", "Stop a running task or blocking compaction"],
|
|
42023
43149
|
["Esc Esc", "Clear draft when idle (press twice quickly)"],
|
|
42024
43150
|
["Up / Down", "Browse previous inputs in this session"],
|
|
@@ -42055,6 +43181,21 @@ function helpLines(platform = process.platform) {
|
|
|
42055
43181
|
["End", "Jump to the newest output"],
|
|
42056
43182
|
["\u2190", "Back"]
|
|
42057
43183
|
]);
|
|
43184
|
+
section("Queue", [
|
|
43185
|
+
["Alt+\u2191", "Select queued messages above the editor"],
|
|
43186
|
+
["\u2191 / \u2193", "Move between queued messages"],
|
|
43187
|
+
["Enter", "Steer the selected message (joins the run after the current tool round)"],
|
|
43188
|
+
["Esc", "Withdraw the selected message into the editor; the run keeps going"]
|
|
43189
|
+
]);
|
|
43190
|
+
section("Web search", [
|
|
43191
|
+
["/search", "Choose the web search provider"],
|
|
43192
|
+
["Enter", "Apply the highlighted provider"]
|
|
43193
|
+
]);
|
|
43194
|
+
section("Context", [
|
|
43195
|
+
["/context", "Tune compaction and tool result pruning"],
|
|
43196
|
+
["Enter", "Choose a setting, then a value to save"],
|
|
43197
|
+
["Esc", "Back"]
|
|
43198
|
+
]);
|
|
42058
43199
|
section("Skills", [
|
|
42059
43200
|
["U", "Install for user"],
|
|
42060
43201
|
["P", "Install for project"],
|
|
@@ -42107,13 +43248,13 @@ class HelpDetail {
|
|
|
42107
43248
|
this.#offset = Math.max(0, Math.min(this.#max, this.#offset + delta));
|
|
42108
43249
|
}
|
|
42109
43250
|
handleKey(data) {
|
|
42110
|
-
if (
|
|
43251
|
+
if (matchesKey8(data, "up"))
|
|
42111
43252
|
this.scroll(-1);
|
|
42112
|
-
else if (
|
|
43253
|
+
else if (matchesKey8(data, "down"))
|
|
42113
43254
|
this.scroll(1);
|
|
42114
|
-
else if (
|
|
43255
|
+
else if (matchesKey8(data, "pageUp"))
|
|
42115
43256
|
this.scroll(-this.#page);
|
|
42116
|
-
else if (
|
|
43257
|
+
else if (matchesKey8(data, "pageDown"))
|
|
42117
43258
|
this.scroll(this.#page);
|
|
42118
43259
|
else
|
|
42119
43260
|
return false;
|
|
@@ -42140,7 +43281,7 @@ import { readFile as readFile9, stat as stat5 } from "fs/promises";
|
|
|
42140
43281
|
import { homedir as homedir8 } from "os";
|
|
42141
43282
|
import { basename as basename8, resolve as resolve5 } from "path";
|
|
42142
43283
|
import { promisify as promisify2 } from "util";
|
|
42143
|
-
import { isKeyRelease as isKeyRelease4, isKeyRepeat as isKeyRepeat2, matchesKey as
|
|
43284
|
+
import { isKeyRelease as isKeyRelease4, isKeyRepeat as isKeyRepeat2, matchesKey as matchesKey9 } from "@earendil-works/pi-tui";
|
|
42144
43285
|
function imagePaths(text, platform = process.platform) {
|
|
42145
43286
|
const paths = [];
|
|
42146
43287
|
let word = "", quote = "", escaped = false;
|
|
@@ -42234,7 +43375,7 @@ end try`]).catch(() => ""));
|
|
|
42234
43375
|
return { text: String(await run(tool, wayland ? ["--no-newline"] : ["-selection", "clipboard", "-o"])) };
|
|
42235
43376
|
}
|
|
42236
43377
|
function isClipboardShortcut(data, platform = process.platform) {
|
|
42237
|
-
return !isKeyRelease4(data) && !isKeyRepeat2(data) &&
|
|
43378
|
+
return !isKeyRelease4(data) && !isKeyRepeat2(data) && matchesKey9(data, platform === "win32" ? "alt+v" : "ctrl+v");
|
|
42238
43379
|
}
|
|
42239
43380
|
var exec;
|
|
42240
43381
|
var init_image_input = __esm(() => {
|
|
@@ -42249,7 +43390,7 @@ function createAppLayout(parts) {
|
|
|
42249
43390
|
parts.approval.height = Math.min(10, Math.max(7, Math.floor(v.height / 2)));
|
|
42250
43391
|
parts.footer.panelOpen = parts.panelOpen();
|
|
42251
43392
|
parts.editor.maxRows = parts.panelOpen() ? 3 : v.height >= 24 ? 7 : 5;
|
|
42252
|
-
parts.panel.height = Math.max(1, v.height - (parts.approvalVisible?.() ? parts.approval?.height ?? 0 : 0) - Math.min(7, Math.max(3, parts.editor.render(v.width).length)) - 2 - Math.min(1, parts.notices.render(v.width).length) - (parts.busyVisible?.() ? Math.min(2, parts.busy?.render(v.width).length ?? 0) : 0));
|
|
43393
|
+
parts.panel.height = Math.max(1, v.height - (parts.approvalVisible?.() ? parts.approval?.height ?? 0 : 0) - Math.min(7, Math.max(3, parts.editor.render(v.width).length)) - 2 - Math.min(1, parts.notices.render(v.width).length) - (parts.busyVisible?.() ? Math.min(2, parts.busy?.render(v.width).length ?? 0) : 0) - (parts.queueVisible?.() ? Math.min(3, parts.queue?.render(v.width).length ?? 0) : 0));
|
|
42253
43394
|
return usableSize(v.width, v.height);
|
|
42254
43395
|
};
|
|
42255
43396
|
return new VStack([
|
|
@@ -42267,6 +43408,12 @@ function createAppLayout(parts) {
|
|
|
42267
43408
|
shrink: 0,
|
|
42268
43409
|
visible: (v) => normal(v) && (parts.busyVisible?.() ?? false)
|
|
42269
43410
|
},
|
|
43411
|
+
{
|
|
43412
|
+
component: parts.queue ?? new Text8("", 0, 0),
|
|
43413
|
+
maxSize: 3,
|
|
43414
|
+
shrink: 0,
|
|
43415
|
+
visible: (v) => normal(v) && (parts.queueVisible?.() ?? false)
|
|
43416
|
+
},
|
|
42270
43417
|
{
|
|
42271
43418
|
component: parts.approval ?? new Text8("", 0, 0),
|
|
42272
43419
|
shrink: 0,
|
|
@@ -42279,9 +43426,154 @@ function createAppLayout(parts) {
|
|
|
42279
43426
|
var usableSize = (width, height) => width >= 40 && height >= 10;
|
|
42280
43427
|
var init_layout = () => {};
|
|
42281
43428
|
|
|
43429
|
+
// packages/tui/src/queue-ui.ts
|
|
43430
|
+
import { matchesKey as matchesKey10, truncateToWidth as truncateToWidth6 } from "@earendil-works/pi-tui";
|
|
43431
|
+
|
|
43432
|
+
class QueueRow {
|
|
43433
|
+
#items = [];
|
|
43434
|
+
#selected;
|
|
43435
|
+
setItems(items) {
|
|
43436
|
+
this.#items = items;
|
|
43437
|
+
if (items.length === 0)
|
|
43438
|
+
this.#selected = undefined;
|
|
43439
|
+
else if (this.#selected !== undefined)
|
|
43440
|
+
this.#selected = Math.min(this.#selected, items.length - 1);
|
|
43441
|
+
}
|
|
43442
|
+
get visible() {
|
|
43443
|
+
return this.#items.length > 0;
|
|
43444
|
+
}
|
|
43445
|
+
get selecting() {
|
|
43446
|
+
return this.#selected !== undefined;
|
|
43447
|
+
}
|
|
43448
|
+
get current() {
|
|
43449
|
+
return this.#selected === undefined ? undefined : this.#items[this.#selected];
|
|
43450
|
+
}
|
|
43451
|
+
select() {
|
|
43452
|
+
if (this.#items.length === 0)
|
|
43453
|
+
return false;
|
|
43454
|
+
this.#selected = this.#items.length - 1;
|
|
43455
|
+
return true;
|
|
43456
|
+
}
|
|
43457
|
+
clear() {
|
|
43458
|
+
this.#selected = undefined;
|
|
43459
|
+
}
|
|
43460
|
+
move(delta) {
|
|
43461
|
+
if (this.#selected === undefined)
|
|
43462
|
+
return;
|
|
43463
|
+
const next = this.#selected + delta;
|
|
43464
|
+
if (next >= this.#items.length)
|
|
43465
|
+
this.#selected = undefined;
|
|
43466
|
+
else
|
|
43467
|
+
this.#selected = Math.max(0, next);
|
|
43468
|
+
}
|
|
43469
|
+
render(width) {
|
|
43470
|
+
const total = this.#items.length;
|
|
43471
|
+
if (total === 0)
|
|
43472
|
+
return [];
|
|
43473
|
+
const end = this.#selected === undefined ? total : Math.max(this.#selected + 1, Math.min(total, QUEUE_ROWS));
|
|
43474
|
+
const start = Math.max(0, end - QUEUE_ROWS);
|
|
43475
|
+
const rows = this.#items.slice(start, end).map((item, offset) => {
|
|
43476
|
+
const picked = start + offset === this.#selected;
|
|
43477
|
+
const text = `${modeLabel(item.mode)} \xB7 ${content2(item)}`;
|
|
43478
|
+
return truncateToWidth6(picked ? ` ${theme.accent(`\u203A ${text}`)}` : ` ${theme.muted(text)}`, width);
|
|
43479
|
+
});
|
|
43480
|
+
const earlier = start > 0 ? `${start} earlier \xB7 ` : "";
|
|
43481
|
+
const later = end < total ? `${total - end} newer \xB7 ` : "";
|
|
43482
|
+
const hint = this.#selected === undefined ? shortcutHints(["Alt+\u2191", "Select queued"]) : shortcutHints(["Enter", "Steer"], ["Esc", "Withdraw"], ["\u2191/\u2193", "Move"]);
|
|
43483
|
+
rows.push(truncateToWidth6(theme.dim(` ${earlier}${later}${hint}`), width));
|
|
43484
|
+
return rows;
|
|
43485
|
+
}
|
|
43486
|
+
invalidate() {}
|
|
43487
|
+
}
|
|
43488
|
+
function queueKeyAction(row, data, autocompleteOpen) {
|
|
43489
|
+
if (!row.selecting)
|
|
43490
|
+
return matchesKey10(data, "alt+up") && row.visible && !autocompleteOpen ? "select" : undefined;
|
|
43491
|
+
if (matchesKey10(data, "up"))
|
|
43492
|
+
return "up";
|
|
43493
|
+
if (matchesKey10(data, "down"))
|
|
43494
|
+
return "down";
|
|
43495
|
+
if (matchesKey10(data, "enter"))
|
|
43496
|
+
return "steer";
|
|
43497
|
+
if (matchesKey10(data, "escape"))
|
|
43498
|
+
return "withdraw";
|
|
43499
|
+
return "leave";
|
|
43500
|
+
}
|
|
43501
|
+
function restoreText(existing, text) {
|
|
43502
|
+
return existing.trim() ? `${existing}
|
|
43503
|
+
${text}` : text;
|
|
43504
|
+
}
|
|
43505
|
+
function steerNotice(outcome) {
|
|
43506
|
+
if (outcome.result === "steered")
|
|
43507
|
+
return { message: "Message will steer the current run.", level: "info" };
|
|
43508
|
+
if (outcome.result === "withdrawn")
|
|
43509
|
+
return { message: "Could not steer; the message was withdrawn into the editor.", level: "warning" };
|
|
43510
|
+
if (outcome.result === "unknown")
|
|
43511
|
+
return { message: "That message is no longer queued.", level: "warning" };
|
|
43512
|
+
return { message: "That message was already sent and cannot be steered.", level: "warning" };
|
|
43513
|
+
}
|
|
43514
|
+
function withdrawNotice(outcome) {
|
|
43515
|
+
if (outcome.result === "withdrawn")
|
|
43516
|
+
return outcome.images ? { message: "Message withdrawn into the editor. Images are not restored; attach them again.", level: "warning" } : { message: "Message withdrawn into the editor.", level: "info" };
|
|
43517
|
+
if (outcome.result === "unknown")
|
|
43518
|
+
return { message: "That message is no longer queued.", level: "warning" };
|
|
43519
|
+
return { message: "That message was already sent and cannot be withdrawn.", level: "warning" };
|
|
43520
|
+
}
|
|
43521
|
+
var QUEUE_ROWS = 2, QUEUE_BLOCK_LINES, modeLabel = (mode) => mode === "steer" ? "Steer" : "Queued", oneLine3 = (text) => text.replace(/\s+/g, " ").trim(), content2 = (item) => oneLine3(item.preview) || (item.images > 0 ? `[${item.images} image${item.images > 1 ? "s" : ""}]` : "");
|
|
43522
|
+
var init_queue_ui = __esm(() => {
|
|
43523
|
+
init_shortcut_hints();
|
|
43524
|
+
init_theme();
|
|
43525
|
+
QUEUE_BLOCK_LINES = QUEUE_ROWS + 1;
|
|
43526
|
+
});
|
|
43527
|
+
|
|
43528
|
+
// packages/tui/src/search-ui.ts
|
|
43529
|
+
function searchItems(settings, credentials) {
|
|
43530
|
+
if (!settings)
|
|
43531
|
+
return [];
|
|
43532
|
+
const source = (provider) => {
|
|
43533
|
+
const status = credentials?.statuses[provider];
|
|
43534
|
+
return status ? SOURCES2[status.source] : "loading";
|
|
43535
|
+
};
|
|
43536
|
+
const deepseekMissing = credentials?.statuses.deepseek.source === "none";
|
|
43537
|
+
const descriptions = {
|
|
43538
|
+
tavily: `Tavily search (${source("tavily")}). Default`,
|
|
43539
|
+
deepseek: deepseekMissing ? "No DeepSeek key. Run /login to set one before choosing it" : `DeepSeek search and fetch with your DeepSeek API key (${source("deepseek")})`
|
|
43540
|
+
};
|
|
43541
|
+
return SEARCH_PROVIDERS.map((value) => ({
|
|
43542
|
+
value,
|
|
43543
|
+
label: `${NAMES[value]}${settings.selected === value ? " (Current)" : ""}`,
|
|
43544
|
+
description: descriptions[value]
|
|
43545
|
+
}));
|
|
43546
|
+
}
|
|
43547
|
+
function openSearchPanel(panel, client, notify) {
|
|
43548
|
+
const items = () => searchItems(client.searchSettings.state.value, client.credentials.state.value);
|
|
43549
|
+
const refresh = () => panel.update(items());
|
|
43550
|
+
const stops = [client.searchSettings.state.subscribe(refresh), client.credentials.state.subscribe(refresh)];
|
|
43551
|
+
panel.emptyText = SEARCH_EMPTY;
|
|
43552
|
+
panel.show("Web search", async () => items(), async (value) => {
|
|
43553
|
+
await client.searchSettings.set({ provider: value });
|
|
43554
|
+
notify(`Web search provider set to ${NAMES[value]}.`);
|
|
43555
|
+
});
|
|
43556
|
+
return () => {
|
|
43557
|
+
for (const stop of stops)
|
|
43558
|
+
stop();
|
|
43559
|
+
};
|
|
43560
|
+
}
|
|
43561
|
+
var SEARCH_EMPTY = "Search settings are unavailable.", NAMES, SOURCES2;
|
|
43562
|
+
var init_search_ui = __esm(() => {
|
|
43563
|
+
init_search_providers();
|
|
43564
|
+
NAMES = { tavily: "Tavily", deepseek: "DeepSeek" };
|
|
43565
|
+
SOURCES2 = {
|
|
43566
|
+
saved: "saved key",
|
|
43567
|
+
environment: "environment variable",
|
|
43568
|
+
runtime: "runtime key",
|
|
43569
|
+
keyless: "keyless, rate-limited",
|
|
43570
|
+
none: "no key"
|
|
43571
|
+
};
|
|
43572
|
+
});
|
|
43573
|
+
|
|
42282
43574
|
// packages/tui/src/skill-market-ui.ts
|
|
42283
43575
|
import { ChordError as ChordError6 } from "@earendil-works/chord";
|
|
42284
|
-
import { Markdown as Markdown3, matchesKey as
|
|
43576
|
+
import { Markdown as Markdown3, matchesKey as matchesKey11, wrapTextWithAnsi as wrapTextWithAnsi5 } from "@earendil-works/pi-tui";
|
|
42285
43577
|
|
|
42286
43578
|
class SkillDetailView {
|
|
42287
43579
|
#skill = null;
|
|
@@ -42320,11 +43612,11 @@ class SkillDetailView {
|
|
|
42320
43612
|
}
|
|
42321
43613
|
handleKey(data) {
|
|
42322
43614
|
if (this.#confirm) {
|
|
42323
|
-
if (
|
|
43615
|
+
if (matchesKey11(data, "y")) {
|
|
42324
43616
|
const pending = this.#confirm;
|
|
42325
43617
|
this.#confirm = null;
|
|
42326
43618
|
this.run(pending.run);
|
|
42327
|
-
} else if (
|
|
43619
|
+
} else if (matchesKey11(data, "n")) {
|
|
42328
43620
|
this.#confirm = null;
|
|
42329
43621
|
}
|
|
42330
43622
|
return true;
|
|
@@ -42333,17 +43625,17 @@ class SkillDetailView {
|
|
|
42333
43625
|
return true;
|
|
42334
43626
|
const skill = this.#skill;
|
|
42335
43627
|
const builtinOnly = skill.installed.builtin && !skill.installed.user && !skill.installed.project;
|
|
42336
|
-
if (
|
|
43628
|
+
if (matchesKey11(data, "v")) {
|
|
42337
43629
|
this.#openPreview(this.#name);
|
|
42338
43630
|
return true;
|
|
42339
43631
|
}
|
|
42340
43632
|
if (builtinOnly)
|
|
42341
43633
|
return true;
|
|
42342
|
-
if (
|
|
43634
|
+
if (matchesKey11(data, "u"))
|
|
42343
43635
|
this.askInstall("user", skill);
|
|
42344
|
-
else if (
|
|
43636
|
+
else if (matchesKey11(data, "p"))
|
|
42345
43637
|
this.askInstall("project", skill);
|
|
42346
|
-
else if (
|
|
43638
|
+
else if (matchesKey11(data, "d"))
|
|
42347
43639
|
this.askUninstall(skill);
|
|
42348
43640
|
else
|
|
42349
43641
|
return false;
|
|
@@ -42468,13 +43760,13 @@ class SkillPreviewView {
|
|
|
42468
43760
|
this.#offset = Math.max(0, Math.min(this.#maxOffset, this.#offset + delta));
|
|
42469
43761
|
}
|
|
42470
43762
|
handleKey(data) {
|
|
42471
|
-
if (
|
|
43763
|
+
if (matchesKey11(data, "up"))
|
|
42472
43764
|
this.scroll(-1);
|
|
42473
|
-
else if (
|
|
43765
|
+
else if (matchesKey11(data, "down"))
|
|
42474
43766
|
this.scroll(1);
|
|
42475
|
-
else if (
|
|
43767
|
+
else if (matchesKey11(data, "pageUp"))
|
|
42476
43768
|
this.scroll(-this.#pageSize);
|
|
42477
|
-
else if (
|
|
43769
|
+
else if (matchesKey11(data, "pageDown"))
|
|
42478
43770
|
this.scroll(this.#pageSize);
|
|
42479
43771
|
else
|
|
42480
43772
|
return false;
|
|
@@ -42526,7 +43818,7 @@ var init_skill_market_ui = __esm(() => {
|
|
|
42526
43818
|
});
|
|
42527
43819
|
|
|
42528
43820
|
// packages/tui/src/subagents-ui.ts
|
|
42529
|
-
import { matchesKey as
|
|
43821
|
+
import { matchesKey as matchesKey12 } from "@earendil-works/pi-tui";
|
|
42530
43822
|
|
|
42531
43823
|
class SubagentDetail {
|
|
42532
43824
|
get help() {
|
|
@@ -42646,13 +43938,13 @@ class SubagentDetail {
|
|
|
42646
43938
|
this.history();
|
|
42647
43939
|
}
|
|
42648
43940
|
handleKey(data) {
|
|
42649
|
-
if (
|
|
43941
|
+
if (matchesKey12(data, "up")) {
|
|
42650
43942
|
this.scroll(-1);
|
|
42651
|
-
} else if (
|
|
43943
|
+
} else if (matchesKey12(data, "down")) {
|
|
42652
43944
|
this.scroll(1);
|
|
42653
|
-
} else if (
|
|
43945
|
+
} else if (matchesKey12(data, "pageUp")) {
|
|
42654
43946
|
this.scroll(-this.#pageSize);
|
|
42655
|
-
} else if (
|
|
43947
|
+
} else if (matchesKey12(data, "pageDown")) {
|
|
42656
43948
|
this.scroll(this.#pageSize);
|
|
42657
43949
|
} else if (data.toLowerCase() === "e") {
|
|
42658
43950
|
this.#expanded = !this.#expanded;
|
|
@@ -42668,7 +43960,7 @@ class SubagentDetail {
|
|
|
42668
43960
|
help: shortcutHints(["Enter", "Stop"], ["Esc", "Back"]),
|
|
42669
43961
|
lines: () => [theme.warning("Stop this task? Existing changes will be kept.")],
|
|
42670
43962
|
handleKey: (key) => {
|
|
42671
|
-
if (!
|
|
43963
|
+
if (!matchesKey12(key, "enter"))
|
|
42672
43964
|
return false;
|
|
42673
43965
|
this.#service.stop(stopTaskId === undefined ? null : { taskId: stopTaskId }).catch((e) => {
|
|
42674
43966
|
this.#error = String(e);
|
|
@@ -42799,7 +44091,7 @@ var init_subagents_ui = __esm(() => {
|
|
|
42799
44091
|
});
|
|
42800
44092
|
|
|
42801
44093
|
// packages/tui/src/version-ui.ts
|
|
42802
|
-
import { matchesKey as
|
|
44094
|
+
import { matchesKey as matchesKey13, Text as Text9 } from "@earendil-works/pi-tui";
|
|
42803
44095
|
function actionFor(state, installed, runningVersion) {
|
|
42804
44096
|
const action = updateAction({ gateway: state, frontend: runningVersion, installed });
|
|
42805
44097
|
return action === "install" && !newer(state?.latestVersion, runningVersion) ? "none" : action;
|
|
@@ -42877,9 +44169,9 @@ class VersionDetail {
|
|
|
42877
44169
|
return lines.slice(this.#offset, this.#offset + height);
|
|
42878
44170
|
}
|
|
42879
44171
|
handleKey(data) {
|
|
42880
|
-
if (
|
|
44172
|
+
if (matchesKey13(data, "up"))
|
|
42881
44173
|
this.#offset = Math.max(0, this.#offset - 1);
|
|
42882
|
-
else if (
|
|
44174
|
+
else if (matchesKey13(data, "down"))
|
|
42883
44175
|
this.#offset++;
|
|
42884
44176
|
else
|
|
42885
44177
|
return false;
|
|
@@ -42944,7 +44236,7 @@ import {
|
|
|
42944
44236
|
Container as Container6,
|
|
42945
44237
|
isKeyRelease as isKeyRelease5,
|
|
42946
44238
|
Loader,
|
|
42947
|
-
matchesKey as
|
|
44239
|
+
matchesKey as matchesKey14,
|
|
42948
44240
|
ProcessTerminal,
|
|
42949
44241
|
ScrollView,
|
|
42950
44242
|
TuiAltScreen
|
|
@@ -42980,6 +44272,10 @@ class NekoTuiApp {
|
|
|
42980
44272
|
#chat;
|
|
42981
44273
|
#agentsPanel = false;
|
|
42982
44274
|
#jobsPanel = false;
|
|
44275
|
+
#queueBusy = false;
|
|
44276
|
+
#queueRow = new QueueRow;
|
|
44277
|
+
#searchStop;
|
|
44278
|
+
#contextStop;
|
|
42983
44279
|
#agentDetails = new Map;
|
|
42984
44280
|
#sessionId = "";
|
|
42985
44281
|
#viewUnsubscribe;
|
|
@@ -43021,6 +44317,10 @@ class NekoTuiApp {
|
|
|
43021
44317
|
this.#thinkingModel = undefined;
|
|
43022
44318
|
this.#agentsPanel = false;
|
|
43023
44319
|
this.#jobsPanel = false;
|
|
44320
|
+
this.#searchStop?.();
|
|
44321
|
+
this.#searchStop = undefined;
|
|
44322
|
+
this.#contextStop?.();
|
|
44323
|
+
this.#contextStop = undefined;
|
|
43024
44324
|
clearInterval(this.#sessionPanelTimer);
|
|
43025
44325
|
this.#sessionPanelTimer = undefined;
|
|
43026
44326
|
this.#ui.setFocus(this.#editor);
|
|
@@ -43081,10 +44381,10 @@ class NekoTuiApp {
|
|
|
43081
44381
|
this.#ui.addInputListener((data) => {
|
|
43082
44382
|
if (isKeyRelease5(data))
|
|
43083
44383
|
return { consume: true };
|
|
43084
|
-
if (!
|
|
44384
|
+
if (!matchesKey14(data, "escape") || this.#panel.open || this.#login.open || this.#busyText !== undefined || this.#lastView?.compaction?.active.length)
|
|
43085
44385
|
this.#editor.resetEscapeSequence();
|
|
43086
44386
|
if (!usableSize(this.#terminal.columns, this.#terminal.rows)) {
|
|
43087
|
-
if (
|
|
44387
|
+
if (matchesKey14(data, "ctrl+c"))
|
|
43088
44388
|
this.exit();
|
|
43089
44389
|
return { consume: true };
|
|
43090
44390
|
}
|
|
@@ -43095,11 +44395,14 @@ class NekoTuiApp {
|
|
|
43095
44395
|
}
|
|
43096
44396
|
if (this.#sessionOperation)
|
|
43097
44397
|
return { consume: true };
|
|
43098
|
-
|
|
44398
|
+
const queueKey = this.#queueKey(data);
|
|
44399
|
+
if (queueKey)
|
|
44400
|
+
return queueKey;
|
|
44401
|
+
if (matchesKey14(data, "right") && this.#lastView?.subagents?.length && this.#editor.getText() === "" && !this.#editor.isShowingAutocomplete()) {
|
|
43099
44402
|
this.#showAgents();
|
|
43100
44403
|
return { consume: true };
|
|
43101
44404
|
}
|
|
43102
|
-
if (
|
|
44405
|
+
if (matchesKey14(data, process.platform === "win32" ? "alt+v" : "ctrl+v")) {
|
|
43103
44406
|
if (isClipboardShortcut(data))
|
|
43104
44407
|
this.pasteClipboard();
|
|
43105
44408
|
return { consume: true };
|
|
@@ -43116,29 +44419,25 @@ class NekoTuiApp {
|
|
|
43116
44419
|
return { consume: true };
|
|
43117
44420
|
}
|
|
43118
44421
|
}
|
|
43119
|
-
if (
|
|
44422
|
+
if (matchesKey14(data, "escape") && this.#editor.isShowingAutocomplete()) {
|
|
43120
44423
|
this.#editor.resetEscapeSequence();
|
|
43121
44424
|
return;
|
|
43122
44425
|
}
|
|
43123
|
-
if (
|
|
44426
|
+
if (matchesKey14(data, "ctrl+c")) {
|
|
43124
44427
|
if (this.#busyText !== undefined)
|
|
43125
44428
|
this.abort();
|
|
43126
44429
|
else
|
|
43127
44430
|
this.exit();
|
|
43128
44431
|
return { consume: true };
|
|
43129
44432
|
}
|
|
43130
|
-
if (
|
|
44433
|
+
if (matchesKey14(data, "escape") && (this.#busyText !== undefined || this.#lastView?.compaction?.active.some((c) => c.reason === "manual" || c.blocking))) {
|
|
43131
44434
|
this.abort();
|
|
43132
44435
|
return { consume: true };
|
|
43133
44436
|
}
|
|
43134
|
-
if (
|
|
44437
|
+
if (matchesKey14(data, "ctrl+s")) {
|
|
43135
44438
|
this.openSessionsOverlay();
|
|
43136
44439
|
return { consume: true };
|
|
43137
44440
|
}
|
|
43138
|
-
if (this.#busyText !== undefined && matchesKey12(data, "alt+enter")) {
|
|
43139
|
-
this.submitFollowUp();
|
|
43140
|
-
return { consume: true };
|
|
43141
|
-
}
|
|
43142
44441
|
return;
|
|
43143
44442
|
});
|
|
43144
44443
|
}
|
|
@@ -43182,6 +44481,8 @@ class NekoTuiApp {
|
|
|
43182
44481
|
notices,
|
|
43183
44482
|
busy: this.#busyLoader,
|
|
43184
44483
|
busyVisible: () => this.#busyText !== undefined,
|
|
44484
|
+
queue: this.#queueRow,
|
|
44485
|
+
queueVisible: () => this.#queueRow.visible,
|
|
43185
44486
|
panelOpen: () => this.#panel.open || this.#login.open
|
|
43186
44487
|
}));
|
|
43187
44488
|
this.#ui.setFocus(this.#editor);
|
|
@@ -43194,7 +44495,7 @@ class NekoTuiApp {
|
|
|
43194
44495
|
const installed = await installedVersion();
|
|
43195
44496
|
if (this.#exitStarted)
|
|
43196
44497
|
return;
|
|
43197
|
-
const notice = versionNotice(this.#gateway.client.application.info.value
|
|
44498
|
+
const notice = versionNotice(this.#gateway.client.application.info.value, installed);
|
|
43198
44499
|
this.#versionNotice.clear();
|
|
43199
44500
|
if (notice)
|
|
43200
44501
|
this.#versionNotice.addChild(new ThemedTruncatedText(() => formatNotice(notice, "info"), 1, 0));
|
|
@@ -43242,6 +44543,14 @@ class NekoTuiApp {
|
|
|
43242
44543
|
this.#showJobs();
|
|
43243
44544
|
return;
|
|
43244
44545
|
}
|
|
44546
|
+
if (trimmed === "/search") {
|
|
44547
|
+
this.#showSearch();
|
|
44548
|
+
return;
|
|
44549
|
+
}
|
|
44550
|
+
if (trimmed === "/context") {
|
|
44551
|
+
this.#showContext();
|
|
44552
|
+
return;
|
|
44553
|
+
}
|
|
43245
44554
|
const instructions = compactInstructions(trimmed);
|
|
43246
44555
|
if (instructions !== null) {
|
|
43247
44556
|
const chat = this.#chat;
|
|
@@ -43293,7 +44602,7 @@ class NekoTuiApp {
|
|
|
43293
44602
|
if (trimmed === "/version") {
|
|
43294
44603
|
if (!this.#exitStarted && !this.#panel.open && !this.#login.open) {
|
|
43295
44604
|
openVersion(this.#panel, async () => ({
|
|
43296
|
-
state: this.#gateway.client.application.info.value
|
|
44605
|
+
state: this.#gateway.client.application.info.value,
|
|
43297
44606
|
installed: await installedVersion()
|
|
43298
44607
|
}), () => this.#gateway.client.application.check(null));
|
|
43299
44608
|
this.#ui.setFocus(this.#panel);
|
|
@@ -43327,17 +44636,12 @@ class NekoTuiApp {
|
|
|
43327
44636
|
const chat = this.#chat;
|
|
43328
44637
|
if (chat === undefined)
|
|
43329
44638
|
return;
|
|
43330
|
-
if (this.#lastView?.agent.model?.provider === "deepseek" && this.#gateway.client.credentials.state.value
|
|
44639
|
+
if (this.#lastView?.agent.model?.provider === "deepseek" && this.#gateway.client.credentials.state.value.statuses.deepseek.source === "none") {
|
|
43331
44640
|
this.#editor.setText(text);
|
|
43332
44641
|
this.addNotice("Configure DeepSeek with /login before sending.", "warning");
|
|
43333
44642
|
return;
|
|
43334
44643
|
}
|
|
43335
|
-
this.sendImages(text
|
|
43336
|
-
}
|
|
43337
|
-
submitFollowUp() {
|
|
43338
|
-
const text = this.#editor.getText();
|
|
43339
|
-
if (text.trim())
|
|
43340
|
-
this.sendImages(text, "followUp");
|
|
44644
|
+
this.sendImages(text);
|
|
43341
44645
|
}
|
|
43342
44646
|
async addImagePaths(paths) {
|
|
43343
44647
|
return paths.map((path) => this.addImage(readImageFile(path, this.#options.cwd ?? process.cwd()), path.split(/[\\/]/).at(-1) ?? "Image")).join(" ");
|
|
@@ -43372,7 +44676,7 @@ class NekoTuiApp {
|
|
|
43372
44676
|
this.#clipboardPending = false;
|
|
43373
44677
|
}
|
|
43374
44678
|
}
|
|
43375
|
-
async sendImages(text
|
|
44679
|
+
async sendImages(text) {
|
|
43376
44680
|
const chat = this.#chat;
|
|
43377
44681
|
const session = this.#sessionId;
|
|
43378
44682
|
if (!chat || this.#sending.has(session))
|
|
@@ -43404,7 +44708,7 @@ class NekoTuiApp {
|
|
|
43404
44708
|
}));
|
|
43405
44709
|
content = content.replaceAll(ref, "");
|
|
43406
44710
|
}
|
|
43407
|
-
await chat.submit({ content: content.trim(), images: ids, requestId
|
|
44711
|
+
await chat.submit({ content: content.trim(), images: ids, requestId });
|
|
43408
44712
|
if (session === this.#sessionId && this.#editor.getText() === before)
|
|
43409
44713
|
this.#editor.setText("");
|
|
43410
44714
|
if (this.#texts.get(session) === before)
|
|
@@ -43445,7 +44749,7 @@ class NekoTuiApp {
|
|
|
43445
44749
|
this.addNotice(messageOf(error), "warning", { wrap: true });
|
|
43446
44750
|
return;
|
|
43447
44751
|
}
|
|
43448
|
-
const summary = this.#gateway.client.directory.state.value
|
|
44752
|
+
const summary = this.#gateway.client.directory.state.value.sessions.find((s) => s.id === sessionId);
|
|
43449
44753
|
const title = summary ? sessionName(summary, "") : "";
|
|
43450
44754
|
this.addNotice("Exporting...");
|
|
43451
44755
|
try {
|
|
@@ -43480,6 +44784,7 @@ class NekoTuiApp {
|
|
|
43480
44784
|
async switchSession(id) {
|
|
43481
44785
|
this.#approval.close();
|
|
43482
44786
|
this.#chat = undefined;
|
|
44787
|
+
this.#queueRow.setItems([]);
|
|
43483
44788
|
if (this.#jobsPanel && this.#panel.open)
|
|
43484
44789
|
this.#panel.close();
|
|
43485
44790
|
if (this.#sessionId)
|
|
@@ -43498,8 +44803,7 @@ class NekoTuiApp {
|
|
|
43498
44803
|
this.#historyItems = undefined;
|
|
43499
44804
|
this.#resetTranscript();
|
|
43500
44805
|
const view = handle.view;
|
|
43501
|
-
|
|
43502
|
-
this.applyView(view.value);
|
|
44806
|
+
this.applyView(view.value);
|
|
43503
44807
|
this.#viewUnsubscribe = view.subscribe(() => {
|
|
43504
44808
|
this.scheduleApply();
|
|
43505
44809
|
});
|
|
@@ -43541,7 +44845,7 @@ class NekoTuiApp {
|
|
|
43541
44845
|
async archiveSession() {
|
|
43542
44846
|
const current = this.#sessionId;
|
|
43543
44847
|
await this.#gateway.client.management.archive(current);
|
|
43544
|
-
const next = recentlyVisited(this.#gateway.client.directory.state.value
|
|
44848
|
+
const next = recentlyVisited(this.#gateway.client.directory.state.value.sessions, this.#options.cwd).find((s) => s.id !== current);
|
|
43545
44849
|
if (next)
|
|
43546
44850
|
await this.switchSession(next.id);
|
|
43547
44851
|
else
|
|
@@ -43550,7 +44854,7 @@ class NekoTuiApp {
|
|
|
43550
44854
|
sessionItems() {
|
|
43551
44855
|
return [
|
|
43552
44856
|
{ value: NEW_SESSION_VALUE, label: "+ New session", description: this.#options.cwd },
|
|
43553
|
-
...sessionPickerItems(this.#gateway.client.directory.state.value
|
|
44857
|
+
...sessionPickerItems(this.#gateway.client.directory.state.value.sessions, this.#options.cwd, this.#sessionId)
|
|
43554
44858
|
];
|
|
43555
44859
|
}
|
|
43556
44860
|
openSessionsOverlay() {
|
|
@@ -43685,7 +44989,7 @@ class NekoTuiApp {
|
|
|
43685
44989
|
this.#panel.pointerSelection = true;
|
|
43686
44990
|
this.#panel.emptyText = JOBS_EMPTY;
|
|
43687
44991
|
this.#panel.listKey = (data, value) => {
|
|
43688
|
-
if (
|
|
44992
|
+
if (matchesKey14(data, "left")) {
|
|
43689
44993
|
this.#panel.close();
|
|
43690
44994
|
return true;
|
|
43691
44995
|
}
|
|
@@ -43699,6 +45003,76 @@ class NekoTuiApp {
|
|
|
43699
45003
|
this.#panel.show("Jobs", async () => jobItems(jobs.list()), async (value) => new JobDetail(jobs, value, this.#panel, changed).open());
|
|
43700
45004
|
this.#ui.setFocus(this.#panel);
|
|
43701
45005
|
}
|
|
45006
|
+
#queueKey(data) {
|
|
45007
|
+
const row = this.#queueRow;
|
|
45008
|
+
if (this.#queueBusy && row.selecting)
|
|
45009
|
+
return { consume: true };
|
|
45010
|
+
const action = queueKeyAction(row, data, this.#editor.isShowingAutocomplete());
|
|
45011
|
+
if (action === undefined)
|
|
45012
|
+
return;
|
|
45013
|
+
if (action === "steer" || action === "withdraw")
|
|
45014
|
+
return this.#queueAct(action);
|
|
45015
|
+
if (action === "select")
|
|
45016
|
+
row.select();
|
|
45017
|
+
else if (action === "up")
|
|
45018
|
+
row.move(-1);
|
|
45019
|
+
else if (action === "down")
|
|
45020
|
+
row.move(1);
|
|
45021
|
+
else {
|
|
45022
|
+
row.clear();
|
|
45023
|
+
this.#ui.requestRender();
|
|
45024
|
+
return;
|
|
45025
|
+
}
|
|
45026
|
+
this.#ui.requestRender();
|
|
45027
|
+
return { consume: true };
|
|
45028
|
+
}
|
|
45029
|
+
#queueAct(action) {
|
|
45030
|
+
const chat = this.#chat;
|
|
45031
|
+
const item = this.#queueRow.current;
|
|
45032
|
+
if (!chat || !item)
|
|
45033
|
+
return { consume: true };
|
|
45034
|
+
if (action === "steer" && item.mode === "steer") {
|
|
45035
|
+
this.addNotice("That message already steers the current run.", "info");
|
|
45036
|
+
return { consume: true };
|
|
45037
|
+
}
|
|
45038
|
+
const queue = queueClient(chat);
|
|
45039
|
+
const session = this.#sessionId;
|
|
45040
|
+
this.#queueBusy = true;
|
|
45041
|
+
const restore = (outcome) => {
|
|
45042
|
+
if (outcome.result === "withdrawn")
|
|
45043
|
+
this.#editor.setText(restoreText(this.#editor.getText(), outcome.text ?? ""));
|
|
45044
|
+
};
|
|
45045
|
+
const run = action === "steer" ? queue.steer(item.id).then((outcome) => {
|
|
45046
|
+
if (session !== this.#sessionId)
|
|
45047
|
+
return;
|
|
45048
|
+
restore(outcome);
|
|
45049
|
+
const notice = steerNotice(outcome);
|
|
45050
|
+
this.addNotice(notice.message, notice.level);
|
|
45051
|
+
}) : queue.withdraw(item.id).then((outcome) => {
|
|
45052
|
+
if (session !== this.#sessionId)
|
|
45053
|
+
return;
|
|
45054
|
+
restore(outcome);
|
|
45055
|
+
const notice = withdrawNotice(outcome);
|
|
45056
|
+
this.addNotice(notice.message, notice.level);
|
|
45057
|
+
});
|
|
45058
|
+
run.catch((error) => this.addNotice(messageOf(error), "error")).finally(() => {
|
|
45059
|
+
this.#queueBusy = false;
|
|
45060
|
+
this.#ui.requestRender();
|
|
45061
|
+
});
|
|
45062
|
+
return { consume: true };
|
|
45063
|
+
}
|
|
45064
|
+
#showSearch() {
|
|
45065
|
+
if (this.#exitStarted || this.#panel.open || this.#login.open)
|
|
45066
|
+
return;
|
|
45067
|
+
this.#searchStop = openSearchPanel(this.#panel, this.#gateway.client, (message) => this.addNotice(message));
|
|
45068
|
+
this.#ui.setFocus(this.#panel);
|
|
45069
|
+
}
|
|
45070
|
+
#showContext() {
|
|
45071
|
+
if (this.#exitStarted || this.#panel.open || this.#login.open)
|
|
45072
|
+
return;
|
|
45073
|
+
this.#contextStop = openContextPanel(this.#panel, this.#gateway.client, (message) => this.addNotice(message));
|
|
45074
|
+
this.#ui.setFocus(this.#panel);
|
|
45075
|
+
}
|
|
43702
45076
|
#agentDetail(target) {
|
|
43703
45077
|
const round = typeof target === "number" ? this.#lastView?.subagents?.find((r) => r.taskId === target) : undefined;
|
|
43704
45078
|
const id = typeof target === "string" ? target : round?.agentId ?? `agent-${round?.childId}`;
|
|
@@ -43765,6 +45139,7 @@ class NekoTuiApp {
|
|
|
43765
45139
|
this.#panel.update(subagentItems(view.subagents ?? [], view.agents));
|
|
43766
45140
|
if (this.#jobsPanel)
|
|
43767
45141
|
this.#panel.update(jobItems(view.jobs ?? []));
|
|
45142
|
+
this.#queueRow.setItems(view.queued ?? []);
|
|
43768
45143
|
for (const identity of view.agents ?? []) {
|
|
43769
45144
|
const detail = this.#agentDetails.get(identity.agentId);
|
|
43770
45145
|
if (detail) {
|
|
@@ -44085,14 +45460,7 @@ async function runTui(options = {}) {
|
|
|
44085
45460
|
}
|
|
44086
45461
|
}
|
|
44087
45462
|
async function initialSessionId(gateway, cwd) {
|
|
44088
|
-
const
|
|
44089
|
-
const start = Date.now();
|
|
44090
|
-
while (state.value === undefined) {
|
|
44091
|
-
if (Date.now() - start > 5000)
|
|
44092
|
-
throw new Error("Timed out loading sessions");
|
|
44093
|
-
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
44094
|
-
}
|
|
44095
|
-
const sessions = state.value?.sessions ?? [];
|
|
45463
|
+
const sessions = gateway.client.directory.state.value.sessions;
|
|
44096
45464
|
const first = recentlyVisited(sessions, cwd)[0];
|
|
44097
45465
|
if (first !== undefined)
|
|
44098
45466
|
return first.id;
|
|
@@ -44118,6 +45486,7 @@ var init_tui = __esm(() => {
|
|
|
44118
45486
|
init_tool_execution();
|
|
44119
45487
|
init_user_message();
|
|
44120
45488
|
init_welcome();
|
|
45489
|
+
init_context_ui();
|
|
44121
45490
|
init_export_command();
|
|
44122
45491
|
init_file_completion();
|
|
44123
45492
|
init_gateway2();
|
|
@@ -44126,6 +45495,8 @@ var init_tui = __esm(() => {
|
|
|
44126
45495
|
init_jobs_ui();
|
|
44127
45496
|
init_labels();
|
|
44128
45497
|
init_layout();
|
|
45498
|
+
init_queue_ui();
|
|
45499
|
+
init_search_ui();
|
|
44129
45500
|
init_skill_market_ui();
|
|
44130
45501
|
init_subagents_ui();
|
|
44131
45502
|
init_theme();
|
|
@@ -44216,7 +45587,7 @@ var USAGE = `\u7528\u6CD5: neko tui [\u9009\u9879]
|
|
|
44216
45587
|
|
|
44217
45588
|
\u6062\u590D\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6700\u8FD1\u4F7F\u7528\u7684\u4F1A\u8BDD\uFF0C\u4E0D\u5B58\u5728\u5219\u521B\u5EFA\u3002
|
|
44218
45589
|
\u6309\u9700\u62C9\u8D77\u7F51\u5173\uFF0C\u5C5E\u4E3B TUI \u9000\u51FA\u65F6\u968F\u4E4B\u505C\u6B62\u3002
|
|
44219
|
-
\u53EF\u7528 /login \u6216 DEEPSEEK_API_KEY \u914D\u7F6E\u51ED\u636E\u3002`;
|
|
45590
|
+
\u53EF\u7528 /login \u6216 DEEPSEEK_API_KEY \u914D\u7F6E\u51ED\u636E\uFF1B/search \u9009\u62E9\u8054\u7F51\u641C\u7D22\u670D\u52A1\uFF08tavily / deepseek\uFF0C\u9ED8\u8BA4 tavily\uFF09\uFF1B/context \u8C03\u6574\u4E0A\u4E0B\u6587\u538B\u7F29\u4E0E\u5DE5\u5177\u7ED3\u679C\u88C1\u526A\u3002`;
|
|
44220
45591
|
var init_main = __esm(async () => {
|
|
44221
45592
|
init_tui();
|
|
44222
45593
|
try {
|
|
@@ -44453,7 +45824,7 @@ var init_gateway_spawn = __esm(() => {
|
|
|
44453
45824
|
|
|
44454
45825
|
// packages/web/src/mobile-admin.ts
|
|
44455
45826
|
import { randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
44456
|
-
import { ChordError as ChordError7
|
|
45827
|
+
import { ChordError as ChordError7 } from "@earendil-works/chord";
|
|
44457
45828
|
function createMobileAdminCaller(gateway, nekoDir, timeouts = {}) {
|
|
44458
45829
|
const serverId = gateway.serverId;
|
|
44459
45830
|
let tokenPromise;
|
|
@@ -44468,7 +45839,7 @@ function createMobileAdminCaller(gateway, nekoDir, timeouts = {}) {
|
|
|
44468
45839
|
const token = await readToken();
|
|
44469
45840
|
signal.throwIfAborted();
|
|
44470
45841
|
const link = gateway.socketPath !== undefined ? await createUnixLink(gateway.socketPath) : createWsLink(gateway.wsUrl);
|
|
44471
|
-
const host =
|
|
45842
|
+
const host = createClientHost(link);
|
|
44472
45843
|
const release = () => {
|
|
44473
45844
|
host.close().catch(() => {
|
|
44474
45845
|
return;
|
|
@@ -44477,7 +45848,8 @@ function createMobileAdminCaller(gateway, nekoDir, timeouts = {}) {
|
|
|
44477
45848
|
};
|
|
44478
45849
|
signal.addEventListener("abort", release, { once: true });
|
|
44479
45850
|
try {
|
|
44480
|
-
|
|
45851
|
+
await assertGatewayIdentity(host, serverId, signal);
|
|
45852
|
+
const admin = await host.get(NekoMobileAdmin, { token }, { signal });
|
|
44481
45853
|
const call = admin[method];
|
|
44482
45854
|
if (call === undefined)
|
|
44483
45855
|
throw new Error(`unknown mobile admin method: ${method}`);
|
|
@@ -45167,7 +46539,8 @@ var GATEWAY_USAGE = `\u7528\u6CD5: neko serve [\u9009\u9879]
|
|
|
45167
46539
|
--config <dir> neko.config.json \u67E5\u627E\u8D77\u59CB\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09
|
|
45168
46540
|
-h, --help \u663E\u793A\u6B64\u5E2E\u52A9
|
|
45169
46541
|
|
|
45170
|
-
\u53EF\u901A\u8FC7\u524D\u7AEF\u8BBE\u7F6E\u6216 DEEPSEEK_API_KEY \u914D\u7F6E\u51ED\u636E\
|
|
46542
|
+
\u53EF\u901A\u8FC7\u524D\u7AEF\u8BBE\u7F6E\u6216 DEEPSEEK_API_KEY / TAVILY_API_KEY \u914D\u7F6E\u51ED\u636E\uFF1B
|
|
46543
|
+
\u8054\u7F51\u641C\u7D22\u670D\u52A1\u53EF\u5728\u524D\u7AEF\u8BBE\u7F6E\uFF0C\u6216\u7528\u914D\u7F6E\u6587\u4EF6 webSearch.provider\uFF08tavily / deepseek\uFF0C\u9ED8\u8BA4 tavily\uFF09\u3002`;
|
|
45171
46544
|
|
|
45172
46545
|
// packages/gateway/src/main.ts
|
|
45173
46546
|
var argv, shutdown2, options, gateway;
|
|
@@ -45211,7 +46584,8 @@ var USAGE3 = `\u7528\u6CD5: neko <\u547D\u4EE4> [\u9009\u9879]
|
|
|
45211
46584
|
-h, --help \u663E\u793A\u6B64\u5E2E\u52A9
|
|
45212
46585
|
|
|
45213
46586
|
\u5404\u547D\u4EE4\u7684\u9009\u9879\u8BE6\u89C1 neko <\u547D\u4EE4> --help\u3002
|
|
45214
|
-
\u53EF\u901A\u8FC7 TUI /login\u3001Web \u8BBE\u7F6E\u9875\u6216 DEEPSEEK_API_KEY / TAVILY_API_KEY \u914D\u7F6E\u51ED\u636E\u3002
|
|
46587
|
+
\u53EF\u901A\u8FC7 TUI /login\u3001Web \u8BBE\u7F6E\u9875\u6216 DEEPSEEK_API_KEY / TAVILY_API_KEY \u914D\u7F6E\u51ED\u636E\u3002
|
|
46588
|
+
\u8054\u7F51\u641C\u7D22\u670D\u52A1\uFF08\u9ED8\u8BA4 Tavily\uFF0C\u53EF\u6539\u9009 DeepSeek\uFF09\u53EF\u7528 TUI /search\u3001Web \u8BBE\u7F6E\u9875\u6216\u914D\u7F6E\u6587\u4EF6 webSearch.provider \u9009\u62E9\u3002`;
|
|
45215
46589
|
var [command2, ...rest] = process.argv.slice(2);
|
|
45216
46590
|
switch (command2) {
|
|
45217
46591
|
case "update": {
|