neko-harness 0.4.8 → 0.5.0
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 +13 -6
- package/dist/client/assets/index-DCZeTvcw.css +2 -0
- package/dist/client/assets/index-v4Ct9JMn.js +396 -0
- package/dist/client/index.html +36 -2
- package/dist/gateway.js +100 -23
- package/dist/neko.js +38846 -36987
- package/package.json +3 -2
- package/dist/client/assets/index-vrkP1dHF.js +0 -395
- package/dist/client/assets/index-wmHY1s4T.css +0 -2
package/dist/client/index.html
CHANGED
|
@@ -4,13 +4,47 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="color-scheme" content="light" />
|
|
7
|
+
<script>
|
|
8
|
+
// 首屏前应用外观偏好(与 src/client/lib/appearance.ts 的键名/属性名对拍,test/appearance.test.ts)
|
|
9
|
+
(() => {
|
|
10
|
+
try {
|
|
11
|
+
const data = JSON.parse(localStorage.getItem("neko.appearance") || "null");
|
|
12
|
+
if (!data || typeof data !== "object") return;
|
|
13
|
+
const attrs = {
|
|
14
|
+
theme: "data-theme",
|
|
15
|
+
font: "data-chat-font",
|
|
16
|
+
size: "data-chat-size",
|
|
17
|
+
weight: "data-chat-weight",
|
|
18
|
+
leading: "data-chat-leading",
|
|
19
|
+
};
|
|
20
|
+
for (const key of Object.keys(attrs)) {
|
|
21
|
+
const value = data[key];
|
|
22
|
+
if (typeof value === "string" && /^[a-z0-9-]{1,16}$/.test(value))
|
|
23
|
+
document.documentElement.setAttribute(attrs[key], value);
|
|
24
|
+
}
|
|
25
|
+
const meta = document.querySelector('meta[name="color-scheme"]');
|
|
26
|
+
if (data.theme === "dark" && meta) meta.setAttribute("content", "dark");
|
|
27
|
+
} catch {}
|
|
28
|
+
})();
|
|
29
|
+
</script>
|
|
30
|
+
<script>
|
|
31
|
+
// 首屏前同步界面语言到 <html lang>(与 src/client/lib/language.ts 的键名对拍,test/language.test.ts)
|
|
32
|
+
(() => {
|
|
33
|
+
try {
|
|
34
|
+
document.documentElement.setAttribute(
|
|
35
|
+
"lang",
|
|
36
|
+
localStorage.getItem("neko.language") === "en" ? "en" : "zh-CN",
|
|
37
|
+
);
|
|
38
|
+
} catch {}
|
|
39
|
+
})();
|
|
40
|
+
</script>
|
|
7
41
|
<link
|
|
8
42
|
rel="icon"
|
|
9
43
|
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='26' fill='%23ffb7c5'/%3E%3Ctext x='50' y='70' font-size='58' text-anchor='middle' fill='%23ffffff' font-family='sans-serif' font-weight='bold'%3En%3C/text%3E%3C/svg%3E"
|
|
10
44
|
/>
|
|
11
45
|
<title>neko harness</title>
|
|
12
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
46
|
+
<script type="module" crossorigin src="/assets/index-v4Ct9JMn.js"></script>
|
|
47
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DCZeTvcw.css">
|
|
14
48
|
</head>
|
|
15
49
|
<body>
|
|
16
50
|
<div id="root"></div>
|
package/dist/gateway.js
CHANGED
|
@@ -17022,14 +17022,20 @@ function normalizeEnabledProviders(saved) {
|
|
|
17022
17022
|
// packages/core/src/credentials.ts
|
|
17023
17023
|
import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
|
|
17024
17024
|
var STALE_LOCK_MS = 30000;
|
|
17025
|
+
var LOCK_ATTEMPTS = 100;
|
|
17026
|
+
var LOCK_RETRY_MS = 25;
|
|
17025
17027
|
var DEFAULT_CREDENTIALS_PATH = join2(homedir2(), ".neko", "credentials.json");
|
|
17026
17028
|
|
|
17027
17029
|
class FileCredentialStore {
|
|
17028
17030
|
path;
|
|
17029
17031
|
#renameDeps;
|
|
17030
|
-
|
|
17032
|
+
#lockAttempts;
|
|
17033
|
+
#lockRetryMs;
|
|
17034
|
+
constructor(path = DEFAULT_CREDENTIALS_PATH, renameDeps = {}, lock = {}) {
|
|
17031
17035
|
this.path = path;
|
|
17032
17036
|
this.#renameDeps = renameDeps;
|
|
17037
|
+
this.#lockAttempts = lock.lockAttempts ?? LOCK_ATTEMPTS;
|
|
17038
|
+
this.#lockRetryMs = lock.lockRetryMs ?? LOCK_RETRY_MS;
|
|
17033
17039
|
}
|
|
17034
17040
|
async#read() {
|
|
17035
17041
|
try {
|
|
@@ -17065,7 +17071,7 @@ class FileCredentialStore {
|
|
|
17065
17071
|
lock = await open(lockPath, "wx", 384);
|
|
17066
17072
|
break;
|
|
17067
17073
|
} catch (error) {
|
|
17068
|
-
if (error.code !== "EEXIST" || attempt >=
|
|
17074
|
+
if (error.code !== "EEXIST" || attempt >= this.#lockAttempts)
|
|
17069
17075
|
throw new Error("Credential storage is busy or unavailable.");
|
|
17070
17076
|
if (!reclaimed) {
|
|
17071
17077
|
const info = await stat(lockPath).catch(() => {
|
|
@@ -17077,7 +17083,7 @@ class FileCredentialStore {
|
|
|
17077
17083
|
continue;
|
|
17078
17084
|
}
|
|
17079
17085
|
}
|
|
17080
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
17086
|
+
await new Promise((resolve) => setTimeout(resolve, this.#lockRetryMs));
|
|
17081
17087
|
}
|
|
17082
17088
|
}
|
|
17083
17089
|
const temporary = `${this.path}.${crypto.randomUUID()}.tmp`;
|
|
@@ -17235,6 +17241,9 @@ function accessibleWorkspace(directory) {
|
|
|
17235
17241
|
}
|
|
17236
17242
|
return path;
|
|
17237
17243
|
}
|
|
17244
|
+
var WORKSPACE_UNKNOWN = "WORKSPACE_UNKNOWN: This workspace is not in the list.";
|
|
17245
|
+
var WORKSPACE_LAST = "WORKSPACE_LAST: Keep at least one workspace.";
|
|
17246
|
+
var WORKSPACE_BUSY = "WORKSPACE_BUSY: Stop or finish the running sessions of this workspace first.";
|
|
17238
17247
|
|
|
17239
17248
|
// packages/core/src/projection.ts
|
|
17240
17249
|
function userText(content) {
|
|
@@ -19605,7 +19614,9 @@ class JobManager {
|
|
|
19605
19614
|
#reporter = createReporter();
|
|
19606
19615
|
#runner = this.#createRunner();
|
|
19607
19616
|
extension;
|
|
19608
|
-
|
|
19617
|
+
#startWindowMs;
|
|
19618
|
+
constructor(options = {}) {
|
|
19619
|
+
this.#startWindowMs = options.startWindowMs ?? JOB_LIMITS.startWindowMs;
|
|
19609
19620
|
this.extension = this.#createExtension();
|
|
19610
19621
|
}
|
|
19611
19622
|
#closing = false;
|
|
@@ -19795,7 +19806,7 @@ class JobManager {
|
|
|
19795
19806
|
throw error;
|
|
19796
19807
|
}
|
|
19797
19808
|
const live = entry;
|
|
19798
|
-
await Promise.race([live.exited, delay(
|
|
19809
|
+
await Promise.race([live.exited, delay(this.#startWindowMs, context.abortSignal)]);
|
|
19799
19810
|
live.windowOpen = false;
|
|
19800
19811
|
if (live.suppressReport)
|
|
19801
19812
|
await live.exited;
|
|
@@ -20996,6 +21007,8 @@ var SessionMetadataDoc = defineDoc5({
|
|
|
20996
21007
|
namingUsage: { models: {}, tools: {} }
|
|
20997
21008
|
})
|
|
20998
21009
|
});
|
|
21010
|
+
var NAMING_REQUEST_TIMEOUT_MS = 20000;
|
|
21011
|
+
var NAMING_RETRY_DELAY_MS = 2000;
|
|
20999
21012
|
function shortText(value, limit = 60) {
|
|
21000
21013
|
return [
|
|
21001
21014
|
...stripVTControlCharacters(value).replace(/\p{Cc}/gu, " ").replace(/\s+/g, " ").trim()
|
|
@@ -21033,7 +21046,7 @@ function mergeUsage(base, extra) {
|
|
|
21033
21046
|
}
|
|
21034
21047
|
return result;
|
|
21035
21048
|
}
|
|
21036
|
-
function namingTask(canUse) {
|
|
21049
|
+
function namingTask(canUse, { requestTimeoutMs, retryDelayMs }) {
|
|
21037
21050
|
let active = 0;
|
|
21038
21051
|
const waits = new Set;
|
|
21039
21052
|
return defineTask3({
|
|
@@ -21084,7 +21097,7 @@ function namingTask(canUse) {
|
|
|
21084
21097
|
return { status: "running", checkpoint: { phase: "generate", attempt: d.attempts } };
|
|
21085
21098
|
}, context);
|
|
21086
21099
|
let title = "", usage;
|
|
21087
|
-
const signal = AbortSignal.any([runtime.signal, AbortSignal.timeout(
|
|
21100
|
+
const signal = AbortSignal.any([runtime.signal, AbortSignal.timeout(requestTimeoutMs)]);
|
|
21088
21101
|
try {
|
|
21089
21102
|
const budget = Math.max(32, Math.min(12000, Math.floor((model.contextWindow - 512) / 4)));
|
|
21090
21103
|
const response = await Promise.race([
|
|
@@ -21123,7 +21136,7 @@ function namingTask(canUse) {
|
|
|
21123
21136
|
d.answer = "";
|
|
21124
21137
|
} else
|
|
21125
21138
|
d.naming = d.attempts >= 2 ? "fallback" : "pending";
|
|
21126
|
-
return title || d.attempts >= 2 ? { status: "terminal", outcome: { status: "completed", result: null } } : { status: "running", checkpoint: { phase: "wait", until: runtime.now() +
|
|
21139
|
+
return title || d.attempts >= 2 ? { status: "terminal", outcome: { status: "completed", result: null } } : { status: "running", checkpoint: { phase: "wait", until: runtime.now() + retryDelayMs } };
|
|
21127
21140
|
}, context);
|
|
21128
21141
|
} finally {
|
|
21129
21142
|
active--;
|
|
@@ -21155,8 +21168,11 @@ class SessionMetadataManager {
|
|
|
21155
21168
|
storage;
|
|
21156
21169
|
context;
|
|
21157
21170
|
report;
|
|
21158
|
-
constructor(harness, storage, context, registry, report = console.error, canUseModel = async () => true) {
|
|
21159
|
-
this.task = namingTask(canUseModel
|
|
21171
|
+
constructor(harness, storage, context, registry, report = console.error, canUseModel = async () => true, timings = {}) {
|
|
21172
|
+
this.task = namingTask(canUseModel, {
|
|
21173
|
+
requestTimeoutMs: timings.requestTimeoutMs ?? NAMING_REQUEST_TIMEOUT_MS,
|
|
21174
|
+
retryDelayMs: timings.retryDelayMs ?? NAMING_RETRY_DELAY_MS
|
|
21175
|
+
});
|
|
21160
21176
|
this.harness = harness;
|
|
21161
21177
|
this.storage = storage;
|
|
21162
21178
|
this.context = context;
|
|
@@ -22605,6 +22621,14 @@ var WorkspacesDoc = defineDoc8({
|
|
|
22605
22621
|
fork: "initial",
|
|
22606
22622
|
initial: () => ({ items: [] })
|
|
22607
22623
|
});
|
|
22624
|
+
var RemovedWorkspacesDoc = defineDoc8({
|
|
22625
|
+
kind: "app.neko.workspaces-removed",
|
|
22626
|
+
version: 1,
|
|
22627
|
+
scope: "conversation",
|
|
22628
|
+
history: "latest",
|
|
22629
|
+
fork: "initial",
|
|
22630
|
+
initial: () => ({ paths: [] })
|
|
22631
|
+
});
|
|
22608
22632
|
var SearchSettingsDoc = defineDoc8({
|
|
22609
22633
|
kind: "app.neko.search-settings",
|
|
22610
22634
|
version: 1,
|
|
@@ -22736,7 +22760,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
22736
22760
|
registry.install(createSubagentExtension(async (id, requestId, cwd) => {
|
|
22737
22761
|
resourceSnapshots.stage(id, requestId, cwd, await loadResources(cwd));
|
|
22738
22762
|
}, resourceSnapshots.discard, (ref) => guardProvider(ref)));
|
|
22739
|
-
const jobs = new JobManager;
|
|
22763
|
+
const jobs = new JobManager(options.timings?.jobStartWindowMs === undefined ? {} : { startWindowMs: options.timings.jobStartWindowMs });
|
|
22740
22764
|
registry.install(jobs.extension);
|
|
22741
22765
|
registry.install(createTodoExtension());
|
|
22742
22766
|
let questions;
|
|
@@ -22754,6 +22778,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
22754
22778
|
models,
|
|
22755
22779
|
registry,
|
|
22756
22780
|
settings: {
|
|
22781
|
+
...options.timings?.retryBaseDelayMs === undefined ? {} : { retry: { baseDelayMs: options.timings.retryBaseDelayMs } },
|
|
22757
22782
|
get compaction() {
|
|
22758
22783
|
return compactionPolicy(contextState, allModels().filter((model) => !isModelProvider(model.provider) || enabled.has(model.provider)).map((model) => model.contextWindow));
|
|
22759
22784
|
}
|
|
@@ -22822,20 +22847,57 @@ async function createNekoRuntime(options = {}) {
|
|
|
22822
22847
|
contextState = resolveContextSettings(options.contextSettings, savedContext);
|
|
22823
22848
|
return contextState;
|
|
22824
22849
|
};
|
|
22825
|
-
const
|
|
22850
|
+
const removedWorkspaces = async () => new Set((await harness.snapshot(RemovedWorkspacesDoc, conversation.id, context))?.paths ?? []);
|
|
22851
|
+
const registerWorkspace = async (path, explicit) => {
|
|
22826
22852
|
await harness.commit(async (tx) => {
|
|
22827
22853
|
const doc = await tx.doc(WorkspacesDoc, conversation.id);
|
|
22854
|
+
const removed = await tx.doc(RemovedWorkspacesDoc, conversation.id);
|
|
22855
|
+
if (removed.paths.includes(path)) {
|
|
22856
|
+
if (!explicit)
|
|
22857
|
+
return;
|
|
22858
|
+
removed.paths = removed.paths.filter((entry) => entry !== path);
|
|
22859
|
+
}
|
|
22828
22860
|
if (!doc.items.some((item) => item.path === path))
|
|
22829
22861
|
doc.items.push({ path, createdAt: Date.now() });
|
|
22830
22862
|
}, context);
|
|
22863
|
+
};
|
|
22864
|
+
const addWorkspace = async (input) => {
|
|
22865
|
+
const path = accessibleWorkspace(input);
|
|
22866
|
+
await registerWorkspace(path, true);
|
|
22831
22867
|
return (await harness.snapshot(WorkspacesDoc, conversation.id, context)).items.find((item) => item.path === path);
|
|
22832
22868
|
};
|
|
22833
|
-
const addWorkspace = async (path) => registerWorkspace(accessibleWorkspace(path));
|
|
22834
22869
|
for (const record of [...existing, { id: conversation.id }]) {
|
|
22835
22870
|
const target = await harness.conversation(record.id, context);
|
|
22836
22871
|
if (target)
|
|
22837
|
-
await registerWorkspace(savedWorkspace((await target.agent(context)).cwd));
|
|
22838
|
-
}
|
|
22872
|
+
await registerWorkspace(savedWorkspace((await target.agent(context)).cwd), false);
|
|
22873
|
+
}
|
|
22874
|
+
await registerWorkspace(defaultCwd, true);
|
|
22875
|
+
const removeWorkspace = async (path) => {
|
|
22876
|
+
const items = (await harness.snapshot(WorkspacesDoc, conversation.id, context))?.items ?? [];
|
|
22877
|
+
if (!items.some((item) => item.path === path))
|
|
22878
|
+
throw new Error(WORKSPACE_UNKNOWN);
|
|
22879
|
+
if (items.length <= 1)
|
|
22880
|
+
throw new Error(WORKSPACE_LAST);
|
|
22881
|
+
for (const record of await scanConversations()) {
|
|
22882
|
+
if (record.owner)
|
|
22883
|
+
continue;
|
|
22884
|
+
if ((await harness.snapshot(ArchivedDoc, record.id, context))?.archived)
|
|
22885
|
+
continue;
|
|
22886
|
+
const target = await harness.conversation(record.id, context);
|
|
22887
|
+
if (!target || savedWorkspace((await target.agent(context)).cwd) !== path)
|
|
22888
|
+
continue;
|
|
22889
|
+
const info = await harness.snapshot(SessionMetadataDoc, record.id, context);
|
|
22890
|
+
if (info?.status === "running" || info?.awaiting === true || info?.awaitingPlan === true)
|
|
22891
|
+
throw new Error(WORKSPACE_BUSY);
|
|
22892
|
+
}
|
|
22893
|
+
await harness.commit(async (tx) => {
|
|
22894
|
+
const doc = await tx.doc(WorkspacesDoc, conversation.id);
|
|
22895
|
+
const removed = await tx.doc(RemovedWorkspacesDoc, conversation.id);
|
|
22896
|
+
doc.items = doc.items.filter((item) => item.path !== path);
|
|
22897
|
+
if (!removed.paths.includes(path))
|
|
22898
|
+
removed.paths.push(path);
|
|
22899
|
+
}, context);
|
|
22900
|
+
};
|
|
22839
22901
|
const jobIo = (id) => ({
|
|
22840
22902
|
commit: (change) => harness.commit(change, context),
|
|
22841
22903
|
state: () => harness.snapshot(JobsDoc, id, context)
|
|
@@ -22852,7 +22914,10 @@ async function createNekoRuntime(options = {}) {
|
|
|
22852
22914
|
};
|
|
22853
22915
|
if (existing.length === 0)
|
|
22854
22916
|
await touchConversation(conversation.id);
|
|
22855
|
-
const metadata = new SessionMetadataManager(harness, storage, context, registry, options.onReport, (provider, modelId) => guardProvider({ provider, modelId }).then(() => true, () => false)
|
|
22917
|
+
const metadata = new SessionMetadataManager(harness, storage, context, registry, options.onReport, (provider, modelId) => guardProvider({ provider, modelId }).then(() => true, () => false), {
|
|
22918
|
+
...options.timings?.namingRequestTimeoutMs === undefined ? {} : { requestTimeoutMs: options.timings.namingRequestTimeoutMs },
|
|
22919
|
+
...options.timings?.namingRetryDelayMs === undefined ? {} : { retryDelayMs: options.timings.namingRetryDelayMs }
|
|
22920
|
+
});
|
|
22856
22921
|
if (existing.length === 0)
|
|
22857
22922
|
await metadata.initialize(conversation.id);
|
|
22858
22923
|
for (const record of existing)
|
|
@@ -23212,13 +23277,14 @@ async function createNekoRuntime(options = {}) {
|
|
|
23212
23277
|
},
|
|
23213
23278
|
touchConversation,
|
|
23214
23279
|
addWorkspace,
|
|
23280
|
+
removeWorkspace,
|
|
23215
23281
|
async listWorkspaces() {
|
|
23216
23282
|
return [...(await harness.snapshot(WorkspacesDoc, conversation.id, context))?.items ?? []];
|
|
23217
23283
|
},
|
|
23218
23284
|
async forkConversation(id) {
|
|
23219
23285
|
const source = await sessionFor(id);
|
|
23220
23286
|
const agent = await source.conversation.agent(context);
|
|
23221
|
-
await registerWorkspace(savedWorkspace(agent.cwd));
|
|
23287
|
+
await registerWorkspace(savedWorkspace(agent.cwd), false);
|
|
23222
23288
|
const newest = await source.conversation.entries({}, 1, undefined, context);
|
|
23223
23289
|
const at = newest.items[0]?.id;
|
|
23224
23290
|
const sourceMetadata = at === undefined ? undefined : await harness.snapshot(SessionMetadataDoc, id, context);
|
|
@@ -23315,6 +23381,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
23315
23381
|
async listConversations() {
|
|
23316
23382
|
const records = await scanConversations();
|
|
23317
23383
|
const infos = [];
|
|
23384
|
+
const removed = await removedWorkspaces();
|
|
23318
23385
|
for (const record of records) {
|
|
23319
23386
|
if (record.owner)
|
|
23320
23387
|
continue;
|
|
@@ -23323,6 +23390,9 @@ async function createNekoRuntime(options = {}) {
|
|
|
23323
23390
|
const target = await harness.conversation(record.id, context);
|
|
23324
23391
|
if (!target)
|
|
23325
23392
|
continue;
|
|
23393
|
+
const workspaceRoot = savedWorkspace((await target.agent(context)).cwd);
|
|
23394
|
+
if (removed.has(workspaceRoot))
|
|
23395
|
+
continue;
|
|
23326
23396
|
const info = await harness.snapshot(SessionMetadataDoc, record.id, context);
|
|
23327
23397
|
infos.push({
|
|
23328
23398
|
title: info.title,
|
|
@@ -23335,7 +23405,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
23335
23405
|
model: (await target.agent(context)).model ?? null,
|
|
23336
23406
|
id: record.id,
|
|
23337
23407
|
archived: false,
|
|
23338
|
-
workspaceRoot
|
|
23408
|
+
workspaceRoot,
|
|
23339
23409
|
lastUsedAt: (await harness.snapshot(ActivityDoc, record.id, context))?.lastUsedAt ?? 0
|
|
23340
23410
|
});
|
|
23341
23411
|
}
|
|
@@ -24064,13 +24134,14 @@ var page = Type8.Object({
|
|
|
24064
24134
|
next: nullableString
|
|
24065
24135
|
});
|
|
24066
24136
|
var NekoWorkspaces = contract("neko.workspaces", {
|
|
24067
|
-
version:
|
|
24137
|
+
version: 2,
|
|
24068
24138
|
input: Type8.Object({}, { additionalProperties: false }),
|
|
24069
24139
|
variants: {
|
|
24070
24140
|
ready: {
|
|
24071
24141
|
state: { state: WorkspaceStateSchema },
|
|
24072
24142
|
methods: {
|
|
24073
24143
|
add: rpc(Type8.Object({ path: str }, { additionalProperties: false }), WorkspaceSummarySchema),
|
|
24144
|
+
remove: rpc(Type8.Object({ path: str }, { additionalProperties: false }), Type8.Null()),
|
|
24074
24145
|
browse: rpc(browse, page)
|
|
24075
24146
|
}
|
|
24076
24147
|
}
|
|
@@ -24969,8 +25040,8 @@ function subagentClient(connect) {
|
|
|
24969
25040
|
import { createHash as createHash2 } from "crypto";
|
|
24970
25041
|
|
|
24971
25042
|
// packages/core/src/application-version.ts
|
|
24972
|
-
var RUNNING_VERSION = "0.
|
|
24973
|
-
var APPLICATION_PROTOCOL =
|
|
25043
|
+
var RUNNING_VERSION = "0.5.0";
|
|
25044
|
+
var APPLICATION_PROTOCOL = 9;
|
|
24974
25045
|
function stableVersion(value) {
|
|
24975
25046
|
return typeof value === "string" && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value) && value.split(".").every((n) => Number.isSafeInteger(Number(n)));
|
|
24976
25047
|
}
|
|
@@ -30067,6 +30138,7 @@ async function createNekoServerServices(options) {
|
|
|
30067
30138
|
"app.neko.activity",
|
|
30068
30139
|
"app.neko.conversation",
|
|
30069
30140
|
"app.neko.workspaces",
|
|
30141
|
+
"app.neko.workspaces-removed",
|
|
30070
30142
|
"pi.agent"
|
|
30071
30143
|
].includes(change.record.kind)))
|
|
30072
30144
|
return;
|
|
@@ -30101,7 +30173,11 @@ async function createNekoServerServices(options) {
|
|
|
30101
30173
|
variant: "ready",
|
|
30102
30174
|
state: shared(self2).workspaces,
|
|
30103
30175
|
browse: (input) => browseWorkspaces(input),
|
|
30104
|
-
add: (input) => serialize(() => runtime.addWorkspace(input.path))
|
|
30176
|
+
add: (input) => serialize(() => runtime.addWorkspace(input.path)),
|
|
30177
|
+
remove: (input) => serialize(async () => {
|
|
30178
|
+
await runtime.removeWorkspace(input.path);
|
|
30179
|
+
return null;
|
|
30180
|
+
})
|
|
30105
30181
|
}));
|
|
30106
30182
|
host.serve(NekoCredentials, (_input, _principal, self2) => {
|
|
30107
30183
|
const hub = shared(self2);
|
|
@@ -30490,7 +30566,8 @@ async function startGateway(options = {}) {
|
|
|
30490
30566
|
...options.extensions !== undefined ? { extensions: options.extensions } : {},
|
|
30491
30567
|
...options.promptHome !== undefined ? { promptHome: options.promptHome } : {},
|
|
30492
30568
|
...fileConfig.shell?.path !== undefined ? { shellPath: fileConfig.shell.path } : {},
|
|
30493
|
-
...options.shellResolver !== undefined ? { shellResolver: options.shellResolver } : {}
|
|
30569
|
+
...options.shellResolver !== undefined ? { shellResolver: options.shellResolver } : {},
|
|
30570
|
+
...options.timings !== undefined ? { timings: options.timings } : {}
|
|
30494
30571
|
});
|
|
30495
30572
|
try {
|
|
30496
30573
|
return await bindGateway(runtime, {
|