neko-harness 0.4.2 → 0.4.3
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-CAwqBiZ6.css +2 -0
- package/dist/client/assets/index-DLyLR2Bn.js +391 -0
- package/dist/client/index.html +2 -2
- package/dist/gateway.js +1768 -1151
- package/dist/neko.js +1535 -535
- 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.3", 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
|
|
|
@@ -1517,6 +1712,132 @@ async function readCompaction(harness, storage, id, context) {
|
|
|
1517
1712
|
}
|
|
1518
1713
|
var init_compaction = () => {};
|
|
1519
1714
|
|
|
1715
|
+
// packages/core/src/context-pruning.ts
|
|
1716
|
+
function estimateTokens(text) {
|
|
1717
|
+
const cjk = text.match(CJK)?.length ?? 0;
|
|
1718
|
+
return cjk * TOKENS_PER_CJK_CHAR + (text.length - cjk) * TOKENS_PER_LATIN_CHAR;
|
|
1719
|
+
}
|
|
1720
|
+
function toolResultChars(message) {
|
|
1721
|
+
return resultTexts(message).reduce((total, text) => total + text.length, 0);
|
|
1722
|
+
}
|
|
1723
|
+
function toolResultTokens(message) {
|
|
1724
|
+
return resultTexts(message).reduce((total, text) => total + estimateTokens(text), 0);
|
|
1725
|
+
}
|
|
1726
|
+
function prunedPlaceholder(message, chars) {
|
|
1727
|
+
const { details: _details, ...rest } = message;
|
|
1728
|
+
return {
|
|
1729
|
+
...rest,
|
|
1730
|
+
content: [
|
|
1731
|
+
{
|
|
1732
|
+
type: "text",
|
|
1733
|
+
text: `[Output of ${message.toolName} omitted from context to save space (${chars} characters). Run the tool again if the content is needed.]`
|
|
1734
|
+
}
|
|
1735
|
+
]
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
function planPrune(entries, settings) {
|
|
1739
|
+
const replaced = new Set;
|
|
1740
|
+
for (const entry of entries)
|
|
1741
|
+
for (const edit of entry.edits ?? [])
|
|
1742
|
+
replaced.add(edit.target);
|
|
1743
|
+
const results = entries.filter(isToolResult);
|
|
1744
|
+
const candidates = results.slice(0, Math.max(0, results.length - settings.keepRecent));
|
|
1745
|
+
const edits = [];
|
|
1746
|
+
const targets = [];
|
|
1747
|
+
let savedChars = 0;
|
|
1748
|
+
let savedTokens = 0;
|
|
1749
|
+
for (const entry of candidates) {
|
|
1750
|
+
if (replaced.has(entry.id))
|
|
1751
|
+
continue;
|
|
1752
|
+
const message = entry.model[0];
|
|
1753
|
+
const chars = toolResultChars(message);
|
|
1754
|
+
if (chars < settings.minChars)
|
|
1755
|
+
continue;
|
|
1756
|
+
const replacement = prunedPlaceholder(message, chars);
|
|
1757
|
+
edits.push({ target: entry.id, action: "replace", messages: [replacement] });
|
|
1758
|
+
targets.push(entry.id);
|
|
1759
|
+
savedChars += chars;
|
|
1760
|
+
savedTokens += toolResultTokens(message);
|
|
1761
|
+
}
|
|
1762
|
+
return edits.length === 0 ? undefined : { edits, targets, savedChars, savedTokens };
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
class ContextPruner {
|
|
1766
|
+
#options;
|
|
1767
|
+
#unsubscribe;
|
|
1768
|
+
#tail = Promise.resolve();
|
|
1769
|
+
#closed = false;
|
|
1770
|
+
constructor(options) {
|
|
1771
|
+
this.#options = options;
|
|
1772
|
+
this.#unsubscribe = options.harness.subscribeCommits(({ changes }) => {
|
|
1773
|
+
if (!options.settings().enabled)
|
|
1774
|
+
return;
|
|
1775
|
+
for (const change of changes) {
|
|
1776
|
+
if (change.type !== "entry" || change.value.kind !== "pi.assistant")
|
|
1777
|
+
continue;
|
|
1778
|
+
const message = change.value.model?.[0];
|
|
1779
|
+
if (message?.role !== "assistant" || message.stopReason === "aborted" || message.stopReason === "error")
|
|
1780
|
+
continue;
|
|
1781
|
+
const usage = message.usage;
|
|
1782
|
+
const tokens = usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
1783
|
+
if (tokens <= 0)
|
|
1784
|
+
continue;
|
|
1785
|
+
const { conversationId, id } = change.value;
|
|
1786
|
+
this.#enqueue(() => this.#evaluate(conversationId, id, tokens));
|
|
1787
|
+
}
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
async flush() {
|
|
1791
|
+
await this.#tail;
|
|
1792
|
+
}
|
|
1793
|
+
close() {
|
|
1794
|
+
this.#closed = true;
|
|
1795
|
+
this.#unsubscribe();
|
|
1796
|
+
}
|
|
1797
|
+
#enqueue(work) {
|
|
1798
|
+
if (this.#closed)
|
|
1799
|
+
return;
|
|
1800
|
+
this.#tail = this.#tail.then(work).catch((error) => {
|
|
1801
|
+
if (!this.#closed)
|
|
1802
|
+
this.#options.report(error);
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
async#evaluate(conversationId, entryId, tokens) {
|
|
1806
|
+
const { harness, context } = this.#options;
|
|
1807
|
+
const settings = this.#options.settings();
|
|
1808
|
+
if (!settings.enabled)
|
|
1809
|
+
return;
|
|
1810
|
+
const window2 = await this.#options.windowOf(conversationId);
|
|
1811
|
+
if (window2 <= 0 || tokens / window2 * 100 < settings.triggerPercent)
|
|
1812
|
+
return;
|
|
1813
|
+
const conversation = await harness.conversation(conversationId, context);
|
|
1814
|
+
if (!conversation)
|
|
1815
|
+
return;
|
|
1816
|
+
const view = await conversation.context(context);
|
|
1817
|
+
const plan = planPrune(view.entries, settings);
|
|
1818
|
+
if (!plan)
|
|
1819
|
+
return;
|
|
1820
|
+
if (plan.savedTokens / window2 * 100 < settings.minSavePercent)
|
|
1821
|
+
return;
|
|
1822
|
+
await conversation.submit({
|
|
1823
|
+
type: "write",
|
|
1824
|
+
requestId: `prune:${entryId}`,
|
|
1825
|
+
entry: {
|
|
1826
|
+
kind: PRUNE_ENTRY_KIND,
|
|
1827
|
+
data: { targets: [...plan.targets], savedChars: plan.savedChars },
|
|
1828
|
+
edits: plan.edits
|
|
1829
|
+
}
|
|
1830
|
+
}, context);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
var PRUNE_ENTRY_KIND = "app.neko.context-prune", TOKENS_PER_LATIN_CHAR = 0.3, TOKENS_PER_CJK_CHAR = 0.6, CJK, isToolResult = (entry) => {
|
|
1834
|
+
const message = entry.model?.[0];
|
|
1835
|
+
return entry.kind === "pi.tool-result" && message?.role === "toolResult";
|
|
1836
|
+
}, resultTexts = (message) => message.content.flatMap((block) => block.type === "text" ? [block.text] : []);
|
|
1837
|
+
var init_context_pruning = __esm(() => {
|
|
1838
|
+
CJK = /[\u3000-\u9fff\uac00-\ud7af\uf900-\ufaff\uff00-\uffef]/g;
|
|
1839
|
+
});
|
|
1840
|
+
|
|
1520
1841
|
// packages/core/src/image-metadata.ts
|
|
1521
1842
|
import { defineExtension as defineExtension2, hook, ToolTask } from "@earendil-works/pi-durable";
|
|
1522
1843
|
import sharp from "sharp";
|
|
@@ -2176,7 +2497,7 @@ class JobRegistry {
|
|
|
2176
2497
|
function reportText(job, tail) {
|
|
2177
2498
|
const body = reportTail(tail);
|
|
2178
2499
|
return [
|
|
2179
|
-
`[Background job ${job.jobId} "${job.title}" ${
|
|
2500
|
+
`[Background job ${job.jobId} "${job.title}" ${describe2(job)}]`,
|
|
2180
2501
|
`Command: ${job.command}`,
|
|
2181
2502
|
body ? `Last output (use the job tool's output action with jobId "${job.jobId}" for more):
|
|
2182
2503
|
${body}` : "It produced no output."
|
|
@@ -2355,7 +2676,7 @@ class JobManager {
|
|
|
2355
2676
|
if (kind === "stop") {
|
|
2356
2677
|
const jobId = stringField(args.jobId, "jobId");
|
|
2357
2678
|
const job = await this.stop(io, api.conversationId, jobId, "model", context);
|
|
2358
|
-
return this.#text(`Job ${job.jobId} ${
|
|
2679
|
+
return this.#text(`Job ${job.jobId} ${describe2(job)}.`);
|
|
2359
2680
|
}
|
|
2360
2681
|
if (kind !== "start")
|
|
2361
2682
|
throw new Error("Invalid job arguments: action must be start, output, stop or list.");
|
|
@@ -2383,7 +2704,7 @@ class JobManager {
|
|
|
2383
2704
|
#listText(items) {
|
|
2384
2705
|
if (!items.length)
|
|
2385
2706
|
return "No jobs.";
|
|
2386
|
-
return items.map((job) => `${job.jobId} [${
|
|
2707
|
+
return items.map((job) => `${job.jobId} [${describe2(job)}] "${job.title}" \u2014 ${job.command}`).join(`
|
|
2387
2708
|
`);
|
|
2388
2709
|
}
|
|
2389
2710
|
async start(io, conversationId, cwd, args, context) {
|
|
@@ -2435,7 +2756,7 @@ class JobManager {
|
|
|
2435
2756
|
return this.#render(job, read.text, read.offset, read.dropped);
|
|
2436
2757
|
}
|
|
2437
2758
|
#render(job, text, offset, dropped) {
|
|
2438
|
-
const lines = [`Job ${job.jobId} ${
|
|
2759
|
+
const lines = [`Job ${job.jobId} ${describe2(job)}.`, `Title: ${job.title}`, `Command: ${job.command}`];
|
|
2439
2760
|
if (dropped > 0)
|
|
2440
2761
|
lines.push(`(${dropped} bytes of earlier output were dropped)`);
|
|
2441
2762
|
lines.push(text ? `Output (next offset ${offset}):
|
|
@@ -2500,7 +2821,7 @@ ${text}` : `No new output (next offset ${offset}).`);
|
|
|
2500
2821
|
this.registry.abortAll();
|
|
2501
2822
|
}
|
|
2502
2823
|
}
|
|
2503
|
-
var plain = (value) => JSON.parse(JSON.stringify(value)), statusWord,
|
|
2824
|
+
var plain = (value) => JSON.parse(JSON.stringify(value)), statusWord, describe2 = (job) => {
|
|
2504
2825
|
const parts = [statusWord[job.status]];
|
|
2505
2826
|
if (job.exitCode !== null)
|
|
2506
2827
|
parts.push(`exit code ${job.exitCode}`);
|
|
@@ -3419,21 +3740,22 @@ var init_tool_approvals = __esm(() => {
|
|
|
3419
3740
|
});
|
|
3420
3741
|
});
|
|
3421
3742
|
|
|
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";
|
|
3743
|
+
// packages/core/src/web-search/http.ts
|
|
3425
3744
|
function redact(text, key) {
|
|
3426
3745
|
for (const secret of key ? [key, encodeURIComponent(key)] : [])
|
|
3427
3746
|
text = text.replaceAll(secret, "[redacted]");
|
|
3428
|
-
return text.replace(
|
|
3747
|
+
return text.replace(/\b(?:tvly|sk)-[a-zA-Z0-9_-]+/g, "[redacted]");
|
|
3429
3748
|
}
|
|
3430
3749
|
function record(value) {
|
|
3431
3750
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3432
3751
|
}
|
|
3433
|
-
|
|
3752
|
+
function truncate(text, max) {
|
|
3753
|
+
return text.length > max ? `${text.slice(0, Math.max(0, max - 1))}\u2026` : text;
|
|
3754
|
+
}
|
|
3755
|
+
async function readBounded(response, signal, label) {
|
|
3434
3756
|
if (Number(response.headers.get("content-length")) > MAX_RESPONSE_BYTES) {
|
|
3435
3757
|
await response.body?.cancel();
|
|
3436
|
-
throw new
|
|
3758
|
+
throw new WebToolError(`${label} response exceeds the 2 MiB limit.`);
|
|
3437
3759
|
}
|
|
3438
3760
|
if (!response.body)
|
|
3439
3761
|
return "";
|
|
@@ -3454,7 +3776,7 @@ async function readBounded(response, signal) {
|
|
|
3454
3776
|
return text + decoder.decode();
|
|
3455
3777
|
size += chunk.value.byteLength;
|
|
3456
3778
|
if (size > MAX_RESPONSE_BYTES)
|
|
3457
|
-
throw new
|
|
3779
|
+
throw new WebToolError(`${label} response exceeds the 2 MiB limit.`);
|
|
3458
3780
|
text += decoder.decode(chunk.value, { stream: true });
|
|
3459
3781
|
}
|
|
3460
3782
|
} finally {
|
|
@@ -3463,7 +3785,8 @@ async function readBounded(response, signal) {
|
|
|
3463
3785
|
reader.releaseLock();
|
|
3464
3786
|
}
|
|
3465
3787
|
}
|
|
3466
|
-
async function
|
|
3788
|
+
async function postJson(options) {
|
|
3789
|
+
const { label, context } = options;
|
|
3467
3790
|
let key;
|
|
3468
3791
|
const signal = AbortSignal.any([
|
|
3469
3792
|
AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
@@ -3471,68 +3794,190 @@ async function tavilyPost(url, body, resolveKey, fetchImpl, context) {
|
|
|
3471
3794
|
]);
|
|
3472
3795
|
try {
|
|
3473
3796
|
signal.throwIfAborted();
|
|
3474
|
-
key = await resolveKey();
|
|
3797
|
+
key = await options.resolveKey();
|
|
3475
3798
|
signal.throwIfAborted();
|
|
3476
|
-
const
|
|
3477
|
-
|
|
3478
|
-
headers
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
const text = redact(await readBounded(response, signal), key);
|
|
3799
|
+
const response = await options.fetchImpl(options.url, {
|
|
3800
|
+
method: "POST",
|
|
3801
|
+
headers: { "content-type": "application/json", ...options.headers(key) },
|
|
3802
|
+
body: JSON.stringify(options.body),
|
|
3803
|
+
signal
|
|
3804
|
+
});
|
|
3805
|
+
const text = redact(await readBounded(response, signal, label), key);
|
|
3483
3806
|
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;
|
|
3807
|
+
throw options.httpError(response.status, text, key);
|
|
3808
|
+
return JSON.parse(text);
|
|
3491
3809
|
} catch (error) {
|
|
3492
3810
|
if (context.abortSignal?.aborted)
|
|
3493
|
-
throw new
|
|
3811
|
+
throw new WebToolError("Web request cancelled.");
|
|
3494
3812
|
if (signal.aborted)
|
|
3495
|
-
throw new
|
|
3496
|
-
if (error instanceof
|
|
3813
|
+
throw new WebToolError(`${label} request timed out. Please retry.`);
|
|
3814
|
+
if (error instanceof WebToolError)
|
|
3497
3815
|
throw error;
|
|
3498
|
-
throw new
|
|
3816
|
+
throw new WebToolError(`${label} request failed. Check the connection and credentials, then retry.`);
|
|
3499
3817
|
}
|
|
3500
3818
|
}
|
|
3819
|
+
var REQUEST_TIMEOUT_MS = 30000, MAX_RESPONSE_BYTES, WebToolError;
|
|
3820
|
+
var init_http = __esm(() => {
|
|
3821
|
+
MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
3822
|
+
WebToolError = class WebToolError extends Error {
|
|
3823
|
+
status;
|
|
3824
|
+
constructor(message, status) {
|
|
3825
|
+
super(message);
|
|
3826
|
+
this.status = status;
|
|
3827
|
+
}
|
|
3828
|
+
};
|
|
3829
|
+
});
|
|
3830
|
+
|
|
3831
|
+
// packages/core/src/web-search/deepseek.ts
|
|
3832
|
+
function defaultSleep(ms, signal) {
|
|
3833
|
+
return new Promise((resolve, reject) => {
|
|
3834
|
+
if (signal?.aborted)
|
|
3835
|
+
return reject(signal.reason);
|
|
3836
|
+
const timer = setTimeout(resolve, ms);
|
|
3837
|
+
signal?.addEventListener("abort", () => {
|
|
3838
|
+
clearTimeout(timer);
|
|
3839
|
+
reject(signal.reason);
|
|
3840
|
+
}, { once: true });
|
|
3841
|
+
});
|
|
3842
|
+
}
|
|
3843
|
+
function apiMessage(text) {
|
|
3844
|
+
try {
|
|
3845
|
+
const data = JSON.parse(text);
|
|
3846
|
+
if (record(data) && record(data.error) && typeof data.error.message === "string")
|
|
3847
|
+
return truncate(data.error.message, 300);
|
|
3848
|
+
} catch {}
|
|
3849
|
+
return truncate(text, 300);
|
|
3850
|
+
}
|
|
3851
|
+
function httpError(status, text) {
|
|
3852
|
+
const guidance = "Check the key in /login or the Web settings, or switch the search provider.";
|
|
3853
|
+
if (status === 401)
|
|
3854
|
+
return new WebToolError(`DeepSeek API key is missing or invalid. ${guidance}`, status);
|
|
3855
|
+
if (status === 402)
|
|
3856
|
+
return new WebToolError("DeepSeek account balance is insufficient. Recharge, or switch the search provider to Tavily.", status);
|
|
3857
|
+
if (status === 403)
|
|
3858
|
+
return new WebToolError("DeepSeek blocked this request by its security policy.", status);
|
|
3859
|
+
if (status === 404)
|
|
3860
|
+
return new WebToolError("DeepSeek could not fetch a usable page for this URL.", status);
|
|
3861
|
+
if (status === 429)
|
|
3862
|
+
return new WebToolError("DeepSeek rate limit reached. Retry shortly.", status);
|
|
3863
|
+
if (status === 408 || status >= 500)
|
|
3864
|
+
return new WebToolError(`DeepSeek service error (${status}). Retry shortly.`, status);
|
|
3865
|
+
return new WebToolError(`DeepSeek request failed (${status}): ${apiMessage(text)}`, status);
|
|
3866
|
+
}
|
|
3867
|
+
function dateOnly(value) {
|
|
3868
|
+
return typeof value === "string" && /^\d{4}-\d{2}-\d{2}/.test(value) ? value.slice(0, 10) : undefined;
|
|
3869
|
+
}
|
|
3870
|
+
function createDeepseekProvider(resolveKey, fetchImpl = fetch, options = {}) {
|
|
3871
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
3872
|
+
const requireKey = async () => {
|
|
3873
|
+
const key = await resolveKey();
|
|
3874
|
+
if (!key)
|
|
3875
|
+
throw new WebToolError(DEEPSEEK_MISSING_KEY);
|
|
3876
|
+
return key;
|
|
3877
|
+
};
|
|
3878
|
+
const post = (url, body, context) => postJson({
|
|
3879
|
+
label: "DeepSeek",
|
|
3880
|
+
url,
|
|
3881
|
+
body,
|
|
3882
|
+
fetchImpl,
|
|
3883
|
+
context,
|
|
3884
|
+
resolveKey: requireKey,
|
|
3885
|
+
headers: (key) => ({ authorization: `Bearer ${key}` }),
|
|
3886
|
+
httpError
|
|
3887
|
+
});
|
|
3888
|
+
return {
|
|
3889
|
+
id: "deepseek",
|
|
3890
|
+
async search(query, limit, context) {
|
|
3891
|
+
if (!query.trim())
|
|
3892
|
+
throw new WebToolError("Provide a non-empty query.");
|
|
3893
|
+
if ([...query].length > DEEPSEEK_QUERY_MAX_CHARS)
|
|
3894
|
+
throw new WebToolError(`DeepSeek search queries are limited to ${DEEPSEEK_QUERY_MAX_CHARS} characters. Shorten the query and retry.`);
|
|
3895
|
+
await requireKey();
|
|
3896
|
+
const data = await post(SEARCH_URL, { query, count: limit, need_content: false }, context);
|
|
3897
|
+
if (!record(data) || !Array.isArray(data.results) || !data.results.every(record))
|
|
3898
|
+
throw new WebToolError("Invalid DeepSeek response.");
|
|
3899
|
+
return data.results.slice(0, limit).map((item) => {
|
|
3900
|
+
const date = dateOnly(item.date);
|
|
3901
|
+
return {
|
|
3902
|
+
title: String(item.title ?? "(untitled)"),
|
|
3903
|
+
url: String(item.url ?? ""),
|
|
3904
|
+
text: String(item.summary || item.snippet || ""),
|
|
3905
|
+
...date ? { date } : {}
|
|
3906
|
+
};
|
|
3907
|
+
});
|
|
3908
|
+
},
|
|
3909
|
+
async extract(urls, context) {
|
|
3910
|
+
await requireKey();
|
|
3911
|
+
const outcomes = new Array(urls.length);
|
|
3912
|
+
let next = 0;
|
|
3913
|
+
let fatal;
|
|
3914
|
+
const fetchOne = async (url) => {
|
|
3915
|
+
for (let attempt = 0;; attempt++) {
|
|
3916
|
+
try {
|
|
3917
|
+
const data = await post(FETCH_URL, { url }, context);
|
|
3918
|
+
if (!record(data) || typeof data.content !== "string")
|
|
3919
|
+
throw new WebToolError("Invalid DeepSeek response.");
|
|
3920
|
+
return { url, text: data.content };
|
|
3921
|
+
} catch (error) {
|
|
3922
|
+
const failure = error instanceof WebToolError ? error : new WebToolError("DeepSeek request failed.");
|
|
3923
|
+
if (context.abortSignal?.aborted || wholeCall(failure.status))
|
|
3924
|
+
throw failure;
|
|
3925
|
+
if (!retryable(failure.status) || attempt >= MAX_RETRIES)
|
|
3926
|
+
return { url, error: failure.message };
|
|
3927
|
+
try {
|
|
3928
|
+
await sleep(BACKOFF_MS[attempt] ?? 750, context.abortSignal);
|
|
3929
|
+
} catch {
|
|
3930
|
+
throw new WebToolError("Web request cancelled.");
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
};
|
|
3935
|
+
const worker = async () => {
|
|
3936
|
+
while (!fatal) {
|
|
3937
|
+
const index = next++;
|
|
3938
|
+
if (index >= urls.length)
|
|
3939
|
+
return;
|
|
3940
|
+
try {
|
|
3941
|
+
outcomes[index] = await fetchOne(urls[index]);
|
|
3942
|
+
} catch (error) {
|
|
3943
|
+
fatal ??= error instanceof WebToolError ? error : new WebToolError("DeepSeek request failed.");
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
};
|
|
3947
|
+
await Promise.all(Array.from({ length: Math.min(FETCH_CONCURRENCY, urls.length) }, worker));
|
|
3948
|
+
if (fatal)
|
|
3949
|
+
throw fatal;
|
|
3950
|
+
return outcomes;
|
|
3951
|
+
}
|
|
3952
|
+
};
|
|
3953
|
+
}
|
|
3954
|
+
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;
|
|
3955
|
+
var init_deepseek = __esm(() => {
|
|
3956
|
+
init_http();
|
|
3957
|
+
BACKOFF_MS = [250, 750];
|
|
3958
|
+
});
|
|
3959
|
+
|
|
3960
|
+
// packages/core/src/web-search/index.ts
|
|
3961
|
+
import { Type as Type4 } from "@earendil-works/pi-ai";
|
|
3962
|
+
import { defineExtension as defineExtension8, defineTool as defineTool4 } from "@earendil-works/pi-durable";
|
|
3501
3963
|
function toolError(error) {
|
|
3502
3964
|
return {
|
|
3503
3965
|
output: [
|
|
3504
|
-
{ type: "text", text: error instanceof
|
|
3966
|
+
{ type: "text", text: error instanceof WebToolError ? error.message : "Web tool failed. Please retry." }
|
|
3505
3967
|
],
|
|
3506
3968
|
isError: true
|
|
3507
3969
|
};
|
|
3508
3970
|
}
|
|
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
3971
|
function content(text) {
|
|
3519
3972
|
return { output: [{ type: "text", text }] };
|
|
3520
3973
|
}
|
|
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) {
|
|
3974
|
+
function createWebSearchTools(resolveProvider) {
|
|
3530
3975
|
return defineExtension8({
|
|
3531
3976
|
name: "neko-web-search",
|
|
3532
3977
|
tools: [
|
|
3533
3978
|
defineTool4({
|
|
3534
3979
|
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.",
|
|
3980
|
+
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
3981
|
parameters: Type4.Object({
|
|
3537
3982
|
query: Type4.String({ minLength: 1, maxLength: 4096, description: "The search query" }),
|
|
3538
3983
|
maxResults: Type4.Optional(Type4.Number({ description: `Maximum number of results (1-${MAX_RESULTS}, default ${DEFAULT_RESULTS})` }))
|
|
@@ -3540,18 +3985,19 @@ function createWebSearchTools(resolveKey, fetchImpl = fetch) {
|
|
|
3540
3985
|
async execute({ query, maxResults }, _api, context) {
|
|
3541
3986
|
try {
|
|
3542
3987
|
if (!query.trim() || query.length > 4096)
|
|
3543
|
-
throw new
|
|
3988
|
+
throw new WebToolError("Provide a non-empty query of at most 4096 characters.");
|
|
3544
3989
|
if (maxResults !== undefined && !Number.isFinite(maxResults))
|
|
3545
|
-
throw new
|
|
3990
|
+
throw new WebToolError("maxResults must be a finite number.");
|
|
3546
3991
|
const limit = Math.max(1, Math.min(MAX_RESULTS, Math.trunc(maxResults ?? DEFAULT_RESULTS)));
|
|
3547
|
-
const
|
|
3548
|
-
const results =
|
|
3992
|
+
const provider = await resolveProvider(context);
|
|
3993
|
+
const results = (await provider.search(query, limit, context)).slice(0, limit);
|
|
3549
3994
|
const budget = Math.floor(MAX_TOTAL_CHARS / Math.max(1, results.length)) - 2;
|
|
3550
3995
|
return content(results.map((item, index) => {
|
|
3551
|
-
const header = `${index + 1}. ${truncate(
|
|
3552
|
-
${truncate(
|
|
3553
|
-
|
|
3554
|
-
|
|
3996
|
+
const header = `${index + 1}. ${truncate(item.title, 200)}
|
|
3997
|
+
${truncate(item.url, 300)}
|
|
3998
|
+
${item.date ? `Published: ${item.date}
|
|
3999
|
+
` : ""}`;
|
|
4000
|
+
return header + truncate(item.text, Math.min(MAX_SOURCE_CHARS, budget - header.length));
|
|
3555
4001
|
}).join(`
|
|
3556
4002
|
|
|
3557
4003
|
`) || "No results.");
|
|
@@ -3573,28 +4019,25 @@ ${truncate(String(item.url ?? ""), 300)}
|
|
|
3573
4019
|
async execute({ urls }, _api, context) {
|
|
3574
4020
|
try {
|
|
3575
4021
|
if (!urls.length || urls.length > MAX_URLS)
|
|
3576
|
-
throw new
|
|
4022
|
+
throw new WebToolError(`Provide between 1 and ${MAX_URLS} URLs.`);
|
|
3577
4023
|
for (const url of urls) {
|
|
3578
4024
|
let parsed;
|
|
3579
4025
|
try {
|
|
3580
4026
|
parsed = new URL(url);
|
|
3581
4027
|
} catch {
|
|
3582
|
-
throw new
|
|
4028
|
+
throw new WebToolError("Provide valid http(s) URLs.");
|
|
3583
4029
|
}
|
|
3584
4030
|
if (url.length > 2048 || !["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password)
|
|
3585
|
-
throw new
|
|
4031
|
+
throw new WebToolError("Provide http(s) URLs of at most 2048 characters without embedded credentials.");
|
|
3586
4032
|
}
|
|
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);
|
|
4033
|
+
const provider = await resolveProvider(context);
|
|
4034
|
+
const outcomes = await provider.extract(urls, context);
|
|
3592
4035
|
const budget = Math.floor(MAX_TOTAL_CHARS / urls.length) - 2;
|
|
3593
4036
|
return content(urls.map((url, index) => {
|
|
3594
4037
|
const header = `## ${index + 1}. ${truncate(url, 300)}
|
|
3595
4038
|
`;
|
|
3596
|
-
const
|
|
3597
|
-
const text =
|
|
4039
|
+
const outcome = outcomes[index];
|
|
4040
|
+
const text = outcome && "text" in outcome ? outcome.text : `Extraction failed: ${outcome?.error ?? "No result returned for this URL"}`;
|
|
3598
4041
|
return header + truncate(text, Math.min(MAX_SOURCE_CHARS, budget - header.length));
|
|
3599
4042
|
}).join(`
|
|
3600
4043
|
|
|
@@ -3607,11 +4050,75 @@ ${truncate(String(item.url ?? ""), 300)}
|
|
|
3607
4050
|
]
|
|
3608
4051
|
});
|
|
3609
4052
|
}
|
|
3610
|
-
var
|
|
4053
|
+
var MAX_URLS = 20, MAX_RESULTS = 10, DEFAULT_RESULTS = 5, MAX_SOURCE_CHARS = 2000, MAX_TOTAL_CHARS = 12000;
|
|
3611
4054
|
var init_web_search = __esm(() => {
|
|
3612
|
-
|
|
3613
|
-
|
|
4055
|
+
init_http();
|
|
4056
|
+
init_http();
|
|
4057
|
+
});
|
|
4058
|
+
|
|
4059
|
+
// packages/core/src/web-search/tavily.ts
|
|
4060
|
+
function rows(data, field) {
|
|
4061
|
+
const value = data[field];
|
|
4062
|
+
if (!Array.isArray(value) || !value.every(record))
|
|
4063
|
+
throw new WebToolError("Invalid Tavily response.");
|
|
4064
|
+
return value;
|
|
4065
|
+
}
|
|
4066
|
+
function urlKey(value) {
|
|
4067
|
+
const text = String(value);
|
|
4068
|
+
try {
|
|
4069
|
+
return new URL(text).href;
|
|
4070
|
+
} catch {
|
|
4071
|
+
return text;
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
function createTavilyProvider(resolveKey, fetchImpl = fetch) {
|
|
4075
|
+
const post = async (url, body, context) => {
|
|
4076
|
+
const data = await postJson({
|
|
4077
|
+
label: "Tavily",
|
|
4078
|
+
url,
|
|
4079
|
+
body,
|
|
4080
|
+
fetchImpl,
|
|
4081
|
+
context,
|
|
4082
|
+
resolveKey,
|
|
4083
|
+
headers: (key) => key ? { authorization: `Bearer ${key}` } : { "x-tavily-access-mode": "keyless" },
|
|
4084
|
+
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)
|
|
4085
|
+
});
|
|
4086
|
+
if (!record(data))
|
|
4087
|
+
throw new WebToolError("Invalid Tavily response.");
|
|
4088
|
+
if (data.error)
|
|
4089
|
+
throw new WebToolError(`Tavily request failed: ${truncate(JSON.stringify(data.error), 500)}`);
|
|
4090
|
+
return data;
|
|
4091
|
+
};
|
|
4092
|
+
return {
|
|
4093
|
+
id: "tavily",
|
|
4094
|
+
async search(query, limit, context) {
|
|
4095
|
+
const data = await post(SEARCH_URL2, { query, max_results: limit, search_depth: "basic" }, context);
|
|
4096
|
+
return rows(data, "results").slice(0, limit).map((item) => ({
|
|
4097
|
+
title: String(item.title ?? "(untitled)"),
|
|
4098
|
+
url: String(item.url ?? ""),
|
|
4099
|
+
text: String(item.content ?? "")
|
|
4100
|
+
}));
|
|
4101
|
+
},
|
|
4102
|
+
async extract(urls, context) {
|
|
4103
|
+
const data = await post(EXTRACT_URL, { urls, format: "markdown" }, context);
|
|
4104
|
+
const results = rows(data, "results");
|
|
4105
|
+
const failed = data.failed_results === undefined ? [] : rows(data, "failed_results");
|
|
4106
|
+
const byUrl = (items) => new Map(items.map((item) => [urlKey(item.url), item]));
|
|
4107
|
+
const successes = byUrl(results);
|
|
4108
|
+
const failures = byUrl(failed);
|
|
4109
|
+
return urls.map((url) => {
|
|
4110
|
+
const result = successes.get(urlKey(url));
|
|
4111
|
+
if (result)
|
|
4112
|
+
return { url, text: String(result.raw_content ?? "") };
|
|
4113
|
+
const failure = failures.get(urlKey(url));
|
|
4114
|
+
return { url, error: String(failure?.error ?? "No result returned for this URL") };
|
|
4115
|
+
});
|
|
4116
|
+
}
|
|
3614
4117
|
};
|
|
4118
|
+
}
|
|
4119
|
+
var SEARCH_URL2 = "https://api.tavily.com/search", EXTRACT_URL = "https://api.tavily.com/extract";
|
|
4120
|
+
var init_tavily = __esm(() => {
|
|
4121
|
+
init_http();
|
|
3615
4122
|
});
|
|
3616
4123
|
|
|
3617
4124
|
// packages/core/src/workspace-files.ts
|
|
@@ -4034,12 +4541,21 @@ async function createNekoRuntime(options = {}) {
|
|
|
4034
4541
|
registry.install(createCodingTools({ images: await createNodePhotonImages() }));
|
|
4035
4542
|
registry.install(ImageDetails);
|
|
4036
4543
|
registry.install(AgentFileSearch);
|
|
4037
|
-
|
|
4544
|
+
const tavily = createTavilyProvider(async () => {
|
|
4038
4545
|
const saved = await effectiveCredentials.read("tavily");
|
|
4039
4546
|
if (saved?.type === "api_key" && saved.key)
|
|
4040
4547
|
return saved.key;
|
|
4041
4548
|
return process.env.TAVILY_API_KEY || undefined;
|
|
4042
|
-
})
|
|
4549
|
+
});
|
|
4550
|
+
const deepseekApiKey = async () => {
|
|
4551
|
+
const saved = await effectiveCredentials.read("deepseek");
|
|
4552
|
+
if (saved?.type === "api_key" && saved.key)
|
|
4553
|
+
return saved.key;
|
|
4554
|
+
return process.env.DEEPSEEK_API_KEY || undefined;
|
|
4555
|
+
};
|
|
4556
|
+
const deepseek = createDeepseekProvider(deepseekApiKey);
|
|
4557
|
+
let searchProvider = options.searchProvider ?? DEFAULT_SEARCH_PROVIDER;
|
|
4558
|
+
registry.install(createWebSearchTools(async () => searchProvider === "deepseek" ? deepseek : tavily));
|
|
4043
4559
|
const loadResources = createResourceLoader(options.promptHome);
|
|
4044
4560
|
const resourceSnapshots = createResourceSnapshots(loadResources, (id, requestId) => storage.submissionByRequest(id, requestId, context));
|
|
4045
4561
|
registry.install(createNekoPrompt(resourceSnapshots.current));
|
|
@@ -4053,9 +4569,16 @@ async function createNekoRuntime(options = {}) {
|
|
|
4053
4569
|
let approvals;
|
|
4054
4570
|
const approvalExtension = createApprovalExtension(() => approvals);
|
|
4055
4571
|
registry.install(approvalExtension);
|
|
4572
|
+
let savedContext = emptySavedContextSettings();
|
|
4573
|
+
let contextState = resolveContextSettings(options.contextSettings, savedContext);
|
|
4056
4574
|
const harness = await Harness.open(storage, {
|
|
4057
4575
|
models,
|
|
4058
4576
|
registry,
|
|
4577
|
+
settings: {
|
|
4578
|
+
get compaction() {
|
|
4579
|
+
return compactionPolicy(contextState, models.getModels().map((model) => model.contextWindow));
|
|
4580
|
+
}
|
|
4581
|
+
},
|
|
4059
4582
|
onReport: options.onReport,
|
|
4060
4583
|
conversationCreated: async (tx, record) => {
|
|
4061
4584
|
(await tx.doc(SessionMetadataDoc, record.id)).createdAt = Date.now();
|
|
@@ -4097,6 +4620,21 @@ async function createNekoRuntime(options = {}) {
|
|
|
4097
4620
|
cwd: defaultCwd
|
|
4098
4621
|
}
|
|
4099
4622
|
});
|
|
4623
|
+
const savedSearch = (await harness.snapshot(SearchSettingsDoc, conversation.id, context))?.provider;
|
|
4624
|
+
if (isSearchProvider(savedSearch))
|
|
4625
|
+
searchProvider = savedSearch;
|
|
4626
|
+
savedContext = sanitizeSavedContextSettings(await harness.snapshot(ContextSettingsDoc, conversation.id, context));
|
|
4627
|
+
contextState = resolveContextSettings(options.contextSettings, savedContext);
|
|
4628
|
+
const persistContext = async (next) => {
|
|
4629
|
+
await harness.commit(async (tx) => {
|
|
4630
|
+
const doc = await tx.doc(ContextSettingsDoc, conversation.id);
|
|
4631
|
+
doc.compaction = next.compaction;
|
|
4632
|
+
doc.pruning = next.pruning;
|
|
4633
|
+
}, context);
|
|
4634
|
+
savedContext = next;
|
|
4635
|
+
contextState = resolveContextSettings(options.contextSettings, savedContext);
|
|
4636
|
+
return contextState;
|
|
4637
|
+
};
|
|
4100
4638
|
const registerWorkspace = async (path) => {
|
|
4101
4639
|
await harness.commit(async (tx) => {
|
|
4102
4640
|
const doc = await tx.doc(WorkspacesDoc, conversation.id);
|
|
@@ -4133,10 +4671,22 @@ async function createNekoRuntime(options = {}) {
|
|
|
4133
4671
|
for (const record of existing)
|
|
4134
4672
|
if (!record.owner && !(await harness.snapshot(ArchivedDoc, record.id, context))?.archived)
|
|
4135
4673
|
await metadata.restore(record.id);
|
|
4674
|
+
const pruner = new ContextPruner({
|
|
4675
|
+
harness,
|
|
4676
|
+
context,
|
|
4677
|
+
settings: () => contextState.effective.pruning,
|
|
4678
|
+
windowOf: async (id) => {
|
|
4679
|
+
const target = await harness.conversation(id, context);
|
|
4680
|
+
const model = (await target?.agent(context))?.model;
|
|
4681
|
+
return model && models.getModel(model.provider, model.modelId)?.contextWindow || 0;
|
|
4682
|
+
},
|
|
4683
|
+
report: (error) => options.onReport?.(error)
|
|
4684
|
+
});
|
|
4136
4685
|
let closing;
|
|
4137
4686
|
const cleanup = () => {
|
|
4138
4687
|
if (!closing)
|
|
4139
4688
|
closing = (async () => {
|
|
4689
|
+
pruner.close();
|
|
4140
4690
|
jobs.shutdown();
|
|
4141
4691
|
await workspaceFiles.close();
|
|
4142
4692
|
await metadata.close();
|
|
@@ -4503,6 +5053,26 @@ async function createNekoRuntime(options = {}) {
|
|
|
4503
5053
|
source: runtimeKeys.has(spec.id) ? "runtime" : savedConfigured ? "saved" : environmentConfigured ? "environment" : spec.keyless ? "keyless" : "none"
|
|
4504
5054
|
};
|
|
4505
5055
|
},
|
|
5056
|
+
async contextSettings() {
|
|
5057
|
+
return contextState;
|
|
5058
|
+
},
|
|
5059
|
+
async setContextSettings(patch) {
|
|
5060
|
+
return persistContext(applyContextPatch(savedContext, parseContextSettingsPatch(patch)));
|
|
5061
|
+
},
|
|
5062
|
+
async resetContextSettings() {
|
|
5063
|
+
return persistContext(emptySavedContextSettings());
|
|
5064
|
+
},
|
|
5065
|
+
async searchSettings() {
|
|
5066
|
+
return { selected: searchProvider };
|
|
5067
|
+
},
|
|
5068
|
+
async setSearchProvider(provider) {
|
|
5069
|
+
if (!isSearchProvider(provider))
|
|
5070
|
+
throw new Error("Unknown search provider.");
|
|
5071
|
+
await harness.commit(async (tx) => {
|
|
5072
|
+
(await tx.doc(SearchSettingsDoc, conversation.id)).provider = provider;
|
|
5073
|
+
}, context);
|
|
5074
|
+
searchProvider = provider;
|
|
5075
|
+
},
|
|
4506
5076
|
async saveCredential(provider, key) {
|
|
4507
5077
|
const spec = credentialProvider(provider);
|
|
4508
5078
|
if (typeof key !== "string" || !key.trim() || key.length > 4096 || Array.from(key).some((char) => char.charCodeAt(0) < 32))
|
|
@@ -4576,10 +5146,12 @@ async function createNekoRuntime(options = {}) {
|
|
|
4576
5146
|
}
|
|
4577
5147
|
};
|
|
4578
5148
|
}
|
|
4579
|
-
var DEFAULT_MODEL, DEFAULT_STORAGE_PATH, ArchivedDoc, WorkspacesDoc, ActivityDoc;
|
|
5149
|
+
var DEFAULT_MODEL, DEFAULT_STORAGE_PATH, ArchivedDoc, WorkspacesDoc, SearchSettingsDoc, ContextSettingsDoc, ActivityDoc;
|
|
4580
5150
|
var init_runtime = __esm(() => {
|
|
4581
5151
|
init_agent_file_search();
|
|
4582
5152
|
init_compaction();
|
|
5153
|
+
init_context_pruning();
|
|
5154
|
+
init_context_settings();
|
|
4583
5155
|
init_credentials();
|
|
4584
5156
|
init_image_metadata();
|
|
4585
5157
|
init_image_prompt();
|
|
@@ -4591,11 +5163,14 @@ var init_runtime = __esm(() => {
|
|
|
4591
5163
|
init_invoke();
|
|
4592
5164
|
init_prompt();
|
|
4593
5165
|
init_resources();
|
|
5166
|
+
init_search_providers();
|
|
4594
5167
|
init_session_metadata();
|
|
4595
5168
|
init_subagent_usage();
|
|
4596
5169
|
init_subagents();
|
|
4597
5170
|
init_tool_approvals();
|
|
5171
|
+
init_deepseek();
|
|
4598
5172
|
init_web_search();
|
|
5173
|
+
init_tavily();
|
|
4599
5174
|
init_workspace();
|
|
4600
5175
|
init_workspace_files();
|
|
4601
5176
|
DEFAULT_MODEL = { provider: "deepseek", modelId: "deepseek-flash" };
|
|
@@ -4616,6 +5191,22 @@ var init_runtime = __esm(() => {
|
|
|
4616
5191
|
fork: "initial",
|
|
4617
5192
|
initial: () => ({ items: [] })
|
|
4618
5193
|
});
|
|
5194
|
+
SearchSettingsDoc = defineDoc5({
|
|
5195
|
+
kind: "app.neko.search-settings",
|
|
5196
|
+
version: 1,
|
|
5197
|
+
scope: "conversation",
|
|
5198
|
+
history: "latest",
|
|
5199
|
+
fork: "initial",
|
|
5200
|
+
initial: () => ({ provider: null })
|
|
5201
|
+
});
|
|
5202
|
+
ContextSettingsDoc = defineDoc5({
|
|
5203
|
+
kind: "app.neko.context-settings",
|
|
5204
|
+
version: 1,
|
|
5205
|
+
scope: "conversation",
|
|
5206
|
+
history: "latest",
|
|
5207
|
+
fork: "initial",
|
|
5208
|
+
initial: emptySavedContextSettings
|
|
5209
|
+
});
|
|
4619
5210
|
ActivityDoc = defineDoc5({
|
|
4620
5211
|
kind: "app.neko.activity",
|
|
4621
5212
|
version: 1,
|
|
@@ -4641,12 +5232,14 @@ function mergeTranscriptItems(previous, incoming) {
|
|
|
4641
5232
|
// packages/core/src/index.ts
|
|
4642
5233
|
var init_src = __esm(() => {
|
|
4643
5234
|
init_config();
|
|
5235
|
+
init_context_settings();
|
|
4644
5236
|
init_credentials();
|
|
4645
5237
|
init_images();
|
|
4646
5238
|
init_jobs();
|
|
4647
5239
|
init_projection();
|
|
4648
5240
|
init_frontmatter();
|
|
4649
5241
|
init_runtime();
|
|
5242
|
+
init_search_providers();
|
|
4650
5243
|
init_session_metadata();
|
|
4651
5244
|
init_subagent_usage();
|
|
4652
5245
|
init_subagents();
|
|
@@ -4763,7 +5356,7 @@ function relativeTime(at, now, language = "en") {
|
|
|
4763
5356
|
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
5357
|
|
|
4765
5358
|
// packages/gateway/src/contract.ts
|
|
4766
|
-
import { method, remote } from "@earendil-works/chord";
|
|
5359
|
+
import { hostNamed, method, remote } from "@earendil-works/chord";
|
|
4767
5360
|
import { Type as Type5 } from "typebox";
|
|
4768
5361
|
function contract(name, def) {
|
|
4769
5362
|
const variants = Object.fromEntries(Object.entries(def.variants).map(([key, value]) => [
|
|
@@ -4773,9 +5366,9 @@ function contract(name, def) {
|
|
|
4773
5366
|
methods: Object.fromEntries(Object.entries(value.methods ?? {}).map(([member, [args, result]]) => [member, method(args, result)]))
|
|
4774
5367
|
}
|
|
4775
5368
|
]));
|
|
4776
|
-
return remote(name, { ...def, variants });
|
|
5369
|
+
return remote(name, { ...def, host: hostNamed(GATEWAY_HOST), variants });
|
|
4777
5370
|
}
|
|
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;
|
|
5371
|
+
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, 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
5372
|
var init_contract = __esm(() => {
|
|
4780
5373
|
str = Type5.String();
|
|
4781
5374
|
num = Type5.Number();
|
|
@@ -5130,6 +5723,77 @@ var init_contract = __esm(() => {
|
|
|
5130
5723
|
}
|
|
5131
5724
|
}
|
|
5132
5725
|
});
|
|
5726
|
+
SearchSettingsStateSchema = Type5.Object({
|
|
5727
|
+
revision: integer,
|
|
5728
|
+
selected: Type5.Enum(["tavily", "deepseek"])
|
|
5729
|
+
});
|
|
5730
|
+
NekoSearchSettings = contract("neko.search-settings", {
|
|
5731
|
+
version: 1,
|
|
5732
|
+
input: Type5.Object({}, { additionalProperties: false }),
|
|
5733
|
+
variants: {
|
|
5734
|
+
ready: {
|
|
5735
|
+
state: { state: SearchSettingsStateSchema },
|
|
5736
|
+
methods: {
|
|
5737
|
+
set: rpc(Type5.Object({ provider: Type5.Enum(["tavily", "deepseek"]) }, { additionalProperties: false }), nil)
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
}
|
|
5741
|
+
});
|
|
5742
|
+
contextSource = Type5.Enum(["default", "config", "saved"]);
|
|
5743
|
+
ContextSettingsStateSchema = Type5.Object({
|
|
5744
|
+
revision: integer,
|
|
5745
|
+
effective: Type5.Object({
|
|
5746
|
+
compaction: Type5.Object({
|
|
5747
|
+
enabled: bool,
|
|
5748
|
+
triggerPercent: Type5.Union([integer, nil]),
|
|
5749
|
+
keepRecentTokens: integer
|
|
5750
|
+
}),
|
|
5751
|
+
pruning: Type5.Object({
|
|
5752
|
+
enabled: bool,
|
|
5753
|
+
triggerPercent: integer,
|
|
5754
|
+
keepRecent: integer,
|
|
5755
|
+
minChars: integer,
|
|
5756
|
+
minSavePercent: integer
|
|
5757
|
+
})
|
|
5758
|
+
}),
|
|
5759
|
+
sources: Type5.Object({
|
|
5760
|
+
compaction: Type5.Object({ enabled: contextSource, triggerPercent: contextSource, keepRecentTokens: contextSource }),
|
|
5761
|
+
pruning: Type5.Object({
|
|
5762
|
+
enabled: contextSource,
|
|
5763
|
+
triggerPercent: contextSource,
|
|
5764
|
+
keepRecent: contextSource,
|
|
5765
|
+
minChars: contextSource,
|
|
5766
|
+
minSavePercent: contextSource
|
|
5767
|
+
})
|
|
5768
|
+
})
|
|
5769
|
+
});
|
|
5770
|
+
ContextSettingsPatchSchema = Type5.Object({
|
|
5771
|
+
compaction: Type5.Optional(Type5.Object({
|
|
5772
|
+
enabled: Type5.Optional(nullable(bool)),
|
|
5773
|
+
triggerPercent: Type5.Optional(nullable(integer)),
|
|
5774
|
+
keepRecentTokens: Type5.Optional(nullable(integer))
|
|
5775
|
+
}, { additionalProperties: false })),
|
|
5776
|
+
pruning: Type5.Optional(Type5.Object({
|
|
5777
|
+
enabled: Type5.Optional(nullable(bool)),
|
|
5778
|
+
triggerPercent: Type5.Optional(nullable(integer)),
|
|
5779
|
+
keepRecent: Type5.Optional(nullable(integer)),
|
|
5780
|
+
minChars: Type5.Optional(nullable(integer)),
|
|
5781
|
+
minSavePercent: Type5.Optional(nullable(integer))
|
|
5782
|
+
}, { additionalProperties: false }))
|
|
5783
|
+
}, { additionalProperties: false });
|
|
5784
|
+
NekoContextSettings = contract("neko.context-settings", {
|
|
5785
|
+
version: 1,
|
|
5786
|
+
input: Type5.Object({}, { additionalProperties: false }),
|
|
5787
|
+
variants: {
|
|
5788
|
+
ready: {
|
|
5789
|
+
state: { state: ContextSettingsStateSchema },
|
|
5790
|
+
methods: {
|
|
5791
|
+
set: rpc(ContextSettingsPatchSchema, nil),
|
|
5792
|
+
reset: rpc(Type5.Object({}, { additionalProperties: false }), nil)
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
}
|
|
5796
|
+
});
|
|
5133
5797
|
FileCandidatesSchema = Type5.Object({
|
|
5134
5798
|
entries: Type5.Array(Type5.Object({ name: str, path: str, kind: Type5.Enum(["file", "directory"]) }), { maxItems: 100 }),
|
|
5135
5799
|
limited: bool,
|
|
@@ -5183,18 +5847,24 @@ var init_contract = __esm(() => {
|
|
|
5183
5847
|
protocolVersion: Type5.Integer({ minimum: 1 }),
|
|
5184
5848
|
latestVersion: nullableString,
|
|
5185
5849
|
checkedAt: Type5.Union([integer, nil]),
|
|
5186
|
-
checkState: Type5.Enum(["unknown", "checking", "ok", "failed", "development"])
|
|
5850
|
+
checkState: Type5.Enum(["unknown", "checking", "ok", "failed", "development"]),
|
|
5851
|
+
serverId: str
|
|
5187
5852
|
});
|
|
5188
5853
|
NekoApplicationInfo = contract("neko.application-info", {
|
|
5189
5854
|
version: 1,
|
|
5190
5855
|
input: Type5.Object({}),
|
|
5191
|
-
variants: {
|
|
5856
|
+
variants: {
|
|
5857
|
+
ready: { state: { info: ApplicationInfoSchema }, methods: { check: rpc(nil, ApplicationInfoSchema) } }
|
|
5858
|
+
}
|
|
5192
5859
|
});
|
|
5193
5860
|
});
|
|
5194
5861
|
|
|
5195
5862
|
// packages/gateway/src/link.ts
|
|
5196
|
-
import {
|
|
5197
|
-
|
|
5863
|
+
import {
|
|
5864
|
+
createHost,
|
|
5865
|
+
webSocketLink
|
|
5866
|
+
} from "@earendil-works/chord";
|
|
5867
|
+
function boundedWebSocketLink(socket, max = MAX_LINK_BYTES, options) {
|
|
5198
5868
|
let queued = 0, incoming = 0, receiving = false;
|
|
5199
5869
|
let native;
|
|
5200
5870
|
const bridge = {
|
|
@@ -5210,18 +5880,22 @@ function boundedWebSocketLink(socket, max = MAX_LINK_BYTES) {
|
|
|
5210
5880
|
},
|
|
5211
5881
|
close: (code, reason) => socket.close(code, reason),
|
|
5212
5882
|
addEventListener(type, handler) {
|
|
5213
|
-
if (type === "message")
|
|
5214
|
-
|
|
5215
|
-
const bytes = size(
|
|
5883
|
+
if (type === "message") {
|
|
5884
|
+
const deliver = (data) => {
|
|
5885
|
+
const bytes = size(data);
|
|
5216
5886
|
if (!receiving)
|
|
5217
5887
|
incoming += bytes;
|
|
5218
5888
|
if (bytes > max || incoming > max) {
|
|
5219
5889
|
native.close("frame limit exceeded");
|
|
5220
5890
|
return;
|
|
5221
5891
|
}
|
|
5222
|
-
handler(
|
|
5223
|
-
}
|
|
5224
|
-
|
|
5892
|
+
handler({ data });
|
|
5893
|
+
};
|
|
5894
|
+
if (socket.on)
|
|
5895
|
+
socket.on("message", (data, isBinary) => deliver(isBinary ? asBytes(data) : new TextDecoder().decode(asBytes(data))));
|
|
5896
|
+
else
|
|
5897
|
+
socket.addEventListener("message", (event) => deliver(typeof event.data === "string" || event.data instanceof Uint8Array ? event.data : String(event.data)));
|
|
5898
|
+
} else
|
|
5225
5899
|
socket.addEventListener(type, () => {
|
|
5226
5900
|
if (type === "open")
|
|
5227
5901
|
queued = 0;
|
|
@@ -5233,7 +5907,7 @@ function boundedWebSocketLink(socket, max = MAX_LINK_BYTES) {
|
|
|
5233
5907
|
});
|
|
5234
5908
|
}
|
|
5235
5909
|
};
|
|
5236
|
-
native = webSocketLink(bridge);
|
|
5910
|
+
native = webSocketLink(bridge, options);
|
|
5237
5911
|
socket.addEventListener("error", () => native.close("socket error"));
|
|
5238
5912
|
return {
|
|
5239
5913
|
get closed() {
|
|
@@ -5253,7 +5927,7 @@ function boundedWebSocketLink(socket, max = MAX_LINK_BYTES) {
|
|
|
5253
5927
|
}
|
|
5254
5928
|
if (socket.readyState === 0)
|
|
5255
5929
|
queued += bytes;
|
|
5256
|
-
native.send(frame);
|
|
5930
|
+
return native.send(frame);
|
|
5257
5931
|
},
|
|
5258
5932
|
receive(handler) {
|
|
5259
5933
|
receiving = true;
|
|
@@ -5264,49 +5938,19 @@ function boundedWebSocketLink(socket, max = MAX_LINK_BYTES) {
|
|
|
5264
5938
|
close: (reason) => native.close(reason)
|
|
5265
5939
|
};
|
|
5266
5940
|
}
|
|
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
|
-
}
|
|
5941
|
+
function createClientHost(link, options = {}) {
|
|
5942
|
+
return createHost({
|
|
5943
|
+
kind: "client",
|
|
5944
|
+
self: { kind: "client" },
|
|
5945
|
+
upstream: {
|
|
5946
|
+
link,
|
|
5947
|
+
principal: { kind: "server" },
|
|
5948
|
+
...options.onProtocolMismatch ? { onProtocolMismatch: options.onProtocolMismatch } : {}
|
|
5304
5949
|
},
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
};
|
|
5950
|
+
onError: () => {}
|
|
5951
|
+
});
|
|
5308
5952
|
}
|
|
5309
|
-
var MAX_LINK_BYTES, size = (
|
|
5953
|
+
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
5954
|
var init_link = __esm(() => {
|
|
5311
5955
|
MAX_LINK_BYTES = 16 * 1024 * 1024;
|
|
5312
5956
|
});
|
|
@@ -5494,35 +6138,14 @@ class ApprovalQueue {
|
|
|
5494
6138
|
}
|
|
5495
6139
|
|
|
5496
6140
|
// 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
|
-
});
|
|
6141
|
+
async function assertGatewayIdentity(host, serverId, signal) {
|
|
6142
|
+
const application = await host.get(NekoApplicationInfo, {}, signal ? { signal } : {});
|
|
6143
|
+
try {
|
|
6144
|
+
if (application.info.value.serverId !== serverId)
|
|
6145
|
+
throw new GatewayIdentityError;
|
|
6146
|
+
} finally {
|
|
6147
|
+
application.release();
|
|
6148
|
+
}
|
|
5526
6149
|
}
|
|
5527
6150
|
function jobsClient(chat) {
|
|
5528
6151
|
const supported = () => chat.view.value?.jobs !== undefined;
|
|
@@ -5546,16 +6169,37 @@ function jobsClient(chat) {
|
|
|
5546
6169
|
};
|
|
5547
6170
|
}
|
|
5548
6171
|
function createGatewayConnection(link, serverId) {
|
|
5549
|
-
|
|
5550
|
-
const
|
|
6172
|
+
let protocolMismatch = false;
|
|
6173
|
+
const linkClosed = new AbortController;
|
|
6174
|
+
let closeReason = "";
|
|
6175
|
+
link.onClose((reason) => {
|
|
6176
|
+
closeReason = reason;
|
|
6177
|
+
linkClosed.abort(new Error(`Gateway connection closed: ${reason}`));
|
|
6178
|
+
});
|
|
6179
|
+
const host = createClientHost(link, {
|
|
6180
|
+
onProtocolMismatch: () => {
|
|
6181
|
+
protocolMismatch = true;
|
|
6182
|
+
}
|
|
6183
|
+
});
|
|
6184
|
+
const get = (type) => host.hold(type, {});
|
|
5551
6185
|
const application = get(NekoApplicationInfo);
|
|
5552
6186
|
const keep = [application.info.subscribe(() => {})];
|
|
5553
6187
|
let business;
|
|
5554
6188
|
let protocolChecked = false;
|
|
5555
6189
|
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 {
|
|
6190
|
+
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);
|
|
6191
|
+
keep.push(directory.state.subscribe(() => {}), workspaces.state.subscribe(() => {}), credentials.state.subscribe(() => {}), searchSettings.state.subscribe(() => {}), contextSettings.state.subscribe(() => {}));
|
|
6192
|
+
return {
|
|
6193
|
+
directory,
|
|
6194
|
+
management,
|
|
6195
|
+
models,
|
|
6196
|
+
workspaces,
|
|
6197
|
+
workspaceFiles,
|
|
6198
|
+
credentials,
|
|
6199
|
+
searchSettings,
|
|
6200
|
+
contextSettings,
|
|
6201
|
+
skillMarket
|
|
6202
|
+
};
|
|
5559
6203
|
}
|
|
5560
6204
|
const services = () => {
|
|
5561
6205
|
if (!protocolChecked)
|
|
@@ -5564,6 +6208,37 @@ function createGatewayConnection(link, serverId) {
|
|
|
5564
6208
|
business = bindBusiness();
|
|
5565
6209
|
return business;
|
|
5566
6210
|
};
|
|
6211
|
+
const checkReady = async (signal) => {
|
|
6212
|
+
await deadline(application.ready, signal);
|
|
6213
|
+
const info = application.info.value;
|
|
6214
|
+
if (info.serverId !== serverId)
|
|
6215
|
+
throw new GatewayIdentityError;
|
|
6216
|
+
if (info.protocolVersion !== APPLICATION_PROTOCOL)
|
|
6217
|
+
throw new ApplicationProtocolError;
|
|
6218
|
+
protocolChecked = true;
|
|
6219
|
+
const {
|
|
6220
|
+
directory,
|
|
6221
|
+
management,
|
|
6222
|
+
models,
|
|
6223
|
+
workspaces,
|
|
6224
|
+
credentials,
|
|
6225
|
+
searchSettings,
|
|
6226
|
+
contextSettings,
|
|
6227
|
+
workspaceFiles,
|
|
6228
|
+
skillMarket
|
|
6229
|
+
} = services();
|
|
6230
|
+
await deadline(Promise.all([
|
|
6231
|
+
directory.ready,
|
|
6232
|
+
management.ready,
|
|
6233
|
+
models.ready,
|
|
6234
|
+
workspaces.ready,
|
|
6235
|
+
credentials.ready,
|
|
6236
|
+
searchSettings.ready,
|
|
6237
|
+
contextSettings.ready,
|
|
6238
|
+
workspaceFiles.ready,
|
|
6239
|
+
skillMarket.ready
|
|
6240
|
+
]), signal);
|
|
6241
|
+
};
|
|
5567
6242
|
let chat;
|
|
5568
6243
|
let attachment;
|
|
5569
6244
|
let stopView;
|
|
@@ -5575,7 +6250,7 @@ function createGatewayConnection(link, serverId) {
|
|
|
5575
6250
|
if (!attachment)
|
|
5576
6251
|
throw new Error("No active session");
|
|
5577
6252
|
const input = { ...attachment, ...typeof target === "string" ? { agentId: target } : { taskId: target } };
|
|
5578
|
-
return subagentClient(() => host.
|
|
6253
|
+
return subagentClient(() => host.hold(NekoSubagent, input));
|
|
5579
6254
|
},
|
|
5580
6255
|
host,
|
|
5581
6256
|
link,
|
|
@@ -5597,6 +6272,12 @@ function createGatewayConnection(link, serverId) {
|
|
|
5597
6272
|
get credentials() {
|
|
5598
6273
|
return services().credentials;
|
|
5599
6274
|
},
|
|
6275
|
+
get searchSettings() {
|
|
6276
|
+
return services().searchSettings;
|
|
6277
|
+
},
|
|
6278
|
+
get contextSettings() {
|
|
6279
|
+
return services().contextSettings;
|
|
6280
|
+
},
|
|
5600
6281
|
get skillMarket() {
|
|
5601
6282
|
return services().skillMarket;
|
|
5602
6283
|
},
|
|
@@ -5604,26 +6285,13 @@ function createGatewayConnection(link, serverId) {
|
|
|
5604
6285
|
return attachment;
|
|
5605
6286
|
},
|
|
5606
6287
|
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
|
-
]);
|
|
6288
|
+
try {
|
|
6289
|
+
await checkReady(signal ? AbortSignal.any([signal, linkClosed.signal]) : linkClosed.signal);
|
|
6290
|
+
} catch (error) {
|
|
6291
|
+
if (protocolMismatch || closeReason.startsWith("protocol"))
|
|
6292
|
+
throw new ApplicationProtocolError;
|
|
6293
|
+
throw error;
|
|
6294
|
+
}
|
|
5627
6295
|
},
|
|
5628
6296
|
async attach(sessionId, ctx) {
|
|
5629
6297
|
const next = await connection.management.attach(sessionId, ctx);
|
|
@@ -5632,12 +6300,11 @@ function createGatewayConnection(link, serverId) {
|
|
|
5632
6300
|
stopView?.();
|
|
5633
6301
|
chat?.release();
|
|
5634
6302
|
attachment = next;
|
|
5635
|
-
const current = host.
|
|
6303
|
+
const current = host.hold(NekoChat, next);
|
|
5636
6304
|
chat = current;
|
|
5637
6305
|
stopView = current.view.subscribe(() => {});
|
|
5638
6306
|
try {
|
|
5639
6307
|
await deadline(current.ready, ctx?.signal);
|
|
5640
|
-
await waitForValue(current.view, ctx?.signal);
|
|
5641
6308
|
if (current !== chat)
|
|
5642
6309
|
throw new Error("Attachment changed");
|
|
5643
6310
|
return current;
|
|
@@ -5697,11 +6364,17 @@ function deadline(work, signal) {
|
|
|
5697
6364
|
signal?.removeEventListener("abort", abort);
|
|
5698
6365
|
});
|
|
5699
6366
|
}
|
|
5700
|
-
var JOBS_UNSUPPORTED = "This gateway does not support background jobs. Restart the gateway to enable them.";
|
|
6367
|
+
var GatewayIdentityError, JOBS_UNSUPPORTED = "This gateway does not support background jobs. Restart the gateway to enable them.";
|
|
5701
6368
|
var init_client_session = __esm(() => {
|
|
5702
6369
|
init_application_version();
|
|
5703
6370
|
init_contract();
|
|
5704
6371
|
init_link();
|
|
6372
|
+
GatewayIdentityError = class GatewayIdentityError extends Error {
|
|
6373
|
+
constructor() {
|
|
6374
|
+
super("Gateway identity mismatch");
|
|
6375
|
+
this.name = "GatewayIdentityError";
|
|
6376
|
+
}
|
|
6377
|
+
};
|
|
5705
6378
|
});
|
|
5706
6379
|
|
|
5707
6380
|
// packages/tui/src/theme/palette.ts
|
|
@@ -17881,6 +18554,8 @@ var init_labels = __esm(() => {
|
|
|
17881
18554
|
{ name: "model", description: "Choose a model" },
|
|
17882
18555
|
{ name: "agents", description: "View agent tasks and activity" },
|
|
17883
18556
|
{ name: "jobs", description: "View background jobs" },
|
|
18557
|
+
{ name: "search", description: "Choose the web search provider (Tavily/DeepSeek)" },
|
|
18558
|
+
{ name: "context", description: "Tune context compaction and tool result pruning" },
|
|
17884
18559
|
{ name: "theme", description: "Choose color theme (auto/light/dark)" },
|
|
17885
18560
|
{ name: "think", description: "Set thinking level (/think high)" },
|
|
17886
18561
|
{ name: "fork", description: "Fork this session" },
|
|
@@ -19390,6 +20065,227 @@ var init_welcome = __esm(() => {
|
|
|
19390
20065
|
CAT = ["|\\___/|", ") (", "=\\^_^ /=", ")---("];
|
|
19391
20066
|
});
|
|
19392
20067
|
|
|
20068
|
+
// packages/tui/src/context-ui.ts
|
|
20069
|
+
import { matchesKey as matchesKey7 } from "@earendil-works/pi-tui";
|
|
20070
|
+
function format(kind, value) {
|
|
20071
|
+
if (value === null)
|
|
20072
|
+
return "Default";
|
|
20073
|
+
if (typeof value === "boolean")
|
|
20074
|
+
return value ? "On" : "Off";
|
|
20075
|
+
switch (kind) {
|
|
20076
|
+
case "percent":
|
|
20077
|
+
return `${value}%`;
|
|
20078
|
+
case "tokens":
|
|
20079
|
+
return value >= 1000 ? `${Math.round(value / 1000)}k tokens` : `${value} tokens`;
|
|
20080
|
+
case "chars":
|
|
20081
|
+
return value >= 1000 ? `${Math.round(value / 1000)}k chars` : `${value} chars`;
|
|
20082
|
+
default:
|
|
20083
|
+
return String(value);
|
|
20084
|
+
}
|
|
20085
|
+
}
|
|
20086
|
+
function read(state, row) {
|
|
20087
|
+
const effective = state.effective[row.group];
|
|
20088
|
+
const sources = state.sources[row.group];
|
|
20089
|
+
return { value: effective[row.field] ?? null, source: sources[row.field] ?? "default" };
|
|
20090
|
+
}
|
|
20091
|
+
function contextItems(state) {
|
|
20092
|
+
if (!state)
|
|
20093
|
+
return [];
|
|
20094
|
+
const items = [];
|
|
20095
|
+
let group = "";
|
|
20096
|
+
for (const row of CONTEXT_ROWS) {
|
|
20097
|
+
if (row.group !== group) {
|
|
20098
|
+
group = row.group;
|
|
20099
|
+
items.push({
|
|
20100
|
+
value: `header:${group}`,
|
|
20101
|
+
label: group === "compaction" ? "Compaction" : "Tool result pruning",
|
|
20102
|
+
header: true
|
|
20103
|
+
});
|
|
20104
|
+
}
|
|
20105
|
+
const { value, source } = read(state, row);
|
|
20106
|
+
const custom = source !== "default" && value !== null && !row.presets.includes(value);
|
|
20107
|
+
items.push({
|
|
20108
|
+
value: row.key,
|
|
20109
|
+
label: row.label,
|
|
20110
|
+
rightLabel: `${format(row.kind, value)} ${SOURCES[source]}${custom ? " \xB7 Custom" : ""}`,
|
|
20111
|
+
description: row.description
|
|
20112
|
+
});
|
|
20113
|
+
}
|
|
20114
|
+
items.push({
|
|
20115
|
+
value: RESET_VALUE,
|
|
20116
|
+
label: "Reset to defaults",
|
|
20117
|
+
description: "Clear every saved value. Config file values still apply."
|
|
20118
|
+
});
|
|
20119
|
+
return items;
|
|
20120
|
+
}
|
|
20121
|
+
|
|
20122
|
+
class PresetDetail {
|
|
20123
|
+
help = shortcutHints(["\u2191\u2193", "Select"], ["Enter", "Save"], ["Esc", "Back"]);
|
|
20124
|
+
#index = 0;
|
|
20125
|
+
#error = "";
|
|
20126
|
+
#saving = false;
|
|
20127
|
+
#options;
|
|
20128
|
+
#row;
|
|
20129
|
+
#client;
|
|
20130
|
+
#panel;
|
|
20131
|
+
#notify;
|
|
20132
|
+
#changed;
|
|
20133
|
+
constructor(row, client, panel, notify, changed) {
|
|
20134
|
+
this.#row = row;
|
|
20135
|
+
this.#client = client;
|
|
20136
|
+
this.#panel = panel;
|
|
20137
|
+
this.#notify = notify;
|
|
20138
|
+
this.#changed = changed;
|
|
20139
|
+
const state = client.contextSettings.state.value;
|
|
20140
|
+
const current = state ? read(state, row) : { value: null, source: "default" };
|
|
20141
|
+
const options = [null, ...row.presets];
|
|
20142
|
+
const custom = current.source !== "default" && current.value !== null && !row.presets.includes(current.value);
|
|
20143
|
+
if (custom)
|
|
20144
|
+
options.push(current.value);
|
|
20145
|
+
this.#options = options;
|
|
20146
|
+
this.#index = current.source === "saved" || custom ? Math.max(0, options.indexOf(current.value)) : 0;
|
|
20147
|
+
}
|
|
20148
|
+
lines(_width, height) {
|
|
20149
|
+
const state = this.#client.contextSettings.state.value;
|
|
20150
|
+
const current = state ? read(state, this.#row) : undefined;
|
|
20151
|
+
const head = [theme.accent(this.#row.label), theme.dim(this.#row.description)];
|
|
20152
|
+
if (this.#saving)
|
|
20153
|
+
head.push("Saving...");
|
|
20154
|
+
if (this.#error)
|
|
20155
|
+
head.push(theme.error(`Error: ${this.#error}`));
|
|
20156
|
+
const options = this.#options.map((option, i) => {
|
|
20157
|
+
const custom = option !== null && !this.#row.presets.includes(option);
|
|
20158
|
+
const text = `${format(this.#row.kind, option)}${custom ? " (Custom)" : ""}${option === null && current ? ` - now ${SOURCES[current.source]}` : ""}`;
|
|
20159
|
+
return i === this.#index ? theme.selection(`\u276F ${text}`) : ` ${text}`;
|
|
20160
|
+
});
|
|
20161
|
+
const count = Math.max(1, height - head.length);
|
|
20162
|
+
const start = Math.max(0, Math.min(this.#index - Math.floor(count / 2), options.length - count));
|
|
20163
|
+
return [...head, ...options.slice(start, start + count)];
|
|
20164
|
+
}
|
|
20165
|
+
handleKey(data) {
|
|
20166
|
+
if (this.#saving)
|
|
20167
|
+
return true;
|
|
20168
|
+
if (matchesKey7(data, "up"))
|
|
20169
|
+
this.#index = Math.max(0, this.#index - 1);
|
|
20170
|
+
else if (matchesKey7(data, "down"))
|
|
20171
|
+
this.#index = Math.min(this.#options.length - 1, this.#index + 1);
|
|
20172
|
+
else if (matchesKey7(data, "enter")) {
|
|
20173
|
+
const value = this.#options[this.#index] ?? null;
|
|
20174
|
+
this.#saving = true;
|
|
20175
|
+
this.#error = "";
|
|
20176
|
+
const patch = { [this.#row.group]: { [this.#row.field]: value } };
|
|
20177
|
+
this.#client.contextSettings.set(patch).then(() => {
|
|
20178
|
+
this.#saving = false;
|
|
20179
|
+
this.#notify(`${this.#row.label} set to ${format(this.#row.kind, value)}.`);
|
|
20180
|
+
this.#panel.popDetail();
|
|
20181
|
+
}, (error) => {
|
|
20182
|
+
this.#saving = false;
|
|
20183
|
+
this.#error = message(error);
|
|
20184
|
+
this.#changed();
|
|
20185
|
+
});
|
|
20186
|
+
} else
|
|
20187
|
+
return false;
|
|
20188
|
+
return true;
|
|
20189
|
+
}
|
|
20190
|
+
}
|
|
20191
|
+
function openContextPanel(panel, client, notify) {
|
|
20192
|
+
const items = () => contextItems(client.contextSettings.state.value);
|
|
20193
|
+
const stop = client.contextSettings.state.subscribe(() => panel.update(items()));
|
|
20194
|
+
panel.emptyText = CONTEXT_EMPTY;
|
|
20195
|
+
panel.show("Context settings", async () => items(), async (value) => {
|
|
20196
|
+
if (value === RESET_VALUE) {
|
|
20197
|
+
await client.contextSettings.reset({});
|
|
20198
|
+
notify("Context settings reset to defaults.");
|
|
20199
|
+
return;
|
|
20200
|
+
}
|
|
20201
|
+
const row = CONTEXT_ROWS.find((item) => item.key === value);
|
|
20202
|
+
if (!row)
|
|
20203
|
+
return;
|
|
20204
|
+
return new PresetDetail(row, client, panel, notify, () => panel.changed());
|
|
20205
|
+
});
|
|
20206
|
+
return stop;
|
|
20207
|
+
}
|
|
20208
|
+
var CONTEXT_EMPTY = "Context settings are unavailable.", CONTEXT_ROWS, SOURCES, RESET_VALUE = "reset", message = (error) => error instanceof Error ? error.message : String(error);
|
|
20209
|
+
var init_context_ui = __esm(() => {
|
|
20210
|
+
init_shortcut_hints();
|
|
20211
|
+
init_theme();
|
|
20212
|
+
CONTEXT_ROWS = [
|
|
20213
|
+
{
|
|
20214
|
+
key: "compaction.enabled",
|
|
20215
|
+
group: "compaction",
|
|
20216
|
+
field: "enabled",
|
|
20217
|
+
label: "Auto compaction",
|
|
20218
|
+
kind: "bool",
|
|
20219
|
+
description: "Compact automatically near the limit. /compact always works.",
|
|
20220
|
+
presets: [true, false]
|
|
20221
|
+
},
|
|
20222
|
+
{
|
|
20223
|
+
key: "compaction.triggerPercent",
|
|
20224
|
+
group: "compaction",
|
|
20225
|
+
field: "triggerPercent",
|
|
20226
|
+
label: "Compact at",
|
|
20227
|
+
kind: "percent",
|
|
20228
|
+
description: "Compact when context reaches this share of the smallest model window. Default is near the limit.",
|
|
20229
|
+
presets: [30, 40, 50, 60, 70, 80]
|
|
20230
|
+
},
|
|
20231
|
+
{
|
|
20232
|
+
key: "compaction.keepRecentTokens",
|
|
20233
|
+
group: "compaction",
|
|
20234
|
+
field: "keepRecentTokens",
|
|
20235
|
+
label: "Keep recent",
|
|
20236
|
+
kind: "tokens",
|
|
20237
|
+
description: "Recent context a compaction keeps verbatim.",
|
|
20238
|
+
presets: [1e4, 20000, 40000, 80000]
|
|
20239
|
+
},
|
|
20240
|
+
{
|
|
20241
|
+
key: "pruning.enabled",
|
|
20242
|
+
group: "pruning",
|
|
20243
|
+
field: "enabled",
|
|
20244
|
+
label: "Prune tool results",
|
|
20245
|
+
kind: "bool",
|
|
20246
|
+
description: "Replace old, large tool results with a short note in what the model sees. History is kept.",
|
|
20247
|
+
presets: [true, false]
|
|
20248
|
+
},
|
|
20249
|
+
{
|
|
20250
|
+
key: "pruning.triggerPercent",
|
|
20251
|
+
group: "pruning",
|
|
20252
|
+
field: "triggerPercent",
|
|
20253
|
+
label: "Prune at",
|
|
20254
|
+
kind: "percent",
|
|
20255
|
+
description: "Prune when context reaches this share of the model window. Each prune invalidates the prompt cache.",
|
|
20256
|
+
presets: [20, 30, 35, 45, 55]
|
|
20257
|
+
},
|
|
20258
|
+
{
|
|
20259
|
+
key: "pruning.keepRecent",
|
|
20260
|
+
group: "pruning",
|
|
20261
|
+
field: "keepRecent",
|
|
20262
|
+
label: "Keep recent results",
|
|
20263
|
+
kind: "count",
|
|
20264
|
+
description: "Newest tool results that are never pruned.",
|
|
20265
|
+
presets: [5, 10, 20, 40]
|
|
20266
|
+
},
|
|
20267
|
+
{
|
|
20268
|
+
key: "pruning.minChars",
|
|
20269
|
+
group: "pruning",
|
|
20270
|
+
field: "minChars",
|
|
20271
|
+
label: "Min result size",
|
|
20272
|
+
kind: "chars",
|
|
20273
|
+
description: "Results shorter than this are never pruned.",
|
|
20274
|
+
presets: [2000, 4000, 8000, 16000]
|
|
20275
|
+
},
|
|
20276
|
+
{
|
|
20277
|
+
key: "pruning.minSavePercent",
|
|
20278
|
+
group: "pruning",
|
|
20279
|
+
field: "minSavePercent",
|
|
20280
|
+
label: "Min release",
|
|
20281
|
+
kind: "percent",
|
|
20282
|
+
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.",
|
|
20283
|
+
presets: [2, 5, 10, 15]
|
|
20284
|
+
}
|
|
20285
|
+
];
|
|
20286
|
+
SOURCES = { default: "Default", config: "Config", saved: "Saved" };
|
|
20287
|
+
});
|
|
20288
|
+
|
|
19393
20289
|
// packages/tui/src/detect-theme.ts
|
|
19394
20290
|
function detectColorFgBgMode(env = process.env) {
|
|
19395
20291
|
const bg = env.COLORFGBG?.split(";").at(-1)?.trim();
|
|
@@ -19695,7 +20591,7 @@ class UnixLink {
|
|
|
19695
20591
|
}
|
|
19696
20592
|
send(frame) {
|
|
19697
20593
|
try {
|
|
19698
|
-
this.#native.send(frame);
|
|
20594
|
+
return this.#native.send(frame);
|
|
19699
20595
|
} catch (error) {
|
|
19700
20596
|
this.#native.close("pending byte limit exceeded");
|
|
19701
20597
|
throw error;
|
|
@@ -19829,7 +20725,7 @@ function createWsListener(options) {
|
|
|
19829
20725
|
server.on("error", (error) => options.onError?.(error));
|
|
19830
20726
|
server.on("connection", (socket) => {
|
|
19831
20727
|
socket.on("error", (error) => options.onError?.(error));
|
|
19832
|
-
accept(boundedWebSocketLink(socket, options.maxPendingBytes));
|
|
20728
|
+
accept(boundedWebSocketLink(socket, options.maxPendingBytes, { acceptPacked: false }));
|
|
19833
20729
|
});
|
|
19834
20730
|
},
|
|
19835
20731
|
async close() {
|
|
@@ -19879,6 +20775,25 @@ function isServerId(value) {
|
|
|
19879
20775
|
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
20776
|
}
|
|
19881
20777
|
|
|
20778
|
+
// packages/gateway/src/principal.ts
|
|
20779
|
+
function gatewayPrincipalKey(principal) {
|
|
20780
|
+
switch (principal.kind) {
|
|
20781
|
+
case "mobile":
|
|
20782
|
+
return `mobile:${principal.deviceId}:${principal.grantRevision}:${principal.connectionId}`;
|
|
20783
|
+
case "local":
|
|
20784
|
+
return `local:${principal.connectionId}`;
|
|
20785
|
+
case "host":
|
|
20786
|
+
return "host";
|
|
20787
|
+
}
|
|
20788
|
+
}
|
|
20789
|
+
function isMobilePrincipal(principal) {
|
|
20790
|
+
return typeof principal === "object" && principal !== null && principal.kind === "mobile";
|
|
20791
|
+
}
|
|
20792
|
+
var HOST_PRINCIPAL;
|
|
20793
|
+
var init_principal = __esm(() => {
|
|
20794
|
+
HOST_PRINCIPAL = { kind: "host" };
|
|
20795
|
+
});
|
|
20796
|
+
|
|
19882
20797
|
// packages/gateway/src/mobile/store.ts
|
|
19883
20798
|
import { randomBytes } from "crypto";
|
|
19884
20799
|
import { chmod as chmod2, mkdir as mkdir5, readFile as readFile6, rename as rename3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -20189,20 +21104,8 @@ var init_store = __esm(() => {
|
|
|
20189
21104
|
|
|
20190
21105
|
// packages/gateway/src/mobile/admin.ts
|
|
20191
21106
|
import { createHash, timingSafeEqual } from "crypto";
|
|
20192
|
-
import { ChordError
|
|
21107
|
+
import { ChordError } from "@earendil-works/chord";
|
|
20193
21108
|
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
21109
|
function digest(value) {
|
|
20207
21110
|
return createHash("sha256").update(value, "utf8").digest();
|
|
20208
21111
|
}
|
|
@@ -20231,7 +21134,7 @@ function createMobileAdminFactory(deps) {
|
|
|
20231
21134
|
throw new ChordError("forbidden", "forbidden");
|
|
20232
21135
|
};
|
|
20233
21136
|
return {
|
|
20234
|
-
|
|
21137
|
+
variant: "admin",
|
|
20235
21138
|
async state() {
|
|
20236
21139
|
await guard();
|
|
20237
21140
|
return deps.mobile.adminState();
|
|
@@ -20269,6 +21172,8 @@ function createMobileAdminFactory(deps) {
|
|
|
20269
21172
|
}
|
|
20270
21173
|
var listenerStateSchema, adminDeviceSchema, NekoMobileAdmin;
|
|
20271
21174
|
var init_admin = __esm(() => {
|
|
21175
|
+
init_contract();
|
|
21176
|
+
init_principal();
|
|
20272
21177
|
init_store();
|
|
20273
21178
|
listenerStateSchema = Type6.Union([
|
|
20274
21179
|
Type6.Object({ status: Type6.Literal("disabled") }),
|
|
@@ -20290,54 +21195,42 @@ var init_admin = __esm(() => {
|
|
|
20290
21195
|
lastSeenAt: Type6.Integer(),
|
|
20291
21196
|
online: Type6.Boolean()
|
|
20292
21197
|
});
|
|
20293
|
-
NekoMobileAdmin =
|
|
21198
|
+
NekoMobileAdmin = contract("neko.mobile-admin", {
|
|
20294
21199
|
version: 1,
|
|
20295
21200
|
input: Type6.Object({ token: Type6.String() }),
|
|
20296
21201
|
variants: {
|
|
20297
21202
|
admin: {
|
|
20298
21203
|
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
|
-
]
|
|
21204
|
+
state: rpc(Type6.Null(), Type6.Object({
|
|
21205
|
+
state: listenerStateSchema,
|
|
21206
|
+
candidates: Type6.Array(Type6.String()),
|
|
21207
|
+
port: Type6.Integer({ minimum: 1, maximum: 65535 }),
|
|
21208
|
+
certificate: Type6.Union([
|
|
21209
|
+
Type6.Object({ fingerprint: Type6.String(), expiresAt: Type6.Integer() }),
|
|
21210
|
+
Type6.Null()
|
|
21211
|
+
]),
|
|
21212
|
+
devices: Type6.Array(adminDeviceSchema),
|
|
21213
|
+
lastConnectedAddress: Type6.Union([Type6.String(), Type6.Null()]),
|
|
21214
|
+
wsl: Type6.Optional(Type6.Union([
|
|
21215
|
+
Type6.Object({ interop: Type6.Boolean(), error: Type6.Union([Type6.String(), Type6.Null()]) }),
|
|
21216
|
+
Type6.Null()
|
|
21217
|
+
]))
|
|
21218
|
+
})),
|
|
21219
|
+
enable: rpc(Type6.Object({
|
|
21220
|
+
address: Type6.String(),
|
|
21221
|
+
port: Type6.Optional(Type6.Integer({ minimum: 1, maximum: 65535 }))
|
|
21222
|
+
}), Type6.Object({
|
|
21223
|
+
status: Type6.Literal("listening"),
|
|
21224
|
+
address: Type6.String(),
|
|
21225
|
+
port: Type6.Integer({ minimum: 1, maximum: 65535 }),
|
|
21226
|
+
bindAddress: Type6.Optional(Type6.String()),
|
|
21227
|
+
mode: Type6.Optional(Type6.Union([Type6.Literal("direct"), Type6.Literal("wsl-relay")]))
|
|
21228
|
+
})),
|
|
21229
|
+
disable: rpc(Type6.Null(), listenerStateSchema),
|
|
21230
|
+
createPairing: rpc(Type6.Null(), Type6.Object({ pairingText: Type6.String(), expiresAt: Type6.Integer() })),
|
|
21231
|
+
cancelPairing: rpc(Type6.Null(), Type6.Null()),
|
|
21232
|
+
revoke: rpc(Type6.Object({ deviceId: Type6.String({ minLength: 1 }) }), Type6.Object({ revoked: Type6.Boolean() })),
|
|
21233
|
+
regenerateCertificate: rpc(Type6.Null(), Type6.Object({ fingerprint: Type6.String(), expiresAt: Type6.Integer() }))
|
|
20341
21234
|
}
|
|
20342
21235
|
}
|
|
20343
21236
|
}
|
|
@@ -20401,7 +21294,7 @@ function buildHelloResultSchema() {
|
|
|
20401
21294
|
})
|
|
20402
21295
|
});
|
|
20403
21296
|
}
|
|
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,
|
|
21297
|
+
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, MobileChatStatusSchema, MobileChatSnapshotSchema, MobileWorkspaceSchema, MobileSessionSummarySchema, MobileApprovalSchema, sessionListParams, sessionListResult, subscription, pageResult, pageParams, chunkResult, MOBILE_API_V1_METHODS, patchBody, MOBILE_API_V1_NOTIFICATIONS;
|
|
20405
21298
|
var init_protocol = __esm(() => {
|
|
20406
21299
|
init_application_version();
|
|
20407
21300
|
MOBILE_LIMITS = {
|
|
@@ -20520,7 +21413,7 @@ var init_protocol = __esm(() => {
|
|
|
20520
21413
|
approvalCount: int,
|
|
20521
21414
|
readonly: Type7.Boolean(),
|
|
20522
21415
|
createdAt: Type7.Number(),
|
|
20523
|
-
endedAt:
|
|
21416
|
+
endedAt: nullable2(Type7.Number()),
|
|
20524
21417
|
usage: opt(MobileUsageSchema)
|
|
20525
21418
|
});
|
|
20526
21419
|
MobileAgentSchema = Type7.Object({
|
|
@@ -20528,18 +21421,18 @@ var init_protocol = __esm(() => {
|
|
|
20528
21421
|
role: str2,
|
|
20529
21422
|
title: str2,
|
|
20530
21423
|
status: Type7.Union([Type7.Literal("idle"), Type7.Literal("queued"), Type7.Literal("running")]),
|
|
20531
|
-
activeRoundId:
|
|
21424
|
+
activeRoundId: nullable2(int),
|
|
20532
21425
|
lastRoundId: int,
|
|
20533
21426
|
roundCount: int,
|
|
20534
21427
|
approvalCount: int
|
|
20535
21428
|
});
|
|
20536
21429
|
MobileChatStatusSchema = Type7.Object({
|
|
20537
21430
|
busy: Type7.Boolean(),
|
|
20538
|
-
status:
|
|
21431
|
+
status: nullable2(str2),
|
|
20539
21432
|
usage: MobileUsageSchema,
|
|
20540
21433
|
taskUsage: opt(MobileUsageSchema),
|
|
20541
|
-
contextUsage: opt(Type7.Object({ tokens:
|
|
20542
|
-
agent: Type7.Object({ model:
|
|
21434
|
+
contextUsage: opt(Type7.Object({ tokens: nullable2(Type7.Number()), window: Type7.Number(), percent: nullable2(Type7.Number()) })),
|
|
21435
|
+
agent: Type7.Object({ model: nullable2(str2), thinkingLevel: nullable2(str2) }),
|
|
20543
21436
|
subagents: Type7.Array(MobileSubagentRoundSchema),
|
|
20544
21437
|
agents: Type7.Array(MobileAgentSchema),
|
|
20545
21438
|
approvalCount: int,
|
|
@@ -20549,8 +21442,8 @@ var init_protocol = __esm(() => {
|
|
|
20549
21442
|
MobileChatSnapshotSchema = Type7.Object({
|
|
20550
21443
|
...MobileChatStatusSchema.properties,
|
|
20551
21444
|
items: Type7.Array(MobileChatItemSchema),
|
|
20552
|
-
historyBefore:
|
|
20553
|
-
streaming:
|
|
21445
|
+
historyBefore: nullable2(str2),
|
|
21446
|
+
streaming: nullable2(MobileStreamingSchema)
|
|
20554
21447
|
});
|
|
20555
21448
|
MobileWorkspaceSchema = Type7.Object({ root: str2, name: str2 });
|
|
20556
21449
|
MobileSessionSummarySchema = Type7.Object({
|
|
@@ -20583,7 +21476,7 @@ var init_protocol = __esm(() => {
|
|
|
20583
21476
|
nextCursor: opt(str2)
|
|
20584
21477
|
});
|
|
20585
21478
|
subscription = { subscriptionId: Type7.String({ minLength: 1 }) };
|
|
20586
|
-
pageResult = Type7.Object({ items: Type7.Array(MobileChatItemSchema), historyBefore:
|
|
21479
|
+
pageResult = Type7.Object({ items: Type7.Array(MobileChatItemSchema), historyBefore: nullable2(str2) });
|
|
20587
21480
|
pageParams = Type7.Object({
|
|
20588
21481
|
...subscription,
|
|
20589
21482
|
before: Type7.String({ minLength: 1 }),
|
|
@@ -20680,7 +21573,7 @@ var init_protocol = __esm(() => {
|
|
|
20680
21573
|
seq: Type7.Integer({ minimum: 1 }),
|
|
20681
21574
|
upserts: Type7.Array(MobileChatItemSchema),
|
|
20682
21575
|
status: opt(MobileChatStatusSchema),
|
|
20683
|
-
streaming: opt(
|
|
21576
|
+
streaming: opt(nullable2(MobileStreamingSchema)),
|
|
20684
21577
|
streamingAppend: opt(Type7.Object({ text: opt(str2), thinking: opt(str2) }))
|
|
20685
21578
|
};
|
|
20686
21579
|
MOBILE_API_V1_NOTIFICATIONS = {
|
|
@@ -21183,7 +22076,7 @@ function chordToMobileCode(error) {
|
|
|
21183
22076
|
return "internal";
|
|
21184
22077
|
}
|
|
21185
22078
|
}
|
|
21186
|
-
async function
|
|
22079
|
+
async function remote2(work, invalidAs = "invalid_params") {
|
|
21187
22080
|
try {
|
|
21188
22081
|
return await work();
|
|
21189
22082
|
} catch (error) {
|
|
@@ -21397,14 +22290,17 @@ class MobileAdapter {
|
|
|
21397
22290
|
}
|
|
21398
22291
|
}
|
|
21399
22292
|
async workspacesList() {
|
|
21400
|
-
const
|
|
22293
|
+
const { workspaces } = this.services();
|
|
22294
|
+
await workspaces.ready;
|
|
22295
|
+
const state = workspaces.state.value;
|
|
21401
22296
|
return {
|
|
21402
22297
|
workspaces: state.workspaces.map((workspace) => ({ root: workspace.path, name: baseName(workspace.path) }))
|
|
21403
22298
|
};
|
|
21404
22299
|
}
|
|
21405
22300
|
async registeredWorkspaceRoots() {
|
|
21406
|
-
const
|
|
21407
|
-
|
|
22301
|
+
const { workspaces } = this.services();
|
|
22302
|
+
await workspaces.ready;
|
|
22303
|
+
return workspaces.state.value.workspaces.map((workspace) => workspace.path);
|
|
21408
22304
|
}
|
|
21409
22305
|
async sessionsList(params) {
|
|
21410
22306
|
const roots = await this.registeredWorkspaceRoots();
|
|
@@ -21413,7 +22309,9 @@ class MobileAdapter {
|
|
|
21413
22309
|
const offset = params.cursor === undefined ? 0 : Number.parseInt(params.cursor, 10);
|
|
21414
22310
|
if (!Number.isInteger(offset) || offset < 0)
|
|
21415
22311
|
throw new MobileMethodError("invalid_params", "invalid cursor");
|
|
21416
|
-
const directory =
|
|
22312
|
+
const { directory: directoryRef } = this.services();
|
|
22313
|
+
await directoryRef.ready;
|
|
22314
|
+
const directory = directoryRef.state.value;
|
|
21417
22315
|
let sessions = directory.sessions.filter((session) => session.workspaceRoot === params.workspace);
|
|
21418
22316
|
if (params.query !== undefined) {
|
|
21419
22317
|
const query = params.query.toLowerCase();
|
|
@@ -21439,7 +22337,7 @@ class MobileAdapter {
|
|
|
21439
22337
|
if (!roots.includes(params.workspace))
|
|
21440
22338
|
throw new MobileMethodError("invalid_params", `unknown workspace: ${params.workspace}`);
|
|
21441
22339
|
this.assertAuthorized();
|
|
21442
|
-
const created = await
|
|
22340
|
+
const created = await remote2(() => this.services().management.create({ cwd: params.workspace }));
|
|
21443
22341
|
return {
|
|
21444
22342
|
id: created.id,
|
|
21445
22343
|
workspace: created.workspaceRoot,
|
|
@@ -21476,16 +22374,13 @@ class MobileAdapter {
|
|
|
21476
22374
|
const generation = ++this.attachGeneration;
|
|
21477
22375
|
this.closeSubagent();
|
|
21478
22376
|
this.releaseChat();
|
|
21479
|
-
const attachment = await
|
|
22377
|
+
const attachment = await remote2(() => services.management.attach(params.sessionId));
|
|
21480
22378
|
if (generation !== this.attachGeneration || this.disposed)
|
|
21481
22379
|
throw new MobileMethodError("gone", "superseded by a newer attach");
|
|
21482
22380
|
const chat = services.openChat(attachment);
|
|
21483
22381
|
const stopView = chat.view.subscribe(() => {});
|
|
21484
22382
|
try {
|
|
21485
|
-
await
|
|
21486
|
-
await chat.ready;
|
|
21487
|
-
await waitForValue(chat.view);
|
|
21488
|
-
});
|
|
22383
|
+
await remote2(() => chat.ready);
|
|
21489
22384
|
} catch (error) {
|
|
21490
22385
|
stopView();
|
|
21491
22386
|
chat.release();
|
|
@@ -21547,7 +22442,7 @@ class MobileAdapter {
|
|
|
21547
22442
|
this.attachGeneration += 1;
|
|
21548
22443
|
this.closeSubagent();
|
|
21549
22444
|
this.releaseChat();
|
|
21550
|
-
await
|
|
22445
|
+
await remote2(() => this.services().management.detach(null)).catch(() => {
|
|
21551
22446
|
return;
|
|
21552
22447
|
});
|
|
21553
22448
|
return null;
|
|
@@ -21578,7 +22473,7 @@ class MobileAdapter {
|
|
|
21578
22473
|
const state = this.requireChat(params);
|
|
21579
22474
|
const busy = state.chat.view.value?.busy ?? false;
|
|
21580
22475
|
this.assertAuthorized();
|
|
21581
|
-
await
|
|
22476
|
+
await remote2(() => state.chat.submit({
|
|
21582
22477
|
content: params.text,
|
|
21583
22478
|
requestId: params.requestId,
|
|
21584
22479
|
...params.mode === "steer" ? { whenBusy: "steer" } : {}
|
|
@@ -21595,7 +22490,7 @@ class MobileAdapter {
|
|
|
21595
22490
|
async chatAbort(params) {
|
|
21596
22491
|
const state = this.requireChat(params);
|
|
21597
22492
|
this.assertAuthorized();
|
|
21598
|
-
await
|
|
22493
|
+
await remote2(() => state.chat.abort(null));
|
|
21599
22494
|
return { accepted: true };
|
|
21600
22495
|
}
|
|
21601
22496
|
async approvalRecords(state) {
|
|
@@ -21674,7 +22569,7 @@ class MobileAdapter {
|
|
|
21674
22569
|
this.assertAuthorized();
|
|
21675
22570
|
const handle = openSubagent({ ...state.attachment, taskId: params.taskId });
|
|
21676
22571
|
try {
|
|
21677
|
-
const status = await
|
|
22572
|
+
const status = await remote2(() => handle.decideApproval({
|
|
21678
22573
|
id: params.id,
|
|
21679
22574
|
version: params.version,
|
|
21680
22575
|
decision: params.decision === "allow" ? "allowed" : "denied"
|
|
@@ -21754,7 +22649,7 @@ class MobileAdapter {
|
|
|
21754
22649
|
return () => listeners.delete(listener);
|
|
21755
22650
|
},
|
|
21756
22651
|
older: async (before) => {
|
|
21757
|
-
const page = await
|
|
22652
|
+
const page = await remote2(() => client.history({ before }));
|
|
21758
22653
|
return { items: page.items, next: page.next };
|
|
21759
22654
|
}
|
|
21760
22655
|
};
|
|
@@ -21798,7 +22693,7 @@ class MobileAdapter {
|
|
|
21798
22693
|
this.assertAuthorized();
|
|
21799
22694
|
const handle = openSubagent({ ...chat.attachment, taskId: params.taskId });
|
|
21800
22695
|
try {
|
|
21801
|
-
await
|
|
22696
|
+
await remote2(() => handle.stop({ taskId: params.taskId }));
|
|
21802
22697
|
} finally {
|
|
21803
22698
|
handle.release();
|
|
21804
22699
|
}
|
|
@@ -21812,9 +22707,9 @@ class MobileAdapter {
|
|
|
21812
22707
|
const state = this.subagentState;
|
|
21813
22708
|
if (state === undefined || state.agentId !== params.agentId || state.chatSubscriptionId !== chat.subscriptionId)
|
|
21814
22709
|
throw new MobileMethodError("gone", "subagent view is no longer open");
|
|
21815
|
-
chunk = await
|
|
22710
|
+
chunk = await remote2(() => state.client.getImage(input));
|
|
21816
22711
|
} else {
|
|
21817
|
-
chunk = await
|
|
22712
|
+
chunk = await remote2(() => chat.chat.getImage(input));
|
|
21818
22713
|
}
|
|
21819
22714
|
const bytes = Buffer.from(chunk.data, "base64").subarray(0, MOBILE_LIMITS.chunkBytes);
|
|
21820
22715
|
const nextOffset = params.offset + bytes.length;
|
|
@@ -21864,7 +22759,6 @@ class MobileAdapter {
|
|
|
21864
22759
|
var CLOSE_INVALID_JSON = 1003, CLOSE_PROTOCOL_ERROR = 1002, ACTIVE_ROUND, MobileMethodError;
|
|
21865
22760
|
var init_adapter = __esm(() => {
|
|
21866
22761
|
init_src();
|
|
21867
|
-
init_client_session();
|
|
21868
22762
|
init_projection2();
|
|
21869
22763
|
init_protocol();
|
|
21870
22764
|
ACTIVE_ROUND = new Set(["queued", "running"]);
|
|
@@ -39188,7 +40082,7 @@ var init_wsl = __esm(() => {
|
|
|
39188
40082
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
39189
40083
|
import { createServer as createServer2 } from "https";
|
|
39190
40084
|
import { hostname } from "os";
|
|
39191
|
-
import {
|
|
40085
|
+
import { memoryLinkPair } from "@earendil-works/chord";
|
|
39192
40086
|
import { WebSocketServer as WebSocketServer2 } from "ws";
|
|
39193
40087
|
function defaultServerName() {
|
|
39194
40088
|
const name = hostname().trim();
|
|
@@ -39220,9 +40114,6 @@ function describeRelayError(error, address, port, bindAddress) {
|
|
|
39220
40114
|
return `Windows \u4E2D\u7EE7\u542F\u52A8\u5931\u8D25\uFF1A${error.message}`;
|
|
39221
40115
|
}
|
|
39222
40116
|
}
|
|
39223
|
-
function isMobilePrincipal2(principal) {
|
|
39224
|
-
return typeof principal === "object" && principal !== null && principal.kind === "mobile";
|
|
39225
|
-
}
|
|
39226
40117
|
function toMessage(error) {
|
|
39227
40118
|
if (error instanceof Error) {
|
|
39228
40119
|
const code = error.code;
|
|
@@ -39401,7 +40292,7 @@ class MobileGateway {
|
|
|
39401
40292
|
await store.clearMobileAuthorizations();
|
|
39402
40293
|
this.certificate = certificate;
|
|
39403
40294
|
this.pairingValue?.invalidate();
|
|
39404
|
-
await this.chord.invalidatePrincipal(
|
|
40295
|
+
await this.chord.invalidatePrincipal(isMobilePrincipal);
|
|
39405
40296
|
for (const connection of [...this.connections.values()])
|
|
39406
40297
|
connection.close(MOBILE_CLOSE_REVOKED, "revoked");
|
|
39407
40298
|
const current = this.stateValue;
|
|
@@ -39477,11 +40368,7 @@ class MobileGateway {
|
|
|
39477
40368
|
store.rememberConnectedAddress(learned).catch((error) => this.onError?.(toError(error)));
|
|
39478
40369
|
}
|
|
39479
40370
|
}
|
|
39480
|
-
const clientHost =
|
|
39481
|
-
kind: "client",
|
|
39482
|
-
upstream: identifiedLink(clientEnd, this.serverId),
|
|
39483
|
-
onError: () => {}
|
|
39484
|
-
});
|
|
40371
|
+
const clientHost = createClientHost(clientEnd);
|
|
39485
40372
|
let servicesValue;
|
|
39486
40373
|
const adapter = new MobileAdapter({
|
|
39487
40374
|
socket,
|
|
@@ -39493,11 +40380,11 @@ class MobileGateway {
|
|
|
39493
40380
|
services: () => {
|
|
39494
40381
|
if (servicesValue === undefined) {
|
|
39495
40382
|
servicesValue = {
|
|
39496
|
-
directory: clientHost.
|
|
39497
|
-
management: clientHost.
|
|
39498
|
-
workspaces: clientHost.
|
|
39499
|
-
openChat: (attachment) => clientHost.
|
|
39500
|
-
openSubagent: (input) => clientHost.
|
|
40383
|
+
directory: clientHost.hold(NekoSessionDirectory, {}),
|
|
40384
|
+
management: clientHost.hold(NekoSessionManagement, {}),
|
|
40385
|
+
workspaces: clientHost.hold(NekoWorkspaces, {}),
|
|
40386
|
+
openChat: (attachment) => clientHost.hold(NekoChat, attachment),
|
|
40387
|
+
openSubagent: (input) => clientHost.hold(NekoSubagent, input)
|
|
39501
40388
|
};
|
|
39502
40389
|
}
|
|
39503
40390
|
return servicesValue;
|
|
@@ -39521,10 +40408,13 @@ class MobileGateway {
|
|
|
39521
40408
|
if (this.notificationCleanup !== undefined)
|
|
39522
40409
|
return;
|
|
39523
40410
|
const [serverEnd, clientEnd] = memoryLinkPair();
|
|
39524
|
-
this.chord.accept(serverEnd, {
|
|
39525
|
-
|
|
39526
|
-
|
|
39527
|
-
|
|
40411
|
+
this.chord.accept(serverEnd, {
|
|
40412
|
+
principal: { kind: "local", connectionId: "mobile-notification-hub" },
|
|
40413
|
+
kind: "client"
|
|
40414
|
+
});
|
|
40415
|
+
const host = createClientHost(clientEnd);
|
|
40416
|
+
const directory = host.hold(NekoSessionDirectory, {});
|
|
40417
|
+
const workspaces = host.hold(NekoWorkspaces, {});
|
|
39528
40418
|
const last = { directory: -1, workspaces: -1 };
|
|
39529
40419
|
const unsubscribe = [
|
|
39530
40420
|
directory.state.subscribe(() => {
|
|
@@ -39808,6 +40698,7 @@ var RELAY_PORT_RETRY_MS = 1e4, RELAY_PORT_RETRY_INTERVAL_MS = 1000, WSL_INTEROP_
|
|
|
39808
40698
|
var init_listener = __esm(() => {
|
|
39809
40699
|
init_contract();
|
|
39810
40700
|
init_link();
|
|
40701
|
+
init_principal();
|
|
39811
40702
|
init_adapter();
|
|
39812
40703
|
init_auth();
|
|
39813
40704
|
init_certificate();
|
|
@@ -39942,15 +40833,7 @@ var init_image_uploads = __esm(() => {
|
|
|
39942
40833
|
});
|
|
39943
40834
|
|
|
39944
40835
|
// 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";
|
|
40836
|
+
import { ChordError as ChordError3, local, opaque } from "@earendil-works/chord";
|
|
39954
40837
|
import { UsageDoc as UsageDoc3 } from "@earendil-works/pi-durable";
|
|
39955
40838
|
import { Type as Type8 } from "typebox";
|
|
39956
40839
|
async function openReader(runtime, childId, parentId, context, through, historyUsage) {
|
|
@@ -40077,31 +40960,27 @@ async function openReader(runtime, childId, parentId, context, through, historyU
|
|
|
40077
40960
|
}
|
|
40078
40961
|
function installSubagentHost(host, runtime, context) {
|
|
40079
40962
|
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
|
-
})
|
|
40963
|
+
const revision = self2.state("revision", Type8.Integer(), 0);
|
|
40964
|
+
const reader = self2.resource("reader", {
|
|
40965
|
+
version: 1,
|
|
40966
|
+
async open() {
|
|
40967
|
+
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);
|
|
40968
|
+
if (!entry)
|
|
40969
|
+
throw new ChordError3("gone", "Subagent unavailable");
|
|
40970
|
+
const r = await openReader(runtime, entry.childId, input.parentId, context, entry.historyThrough, entry.historyUsage);
|
|
40971
|
+
try {
|
|
40972
|
+
const initial = await r.refresh();
|
|
40973
|
+
await host.root((ctx) => revision.replace(ctx, initial));
|
|
40974
|
+
return r;
|
|
40975
|
+
} catch (error) {
|
|
40976
|
+
r.close();
|
|
40977
|
+
throw error;
|
|
40978
|
+
}
|
|
40979
|
+
},
|
|
40980
|
+
close: (r) => r.close()
|
|
40100
40981
|
});
|
|
40101
|
-
self2.
|
|
40102
|
-
|
|
40103
|
-
revision.replace(ctx, await reader.value.refresh());
|
|
40104
|
-
})
|
|
40982
|
+
self2.on("view", reader, (r) => r, async (_event, ctx) => {
|
|
40983
|
+
revision.replace(ctx, await reader.value.refresh());
|
|
40105
40984
|
});
|
|
40106
40985
|
return { revision, reader: self2.expose(reader) };
|
|
40107
40986
|
});
|
|
@@ -40117,23 +40996,24 @@ function installSubagentHost(host, runtime, context) {
|
|
|
40117
40996
|
throw new ChordError3("forbidden", "Subagent does not belong to this session");
|
|
40118
40997
|
return entry;
|
|
40119
40998
|
};
|
|
40120
|
-
const
|
|
40121
|
-
const shared = self2.get(ChildProjection, {
|
|
40122
|
-
|
|
40123
|
-
|
|
40124
|
-
|
|
40125
|
-
|
|
40126
|
-
|
|
40127
|
-
|
|
40128
|
-
|
|
40129
|
-
|
|
40130
|
-
|
|
40131
|
-
authorizedRevision.replace(ctx,
|
|
40132
|
-
|
|
40133
|
-
|
|
40999
|
+
const target = self2.resource("target", { version: 1, deps: [owner], open: check });
|
|
41000
|
+
const shared = self2.get(ChildProjection, {
|
|
41001
|
+
parentId: Number(input.sessionId),
|
|
41002
|
+
agentId: input.agentId ?? `round-${input.taskId}`
|
|
41003
|
+
});
|
|
41004
|
+
const authorizedRevision = self2.state("authorizedRevision", Type8.Integer(), 0);
|
|
41005
|
+
self2.on("authorizedView", shared.revision, async (value, ctx) => {
|
|
41006
|
+
try {
|
|
41007
|
+
target.value;
|
|
41008
|
+
await check();
|
|
41009
|
+
if (value)
|
|
41010
|
+
authorizedRevision.replace(ctx, value);
|
|
41011
|
+
} catch {
|
|
41012
|
+
authorizedRevision.replace(ctx, 0);
|
|
41013
|
+
}
|
|
40134
41014
|
});
|
|
40135
41015
|
return {
|
|
40136
|
-
|
|
41016
|
+
variant: "ready",
|
|
40137
41017
|
revision: authorizedRevision,
|
|
40138
41018
|
chunk: async (request) => {
|
|
40139
41019
|
await check();
|
|
@@ -40193,12 +41073,8 @@ import { open as open3 } from "fs/promises";
|
|
|
40193
41073
|
import { basename as basename5, extname } from "path";
|
|
40194
41074
|
import {
|
|
40195
41075
|
ChordError as ChordError4,
|
|
40196
|
-
cell as cell2,
|
|
40197
|
-
hostPrincipal as hostPrincipal2,
|
|
40198
|
-
listener as listener2,
|
|
40199
41076
|
local as local2,
|
|
40200
|
-
opaque as opaque2
|
|
40201
|
-
resource as resource2
|
|
41077
|
+
opaque as opaque2
|
|
40202
41078
|
} from "@earendil-works/chord";
|
|
40203
41079
|
import { withAbortSignal as withAbortSignal4 } from "@earendil-works/chord/context";
|
|
40204
41080
|
import { UsageDoc as UsageDoc4 } from "@earendil-works/pi-durable";
|
|
@@ -40292,65 +41168,61 @@ async function openProjection(runtime, session, context) {
|
|
|
40292
41168
|
function serveSessions(host, runtime, context) {
|
|
40293
41169
|
installSubagentHost(host, runtime, context);
|
|
40294
41170
|
host.serve(Owner, (_input, _principal, self2) => {
|
|
40295
|
-
const
|
|
40296
|
-
|
|
40297
|
-
|
|
40298
|
-
|
|
40299
|
-
|
|
40300
|
-
|
|
40301
|
-
}
|
|
40302
|
-
})
|
|
41171
|
+
const owner = self2.resource("owner", {
|
|
41172
|
+
version: 1,
|
|
41173
|
+
open: () => ({ active: null }),
|
|
41174
|
+
close: (value) => {
|
|
41175
|
+
value.active = null;
|
|
41176
|
+
}
|
|
40303
41177
|
});
|
|
40304
41178
|
return { owner: self2.expose(owner) };
|
|
40305
41179
|
});
|
|
40306
41180
|
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
|
-
})
|
|
41181
|
+
const view = self2.state("view", ChatViewSchema, emptyView);
|
|
41182
|
+
const projection = self2.resource("projection", {
|
|
41183
|
+
version: 1,
|
|
41184
|
+
async open({ signal }) {
|
|
41185
|
+
const session = await runtime.getConversation(Number(input.id));
|
|
41186
|
+
if (!session)
|
|
41187
|
+
throw new ChordError4("gone", "Session unavailable");
|
|
41188
|
+
const opened = await openProjection(runtime, session, context);
|
|
41189
|
+
try {
|
|
41190
|
+
const initial = await opened.project();
|
|
41191
|
+
await host.root((ctx) => view.replace(ctx, initial), { signal });
|
|
41192
|
+
return opened;
|
|
41193
|
+
} catch (error) {
|
|
41194
|
+
opened.close();
|
|
41195
|
+
throw error;
|
|
41196
|
+
}
|
|
41197
|
+
},
|
|
41198
|
+
close: (value) => value.close()
|
|
40327
41199
|
});
|
|
40328
|
-
self2.
|
|
40329
|
-
view
|
|
40330
|
-
view.replace(ctx, await projection.value.project());
|
|
40331
|
-
})
|
|
41200
|
+
self2.on("view", projection, (value) => value, async (_event, ctx) => {
|
|
41201
|
+
view.replace(ctx, await projection.value.project());
|
|
40332
41202
|
});
|
|
40333
41203
|
return { view, reader: self2.expose(projection) };
|
|
40334
41204
|
});
|
|
40335
41205
|
host.serve(NekoChat, (input, principal, self2) => {
|
|
40336
41206
|
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
|
-
|
|
41207
|
+
const shared = self2.get(Projection, { id: input.sessionId });
|
|
41208
|
+
const uploads = self2.resource("uploads", {
|
|
41209
|
+
version: 1,
|
|
41210
|
+
open: () => new ImageUploads,
|
|
41211
|
+
close: (value) => value.close()
|
|
41212
|
+
});
|
|
41213
|
+
const projection = self2.resource("projection", {
|
|
41214
|
+
version: 1,
|
|
41215
|
+
deps: [owner],
|
|
41216
|
+
async open() {
|
|
41217
|
+
if (owner.owner?.active?.attachmentId !== input.attachmentId || owner.owner.active.sessionId !== input.sessionId)
|
|
41218
|
+
throw new ChordError4("forbidden", "Attachment does not belong to this connection");
|
|
41219
|
+
const session = await runtime.getConversation(Number(input.sessionId));
|
|
41220
|
+
if (!session)
|
|
41221
|
+
throw new ChordError4("gone", "Session unavailable");
|
|
41222
|
+
return {
|
|
41223
|
+
session
|
|
41224
|
+
};
|
|
41225
|
+
}
|
|
40354
41226
|
});
|
|
40355
41227
|
const safeUpload = (action) => {
|
|
40356
41228
|
try {
|
|
@@ -40360,7 +41232,7 @@ function serveSessions(host, runtime, context) {
|
|
|
40360
41232
|
}
|
|
40361
41233
|
};
|
|
40362
41234
|
const behavior = {
|
|
40363
|
-
|
|
41235
|
+
variant: "ready",
|
|
40364
41236
|
view: shared.view,
|
|
40365
41237
|
draftImage: async ({ id, offset }) => safeUpload(() => uploads.value.read(id, offset)),
|
|
40366
41238
|
importImage: async (path, ctx) => {
|
|
@@ -41224,20 +42096,14 @@ var init_workspaces = __esm(() => {
|
|
|
41224
42096
|
|
|
41225
42097
|
// packages/gateway/src/server-services.ts
|
|
41226
42098
|
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";
|
|
42099
|
+
import { ChordError as ChordError5, local as local3, method as method2 } from "@earendil-works/chord";
|
|
41235
42100
|
import { Type as Type10 } from "typebox";
|
|
41236
42101
|
async function createNekoServerServices(options) {
|
|
41237
42102
|
const { runtime, context, host } = options;
|
|
41238
42103
|
const updates = new UpdateChecker(options.updates);
|
|
41239
42104
|
await updates.local();
|
|
41240
42105
|
const updateListeners = new Set;
|
|
42106
|
+
const info = (value) => ({ ...value, serverId: options.serverId });
|
|
41241
42107
|
let published = JSON.stringify(updates.state), closed = false;
|
|
41242
42108
|
const publishUpdates = () => {
|
|
41243
42109
|
if (closed)
|
|
@@ -41250,22 +42116,20 @@ async function createNekoServerServices(options) {
|
|
|
41250
42116
|
listener(updates.state);
|
|
41251
42117
|
};
|
|
41252
42118
|
host.serve(NekoApplicationInfo, (_input, _principal, self2) => {
|
|
41253
|
-
const
|
|
41254
|
-
self2.
|
|
41255
|
-
|
|
41256
|
-
|
|
41257
|
-
|
|
41258
|
-
|
|
41259
|
-
|
|
41260
|
-
}, (value, ctx) => state.info.replace(ctx, value))
|
|
41261
|
-
});
|
|
42119
|
+
const current = self2.state("info", ApplicationInfoSchema, info(updates.state));
|
|
42120
|
+
self2.on("updates", {
|
|
42121
|
+
subscribe: (fn) => {
|
|
42122
|
+
updateListeners.add(fn);
|
|
42123
|
+
return () => updateListeners.delete(fn);
|
|
42124
|
+
}
|
|
42125
|
+
}, (value, ctx) => current.replace(ctx, info(value)));
|
|
41262
42126
|
return {
|
|
41263
|
-
|
|
41264
|
-
info:
|
|
42127
|
+
variant: "ready",
|
|
42128
|
+
info: current,
|
|
41265
42129
|
check: async () => {
|
|
41266
42130
|
await updates.check(true);
|
|
41267
42131
|
publishUpdates();
|
|
41268
|
-
return updates.state;
|
|
42132
|
+
return info(updates.state);
|
|
41269
42133
|
}
|
|
41270
42134
|
};
|
|
41271
42135
|
});
|
|
@@ -41309,7 +42173,13 @@ async function createNekoServerServices(options) {
|
|
|
41309
42173
|
status: info.status
|
|
41310
42174
|
}));
|
|
41311
42175
|
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([
|
|
42176
|
+
const [sessions, workspaces, statuses, search, contextSettings0] = await Promise.all([
|
|
42177
|
+
list(),
|
|
42178
|
+
runtime.listWorkspaces(),
|
|
42179
|
+
readStatuses(),
|
|
42180
|
+
runtime.searchSettings(),
|
|
42181
|
+
runtime.contextSettings()
|
|
42182
|
+
]);
|
|
41313
42183
|
let mutationTail = Promise.resolve();
|
|
41314
42184
|
const serialize = (fn) => {
|
|
41315
42185
|
const result = mutationTail.catch(() => {}).then(fn);
|
|
@@ -41317,43 +42187,64 @@ async function createNekoServerServices(options) {
|
|
|
41317
42187
|
return result;
|
|
41318
42188
|
};
|
|
41319
42189
|
host.serve(ServerState, (_input, _principal, self2) => {
|
|
41320
|
-
const
|
|
41321
|
-
|
|
41322
|
-
|
|
41323
|
-
|
|
42190
|
+
const directory = self2.state("directory", DirectoryStateSchema, { revision: 1, sessions });
|
|
42191
|
+
const workspaceState = self2.state("workspaces", WorkspaceStateSchema, { revision: 1, workspaces });
|
|
42192
|
+
const credentials = self2.state("credentials", CredentialStateSchema, { revision: 1, statuses });
|
|
42193
|
+
const searchSettings = self2.state("searchSettings", SearchSettingsStateSchema, { revision: 1, ...search });
|
|
42194
|
+
const contextSettings = self2.state("contextSettings", ContextSettingsStateSchema, {
|
|
42195
|
+
revision: 1,
|
|
42196
|
+
...contextSettings0
|
|
41324
42197
|
});
|
|
41325
|
-
|
|
41326
|
-
|
|
41327
|
-
|
|
41328
|
-
|
|
41329
|
-
|
|
41330
|
-
|
|
41331
|
-
|
|
41332
|
-
|
|
41333
|
-
|
|
41334
|
-
|
|
41335
|
-
|
|
41336
|
-
|
|
41337
|
-
|
|
41338
|
-
|
|
41339
|
-
|
|
41340
|
-
|
|
42198
|
+
const refreshContextSettings = async (_input, ctx) => {
|
|
42199
|
+
const next = await runtime.contextSettings();
|
|
42200
|
+
const { revision, ...current } = contextSettings.value;
|
|
42201
|
+
if (JSON.stringify(next) !== JSON.stringify(current))
|
|
42202
|
+
contextSettings.replace(ctx, { revision: revision + 1, ...next });
|
|
42203
|
+
return null;
|
|
42204
|
+
};
|
|
42205
|
+
const refreshSearchSettings = async (_input, ctx) => {
|
|
42206
|
+
const next = await runtime.searchSettings();
|
|
42207
|
+
const current = searchSettings.value;
|
|
42208
|
+
if (next.selected !== current.selected)
|
|
42209
|
+
searchSettings.replace(ctx, { revision: current.revision + 1, ...next });
|
|
42210
|
+
return null;
|
|
42211
|
+
};
|
|
42212
|
+
self2.on("directory", { subscribe: runtime.harness.subscribeCommits.bind(runtime.harness) }, async (event, ctx) => {
|
|
42213
|
+
if (!event.changes.some((change) => change.type === "conversation" || change.type === "document" && [
|
|
42214
|
+
"app.neko.session-metadata",
|
|
42215
|
+
"app.neko.activity",
|
|
42216
|
+
"app.neko.conversation",
|
|
42217
|
+
"app.neko.workspaces",
|
|
42218
|
+
"pi.agent"
|
|
42219
|
+
].includes(change.record.kind)))
|
|
42220
|
+
return;
|
|
42221
|
+
await serialize(async () => {
|
|
42222
|
+
const [items, roots] = await Promise.all([list(), runtime.listWorkspaces()]);
|
|
42223
|
+
directory.replace(ctx, { revision: directory.value.revision + 1, sessions: items });
|
|
42224
|
+
workspaceState.replace(ctx, { revision: workspaceState.value.revision + 1, workspaces: roots });
|
|
42225
|
+
});
|
|
41341
42226
|
});
|
|
41342
42227
|
return {
|
|
41343
|
-
|
|
42228
|
+
directory,
|
|
42229
|
+
workspaces: workspaceState,
|
|
42230
|
+
credentials,
|
|
42231
|
+
searchSettings,
|
|
42232
|
+
contextSettings,
|
|
41344
42233
|
async refreshCredentials(_input, ctx) {
|
|
41345
|
-
|
|
41346
|
-
revision:
|
|
42234
|
+
credentials.replace(ctx, {
|
|
42235
|
+
revision: credentials.value.revision + 1,
|
|
41347
42236
|
statuses: await readStatuses()
|
|
41348
42237
|
});
|
|
41349
42238
|
return null;
|
|
41350
|
-
}
|
|
42239
|
+
},
|
|
42240
|
+
refreshSearchSettings,
|
|
42241
|
+
refreshContextSettings
|
|
41351
42242
|
};
|
|
41352
|
-
}, {
|
|
41353
|
-
const shared = (self2) => self2.get(ServerState, {}
|
|
41354
|
-
host.serve(NekoSessionDirectory, (_input, _principal, self2) => ({
|
|
42243
|
+
}, { lifetime: "host" });
|
|
42244
|
+
const shared = (self2) => self2.get(ServerState, {});
|
|
42245
|
+
host.serve(NekoSessionDirectory, (_input, _principal, self2) => ({ variant: "ready", state: shared(self2).directory }));
|
|
41355
42246
|
host.serve(NekoWorkspaces, (_input, _principal, self2) => ({
|
|
41356
|
-
|
|
42247
|
+
variant: "ready",
|
|
41357
42248
|
state: shared(self2).workspaces,
|
|
41358
42249
|
browse: (input) => browseWorkspaces(input),
|
|
41359
42250
|
add: (input) => serialize(() => runtime.addWorkspace(input.path))
|
|
@@ -41361,7 +42252,7 @@ async function createNekoServerServices(options) {
|
|
|
41361
42252
|
host.serve(NekoCredentials, (_input, _principal, self2) => {
|
|
41362
42253
|
const hub = shared(self2);
|
|
41363
42254
|
return {
|
|
41364
|
-
|
|
42255
|
+
variant: "ready",
|
|
41365
42256
|
state: hub.credentials,
|
|
41366
42257
|
save: async (input, ctx) => {
|
|
41367
42258
|
await runtime.saveCredential(input.provider, input.key);
|
|
@@ -41373,8 +42264,34 @@ async function createNekoServerServices(options) {
|
|
|
41373
42264
|
}
|
|
41374
42265
|
};
|
|
41375
42266
|
});
|
|
42267
|
+
host.serve(NekoSearchSettings, (_input, _principal, self2) => {
|
|
42268
|
+
const hub = shared(self2);
|
|
42269
|
+
return {
|
|
42270
|
+
variant: "ready",
|
|
42271
|
+
state: hub.searchSettings,
|
|
42272
|
+
set: (input, ctx) => serialize(async () => {
|
|
42273
|
+
await runtime.setSearchProvider(input.provider);
|
|
42274
|
+
return hub.refreshSearchSettings(null, ctx);
|
|
42275
|
+
})
|
|
42276
|
+
};
|
|
42277
|
+
});
|
|
42278
|
+
host.serve(NekoContextSettings, (_input, _principal, self2) => {
|
|
42279
|
+
const hub = shared(self2);
|
|
42280
|
+
return {
|
|
42281
|
+
variant: "ready",
|
|
42282
|
+
state: hub.contextSettings,
|
|
42283
|
+
set: (input, ctx) => serialize(async () => {
|
|
42284
|
+
await runtime.setContextSettings(input);
|
|
42285
|
+
return hub.refreshContextSettings(null, ctx);
|
|
42286
|
+
}),
|
|
42287
|
+
reset: (_input, ctx) => serialize(async () => {
|
|
42288
|
+
await runtime.resetContextSettings();
|
|
42289
|
+
return hub.refreshContextSettings(null, ctx);
|
|
42290
|
+
})
|
|
42291
|
+
};
|
|
42292
|
+
});
|
|
41376
42293
|
host.serve(NekoModels, () => ({
|
|
41377
|
-
|
|
42294
|
+
variant: "ready",
|
|
41378
42295
|
list: async () => [...runtime.listModels()].map((value) => ({
|
|
41379
42296
|
...value,
|
|
41380
42297
|
input: [...value.input],
|
|
@@ -41382,7 +42299,7 @@ async function createNekoServerServices(options) {
|
|
|
41382
42299
|
}))
|
|
41383
42300
|
}));
|
|
41384
42301
|
host.serve(NekoWorkspaceFiles, () => ({
|
|
41385
|
-
|
|
42302
|
+
variant: "ready",
|
|
41386
42303
|
search: async (input, ctx) => {
|
|
41387
42304
|
const roots = await runtime.listWorkspaces();
|
|
41388
42305
|
if (!roots.some((root) => root.path === input.workspaceRoot))
|
|
@@ -41400,7 +42317,7 @@ async function createNekoServerServices(options) {
|
|
|
41400
42317
|
return found;
|
|
41401
42318
|
};
|
|
41402
42319
|
return {
|
|
41403
|
-
|
|
42320
|
+
variant: "ready",
|
|
41404
42321
|
create: (input) => serialize(async () => summary((await runtime.createConversation(input)).id)),
|
|
41405
42322
|
fork: (id) => serialize(async () => summary((await runtime.forkConversation(toConversationId(id))).id)),
|
|
41406
42323
|
archive: async (id) => {
|
|
@@ -41423,7 +42340,7 @@ async function createNekoServerServices(options) {
|
|
|
41423
42340
|
};
|
|
41424
42341
|
});
|
|
41425
42342
|
host.serve(NekoSkillMarket, () => ({
|
|
41426
|
-
|
|
42343
|
+
variant: "ready",
|
|
41427
42344
|
list: (input) => market.list(input.query).then((skills) => Promise.all(skills.map((skill) => withInstallState(skill, input.workspaceRoot)))).catch(toRemote),
|
|
41428
42345
|
detail: (input) => market.detail(input.name).then((skill) => withInstallState(skill, input.workspaceRoot)).catch(toRemote),
|
|
41429
42346
|
preview: (input) => market.preview(input.name).then((value) => ({ ...value, files: [...value.files] })).catch(toRemote),
|
|
@@ -41465,7 +42382,11 @@ var init_server_services = __esm(() => {
|
|
|
41465
42382
|
directory: DirectoryStateSchema,
|
|
41466
42383
|
workspaces: WorkspaceStateSchema,
|
|
41467
42384
|
credentials: CredentialStateSchema,
|
|
41468
|
-
|
|
42385
|
+
searchSettings: SearchSettingsStateSchema,
|
|
42386
|
+
contextSettings: ContextSettingsStateSchema,
|
|
42387
|
+
refreshCredentials: method2(Type10.Null(), Type10.Null()),
|
|
42388
|
+
refreshSearchSettings: method2(Type10.Null(), Type10.Null()),
|
|
42389
|
+
refreshContextSettings: method2(Type10.Null(), Type10.Null())
|
|
41469
42390
|
}
|
|
41470
42391
|
});
|
|
41471
42392
|
});
|
|
@@ -41474,7 +42395,7 @@ var init_server_services = __esm(() => {
|
|
|
41474
42395
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
41475
42396
|
import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
|
|
41476
42397
|
import { basename as basename6, dirname as dirname10, join as join14 } from "path";
|
|
41477
|
-
import { createHost as
|
|
42398
|
+
import { createHost as createHost2 } from "@earendil-works/chord";
|
|
41478
42399
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT4 } from "@earendil-works/chord/context";
|
|
41479
42400
|
async function startGateway(options = {}) {
|
|
41480
42401
|
const context = options.context ?? BACKGROUND_CONTEXT4;
|
|
@@ -41483,6 +42404,7 @@ async function startGateway(options = {}) {
|
|
|
41483
42404
|
const storagePath = options.storagePath ?? fileConfig.storagePath ?? DEFAULT_STORAGE_PATH;
|
|
41484
42405
|
const host = options.host ?? fileConfig.gateway?.host ?? DEFAULT_HOST;
|
|
41485
42406
|
const port = options.port !== undefined ? options.port : fileConfig.gateway?.port ?? (process.platform === "win32" ? 0 : DEFAULT_PORT);
|
|
42407
|
+
const searchProvider = options.searchProvider ?? fileConfig.webSearch?.provider;
|
|
41486
42408
|
const marketUrl = options.marketUrl ?? fileConfig.skills?.marketUrl ?? DEFAULT_SKILL_MARKET_URL;
|
|
41487
42409
|
const nekoDir = dirname10(storagePath);
|
|
41488
42410
|
const serverId = options.serverId ?? await loadOrCreateServerId(nekoDir);
|
|
@@ -41494,6 +42416,8 @@ async function startGateway(options = {}) {
|
|
|
41494
42416
|
...options.storage !== "memory" ? { storagePath } : {},
|
|
41495
42417
|
...options.credentialsPath !== undefined ? { credentialsPath: options.credentialsPath } : {},
|
|
41496
42418
|
...options.apiKey !== undefined ? { apiKey: options.apiKey } : {},
|
|
42419
|
+
...searchProvider !== undefined ? { searchProvider } : {},
|
|
42420
|
+
...fileConfig.context !== undefined ? { contextSettings: fileConfig.context } : {},
|
|
41497
42421
|
model,
|
|
41498
42422
|
thinkingLevel: options.thinkingLevel ?? fileConfig.thinkingLevel ?? "high",
|
|
41499
42423
|
...options.cwd !== undefined ? { cwd: options.cwd } : {},
|
|
@@ -41524,8 +42448,9 @@ async function startGateway(options = {}) {
|
|
|
41524
42448
|
}
|
|
41525
42449
|
async function bindGateway(runtime, options) {
|
|
41526
42450
|
const { context, host, port, socketPath, serverId, nekoDir } = options;
|
|
41527
|
-
const chord =
|
|
41528
|
-
name:
|
|
42451
|
+
const chord = createHost2({
|
|
42452
|
+
name: GATEWAY_HOST,
|
|
42453
|
+
self: HOST_PRINCIPAL,
|
|
41529
42454
|
dev: true,
|
|
41530
42455
|
onError: (error) => options.onError?.(error instanceof Error ? error : new Error(String(error))),
|
|
41531
42456
|
principalKey: gatewayPrincipalKey
|
|
@@ -41534,6 +42459,7 @@ async function bindGateway(runtime, options) {
|
|
|
41534
42459
|
host: chord,
|
|
41535
42460
|
runtime,
|
|
41536
42461
|
context,
|
|
42462
|
+
serverId,
|
|
41537
42463
|
marketUrl: options.marketUrl,
|
|
41538
42464
|
...options.updates ? { updates: options.updates } : {},
|
|
41539
42465
|
...options.updateIntervalMs ? { updateIntervalMs: options.updateIntervalMs } : {},
|
|
@@ -41549,7 +42475,10 @@ async function bindGateway(runtime, options) {
|
|
|
41549
42475
|
}
|
|
41550
42476
|
try {
|
|
41551
42477
|
for (const listener of listeners)
|
|
41552
|
-
await listener.start((link) => chord.accept(link, {
|
|
42478
|
+
await listener.start((link) => chord.accept(link, {
|
|
42479
|
+
principal: { kind: "local", connectionId: randomUUID5() },
|
|
42480
|
+
onProtocolMismatch: ({ peer }) => options.onError?.(new Error(`Rejected a client on Chord protocol ${peer ?? "unknown"}; restart or update it.`))
|
|
42481
|
+
}));
|
|
41553
42482
|
} catch (error) {
|
|
41554
42483
|
await Promise.allSettled(listeners.map((listener) => listener.close()));
|
|
41555
42484
|
await chord.close();
|
|
@@ -41612,16 +42541,6 @@ async function bindGateway(runtime, options) {
|
|
|
41612
42541
|
}).catch((error) => options.onError?.(error instanceof Error ? error : new Error(String(error))));
|
|
41613
42542
|
return gateway;
|
|
41614
42543
|
}
|
|
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
42544
|
function toStartupError(error, info) {
|
|
41626
42545
|
const message = error instanceof Error ? error.message : String(error);
|
|
41627
42546
|
const existingServerId = info.socketPath === null ? undefined : serverIdFromSocketPath(info.socketPath);
|
|
@@ -41661,9 +42580,11 @@ async function loadOrCreateServerId(nekoDir) {
|
|
|
41661
42580
|
var DEFAULT_HOST = "127.0.0.1", DEFAULT_PORT = 8321, SERVER_ID_FILENAME = "gateway-server-id";
|
|
41662
42581
|
var init_gateway = __esm(() => {
|
|
41663
42582
|
init_src();
|
|
42583
|
+
init_contract();
|
|
41664
42584
|
init_admin();
|
|
41665
42585
|
init_listener();
|
|
41666
42586
|
init_port_file();
|
|
42587
|
+
init_principal();
|
|
41667
42588
|
init_server_services();
|
|
41668
42589
|
init_unix_link();
|
|
41669
42590
|
init_ws_listener();
|
|
@@ -41768,6 +42689,7 @@ var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5000;
|
|
|
41768
42689
|
// packages/gateway/src/index.ts
|
|
41769
42690
|
var init_src2 = __esm(() => {
|
|
41770
42691
|
init_client();
|
|
42692
|
+
init_client_session();
|
|
41771
42693
|
init_contract();
|
|
41772
42694
|
init_gateway();
|
|
41773
42695
|
init_link();
|
|
@@ -41838,7 +42760,7 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
41838
42760
|
const timeoutMs = plan.spawnTimeoutMs ?? 20000;
|
|
41839
42761
|
if (plan.platform === "win32") {
|
|
41840
42762
|
const timeoutAt = Date.now() + timeoutMs;
|
|
41841
|
-
const serverId = plan.fileServerId ?? await
|
|
42763
|
+
const serverId = plan.fileServerId ?? await waitForValue(() => readServerId(join15(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
|
|
41842
42764
|
let lastError = new Error("Gateway port file unavailable");
|
|
41843
42765
|
while (Date.now() < timeoutAt) {
|
|
41844
42766
|
if (child.exitCode !== null || child.signalCode !== null || spawnError() !== undefined) {
|
|
@@ -41875,10 +42797,10 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
41875
42797
|
}
|
|
41876
42798
|
let socketPath = plan.socketPath;
|
|
41877
42799
|
if (socketPath === undefined) {
|
|
41878
|
-
const generated = await
|
|
42800
|
+
const generated = await waitForValue(() => readServerId(join15(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
|
|
41879
42801
|
socketPath = join15(plan.nekoDir, `${generated}.sock`);
|
|
41880
42802
|
}
|
|
41881
|
-
await
|
|
42803
|
+
await waitForValue(() => existsSync3(socketPath) ? true : undefined, `gateway ready (socket: ${socketPath})`, timeoutMs, child, outputTail, spawnError);
|
|
41882
42804
|
const serverId = plan.fileServerId ?? serverIdFromSocketPath2(socketPath) ?? readServerId(join15(plan.nekoDir, SERVER_ID_FILENAME2));
|
|
41883
42805
|
if (serverId === undefined) {
|
|
41884
42806
|
throw new Error(`Cannot determine gateway serverId (socket: ${socketPath})`);
|
|
@@ -41975,7 +42897,7 @@ function captureOutput(child) {
|
|
|
41975
42897
|
return () => [stdout.trimEnd(), stderr.trimEnd()].filter((part) => part !== "").join(`
|
|
41976
42898
|
`);
|
|
41977
42899
|
}
|
|
41978
|
-
async function
|
|
42900
|
+
async function waitForValue(poll, what, timeoutMs, child, outputTail, spawnError) {
|
|
41979
42901
|
const start = Date.now();
|
|
41980
42902
|
for (;; ) {
|
|
41981
42903
|
const error = spawnError();
|
|
@@ -42010,7 +42932,7 @@ var init_gateway2 = __esm(() => {
|
|
|
42010
42932
|
});
|
|
42011
42933
|
|
|
42012
42934
|
// packages/tui/src/help-ui.ts
|
|
42013
|
-
import { getKeybindings, matchesKey as
|
|
42935
|
+
import { getKeybindings, matchesKey as matchesKey8, TUI_KEYBINDINGS, wrapTextWithAnsi as wrapTextWithAnsi4 } from "@earendil-works/pi-tui";
|
|
42014
42936
|
function helpLines(platform = process.platform) {
|
|
42015
42937
|
const rows = [];
|
|
42016
42938
|
const section = (title, hints) => {
|
|
@@ -42055,6 +42977,15 @@ function helpLines(platform = process.platform) {
|
|
|
42055
42977
|
["End", "Jump to the newest output"],
|
|
42056
42978
|
["\u2190", "Back"]
|
|
42057
42979
|
]);
|
|
42980
|
+
section("Web search", [
|
|
42981
|
+
["/search", "Choose the web search provider"],
|
|
42982
|
+
["Enter", "Apply the highlighted provider"]
|
|
42983
|
+
]);
|
|
42984
|
+
section("Context", [
|
|
42985
|
+
["/context", "Tune compaction and tool result pruning"],
|
|
42986
|
+
["Enter", "Choose a setting, then a value to save"],
|
|
42987
|
+
["Esc", "Back"]
|
|
42988
|
+
]);
|
|
42058
42989
|
section("Skills", [
|
|
42059
42990
|
["U", "Install for user"],
|
|
42060
42991
|
["P", "Install for project"],
|
|
@@ -42107,13 +43038,13 @@ class HelpDetail {
|
|
|
42107
43038
|
this.#offset = Math.max(0, Math.min(this.#max, this.#offset + delta));
|
|
42108
43039
|
}
|
|
42109
43040
|
handleKey(data) {
|
|
42110
|
-
if (
|
|
43041
|
+
if (matchesKey8(data, "up"))
|
|
42111
43042
|
this.scroll(-1);
|
|
42112
|
-
else if (
|
|
43043
|
+
else if (matchesKey8(data, "down"))
|
|
42113
43044
|
this.scroll(1);
|
|
42114
|
-
else if (
|
|
43045
|
+
else if (matchesKey8(data, "pageUp"))
|
|
42115
43046
|
this.scroll(-this.#page);
|
|
42116
|
-
else if (
|
|
43047
|
+
else if (matchesKey8(data, "pageDown"))
|
|
42117
43048
|
this.scroll(this.#page);
|
|
42118
43049
|
else
|
|
42119
43050
|
return false;
|
|
@@ -42140,7 +43071,7 @@ import { readFile as readFile9, stat as stat5 } from "fs/promises";
|
|
|
42140
43071
|
import { homedir as homedir8 } from "os";
|
|
42141
43072
|
import { basename as basename8, resolve as resolve5 } from "path";
|
|
42142
43073
|
import { promisify as promisify2 } from "util";
|
|
42143
|
-
import { isKeyRelease as isKeyRelease4, isKeyRepeat as isKeyRepeat2, matchesKey as
|
|
43074
|
+
import { isKeyRelease as isKeyRelease4, isKeyRepeat as isKeyRepeat2, matchesKey as matchesKey9 } from "@earendil-works/pi-tui";
|
|
42144
43075
|
function imagePaths(text, platform = process.platform) {
|
|
42145
43076
|
const paths = [];
|
|
42146
43077
|
let word = "", quote = "", escaped = false;
|
|
@@ -42234,7 +43165,7 @@ end try`]).catch(() => ""));
|
|
|
42234
43165
|
return { text: String(await run(tool, wayland ? ["--no-newline"] : ["-selection", "clipboard", "-o"])) };
|
|
42235
43166
|
}
|
|
42236
43167
|
function isClipboardShortcut(data, platform = process.platform) {
|
|
42237
|
-
return !isKeyRelease4(data) && !isKeyRepeat2(data) &&
|
|
43168
|
+
return !isKeyRelease4(data) && !isKeyRepeat2(data) && matchesKey9(data, platform === "win32" ? "alt+v" : "ctrl+v");
|
|
42238
43169
|
}
|
|
42239
43170
|
var exec;
|
|
42240
43171
|
var init_image_input = __esm(() => {
|
|
@@ -42279,9 +43210,55 @@ function createAppLayout(parts) {
|
|
|
42279
43210
|
var usableSize = (width, height) => width >= 40 && height >= 10;
|
|
42280
43211
|
var init_layout = () => {};
|
|
42281
43212
|
|
|
43213
|
+
// packages/tui/src/search-ui.ts
|
|
43214
|
+
function searchItems(settings, credentials) {
|
|
43215
|
+
if (!settings)
|
|
43216
|
+
return [];
|
|
43217
|
+
const source = (provider) => {
|
|
43218
|
+
const status = credentials?.statuses[provider];
|
|
43219
|
+
return status ? SOURCES2[status.source] : "loading";
|
|
43220
|
+
};
|
|
43221
|
+
const deepseekMissing = credentials?.statuses.deepseek.source === "none";
|
|
43222
|
+
const descriptions = {
|
|
43223
|
+
tavily: `Tavily search (${source("tavily")}). Default`,
|
|
43224
|
+
deepseek: deepseekMissing ? "No DeepSeek key. Run /login to set one before choosing it" : `DeepSeek search and fetch with your DeepSeek API key (${source("deepseek")})`
|
|
43225
|
+
};
|
|
43226
|
+
return SEARCH_PROVIDERS.map((value) => ({
|
|
43227
|
+
value,
|
|
43228
|
+
label: `${NAMES[value]}${settings.selected === value ? " (Current)" : ""}`,
|
|
43229
|
+
description: descriptions[value]
|
|
43230
|
+
}));
|
|
43231
|
+
}
|
|
43232
|
+
function openSearchPanel(panel, client, notify) {
|
|
43233
|
+
const items = () => searchItems(client.searchSettings.state.value, client.credentials.state.value);
|
|
43234
|
+
const refresh = () => panel.update(items());
|
|
43235
|
+
const stops = [client.searchSettings.state.subscribe(refresh), client.credentials.state.subscribe(refresh)];
|
|
43236
|
+
panel.emptyText = SEARCH_EMPTY;
|
|
43237
|
+
panel.show("Web search", async () => items(), async (value) => {
|
|
43238
|
+
await client.searchSettings.set({ provider: value });
|
|
43239
|
+
notify(`Web search provider set to ${NAMES[value]}.`);
|
|
43240
|
+
});
|
|
43241
|
+
return () => {
|
|
43242
|
+
for (const stop of stops)
|
|
43243
|
+
stop();
|
|
43244
|
+
};
|
|
43245
|
+
}
|
|
43246
|
+
var SEARCH_EMPTY = "Search settings are unavailable.", NAMES, SOURCES2;
|
|
43247
|
+
var init_search_ui = __esm(() => {
|
|
43248
|
+
init_search_providers();
|
|
43249
|
+
NAMES = { tavily: "Tavily", deepseek: "DeepSeek" };
|
|
43250
|
+
SOURCES2 = {
|
|
43251
|
+
saved: "saved key",
|
|
43252
|
+
environment: "environment variable",
|
|
43253
|
+
runtime: "runtime key",
|
|
43254
|
+
keyless: "keyless, rate-limited",
|
|
43255
|
+
none: "no key"
|
|
43256
|
+
};
|
|
43257
|
+
});
|
|
43258
|
+
|
|
42282
43259
|
// packages/tui/src/skill-market-ui.ts
|
|
42283
43260
|
import { ChordError as ChordError6 } from "@earendil-works/chord";
|
|
42284
|
-
import { Markdown as Markdown3, matchesKey as
|
|
43261
|
+
import { Markdown as Markdown3, matchesKey as matchesKey10, wrapTextWithAnsi as wrapTextWithAnsi5 } from "@earendil-works/pi-tui";
|
|
42285
43262
|
|
|
42286
43263
|
class SkillDetailView {
|
|
42287
43264
|
#skill = null;
|
|
@@ -42320,11 +43297,11 @@ class SkillDetailView {
|
|
|
42320
43297
|
}
|
|
42321
43298
|
handleKey(data) {
|
|
42322
43299
|
if (this.#confirm) {
|
|
42323
|
-
if (
|
|
43300
|
+
if (matchesKey10(data, "y")) {
|
|
42324
43301
|
const pending = this.#confirm;
|
|
42325
43302
|
this.#confirm = null;
|
|
42326
43303
|
this.run(pending.run);
|
|
42327
|
-
} else if (
|
|
43304
|
+
} else if (matchesKey10(data, "n")) {
|
|
42328
43305
|
this.#confirm = null;
|
|
42329
43306
|
}
|
|
42330
43307
|
return true;
|
|
@@ -42333,17 +43310,17 @@ class SkillDetailView {
|
|
|
42333
43310
|
return true;
|
|
42334
43311
|
const skill = this.#skill;
|
|
42335
43312
|
const builtinOnly = skill.installed.builtin && !skill.installed.user && !skill.installed.project;
|
|
42336
|
-
if (
|
|
43313
|
+
if (matchesKey10(data, "v")) {
|
|
42337
43314
|
this.#openPreview(this.#name);
|
|
42338
43315
|
return true;
|
|
42339
43316
|
}
|
|
42340
43317
|
if (builtinOnly)
|
|
42341
43318
|
return true;
|
|
42342
|
-
if (
|
|
43319
|
+
if (matchesKey10(data, "u"))
|
|
42343
43320
|
this.askInstall("user", skill);
|
|
42344
|
-
else if (
|
|
43321
|
+
else if (matchesKey10(data, "p"))
|
|
42345
43322
|
this.askInstall("project", skill);
|
|
42346
|
-
else if (
|
|
43323
|
+
else if (matchesKey10(data, "d"))
|
|
42347
43324
|
this.askUninstall(skill);
|
|
42348
43325
|
else
|
|
42349
43326
|
return false;
|
|
@@ -42468,13 +43445,13 @@ class SkillPreviewView {
|
|
|
42468
43445
|
this.#offset = Math.max(0, Math.min(this.#maxOffset, this.#offset + delta));
|
|
42469
43446
|
}
|
|
42470
43447
|
handleKey(data) {
|
|
42471
|
-
if (
|
|
43448
|
+
if (matchesKey10(data, "up"))
|
|
42472
43449
|
this.scroll(-1);
|
|
42473
|
-
else if (
|
|
43450
|
+
else if (matchesKey10(data, "down"))
|
|
42474
43451
|
this.scroll(1);
|
|
42475
|
-
else if (
|
|
43452
|
+
else if (matchesKey10(data, "pageUp"))
|
|
42476
43453
|
this.scroll(-this.#pageSize);
|
|
42477
|
-
else if (
|
|
43454
|
+
else if (matchesKey10(data, "pageDown"))
|
|
42478
43455
|
this.scroll(this.#pageSize);
|
|
42479
43456
|
else
|
|
42480
43457
|
return false;
|
|
@@ -42526,7 +43503,7 @@ var init_skill_market_ui = __esm(() => {
|
|
|
42526
43503
|
});
|
|
42527
43504
|
|
|
42528
43505
|
// packages/tui/src/subagents-ui.ts
|
|
42529
|
-
import { matchesKey as
|
|
43506
|
+
import { matchesKey as matchesKey11 } from "@earendil-works/pi-tui";
|
|
42530
43507
|
|
|
42531
43508
|
class SubagentDetail {
|
|
42532
43509
|
get help() {
|
|
@@ -42646,13 +43623,13 @@ class SubagentDetail {
|
|
|
42646
43623
|
this.history();
|
|
42647
43624
|
}
|
|
42648
43625
|
handleKey(data) {
|
|
42649
|
-
if (
|
|
43626
|
+
if (matchesKey11(data, "up")) {
|
|
42650
43627
|
this.scroll(-1);
|
|
42651
|
-
} else if (
|
|
43628
|
+
} else if (matchesKey11(data, "down")) {
|
|
42652
43629
|
this.scroll(1);
|
|
42653
|
-
} else if (
|
|
43630
|
+
} else if (matchesKey11(data, "pageUp")) {
|
|
42654
43631
|
this.scroll(-this.#pageSize);
|
|
42655
|
-
} else if (
|
|
43632
|
+
} else if (matchesKey11(data, "pageDown")) {
|
|
42656
43633
|
this.scroll(this.#pageSize);
|
|
42657
43634
|
} else if (data.toLowerCase() === "e") {
|
|
42658
43635
|
this.#expanded = !this.#expanded;
|
|
@@ -42668,7 +43645,7 @@ class SubagentDetail {
|
|
|
42668
43645
|
help: shortcutHints(["Enter", "Stop"], ["Esc", "Back"]),
|
|
42669
43646
|
lines: () => [theme.warning("Stop this task? Existing changes will be kept.")],
|
|
42670
43647
|
handleKey: (key) => {
|
|
42671
|
-
if (!
|
|
43648
|
+
if (!matchesKey11(key, "enter"))
|
|
42672
43649
|
return false;
|
|
42673
43650
|
this.#service.stop(stopTaskId === undefined ? null : { taskId: stopTaskId }).catch((e) => {
|
|
42674
43651
|
this.#error = String(e);
|
|
@@ -42799,7 +43776,7 @@ var init_subagents_ui = __esm(() => {
|
|
|
42799
43776
|
});
|
|
42800
43777
|
|
|
42801
43778
|
// packages/tui/src/version-ui.ts
|
|
42802
|
-
import { matchesKey as
|
|
43779
|
+
import { matchesKey as matchesKey12, Text as Text9 } from "@earendil-works/pi-tui";
|
|
42803
43780
|
function actionFor(state, installed, runningVersion) {
|
|
42804
43781
|
const action = updateAction({ gateway: state, frontend: runningVersion, installed });
|
|
42805
43782
|
return action === "install" && !newer(state?.latestVersion, runningVersion) ? "none" : action;
|
|
@@ -42877,9 +43854,9 @@ class VersionDetail {
|
|
|
42877
43854
|
return lines.slice(this.#offset, this.#offset + height);
|
|
42878
43855
|
}
|
|
42879
43856
|
handleKey(data) {
|
|
42880
|
-
if (
|
|
43857
|
+
if (matchesKey12(data, "up"))
|
|
42881
43858
|
this.#offset = Math.max(0, this.#offset - 1);
|
|
42882
|
-
else if (
|
|
43859
|
+
else if (matchesKey12(data, "down"))
|
|
42883
43860
|
this.#offset++;
|
|
42884
43861
|
else
|
|
42885
43862
|
return false;
|
|
@@ -42944,7 +43921,7 @@ import {
|
|
|
42944
43921
|
Container as Container6,
|
|
42945
43922
|
isKeyRelease as isKeyRelease5,
|
|
42946
43923
|
Loader,
|
|
42947
|
-
matchesKey as
|
|
43924
|
+
matchesKey as matchesKey13,
|
|
42948
43925
|
ProcessTerminal,
|
|
42949
43926
|
ScrollView,
|
|
42950
43927
|
TuiAltScreen
|
|
@@ -42980,6 +43957,8 @@ class NekoTuiApp {
|
|
|
42980
43957
|
#chat;
|
|
42981
43958
|
#agentsPanel = false;
|
|
42982
43959
|
#jobsPanel = false;
|
|
43960
|
+
#searchStop;
|
|
43961
|
+
#contextStop;
|
|
42983
43962
|
#agentDetails = new Map;
|
|
42984
43963
|
#sessionId = "";
|
|
42985
43964
|
#viewUnsubscribe;
|
|
@@ -43021,6 +44000,10 @@ class NekoTuiApp {
|
|
|
43021
44000
|
this.#thinkingModel = undefined;
|
|
43022
44001
|
this.#agentsPanel = false;
|
|
43023
44002
|
this.#jobsPanel = false;
|
|
44003
|
+
this.#searchStop?.();
|
|
44004
|
+
this.#searchStop = undefined;
|
|
44005
|
+
this.#contextStop?.();
|
|
44006
|
+
this.#contextStop = undefined;
|
|
43024
44007
|
clearInterval(this.#sessionPanelTimer);
|
|
43025
44008
|
this.#sessionPanelTimer = undefined;
|
|
43026
44009
|
this.#ui.setFocus(this.#editor);
|
|
@@ -43081,10 +44064,10 @@ class NekoTuiApp {
|
|
|
43081
44064
|
this.#ui.addInputListener((data) => {
|
|
43082
44065
|
if (isKeyRelease5(data))
|
|
43083
44066
|
return { consume: true };
|
|
43084
|
-
if (!
|
|
44067
|
+
if (!matchesKey13(data, "escape") || this.#panel.open || this.#login.open || this.#busyText !== undefined || this.#lastView?.compaction?.active.length)
|
|
43085
44068
|
this.#editor.resetEscapeSequence();
|
|
43086
44069
|
if (!usableSize(this.#terminal.columns, this.#terminal.rows)) {
|
|
43087
|
-
if (
|
|
44070
|
+
if (matchesKey13(data, "ctrl+c"))
|
|
43088
44071
|
this.exit();
|
|
43089
44072
|
return { consume: true };
|
|
43090
44073
|
}
|
|
@@ -43095,11 +44078,11 @@ class NekoTuiApp {
|
|
|
43095
44078
|
}
|
|
43096
44079
|
if (this.#sessionOperation)
|
|
43097
44080
|
return { consume: true };
|
|
43098
|
-
if (
|
|
44081
|
+
if (matchesKey13(data, "right") && this.#lastView?.subagents?.length && this.#editor.getText() === "" && !this.#editor.isShowingAutocomplete()) {
|
|
43099
44082
|
this.#showAgents();
|
|
43100
44083
|
return { consume: true };
|
|
43101
44084
|
}
|
|
43102
|
-
if (
|
|
44085
|
+
if (matchesKey13(data, process.platform === "win32" ? "alt+v" : "ctrl+v")) {
|
|
43103
44086
|
if (isClipboardShortcut(data))
|
|
43104
44087
|
this.pasteClipboard();
|
|
43105
44088
|
return { consume: true };
|
|
@@ -43116,26 +44099,26 @@ class NekoTuiApp {
|
|
|
43116
44099
|
return { consume: true };
|
|
43117
44100
|
}
|
|
43118
44101
|
}
|
|
43119
|
-
if (
|
|
44102
|
+
if (matchesKey13(data, "escape") && this.#editor.isShowingAutocomplete()) {
|
|
43120
44103
|
this.#editor.resetEscapeSequence();
|
|
43121
44104
|
return;
|
|
43122
44105
|
}
|
|
43123
|
-
if (
|
|
44106
|
+
if (matchesKey13(data, "ctrl+c")) {
|
|
43124
44107
|
if (this.#busyText !== undefined)
|
|
43125
44108
|
this.abort();
|
|
43126
44109
|
else
|
|
43127
44110
|
this.exit();
|
|
43128
44111
|
return { consume: true };
|
|
43129
44112
|
}
|
|
43130
|
-
if (
|
|
44113
|
+
if (matchesKey13(data, "escape") && (this.#busyText !== undefined || this.#lastView?.compaction?.active.some((c) => c.reason === "manual" || c.blocking))) {
|
|
43131
44114
|
this.abort();
|
|
43132
44115
|
return { consume: true };
|
|
43133
44116
|
}
|
|
43134
|
-
if (
|
|
44117
|
+
if (matchesKey13(data, "ctrl+s")) {
|
|
43135
44118
|
this.openSessionsOverlay();
|
|
43136
44119
|
return { consume: true };
|
|
43137
44120
|
}
|
|
43138
|
-
if (this.#busyText !== undefined &&
|
|
44121
|
+
if (this.#busyText !== undefined && matchesKey13(data, "alt+enter")) {
|
|
43139
44122
|
this.submitFollowUp();
|
|
43140
44123
|
return { consume: true };
|
|
43141
44124
|
}
|
|
@@ -43194,7 +44177,7 @@ class NekoTuiApp {
|
|
|
43194
44177
|
const installed = await installedVersion();
|
|
43195
44178
|
if (this.#exitStarted)
|
|
43196
44179
|
return;
|
|
43197
|
-
const notice = versionNotice(this.#gateway.client.application.info.value
|
|
44180
|
+
const notice = versionNotice(this.#gateway.client.application.info.value, installed);
|
|
43198
44181
|
this.#versionNotice.clear();
|
|
43199
44182
|
if (notice)
|
|
43200
44183
|
this.#versionNotice.addChild(new ThemedTruncatedText(() => formatNotice(notice, "info"), 1, 0));
|
|
@@ -43242,6 +44225,14 @@ class NekoTuiApp {
|
|
|
43242
44225
|
this.#showJobs();
|
|
43243
44226
|
return;
|
|
43244
44227
|
}
|
|
44228
|
+
if (trimmed === "/search") {
|
|
44229
|
+
this.#showSearch();
|
|
44230
|
+
return;
|
|
44231
|
+
}
|
|
44232
|
+
if (trimmed === "/context") {
|
|
44233
|
+
this.#showContext();
|
|
44234
|
+
return;
|
|
44235
|
+
}
|
|
43245
44236
|
const instructions = compactInstructions(trimmed);
|
|
43246
44237
|
if (instructions !== null) {
|
|
43247
44238
|
const chat = this.#chat;
|
|
@@ -43293,7 +44284,7 @@ class NekoTuiApp {
|
|
|
43293
44284
|
if (trimmed === "/version") {
|
|
43294
44285
|
if (!this.#exitStarted && !this.#panel.open && !this.#login.open) {
|
|
43295
44286
|
openVersion(this.#panel, async () => ({
|
|
43296
|
-
state: this.#gateway.client.application.info.value
|
|
44287
|
+
state: this.#gateway.client.application.info.value,
|
|
43297
44288
|
installed: await installedVersion()
|
|
43298
44289
|
}), () => this.#gateway.client.application.check(null));
|
|
43299
44290
|
this.#ui.setFocus(this.#panel);
|
|
@@ -43327,7 +44318,7 @@ class NekoTuiApp {
|
|
|
43327
44318
|
const chat = this.#chat;
|
|
43328
44319
|
if (chat === undefined)
|
|
43329
44320
|
return;
|
|
43330
|
-
if (this.#lastView?.agent.model?.provider === "deepseek" && this.#gateway.client.credentials.state.value
|
|
44321
|
+
if (this.#lastView?.agent.model?.provider === "deepseek" && this.#gateway.client.credentials.state.value.statuses.deepseek.source === "none") {
|
|
43331
44322
|
this.#editor.setText(text);
|
|
43332
44323
|
this.addNotice("Configure DeepSeek with /login before sending.", "warning");
|
|
43333
44324
|
return;
|
|
@@ -43445,7 +44436,7 @@ class NekoTuiApp {
|
|
|
43445
44436
|
this.addNotice(messageOf(error), "warning", { wrap: true });
|
|
43446
44437
|
return;
|
|
43447
44438
|
}
|
|
43448
|
-
const summary = this.#gateway.client.directory.state.value
|
|
44439
|
+
const summary = this.#gateway.client.directory.state.value.sessions.find((s) => s.id === sessionId);
|
|
43449
44440
|
const title = summary ? sessionName(summary, "") : "";
|
|
43450
44441
|
this.addNotice("Exporting...");
|
|
43451
44442
|
try {
|
|
@@ -43498,8 +44489,7 @@ class NekoTuiApp {
|
|
|
43498
44489
|
this.#historyItems = undefined;
|
|
43499
44490
|
this.#resetTranscript();
|
|
43500
44491
|
const view = handle.view;
|
|
43501
|
-
|
|
43502
|
-
this.applyView(view.value);
|
|
44492
|
+
this.applyView(view.value);
|
|
43503
44493
|
this.#viewUnsubscribe = view.subscribe(() => {
|
|
43504
44494
|
this.scheduleApply();
|
|
43505
44495
|
});
|
|
@@ -43541,7 +44531,7 @@ class NekoTuiApp {
|
|
|
43541
44531
|
async archiveSession() {
|
|
43542
44532
|
const current = this.#sessionId;
|
|
43543
44533
|
await this.#gateway.client.management.archive(current);
|
|
43544
|
-
const next = recentlyVisited(this.#gateway.client.directory.state.value
|
|
44534
|
+
const next = recentlyVisited(this.#gateway.client.directory.state.value.sessions, this.#options.cwd).find((s) => s.id !== current);
|
|
43545
44535
|
if (next)
|
|
43546
44536
|
await this.switchSession(next.id);
|
|
43547
44537
|
else
|
|
@@ -43550,7 +44540,7 @@ class NekoTuiApp {
|
|
|
43550
44540
|
sessionItems() {
|
|
43551
44541
|
return [
|
|
43552
44542
|
{ value: NEW_SESSION_VALUE, label: "+ New session", description: this.#options.cwd },
|
|
43553
|
-
...sessionPickerItems(this.#gateway.client.directory.state.value
|
|
44543
|
+
...sessionPickerItems(this.#gateway.client.directory.state.value.sessions, this.#options.cwd, this.#sessionId)
|
|
43554
44544
|
];
|
|
43555
44545
|
}
|
|
43556
44546
|
openSessionsOverlay() {
|
|
@@ -43685,7 +44675,7 @@ class NekoTuiApp {
|
|
|
43685
44675
|
this.#panel.pointerSelection = true;
|
|
43686
44676
|
this.#panel.emptyText = JOBS_EMPTY;
|
|
43687
44677
|
this.#panel.listKey = (data, value) => {
|
|
43688
|
-
if (
|
|
44678
|
+
if (matchesKey13(data, "left")) {
|
|
43689
44679
|
this.#panel.close();
|
|
43690
44680
|
return true;
|
|
43691
44681
|
}
|
|
@@ -43699,6 +44689,18 @@ class NekoTuiApp {
|
|
|
43699
44689
|
this.#panel.show("Jobs", async () => jobItems(jobs.list()), async (value) => new JobDetail(jobs, value, this.#panel, changed).open());
|
|
43700
44690
|
this.#ui.setFocus(this.#panel);
|
|
43701
44691
|
}
|
|
44692
|
+
#showSearch() {
|
|
44693
|
+
if (this.#exitStarted || this.#panel.open || this.#login.open)
|
|
44694
|
+
return;
|
|
44695
|
+
this.#searchStop = openSearchPanel(this.#panel, this.#gateway.client, (message) => this.addNotice(message));
|
|
44696
|
+
this.#ui.setFocus(this.#panel);
|
|
44697
|
+
}
|
|
44698
|
+
#showContext() {
|
|
44699
|
+
if (this.#exitStarted || this.#panel.open || this.#login.open)
|
|
44700
|
+
return;
|
|
44701
|
+
this.#contextStop = openContextPanel(this.#panel, this.#gateway.client, (message) => this.addNotice(message));
|
|
44702
|
+
this.#ui.setFocus(this.#panel);
|
|
44703
|
+
}
|
|
43702
44704
|
#agentDetail(target) {
|
|
43703
44705
|
const round = typeof target === "number" ? this.#lastView?.subagents?.find((r) => r.taskId === target) : undefined;
|
|
43704
44706
|
const id = typeof target === "string" ? target : round?.agentId ?? `agent-${round?.childId}`;
|
|
@@ -44085,14 +45087,7 @@ async function runTui(options = {}) {
|
|
|
44085
45087
|
}
|
|
44086
45088
|
}
|
|
44087
45089
|
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 ?? [];
|
|
45090
|
+
const sessions = gateway.client.directory.state.value.sessions;
|
|
44096
45091
|
const first = recentlyVisited(sessions, cwd)[0];
|
|
44097
45092
|
if (first !== undefined)
|
|
44098
45093
|
return first.id;
|
|
@@ -44118,6 +45113,7 @@ var init_tui = __esm(() => {
|
|
|
44118
45113
|
init_tool_execution();
|
|
44119
45114
|
init_user_message();
|
|
44120
45115
|
init_welcome();
|
|
45116
|
+
init_context_ui();
|
|
44121
45117
|
init_export_command();
|
|
44122
45118
|
init_file_completion();
|
|
44123
45119
|
init_gateway2();
|
|
@@ -44126,6 +45122,7 @@ var init_tui = __esm(() => {
|
|
|
44126
45122
|
init_jobs_ui();
|
|
44127
45123
|
init_labels();
|
|
44128
45124
|
init_layout();
|
|
45125
|
+
init_search_ui();
|
|
44129
45126
|
init_skill_market_ui();
|
|
44130
45127
|
init_subagents_ui();
|
|
44131
45128
|
init_theme();
|
|
@@ -44216,7 +45213,7 @@ var USAGE = `\u7528\u6CD5: neko tui [\u9009\u9879]
|
|
|
44216
45213
|
|
|
44217
45214
|
\u6062\u590D\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6700\u8FD1\u4F7F\u7528\u7684\u4F1A\u8BDD\uFF0C\u4E0D\u5B58\u5728\u5219\u521B\u5EFA\u3002
|
|
44218
45215
|
\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`;
|
|
45216
|
+
\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
45217
|
var init_main = __esm(async () => {
|
|
44221
45218
|
init_tui();
|
|
44222
45219
|
try {
|
|
@@ -44453,7 +45450,7 @@ var init_gateway_spawn = __esm(() => {
|
|
|
44453
45450
|
|
|
44454
45451
|
// packages/web/src/mobile-admin.ts
|
|
44455
45452
|
import { randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
44456
|
-
import { ChordError as ChordError7
|
|
45453
|
+
import { ChordError as ChordError7 } from "@earendil-works/chord";
|
|
44457
45454
|
function createMobileAdminCaller(gateway, nekoDir, timeouts = {}) {
|
|
44458
45455
|
const serverId = gateway.serverId;
|
|
44459
45456
|
let tokenPromise;
|
|
@@ -44468,7 +45465,7 @@ function createMobileAdminCaller(gateway, nekoDir, timeouts = {}) {
|
|
|
44468
45465
|
const token = await readToken();
|
|
44469
45466
|
signal.throwIfAborted();
|
|
44470
45467
|
const link = gateway.socketPath !== undefined ? await createUnixLink(gateway.socketPath) : createWsLink(gateway.wsUrl);
|
|
44471
|
-
const host =
|
|
45468
|
+
const host = createClientHost(link);
|
|
44472
45469
|
const release = () => {
|
|
44473
45470
|
host.close().catch(() => {
|
|
44474
45471
|
return;
|
|
@@ -44477,7 +45474,8 @@ function createMobileAdminCaller(gateway, nekoDir, timeouts = {}) {
|
|
|
44477
45474
|
};
|
|
44478
45475
|
signal.addEventListener("abort", release, { once: true });
|
|
44479
45476
|
try {
|
|
44480
|
-
|
|
45477
|
+
await assertGatewayIdentity(host, serverId, signal);
|
|
45478
|
+
const admin = await host.get(NekoMobileAdmin, { token }, { signal });
|
|
44481
45479
|
const call = admin[method];
|
|
44482
45480
|
if (call === undefined)
|
|
44483
45481
|
throw new Error(`unknown mobile admin method: ${method}`);
|
|
@@ -45167,7 +46165,8 @@ var GATEWAY_USAGE = `\u7528\u6CD5: neko serve [\u9009\u9879]
|
|
|
45167
46165
|
--config <dir> neko.config.json \u67E5\u627E\u8D77\u59CB\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09
|
|
45168
46166
|
-h, --help \u663E\u793A\u6B64\u5E2E\u52A9
|
|
45169
46167
|
|
|
45170
|
-
\u53EF\u901A\u8FC7\u524D\u7AEF\u8BBE\u7F6E\u6216 DEEPSEEK_API_KEY \u914D\u7F6E\u51ED\u636E\
|
|
46168
|
+
\u53EF\u901A\u8FC7\u524D\u7AEF\u8BBE\u7F6E\u6216 DEEPSEEK_API_KEY / TAVILY_API_KEY \u914D\u7F6E\u51ED\u636E\uFF1B
|
|
46169
|
+
\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
46170
|
|
|
45172
46171
|
// packages/gateway/src/main.ts
|
|
45173
46172
|
var argv, shutdown2, options, gateway;
|
|
@@ -45211,7 +46210,8 @@ var USAGE3 = `\u7528\u6CD5: neko <\u547D\u4EE4> [\u9009\u9879]
|
|
|
45211
46210
|
-h, --help \u663E\u793A\u6B64\u5E2E\u52A9
|
|
45212
46211
|
|
|
45213
46212
|
\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
|
|
46213
|
+
\u53EF\u901A\u8FC7 TUI /login\u3001Web \u8BBE\u7F6E\u9875\u6216 DEEPSEEK_API_KEY / TAVILY_API_KEY \u914D\u7F6E\u51ED\u636E\u3002
|
|
46214
|
+
\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
46215
|
var [command2, ...rest] = process.argv.slice(2);
|
|
45216
46216
|
switch (command2) {
|
|
45217
46217
|
case "update": {
|