neko-harness 0.4.9 → 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 +8 -4
- package/dist/client/assets/index-DCZeTvcw.css +2 -0
- package/dist/client/assets/{index-LHUxaMG9.js → index-v4Ct9JMn.js} +62 -62
- package/dist/client/index.html +2 -2
- package/dist/gateway.js +78 -13
- package/dist/neko.js +75 -14
- package/package.json +3 -2
- package/dist/client/assets/index-CP-ffuep.css +0 -2
package/dist/client/index.html
CHANGED
|
@@ -43,8 +43,8 @@
|
|
|
43
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"
|
|
44
44
|
/>
|
|
45
45
|
<title>neko harness</title>
|
|
46
|
-
<script type="module" crossorigin src="/assets/index-
|
|
47
|
-
<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">
|
|
48
48
|
</head>
|
|
49
49
|
<body>
|
|
50
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) {
|
|
@@ -22612,6 +22621,14 @@ var WorkspacesDoc = defineDoc8({
|
|
|
22612
22621
|
fork: "initial",
|
|
22613
22622
|
initial: () => ({ items: [] })
|
|
22614
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
|
+
});
|
|
22615
22632
|
var SearchSettingsDoc = defineDoc8({
|
|
22616
22633
|
kind: "app.neko.search-settings",
|
|
22617
22634
|
version: 1,
|
|
@@ -22830,20 +22847,57 @@ async function createNekoRuntime(options = {}) {
|
|
|
22830
22847
|
contextState = resolveContextSettings(options.contextSettings, savedContext);
|
|
22831
22848
|
return contextState;
|
|
22832
22849
|
};
|
|
22833
|
-
const
|
|
22850
|
+
const removedWorkspaces = async () => new Set((await harness.snapshot(RemovedWorkspacesDoc, conversation.id, context))?.paths ?? []);
|
|
22851
|
+
const registerWorkspace = async (path, explicit) => {
|
|
22834
22852
|
await harness.commit(async (tx) => {
|
|
22835
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
|
+
}
|
|
22836
22860
|
if (!doc.items.some((item) => item.path === path))
|
|
22837
22861
|
doc.items.push({ path, createdAt: Date.now() });
|
|
22838
22862
|
}, context);
|
|
22863
|
+
};
|
|
22864
|
+
const addWorkspace = async (input) => {
|
|
22865
|
+
const path = accessibleWorkspace(input);
|
|
22866
|
+
await registerWorkspace(path, true);
|
|
22839
22867
|
return (await harness.snapshot(WorkspacesDoc, conversation.id, context)).items.find((item) => item.path === path);
|
|
22840
22868
|
};
|
|
22841
|
-
const addWorkspace = async (path) => registerWorkspace(accessibleWorkspace(path));
|
|
22842
22869
|
for (const record of [...existing, { id: conversation.id }]) {
|
|
22843
22870
|
const target = await harness.conversation(record.id, context);
|
|
22844
22871
|
if (target)
|
|
22845
|
-
await registerWorkspace(savedWorkspace((await target.agent(context)).cwd));
|
|
22846
|
-
}
|
|
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
|
+
};
|
|
22847
22901
|
const jobIo = (id) => ({
|
|
22848
22902
|
commit: (change) => harness.commit(change, context),
|
|
22849
22903
|
state: () => harness.snapshot(JobsDoc, id, context)
|
|
@@ -23223,13 +23277,14 @@ async function createNekoRuntime(options = {}) {
|
|
|
23223
23277
|
},
|
|
23224
23278
|
touchConversation,
|
|
23225
23279
|
addWorkspace,
|
|
23280
|
+
removeWorkspace,
|
|
23226
23281
|
async listWorkspaces() {
|
|
23227
23282
|
return [...(await harness.snapshot(WorkspacesDoc, conversation.id, context))?.items ?? []];
|
|
23228
23283
|
},
|
|
23229
23284
|
async forkConversation(id) {
|
|
23230
23285
|
const source = await sessionFor(id);
|
|
23231
23286
|
const agent = await source.conversation.agent(context);
|
|
23232
|
-
await registerWorkspace(savedWorkspace(agent.cwd));
|
|
23287
|
+
await registerWorkspace(savedWorkspace(agent.cwd), false);
|
|
23233
23288
|
const newest = await source.conversation.entries({}, 1, undefined, context);
|
|
23234
23289
|
const at = newest.items[0]?.id;
|
|
23235
23290
|
const sourceMetadata = at === undefined ? undefined : await harness.snapshot(SessionMetadataDoc, id, context);
|
|
@@ -23326,6 +23381,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
23326
23381
|
async listConversations() {
|
|
23327
23382
|
const records = await scanConversations();
|
|
23328
23383
|
const infos = [];
|
|
23384
|
+
const removed = await removedWorkspaces();
|
|
23329
23385
|
for (const record of records) {
|
|
23330
23386
|
if (record.owner)
|
|
23331
23387
|
continue;
|
|
@@ -23334,6 +23390,9 @@ async function createNekoRuntime(options = {}) {
|
|
|
23334
23390
|
const target = await harness.conversation(record.id, context);
|
|
23335
23391
|
if (!target)
|
|
23336
23392
|
continue;
|
|
23393
|
+
const workspaceRoot = savedWorkspace((await target.agent(context)).cwd);
|
|
23394
|
+
if (removed.has(workspaceRoot))
|
|
23395
|
+
continue;
|
|
23337
23396
|
const info = await harness.snapshot(SessionMetadataDoc, record.id, context);
|
|
23338
23397
|
infos.push({
|
|
23339
23398
|
title: info.title,
|
|
@@ -23346,7 +23405,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
23346
23405
|
model: (await target.agent(context)).model ?? null,
|
|
23347
23406
|
id: record.id,
|
|
23348
23407
|
archived: false,
|
|
23349
|
-
workspaceRoot
|
|
23408
|
+
workspaceRoot,
|
|
23350
23409
|
lastUsedAt: (await harness.snapshot(ActivityDoc, record.id, context))?.lastUsedAt ?? 0
|
|
23351
23410
|
});
|
|
23352
23411
|
}
|
|
@@ -24075,13 +24134,14 @@ var page = Type8.Object({
|
|
|
24075
24134
|
next: nullableString
|
|
24076
24135
|
});
|
|
24077
24136
|
var NekoWorkspaces = contract("neko.workspaces", {
|
|
24078
|
-
version:
|
|
24137
|
+
version: 2,
|
|
24079
24138
|
input: Type8.Object({}, { additionalProperties: false }),
|
|
24080
24139
|
variants: {
|
|
24081
24140
|
ready: {
|
|
24082
24141
|
state: { state: WorkspaceStateSchema },
|
|
24083
24142
|
methods: {
|
|
24084
24143
|
add: rpc(Type8.Object({ path: str }, { additionalProperties: false }), WorkspaceSummarySchema),
|
|
24144
|
+
remove: rpc(Type8.Object({ path: str }, { additionalProperties: false }), Type8.Null()),
|
|
24085
24145
|
browse: rpc(browse, page)
|
|
24086
24146
|
}
|
|
24087
24147
|
}
|
|
@@ -24980,8 +25040,8 @@ function subagentClient(connect) {
|
|
|
24980
25040
|
import { createHash as createHash2 } from "crypto";
|
|
24981
25041
|
|
|
24982
25042
|
// packages/core/src/application-version.ts
|
|
24983
|
-
var RUNNING_VERSION = "0.
|
|
24984
|
-
var APPLICATION_PROTOCOL =
|
|
25043
|
+
var RUNNING_VERSION = "0.5.0";
|
|
25044
|
+
var APPLICATION_PROTOCOL = 9;
|
|
24985
25045
|
function stableVersion(value) {
|
|
24986
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)));
|
|
24987
25047
|
}
|
|
@@ -30078,6 +30138,7 @@ async function createNekoServerServices(options) {
|
|
|
30078
30138
|
"app.neko.activity",
|
|
30079
30139
|
"app.neko.conversation",
|
|
30080
30140
|
"app.neko.workspaces",
|
|
30141
|
+
"app.neko.workspaces-removed",
|
|
30081
30142
|
"pi.agent"
|
|
30082
30143
|
].includes(change.record.kind)))
|
|
30083
30144
|
return;
|
|
@@ -30112,7 +30173,11 @@ async function createNekoServerServices(options) {
|
|
|
30112
30173
|
variant: "ready",
|
|
30113
30174
|
state: shared(self2).workspaces,
|
|
30114
30175
|
browse: (input) => browseWorkspaces(input),
|
|
30115
|
-
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
|
+
})
|
|
30116
30181
|
}));
|
|
30117
30182
|
host.serve(NekoCredentials, (_input, _principal, self2) => {
|
|
30118
30183
|
const hub = shared(self2);
|
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.
|
|
83
|
+
var RUNNING_VERSION = "0.5.0", APPLICATION_PROTOCOL = 9, ApplicationProtocolError;
|
|
84
84
|
var init_application_version = __esm(() => {
|
|
85
85
|
ApplicationProtocolError = class ApplicationProtocolError extends Error {
|
|
86
86
|
constructor() {
|
|
@@ -805,9 +805,13 @@ import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
|
|
|
805
805
|
class FileCredentialStore {
|
|
806
806
|
path;
|
|
807
807
|
#renameDeps;
|
|
808
|
-
|
|
808
|
+
#lockAttempts;
|
|
809
|
+
#lockRetryMs;
|
|
810
|
+
constructor(path = DEFAULT_CREDENTIALS_PATH, renameDeps = {}, lock = {}) {
|
|
809
811
|
this.path = path;
|
|
810
812
|
this.#renameDeps = renameDeps;
|
|
813
|
+
this.#lockAttempts = lock.lockAttempts ?? LOCK_ATTEMPTS;
|
|
814
|
+
this.#lockRetryMs = lock.lockRetryMs ?? LOCK_RETRY_MS;
|
|
811
815
|
}
|
|
812
816
|
async#read() {
|
|
813
817
|
try {
|
|
@@ -843,7 +847,7 @@ class FileCredentialStore {
|
|
|
843
847
|
lock = await open(lockPath, "wx", 384);
|
|
844
848
|
break;
|
|
845
849
|
} catch (error) {
|
|
846
|
-
if (error.code !== "EEXIST" || attempt >=
|
|
850
|
+
if (error.code !== "EEXIST" || attempt >= this.#lockAttempts)
|
|
847
851
|
throw new Error("Credential storage is busy or unavailable.");
|
|
848
852
|
if (!reclaimed) {
|
|
849
853
|
const info = await stat2(lockPath).catch(() => {
|
|
@@ -855,7 +859,7 @@ class FileCredentialStore {
|
|
|
855
859
|
continue;
|
|
856
860
|
}
|
|
857
861
|
}
|
|
858
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
862
|
+
await new Promise((resolve) => setTimeout(resolve, this.#lockRetryMs));
|
|
859
863
|
}
|
|
860
864
|
}
|
|
861
865
|
const temporary = `${this.path}.${crypto.randomUUID()}.tmp`;
|
|
@@ -902,7 +906,7 @@ class FileCredentialStore {
|
|
|
902
906
|
});
|
|
903
907
|
}
|
|
904
908
|
}
|
|
905
|
-
var STALE_LOCK_MS = 30000, DEFAULT_CREDENTIALS_PATH;
|
|
909
|
+
var STALE_LOCK_MS = 30000, LOCK_ATTEMPTS = 100, LOCK_RETRY_MS = 25, DEFAULT_CREDENTIALS_PATH;
|
|
906
910
|
var init_credentials = __esm(() => {
|
|
907
911
|
init_rename_with_retry();
|
|
908
912
|
init_credential_providers();
|
|
@@ -1025,6 +1029,7 @@ function accessibleWorkspace(directory) {
|
|
|
1025
1029
|
}
|
|
1026
1030
|
return path;
|
|
1027
1031
|
}
|
|
1032
|
+
var WORKSPACE_UNKNOWN = "WORKSPACE_UNKNOWN: This workspace is not in the list.", WORKSPACE_LAST = "WORKSPACE_LAST: Keep at least one workspace.", WORKSPACE_BUSY = "WORKSPACE_BUSY: Stop or finish the running sessions of this workspace first.";
|
|
1028
1033
|
var init_workspace = () => {};
|
|
1029
1034
|
|
|
1030
1035
|
// packages/core/src/projection.ts
|
|
@@ -6634,20 +6639,57 @@ async function createNekoRuntime(options = {}) {
|
|
|
6634
6639
|
contextState = resolveContextSettings(options.contextSettings, savedContext);
|
|
6635
6640
|
return contextState;
|
|
6636
6641
|
};
|
|
6637
|
-
const
|
|
6642
|
+
const removedWorkspaces = async () => new Set((await harness.snapshot(RemovedWorkspacesDoc, conversation.id, context))?.paths ?? []);
|
|
6643
|
+
const registerWorkspace = async (path, explicit) => {
|
|
6638
6644
|
await harness.commit(async (tx) => {
|
|
6639
6645
|
const doc = await tx.doc(WorkspacesDoc, conversation.id);
|
|
6646
|
+
const removed = await tx.doc(RemovedWorkspacesDoc, conversation.id);
|
|
6647
|
+
if (removed.paths.includes(path)) {
|
|
6648
|
+
if (!explicit)
|
|
6649
|
+
return;
|
|
6650
|
+
removed.paths = removed.paths.filter((entry) => entry !== path);
|
|
6651
|
+
}
|
|
6640
6652
|
if (!doc.items.some((item) => item.path === path))
|
|
6641
6653
|
doc.items.push({ path, createdAt: Date.now() });
|
|
6642
6654
|
}, context);
|
|
6655
|
+
};
|
|
6656
|
+
const addWorkspace = async (input) => {
|
|
6657
|
+
const path = accessibleWorkspace(input);
|
|
6658
|
+
await registerWorkspace(path, true);
|
|
6643
6659
|
return (await harness.snapshot(WorkspacesDoc, conversation.id, context)).items.find((item) => item.path === path);
|
|
6644
6660
|
};
|
|
6645
|
-
const addWorkspace = async (path) => registerWorkspace(accessibleWorkspace(path));
|
|
6646
6661
|
for (const record of [...existing, { id: conversation.id }]) {
|
|
6647
6662
|
const target = await harness.conversation(record.id, context);
|
|
6648
6663
|
if (target)
|
|
6649
|
-
await registerWorkspace(savedWorkspace((await target.agent(context)).cwd));
|
|
6650
|
-
}
|
|
6664
|
+
await registerWorkspace(savedWorkspace((await target.agent(context)).cwd), false);
|
|
6665
|
+
}
|
|
6666
|
+
await registerWorkspace(defaultCwd, true);
|
|
6667
|
+
const removeWorkspace = async (path) => {
|
|
6668
|
+
const items = (await harness.snapshot(WorkspacesDoc, conversation.id, context))?.items ?? [];
|
|
6669
|
+
if (!items.some((item) => item.path === path))
|
|
6670
|
+
throw new Error(WORKSPACE_UNKNOWN);
|
|
6671
|
+
if (items.length <= 1)
|
|
6672
|
+
throw new Error(WORKSPACE_LAST);
|
|
6673
|
+
for (const record of await scanConversations()) {
|
|
6674
|
+
if (record.owner)
|
|
6675
|
+
continue;
|
|
6676
|
+
if ((await harness.snapshot(ArchivedDoc, record.id, context))?.archived)
|
|
6677
|
+
continue;
|
|
6678
|
+
const target = await harness.conversation(record.id, context);
|
|
6679
|
+
if (!target || savedWorkspace((await target.agent(context)).cwd) !== path)
|
|
6680
|
+
continue;
|
|
6681
|
+
const info = await harness.snapshot(SessionMetadataDoc, record.id, context);
|
|
6682
|
+
if (info?.status === "running" || info?.awaiting === true || info?.awaitingPlan === true)
|
|
6683
|
+
throw new Error(WORKSPACE_BUSY);
|
|
6684
|
+
}
|
|
6685
|
+
await harness.commit(async (tx) => {
|
|
6686
|
+
const doc = await tx.doc(WorkspacesDoc, conversation.id);
|
|
6687
|
+
const removed = await tx.doc(RemovedWorkspacesDoc, conversation.id);
|
|
6688
|
+
doc.items = doc.items.filter((item) => item.path !== path);
|
|
6689
|
+
if (!removed.paths.includes(path))
|
|
6690
|
+
removed.paths.push(path);
|
|
6691
|
+
}, context);
|
|
6692
|
+
};
|
|
6651
6693
|
const jobIo = (id) => ({
|
|
6652
6694
|
commit: (change) => harness.commit(change, context),
|
|
6653
6695
|
state: () => harness.snapshot(JobsDoc, id, context)
|
|
@@ -7027,13 +7069,14 @@ async function createNekoRuntime(options = {}) {
|
|
|
7027
7069
|
},
|
|
7028
7070
|
touchConversation,
|
|
7029
7071
|
addWorkspace,
|
|
7072
|
+
removeWorkspace,
|
|
7030
7073
|
async listWorkspaces() {
|
|
7031
7074
|
return [...(await harness.snapshot(WorkspacesDoc, conversation.id, context))?.items ?? []];
|
|
7032
7075
|
},
|
|
7033
7076
|
async forkConversation(id) {
|
|
7034
7077
|
const source = await sessionFor(id);
|
|
7035
7078
|
const agent = await source.conversation.agent(context);
|
|
7036
|
-
await registerWorkspace(savedWorkspace(agent.cwd));
|
|
7079
|
+
await registerWorkspace(savedWorkspace(agent.cwd), false);
|
|
7037
7080
|
const newest = await source.conversation.entries({}, 1, undefined, context);
|
|
7038
7081
|
const at = newest.items[0]?.id;
|
|
7039
7082
|
const sourceMetadata = at === undefined ? undefined : await harness.snapshot(SessionMetadataDoc, id, context);
|
|
@@ -7130,6 +7173,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
7130
7173
|
async listConversations() {
|
|
7131
7174
|
const records = await scanConversations();
|
|
7132
7175
|
const infos = [];
|
|
7176
|
+
const removed = await removedWorkspaces();
|
|
7133
7177
|
for (const record of records) {
|
|
7134
7178
|
if (record.owner)
|
|
7135
7179
|
continue;
|
|
@@ -7138,6 +7182,9 @@ async function createNekoRuntime(options = {}) {
|
|
|
7138
7182
|
const target = await harness.conversation(record.id, context);
|
|
7139
7183
|
if (!target)
|
|
7140
7184
|
continue;
|
|
7185
|
+
const workspaceRoot = savedWorkspace((await target.agent(context)).cwd);
|
|
7186
|
+
if (removed.has(workspaceRoot))
|
|
7187
|
+
continue;
|
|
7141
7188
|
const info = await harness.snapshot(SessionMetadataDoc, record.id, context);
|
|
7142
7189
|
infos.push({
|
|
7143
7190
|
title: info.title,
|
|
@@ -7150,7 +7197,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
7150
7197
|
model: (await target.agent(context)).model ?? null,
|
|
7151
7198
|
id: record.id,
|
|
7152
7199
|
archived: false,
|
|
7153
|
-
workspaceRoot
|
|
7200
|
+
workspaceRoot,
|
|
7154
7201
|
lastUsedAt: (await harness.snapshot(ActivityDoc, record.id, context))?.lastUsedAt ?? 0
|
|
7155
7202
|
});
|
|
7156
7203
|
}
|
|
@@ -7347,7 +7394,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
7347
7394
|
}
|
|
7348
7395
|
};
|
|
7349
7396
|
}
|
|
7350
|
-
var DEFAULT_MODEL, DEFAULT_STORAGE_PATH, neverAborted = () => new AbortController().signal, ArchivedDoc, WorkspacesDoc, SearchSettingsDoc, ProviderSettingsDoc, DefaultModelDoc, ContextSettingsDoc, ActivityDoc;
|
|
7397
|
+
var DEFAULT_MODEL, DEFAULT_STORAGE_PATH, neverAborted = () => new AbortController().signal, ArchivedDoc, WorkspacesDoc, RemovedWorkspacesDoc, SearchSettingsDoc, ProviderSettingsDoc, DefaultModelDoc, ContextSettingsDoc, ActivityDoc;
|
|
7351
7398
|
var init_runtime = __esm(() => {
|
|
7352
7399
|
init_agent_file_search();
|
|
7353
7400
|
init_compaction();
|
|
@@ -7399,6 +7446,14 @@ var init_runtime = __esm(() => {
|
|
|
7399
7446
|
fork: "initial",
|
|
7400
7447
|
initial: () => ({ items: [] })
|
|
7401
7448
|
});
|
|
7449
|
+
RemovedWorkspacesDoc = defineDoc8({
|
|
7450
|
+
kind: "app.neko.workspaces-removed",
|
|
7451
|
+
version: 1,
|
|
7452
|
+
scope: "conversation",
|
|
7453
|
+
history: "latest",
|
|
7454
|
+
fork: "initial",
|
|
7455
|
+
initial: () => ({ paths: [] })
|
|
7456
|
+
});
|
|
7402
7457
|
SearchSettingsDoc = defineDoc8({
|
|
7403
7458
|
kind: "app.neko.search-settings",
|
|
7404
7459
|
version: 1,
|
|
@@ -8012,13 +8067,14 @@ var init_contract = __esm(() => {
|
|
|
8012
8067
|
next: nullableString
|
|
8013
8068
|
});
|
|
8014
8069
|
NekoWorkspaces = contract("neko.workspaces", {
|
|
8015
|
-
version:
|
|
8070
|
+
version: 2,
|
|
8016
8071
|
input: Type8.Object({}, { additionalProperties: false }),
|
|
8017
8072
|
variants: {
|
|
8018
8073
|
ready: {
|
|
8019
8074
|
state: { state: WorkspaceStateSchema },
|
|
8020
8075
|
methods: {
|
|
8021
8076
|
add: rpc(Type8.Object({ path: str }, { additionalProperties: false }), WorkspaceSummarySchema),
|
|
8077
|
+
remove: rpc(Type8.Object({ path: str }, { additionalProperties: false }), Type8.Null()),
|
|
8022
8078
|
browse: rpc(browse, page)
|
|
8023
8079
|
}
|
|
8024
8080
|
}
|
|
@@ -30954,6 +31010,7 @@ async function createNekoServerServices(options) {
|
|
|
30954
31010
|
"app.neko.activity",
|
|
30955
31011
|
"app.neko.conversation",
|
|
30956
31012
|
"app.neko.workspaces",
|
|
31013
|
+
"app.neko.workspaces-removed",
|
|
30957
31014
|
"pi.agent"
|
|
30958
31015
|
].includes(change.record.kind)))
|
|
30959
31016
|
return;
|
|
@@ -30988,7 +31045,11 @@ async function createNekoServerServices(options) {
|
|
|
30988
31045
|
variant: "ready",
|
|
30989
31046
|
state: shared(self2).workspaces,
|
|
30990
31047
|
browse: (input) => browseWorkspaces(input),
|
|
30991
|
-
add: (input) => serialize(() => runtime.addWorkspace(input.path))
|
|
31048
|
+
add: (input) => serialize(() => runtime.addWorkspace(input.path)),
|
|
31049
|
+
remove: (input) => serialize(async () => {
|
|
31050
|
+
await runtime.removeWorkspace(input.path);
|
|
31051
|
+
return null;
|
|
31052
|
+
})
|
|
30992
31053
|
}));
|
|
30993
31054
|
host.serve(NekoCredentials, (_input, _principal, self2) => {
|
|
30994
31055
|
const hub = shared(self2);
|
package/package.json
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"build": "bun run --cwd packages/core build && bun run --cwd packages/gateway build && bun run --cwd packages/tui build && bun run --cwd packages/web build && bun run --cwd packages/cli build",
|
|
12
12
|
"check": "bun --bun run biome check . && bun --bun run tsc --noEmit && bun run --cwd packages/web check:client",
|
|
13
13
|
"check:write": "bun --bun run biome check --write . && bun --bun run tsc --noEmit && bun run --cwd packages/web check:client",
|
|
14
|
-
"test": "bun test --conditions=neko-source --
|
|
14
|
+
"test": "bun test --conditions=neko-source --parallel",
|
|
15
|
+
"test:serial": "bun test --conditions=neko-source --isolate",
|
|
15
16
|
"neko": "bun --conditions=neko-source packages/cli/src/main.ts",
|
|
16
17
|
"build:release": "bun run build && bun scripts/build-release.ts",
|
|
17
18
|
"test:package": "bun scripts/test-package.ts"
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
},
|
|
26
27
|
"packageManager": "bun@1.4.2",
|
|
27
28
|
"trustedDependencies": [],
|
|
28
|
-
"version": "0.
|
|
29
|
+
"version": "0.5.0",
|
|
29
30
|
"description": "A local AI coding assistant with terminal and web interfaces, powered by Pi and Bun.",
|
|
30
31
|
"keywords": [
|
|
31
32
|
"ai",
|