@fungi.computer/truffle 0.1.1 → 0.2.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 +282 -34
- package/dist/auth-cli.d.ts +10 -0
- package/dist/auth-cli.js +36 -14
- package/dist/fungi-account-command.d.ts +1 -1
- package/dist/fungi-account-command.js +6 -3
- package/dist/index.d.ts +10 -2
- package/dist/index.js +8 -2
- package/dist/internal/image-fit-metafile.json +106 -0
- package/dist/internal/local-image-fit-worker.mjs +960 -0
- package/dist/internal/private-workerd-sandbox-worker.mjs +11 -12
- package/dist/internal/sandbox-metafile.json +7 -7
- package/dist/local-archive-handoff.d.ts +12 -0
- package/dist/local-archive-handoff.js +21 -0
- package/dist/local-extensions.d.ts +58 -12
- package/dist/local-extensions.js +195 -55
- package/dist/local-image-fit.d.ts +30 -0
- package/dist/local-image-fit.js +105 -0
- package/dist/local-models.d.ts +4 -2
- package/dist/local-models.js +11 -6
- package/dist/local-session-archives.d.ts +8 -0
- package/dist/local-session-archives.js +152 -0
- package/dist/local-skill-source.d.ts +18 -3
- package/dist/local-skill-source.js +216 -140
- package/dist/log-event.d.ts +7 -0
- package/dist/log-event.js +8 -4
- package/dist/private-bounded-response.d.ts +2 -0
- package/dist/private-bounded-response.js +41 -0
- package/dist/private-file-credential-store.d.ts +28 -3
- package/dist/private-file-credential-store.js +57 -9
- package/dist/private-files-patch.d.ts +2 -1
- package/dist/private-files-patch.js +21 -3
- package/dist/private-fungi-account.d.ts +40 -23
- package/dist/private-fungi-account.js +201 -21
- package/dist/private-fungi-mcp-connection.d.ts +4 -0
- package/dist/private-fungi-mcp-connection.js +25 -6
- package/dist/private-fungi-mcp-selection-schema.d.ts +7 -0
- package/dist/private-fungi-mcp-selection-schema.js +15 -0
- package/dist/private-fungi-mcp-selection.d.ts +6 -6
- package/dist/private-fungi-mcp-selection.js +29 -16
- package/dist/private-fungi-model-api.js +4 -4
- package/dist/private-fungi-model-catalog.d.ts +10 -0
- package/dist/private-fungi-model-catalog.js +10 -2
- package/dist/private-fungi-model-provider.d.ts +1 -1
- package/dist/private-fungi-model-provider.js +6 -1
- package/dist/private-fungi-pending-logouts.d.ts +1 -1
- package/dist/private-fungi-pending-logouts.js +1 -2
- package/dist/private-fungi-service-logout.d.ts +44 -0
- package/dist/private-fungi-service-logout.js +59 -0
- package/dist/private-invocation-computer.d.ts +24 -3
- package/dist/private-invocation-computer.js +101 -62
- package/dist/private-invocation-files.d.ts +16 -1
- package/dist/private-invocation-files.js +154 -83
- package/dist/private-local-acp-stdio.js +3 -0
- package/dist/private-local-admission-gate.d.ts +14 -0
- package/dist/private-local-admission-gate.js +52 -0
- package/dist/private-local-agent-control.d.ts +101 -0
- package/dist/private-local-agent-control.js +30 -0
- package/dist/private-local-agent-default-model.d.ts +12 -0
- package/dist/private-local-agent-default-model.js +31 -0
- package/dist/private-local-auth-rpc.d.ts +67 -7
- package/dist/private-local-auth-rpc.js +140 -17
- package/dist/private-local-bwrap.d.ts +13 -0
- package/dist/private-local-bwrap.js +70 -0
- package/dist/private-local-client-version.d.ts +5 -0
- package/dist/private-local-client-version.js +43 -0
- package/dist/private-local-computer-attach.d.ts +3 -3
- package/dist/private-local-computer-attach.js +17 -3
- package/dist/private-local-computer-processes.d.ts +6 -3
- package/dist/private-local-computer-processes.js +55 -31
- package/dist/private-local-config-root.d.ts +21 -0
- package/dist/private-local-config-root.js +19 -0
- package/dist/private-local-containment.d.ts +51 -0
- package/dist/private-local-containment.js +161 -0
- package/dist/private-local-delegation.d.ts +10 -3
- package/dist/private-local-delegation.js +70 -15
- package/dist/private-local-effective-model.d.ts +9 -1
- package/dist/private-local-effective-model.js +3 -0
- package/dist/private-local-exec-environment.d.ts +16 -0
- package/dist/private-local-exec-environment.js +11 -0
- package/dist/private-local-linux-sandbox.d.ts +8 -0
- package/dist/private-local-linux-sandbox.js +67 -0
- package/dist/private-local-mail.d.ts +4 -2
- package/dist/private-local-mail.js +41 -33
- package/dist/private-local-mesh-enrollment.d.ts +34 -0
- package/dist/private-local-mesh-enrollment.js +188 -0
- package/dist/private-local-mesh-links.d.ts +21 -0
- package/dist/private-local-mesh-links.js +229 -0
- package/dist/private-local-mesh-record.d.ts +120 -0
- package/dist/private-local-mesh-record.js +55 -0
- package/dist/private-local-process-identity.d.ts +20 -0
- package/dist/private-local-process-identity.js +40 -0
- package/dist/private-local-runtime-host.d.ts +64 -13
- package/dist/private-local-runtime-host.js +361 -172
- package/dist/private-local-sandbox-policy.d.ts +21 -0
- package/dist/private-local-sandbox-policy.js +121 -0
- package/dist/private-local-sandbox.d.ts +37 -0
- package/dist/private-local-sandbox.js +114 -0
- package/dist/private-local-seatbelt.d.ts +24 -0
- package/dist/private-local-seatbelt.js +68 -0
- package/dist/private-local-service-contract.d.ts +91 -11
- package/dist/private-local-service-discovery.d.ts +129 -13
- package/dist/private-local-service-discovery.js +148 -47
- package/dist/private-local-service-lease.d.ts +16 -0
- package/dist/private-local-service-lease.js +105 -0
- package/dist/private-local-service-listener.d.ts +9 -2
- package/dist/private-local-service-listener.js +260 -64
- package/dist/private-local-service-log.d.ts +128 -0
- package/dist/private-local-service-log.js +321 -0
- package/dist/private-local-service-owner.d.ts +87 -1519
- package/dist/private-local-service-owner.js +1069 -71
- package/dist/private-local-service-process.js +3 -1
- package/dist/private-local-service-termination.d.ts +7 -0
- package/dist/private-local-service-termination.js +67 -0
- package/dist/private-local-service.d.ts +31 -2
- package/dist/private-local-service.js +402 -126
- package/dist/private-local-session-discovery.d.ts +52 -5
- package/dist/private-local-session-discovery.js +132 -11
- package/dist/private-local-shiitake-host.js +2 -2
- package/dist/private-local-startup-model.d.ts +51 -0
- package/dist/private-local-startup-model.js +86 -0
- package/dist/private-local-stop-all.d.ts +27 -0
- package/dist/private-local-stop-all.js +26 -0
- package/dist/private-local-ui-attachment.d.ts +19 -0
- package/dist/private-local-ui-attachment.js +71 -0
- package/dist/private-local-worker-server.d.ts +1 -0
- package/dist/private-local-worker-server.js +13 -5
- package/dist/private-mcp-form-contract.d.ts +186 -0
- package/dist/private-mcp-form-contract.js +53 -0
- package/dist/private-mcp-form-elicitation.d.ts +18 -0
- package/dist/private-mcp-form-elicitation.js +97 -0
- package/dist/private-mcp-form-session.d.ts +172 -0
- package/dist/private-mcp-form-session.js +148 -0
- package/dist/private-openai-claims.d.ts +30 -0
- package/dist/private-openai-claims.js +69 -0
- package/dist/private-oyster-mcp-auth.d.ts +13 -0
- package/dist/private-oyster-mcp-auth.js +57 -0
- package/dist/private-provider-rejection.d.ts +15 -0
- package/dist/private-provider-rejection.js +24 -0
- package/dist/private-sqlite-owner.d.ts +34 -2
- package/dist/private-sqlite-owner.js +103 -37
- package/dist/private-team-computers.d.ts +4 -4
- package/dist/private-team-computers.js +11 -2
- package/dist/seatbelt/safe-base.sbpl +79 -0
- package/dist/subscription-activation.d.ts +9 -0
- package/dist/subscription-activation.js +62 -0
- package/dist/subscription-identity.d.ts +23 -0
- package/dist/subscription-identity.js +124 -0
- package/dist/subscription-turn.d.ts +17 -0
- package/dist/subscription-turn.js +143 -0
- package/dist/team-apps.d.ts +2 -2
- package/dist/team-apps.js +7 -2
- package/native/bin/LICENSE.bubblewrap +502 -0
- package/native/bin/LICENSE.libcap +112 -0
- package/native/bin/LICENSE.sandbox-runtime +201 -0
- package/native/bin/SOURCE.bubblewrap.tar.xz +0 -0
- package/native/bin/bwrap-build-source/Dockerfile +48 -0
- package/native/bin/bwrap-build-source/README.md +96 -0
- package/native/bin/bwrap-build-source/build.mjs +52 -0
- package/native/bin/bwrap-build-source/linux-arm64.ini +14 -0
- package/native/bin/bwrap-build-source/pin.mjs +21 -0
- package/native/bin/bwrap-build-source/record.mjs +74 -0
- package/native/bin/bwrap-build-source/vendor/LICENSE.sandbox-runtime +201 -0
- package/native/bin/bwrap-build-source/vendor/README.md +23 -0
- package/native/bin/bwrap-build-source/vendor/seccomp-unix-block.c +280 -0
- package/native/bin/bwrap-build.json +44 -0
- package/native/bin/fungi-bwrap +0 -0
- package/native/bin/fungi-bwrap.sha256 +1 -0
- package/native/bin/fungi-seccomp.bpf +0 -0
- package/native/bin/fungi-seccomp.bpf.sha256 +1 -0
- package/package.json +44 -31
- package/dist/private-local-model-settings.d.ts +0 -5
- package/dist/private-local-model-settings.js +0 -31
- package/dist/private-local-subagents.d.ts +0 -27
- package/dist/private-local-subagents.js +0 -77
|
@@ -1,15 +1,62 @@
|
|
|
1
1
|
import { type StoredSessionSummary } from "@fungi.computer/shiitake/effect";
|
|
2
|
-
import type { PrivateLocalServiceManager,
|
|
2
|
+
import type { PrivateLocalServiceManager, PrivateLocalDiscoveryOptions, PrivateLocalSessionDiscovery } from "./private-local-service-contract.js";
|
|
3
3
|
export type LocalSummary = StoredSessionSummary & Readonly<{
|
|
4
4
|
databasePath: string;
|
|
5
5
|
sessionId: string;
|
|
6
6
|
}>;
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Which Agent stores a listing returns: the ones in use, only the archived
|
|
9
|
+
* ones (to restore them), or both (to find one by id).
|
|
10
|
+
*/
|
|
11
|
+
export type LocalStoreVisibility = "active" | "archived" | "all";
|
|
12
|
+
/** The one plain phrase for a store written before the current schema, with how to reset it. */
|
|
13
|
+
export declare function unreadableStoreReason(agentId: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Whether a store's Agent is archived. A store this cannot read counts as
|
|
16
|
+
* not archived, so discovery still reports it as a problem.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isArchivedStore(path: string): boolean;
|
|
19
|
+
export declare function listPrivateLocalAgentStores(root: string, visibility?: LocalStoreVisibility): Promise<readonly string[]>;
|
|
20
|
+
/** A Local or Woodstock store outside the current schema; Local never migrates it. */
|
|
21
|
+
export declare class LocalStoreSchemaError extends Error {
|
|
22
|
+
readonly databasePath: string;
|
|
23
|
+
readonly name = "LocalStoreSchemaError";
|
|
24
|
+
constructor(databasePath: string, options?: ErrorOptions);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* One Agent store's chosen display name, default model, whether it is
|
|
28
|
+
* archived and the Agent that created it (absent when a human made it), read
|
|
29
|
+
* without opening a host.
|
|
30
|
+
*/
|
|
31
|
+
export declare function readPrivateLocalAgentProfile(path: string): {
|
|
32
|
+
name: string | undefined;
|
|
33
|
+
createdBy: string | null;
|
|
34
|
+
archived: boolean;
|
|
35
|
+
requireSandbox: boolean;
|
|
36
|
+
folder: string;
|
|
37
|
+
model: {
|
|
38
|
+
readonly providerId: string;
|
|
39
|
+
readonly modelId: string;
|
|
40
|
+
readonly reasoning?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
|
|
41
|
+
} | null;
|
|
42
|
+
};
|
|
8
43
|
export declare function readPrivateLocalSessionSummaries(path: string): Promise<readonly LocalSummary[]>;
|
|
9
|
-
/**
|
|
10
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Discover existing folder sessions without opening or reconciling a host.
|
|
46
|
+
* Archived Agents and their Sessions are left out, so no picker or "newest
|
|
47
|
+
* Session" choice can land on one; `archived` lists only those instead.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readPrivateLocalSessionDiscovery(invocationRoot: string, request?: PrivateLocalDiscoveryOptions): Promise<{
|
|
11
50
|
root: string;
|
|
12
|
-
|
|
51
|
+
agents: Readonly<{
|
|
52
|
+
agentId: string;
|
|
53
|
+
unreadable?: string | undefined;
|
|
54
|
+
name?: string | undefined;
|
|
55
|
+
createdBy?: string | null | undefined;
|
|
56
|
+
databasePath: string;
|
|
57
|
+
sessionCount: number;
|
|
58
|
+
updatedAt?: number | undefined;
|
|
59
|
+
}>[];
|
|
13
60
|
sessions: LocalSummary[];
|
|
14
61
|
problems: Readonly<{
|
|
15
62
|
kind: "corrupt" | "in_use" | "unreadable";
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { readdir, realpath, stat } from "node:fs/promises";
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
3
|
-
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
3
|
import * as Effect from "effect/Effect";
|
|
4
|
+
import { parseModelPreferenceJson } from "@fungi.computer/shiitake/model-preference";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { readAgentDefaultModelJson } from "./private-local-agent-default-model.js";
|
|
7
|
+
import { localAgentFolderSchema, resolveAgentFolder, } from "./private-local-sandbox-policy.js";
|
|
8
|
+
import { logEvent } from "./log-event.js";
|
|
5
9
|
import { readSessionSummaries, } from "@fungi.computer/shiitake/effect";
|
|
6
|
-
import { createPrivateSqliteOwner, privateSqliteFailureCode, } from "./private-sqlite-owner.js";
|
|
10
|
+
import { createPrivateSqliteOwner, openPrivateReadOnlySqliteDatabase, privateSqliteFailureCode, } from "./private-sqlite-owner.js";
|
|
7
11
|
function compareSummaries(left, right) {
|
|
8
12
|
return (right.updatedAt - left.updatedAt ||
|
|
9
13
|
right.createdAt - left.createdAt ||
|
|
@@ -61,12 +65,33 @@ async function immediateStoreCandidates(root, relative) {
|
|
|
61
65
|
}
|
|
62
66
|
})).then((values) => values.filter((value) => value !== undefined));
|
|
63
67
|
}
|
|
64
|
-
|
|
68
|
+
/** The one plain phrase for a store written before the current schema, with how to reset it. */
|
|
69
|
+
export function unreadableStoreReason(agentId) {
|
|
70
|
+
return `predates the current schema; move .fungi/agents/${agentId} aside to reset it`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Whether a store's Agent is archived. A store this cannot read counts as
|
|
74
|
+
* not archived, so discovery still reports it as a problem.
|
|
75
|
+
*/
|
|
76
|
+
export function isArchivedStore(path) {
|
|
77
|
+
try {
|
|
78
|
+
return readPrivateLocalAgentProfile(path).archived;
|
|
79
|
+
}
|
|
80
|
+
catch (cause) {
|
|
81
|
+
if (!(cause instanceof LocalStoreSchemaError))
|
|
82
|
+
logEvent("store-archived", cause);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export async function listPrivateLocalAgentStores(root, visibility = "active") {
|
|
65
87
|
const candidates = await immediateStoreCandidates(root, ".fungi/agents");
|
|
66
88
|
const paths = new Set();
|
|
67
89
|
for (const candidate of candidates) {
|
|
68
90
|
try {
|
|
69
|
-
|
|
91
|
+
const path = await realpath(candidate.path);
|
|
92
|
+
if (visibility === "all" ||
|
|
93
|
+
isArchivedStore(path) === (visibility === "archived"))
|
|
94
|
+
paths.add(path);
|
|
70
95
|
}
|
|
71
96
|
catch (cause) {
|
|
72
97
|
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
|
@@ -76,8 +101,62 @@ export async function listPrivateLocalAgentStores(root) {
|
|
|
76
101
|
}
|
|
77
102
|
return [...paths];
|
|
78
103
|
}
|
|
104
|
+
const storedProfileSchema = z.strictObject({
|
|
105
|
+
name: z.string().nullable(),
|
|
106
|
+
createdBy: z.string().nullable(),
|
|
107
|
+
archived: z.union([z.literal(0), z.literal(1)]),
|
|
108
|
+
requireSandbox: z.union([z.literal(0), z.literal(1)]),
|
|
109
|
+
folder: localAgentFolderSchema,
|
|
110
|
+
model: z.string(),
|
|
111
|
+
});
|
|
112
|
+
/** A Local or Woodstock store outside the current schema; Local never migrates it. */
|
|
113
|
+
export class LocalStoreSchemaError extends Error {
|
|
114
|
+
databasePath;
|
|
115
|
+
name = "LocalStoreSchemaError";
|
|
116
|
+
constructor(databasePath, options) {
|
|
117
|
+
super(`Agent store ${databasePath} is incompatible with this version of Fungi. To reset, stop the owning Fungi service, then move this database, its .owner.sqlite sidecar and any associated SQLite journal files aside together. Create a fresh Agent; existing Sessions are not migrated. Keep the moved files if you need their data.`, options);
|
|
118
|
+
this.databasePath = databasePath;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* One Agent store's chosen display name, default model, whether it is
|
|
123
|
+
* archived and the Agent that created it (absent when a human made it), read
|
|
124
|
+
* without opening a host.
|
|
125
|
+
*/
|
|
126
|
+
export function readPrivateLocalAgentProfile(path) {
|
|
127
|
+
const database = openPrivateReadOnlySqliteDatabase(path);
|
|
128
|
+
try {
|
|
129
|
+
const columns = database
|
|
130
|
+
.prepare("PRAGMA table_info(private_local_identity)")
|
|
131
|
+
.all();
|
|
132
|
+
if (!["name", "created_by", "archived", "require_sandbox", "folder"].every((required) => columns.some((column) => column.name === required)))
|
|
133
|
+
throw new LocalStoreSchemaError(path);
|
|
134
|
+
const identity = database
|
|
135
|
+
.prepare("SELECT name, created_by, archived, require_sandbox, folder FROM private_local_identity WHERE singleton = 1")
|
|
136
|
+
.get();
|
|
137
|
+
const stored = storedProfileSchema.parse({
|
|
138
|
+
name: identity?.name,
|
|
139
|
+
createdBy: identity?.created_by,
|
|
140
|
+
archived: identity?.archived,
|
|
141
|
+
requireSandbox: identity?.require_sandbox,
|
|
142
|
+
folder: identity?.folder,
|
|
143
|
+
model: readAgentDefaultModelJson(database),
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
name: stored.name ?? undefined,
|
|
147
|
+
createdBy: stored.createdBy,
|
|
148
|
+
archived: stored.archived === 1,
|
|
149
|
+
requireSandbox: stored.requireSandbox === 1,
|
|
150
|
+
folder: stored.folder,
|
|
151
|
+
model: parseModelPreferenceJson(stored.model),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
database.close();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
79
158
|
export async function readPrivateLocalSessionSummaries(path) {
|
|
80
|
-
const database =
|
|
159
|
+
const database = openPrivateReadOnlySqliteDatabase(path);
|
|
81
160
|
try {
|
|
82
161
|
database.exec("PRAGMA foreign_keys = ON");
|
|
83
162
|
let sqliteFailure;
|
|
@@ -117,24 +196,65 @@ export async function readPrivateLocalSessionSummaries(path) {
|
|
|
117
196
|
database.close();
|
|
118
197
|
}
|
|
119
198
|
}
|
|
120
|
-
/**
|
|
199
|
+
/**
|
|
200
|
+
* Discover existing folder sessions without opening or reconciling a host.
|
|
201
|
+
* Archived Agents and their Sessions are left out, so no picker or "newest
|
|
202
|
+
* Session" choice can land on one; `archived` lists only those instead.
|
|
203
|
+
*/
|
|
121
204
|
export async function readPrivateLocalSessionDiscovery(invocationRoot, request = {}) {
|
|
122
205
|
request.signal?.throwIfAborted();
|
|
123
206
|
const root = await realpath(resolve(invocationRoot));
|
|
124
|
-
const paths = await listPrivateLocalAgentStores(root);
|
|
207
|
+
const paths = await listPrivateLocalAgentStores(root, request.archived === true ? "archived" : "active");
|
|
125
208
|
const discovered = [];
|
|
209
|
+
const agents = [];
|
|
210
|
+
const profiles = new Map();
|
|
126
211
|
const problems = [];
|
|
127
212
|
for (const path of paths) {
|
|
128
213
|
request.signal?.throwIfAborted();
|
|
214
|
+
const agentId = basename(dirname(path));
|
|
129
215
|
try {
|
|
130
|
-
|
|
216
|
+
const summaries = await readPrivateLocalSessionSummaries(path);
|
|
217
|
+
discovered.push(...summaries);
|
|
218
|
+
let profile;
|
|
219
|
+
let unreadable;
|
|
220
|
+
try {
|
|
221
|
+
profile = readPrivateLocalAgentProfile(path);
|
|
222
|
+
resolveAgentFolder(root, profile.folder);
|
|
223
|
+
profiles.set(agentId, profile);
|
|
224
|
+
}
|
|
225
|
+
catch (cause) {
|
|
226
|
+
// Shown on the row so the user knows to reset the store; opening it
|
|
227
|
+
// refuses with the same error.
|
|
228
|
+
if (!(cause instanceof LocalStoreSchemaError))
|
|
229
|
+
throw cause;
|
|
230
|
+
unreadable = unreadableStoreReason(agentId);
|
|
231
|
+
}
|
|
232
|
+
agents.push({
|
|
233
|
+
agentId,
|
|
234
|
+
name: profile?.name,
|
|
235
|
+
unreadable,
|
|
236
|
+
databasePath: path,
|
|
237
|
+
sessionCount: summaries.length,
|
|
238
|
+
updatedAt: summaries[0]?.updatedAt,
|
|
239
|
+
});
|
|
131
240
|
}
|
|
132
241
|
catch (cause) {
|
|
133
242
|
problems.push({ kind: problemKind(cause) });
|
|
243
|
+
agents.push({ agentId, databasePath: path, sessionCount: 0 });
|
|
134
244
|
}
|
|
135
245
|
}
|
|
136
246
|
discovered.sort(compareSummaries);
|
|
137
|
-
|
|
247
|
+
// The wire carries identity; clients derive the “made by” label from names.
|
|
248
|
+
const labelled = agents.map((agent) => {
|
|
249
|
+
const createdBy = profiles.get(agent.agentId)?.createdBy;
|
|
250
|
+
if (createdBy === undefined)
|
|
251
|
+
return agent;
|
|
252
|
+
return {
|
|
253
|
+
...agent,
|
|
254
|
+
createdBy,
|
|
255
|
+
};
|
|
256
|
+
});
|
|
257
|
+
return { root, agents: labelled, sessions: discovered, problems };
|
|
138
258
|
}
|
|
139
259
|
/** Project the service-owned discovery into choices that resume through it. */
|
|
140
260
|
export function projectPrivateLocalSessionDiscovery(discovered, connect) {
|
|
@@ -147,10 +267,11 @@ export function projectPrivateLocalSessionDiscovery(discovered, connect) {
|
|
|
147
267
|
databasePath,
|
|
148
268
|
sessionId,
|
|
149
269
|
fungiMcp: resumeRequest?.fungiMcp,
|
|
270
|
+
requireSandbox: resumeRequest?.requireSandbox,
|
|
150
271
|
}, resumeRequest),
|
|
151
272
|
}));
|
|
152
273
|
return Object.freeze({
|
|
153
|
-
|
|
274
|
+
agents: Object.freeze(discovered.agents),
|
|
154
275
|
sessions: Object.freeze(sessions),
|
|
155
276
|
problems: Object.freeze(discovered.problems),
|
|
156
277
|
});
|
|
@@ -35,7 +35,7 @@ async function closePrivateShiitakeResources(failures, client, server, owner) {
|
|
|
35
35
|
/** Open one bounded private local Hono host on a loopback-only ephemeral port. */
|
|
36
36
|
export async function openPrivateLocalShiitakeHost(input, openServer, openHost = openPrivateLocalRuntimeHost, eventSource) {
|
|
37
37
|
const owner = await openHost(input);
|
|
38
|
-
const routes = createShiitakeHttpRoutes(owner.getSession, owner.listSessions, owner.createSession, owner.deleteSession, () => owner.extensions);
|
|
38
|
+
const routes = createShiitakeHttpRoutes((sessionId) => owner.getSession(sessionId), owner.listSessions, owner.createSession, owner.deleteSession, () => owner.extensions);
|
|
39
39
|
const fetch = (request) => routes.fetch(request);
|
|
40
40
|
let server;
|
|
41
41
|
let client;
|
|
@@ -107,7 +107,7 @@ export async function openPrivateLocalShiitakeWorkerBatch(inputs, openServer, op
|
|
|
107
107
|
for (const input of validated) {
|
|
108
108
|
const owner = await openHost(input.owner);
|
|
109
109
|
owners.push({ name: input.name, owner });
|
|
110
|
-
const routes = createShiitakeHttpRoutes(owner.getSession, owner.listSessions, owner.createSession, owner.deleteSession, () => owner.extensions);
|
|
110
|
+
const routes = createShiitakeHttpRoutes((sessionId) => owner.getSession(sessionId), owner.listSessions, owner.createSession, owner.deleteSession, () => owner.extensions);
|
|
111
111
|
const routePath = "/workers/" + input.name;
|
|
112
112
|
app.route(routePath, routes);
|
|
113
113
|
workers.push(Object.freeze({
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { getSupportedThinkingLevels, type Api, type Model, type Models, type ThinkingLevel } from "@earendil-works/pi-ai";
|
|
2
|
+
import { type ModelPreference } from "@fungi.computer/shiitake/model-preference";
|
|
3
|
+
/** Which layer decided a startup model: a Session override, the Agent default, or the available login. */
|
|
4
|
+
export type LocalStartupModelSource = "session" | "agent" | "available";
|
|
5
|
+
/** The one model choice a run or a new chat starts from, before it is checked against its provider. */
|
|
6
|
+
export type LocalStartupChoice = Readonly<{
|
|
7
|
+
preference: ModelPreference;
|
|
8
|
+
source: LocalStartupModelSource;
|
|
9
|
+
}>;
|
|
10
|
+
/** The effective model a new chat of one Agent starts with. */
|
|
11
|
+
export type LocalStartupModel = Readonly<{
|
|
12
|
+
providerId: string;
|
|
13
|
+
modelId: string;
|
|
14
|
+
reasoning: ThinkingLevel | undefined;
|
|
15
|
+
source: LocalStartupModelSource;
|
|
16
|
+
}>;
|
|
17
|
+
/** Validate a requested thinking level against the registered model. */
|
|
18
|
+
export declare function validateLocalReasoning(model: Parameters<typeof getSupportedThinkingLevels>[0], requested: ThinkingLevel | undefined): void;
|
|
19
|
+
/** Local's default is medium, clamped through Pi's model capabilities. */
|
|
20
|
+
export declare function defaultLocalReasoning(model: Parameters<typeof getSupportedThinkingLevels>[0]): ThinkingLevel | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* The thinking level a chosen model runs with: the choice's own level, else
|
|
23
|
+
* the opener's level when this model supports it, else Local's default.
|
|
24
|
+
*/
|
|
25
|
+
export declare function startupReasoning(selected: Model<Api>, preference: ModelPreference, ownerReasoning?: ThinkingLevel): ThinkingLevel | undefined;
|
|
26
|
+
/** Resolve explicit layers without checking logins or discovering models. */
|
|
27
|
+
export declare function resolveKnownLocalStartupChoice(input: Readonly<{
|
|
28
|
+
session: ModelPreference | null;
|
|
29
|
+
agent: ModelPreference | null;
|
|
30
|
+
}>): LocalStartupChoice | null;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the explicit choice, or discover the available login. An explicit
|
|
33
|
+
* layer never falls through on an auth failure; the caller reports it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveLocalStartupChoice(input: Readonly<{
|
|
36
|
+
models: Models;
|
|
37
|
+
session: ModelPreference | null;
|
|
38
|
+
agent: ModelPreference | null;
|
|
39
|
+
signal: AbortSignal;
|
|
40
|
+
}>): Promise<LocalStartupChoice | null>;
|
|
41
|
+
/**
|
|
42
|
+
* The model a new chat of the Agent at `databasePath` starts with; no store
|
|
43
|
+
* means a new Agent, which starts from the available login. A store that
|
|
44
|
+
* predates the current schema fails loudly. The landing and a
|
|
45
|
+
* fresh Session both resolve through this chain.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveAgentStartupModel(input: Readonly<{
|
|
48
|
+
models: Models;
|
|
49
|
+
databasePath: string | undefined;
|
|
50
|
+
signal: AbortSignal;
|
|
51
|
+
}>): Promise<LocalStartupModel | null>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { clampThinkingLevel, getSupportedThinkingLevels, } from "@earendil-works/pi-ai";
|
|
2
|
+
import {} from "@fungi.computer/shiitake/model-preference";
|
|
3
|
+
import { readPrivateLocalAgentProfile } from "./private-local-session-discovery.js";
|
|
4
|
+
import { defaultLocalModel } from "./private-local-default-model.js";
|
|
5
|
+
/** Validate a requested thinking level against the registered model. */
|
|
6
|
+
export function validateLocalReasoning(model, requested) {
|
|
7
|
+
if (requested === undefined)
|
|
8
|
+
return;
|
|
9
|
+
const supported = getSupportedThinkingLevels(model).filter((level) => level !== "off");
|
|
10
|
+
if (!supported.includes(requested))
|
|
11
|
+
throw new Error("Reasoning level " +
|
|
12
|
+
requested +
|
|
13
|
+
" is not supported by " +
|
|
14
|
+
model.provider +
|
|
15
|
+
"/" +
|
|
16
|
+
model.id +
|
|
17
|
+
"; supported levels: " +
|
|
18
|
+
(supported.join(", ") || "none"));
|
|
19
|
+
}
|
|
20
|
+
/** Local's default is medium, clamped through Pi's model capabilities. */
|
|
21
|
+
export function defaultLocalReasoning(model) {
|
|
22
|
+
const level = clampThinkingLevel(model, "medium");
|
|
23
|
+
return level === "off" ? undefined : level;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The thinking level a chosen model runs with: the choice's own level, else
|
|
27
|
+
* the opener's level when this model supports it, else Local's default.
|
|
28
|
+
*/
|
|
29
|
+
export function startupReasoning(selected, preference, ownerReasoning) {
|
|
30
|
+
return (preference.reasoning ??
|
|
31
|
+
(ownerReasoning !== undefined &&
|
|
32
|
+
getSupportedThinkingLevels(selected).includes(ownerReasoning)
|
|
33
|
+
? ownerReasoning
|
|
34
|
+
: defaultLocalReasoning(selected)));
|
|
35
|
+
}
|
|
36
|
+
/** Resolve explicit layers without checking logins or discovering models. */
|
|
37
|
+
export function resolveKnownLocalStartupChoice(input) {
|
|
38
|
+
if (input.session !== null)
|
|
39
|
+
return { preference: input.session, source: "session" };
|
|
40
|
+
if (input.agent !== null)
|
|
41
|
+
return { preference: input.agent, source: "agent" };
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the explicit choice, or discover the available login. An explicit
|
|
46
|
+
* layer never falls through on an auth failure; the caller reports it.
|
|
47
|
+
*/
|
|
48
|
+
export async function resolveLocalStartupChoice(input) {
|
|
49
|
+
const known = resolveKnownLocalStartupChoice(input);
|
|
50
|
+
if (known !== null)
|
|
51
|
+
return known;
|
|
52
|
+
const available = await defaultLocalModel(input.models, input.signal);
|
|
53
|
+
return available === null
|
|
54
|
+
? null
|
|
55
|
+
: { preference: available, source: "available" };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The model a new chat of the Agent at `databasePath` starts with; no store
|
|
59
|
+
* means a new Agent, which starts from the available login. A store that
|
|
60
|
+
* predates the current schema fails loudly. The landing and a
|
|
61
|
+
* fresh Session both resolve through this chain.
|
|
62
|
+
*/
|
|
63
|
+
export async function resolveAgentStartupModel(input) {
|
|
64
|
+
const agent = input.databasePath === undefined
|
|
65
|
+
? null
|
|
66
|
+
: readPrivateLocalAgentProfile(input.databasePath).model;
|
|
67
|
+
const choice = await resolveLocalStartupChoice({
|
|
68
|
+
models: input.models,
|
|
69
|
+
session: null,
|
|
70
|
+
agent,
|
|
71
|
+
signal: input.signal,
|
|
72
|
+
});
|
|
73
|
+
if (choice === null)
|
|
74
|
+
return null;
|
|
75
|
+
const { providerId, modelId } = choice.preference;
|
|
76
|
+
const selected = input.models.getModel(providerId, modelId);
|
|
77
|
+
return {
|
|
78
|
+
providerId,
|
|
79
|
+
modelId,
|
|
80
|
+
// A catalog not loaded yet cannot name a level; the run resolves it later.
|
|
81
|
+
reasoning: selected === undefined
|
|
82
|
+
? choice.preference.reasoning
|
|
83
|
+
: startupReasoning(selected, choice.preference),
|
|
84
|
+
source: choice.source,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** The service reports each attempted Agent, including partial failures. */
|
|
3
|
+
export declare const stopAllResultSchema: z.ZodObject<{
|
|
4
|
+
rootAgentId: z.ZodString;
|
|
5
|
+
agents: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
6
|
+
agentId: z.ZodString;
|
|
7
|
+
name: z.ZodString;
|
|
8
|
+
outcome: z.ZodEnum<{
|
|
9
|
+
stopped: "stopped";
|
|
10
|
+
idle: "idle";
|
|
11
|
+
}>;
|
|
12
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
13
|
+
agentId: z.ZodString;
|
|
14
|
+
name: z.ZodString;
|
|
15
|
+
outcome: z.ZodLiteral<"failed">;
|
|
16
|
+
reason: z.ZodString;
|
|
17
|
+
}, z.core.$strict>], "outcome">>;
|
|
18
|
+
discoveryFailures: z.ZodArray<z.ZodObject<{
|
|
19
|
+
agentId: z.ZodString;
|
|
20
|
+
reason: z.ZodString;
|
|
21
|
+
}, z.core.$strict>>;
|
|
22
|
+
}, z.core.$strict>;
|
|
23
|
+
export type StopAllResult = z.infer<typeof stopAllResultSchema>;
|
|
24
|
+
export declare const stopAllRequestSchema: z.ZodObject<{
|
|
25
|
+
invocationRoot: z.ZodString;
|
|
26
|
+
agentId: z.ZodString;
|
|
27
|
+
}, z.core.$strict>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** The service reports each attempted Agent, including partial failures. */
|
|
3
|
+
export const stopAllResultSchema = z.strictObject({
|
|
4
|
+
rootAgentId: z.string(),
|
|
5
|
+
agents: z.array(z.discriminatedUnion("outcome", [
|
|
6
|
+
z.strictObject({
|
|
7
|
+
agentId: z.string(),
|
|
8
|
+
name: z.string(),
|
|
9
|
+
outcome: z.enum(["stopped", "idle"]),
|
|
10
|
+
}),
|
|
11
|
+
z.strictObject({
|
|
12
|
+
agentId: z.string(),
|
|
13
|
+
name: z.string(),
|
|
14
|
+
outcome: z.literal("failed"),
|
|
15
|
+
reason: z.string(),
|
|
16
|
+
}),
|
|
17
|
+
])),
|
|
18
|
+
discoveryFailures: z.array(z.strictObject({
|
|
19
|
+
agentId: z.string(),
|
|
20
|
+
reason: z.string(),
|
|
21
|
+
})),
|
|
22
|
+
});
|
|
23
|
+
export const stopAllRequestSchema = z.strictObject({
|
|
24
|
+
invocationRoot: z.string().min(1),
|
|
25
|
+
agentId: z.string().min(1),
|
|
26
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Json } from "@fungi.computer/shiitake/extensions";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export declare const localUiAttachmentLifetimeSchema: z.ZodEnum<{
|
|
4
|
+
watch: "watch";
|
|
5
|
+
connection: "connection";
|
|
6
|
+
}>;
|
|
7
|
+
export type LocalUiAttachmentLifetime = z.infer<typeof localUiAttachmentLifetimeSchema>;
|
|
8
|
+
/** Host-held authority; a JSON identity or pending id cannot create one. */
|
|
9
|
+
export type LocalUiAttachment = Readonly<{
|
|
10
|
+
sessionId: string;
|
|
11
|
+
signal: AbortSignal;
|
|
12
|
+
close: () => void;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function createLocalUiAttachment(sessionId: string, lifetime?: LocalUiAttachmentLifetime): LocalUiAttachment;
|
|
15
|
+
export declare function startLocalUiWatch(attachment: LocalUiAttachment, sessionId: string): (() => void) | undefined;
|
|
16
|
+
/** Authority lasts for one command, not model work that might wake later. */
|
|
17
|
+
export declare function runLocalUiCommand(attachment: LocalUiAttachment, sessionId: string, body: () => Promise<Json>): Promise<Json>;
|
|
18
|
+
/** Consume host authority before registering the post-commit continuation. */
|
|
19
|
+
export declare function consumeLocalUiCommand(sessionId: string): ((complete: (committed: boolean) => Promise<Json>) => void) | undefined;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const localUiAttachmentLifetimeSchema = z.enum(["watch", "connection"]);
|
|
4
|
+
const attachments = new WeakMap();
|
|
5
|
+
export function createLocalUiAttachment(sessionId, lifetime = "watch") {
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
const attachment = {
|
|
8
|
+
sessionId,
|
|
9
|
+
signal: controller.signal,
|
|
10
|
+
close: () => {
|
|
11
|
+
attachments.delete(attachment);
|
|
12
|
+
controller.abort();
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
attachments.set(attachment, { watches: 0, lifetime });
|
|
16
|
+
return attachment;
|
|
17
|
+
}
|
|
18
|
+
export function startLocalUiWatch(attachment, sessionId) {
|
|
19
|
+
const state = attachments.get(attachment);
|
|
20
|
+
if (state === undefined || attachment.sessionId !== sessionId)
|
|
21
|
+
return undefined;
|
|
22
|
+
state.watches += 1;
|
|
23
|
+
let watching = true;
|
|
24
|
+
return () => {
|
|
25
|
+
if (!watching)
|
|
26
|
+
return;
|
|
27
|
+
watching = false;
|
|
28
|
+
state.watches -= 1;
|
|
29
|
+
// TUI/form authority is watch-scoped. ACP closes each prompt's watch,
|
|
30
|
+
// but its host-issued route belongs to the retained connection binding.
|
|
31
|
+
if (state.lifetime === "watch")
|
|
32
|
+
attachment.close();
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const commands = new AsyncLocalStorage();
|
|
36
|
+
function isAttached(command) {
|
|
37
|
+
return (command.attachment.sessionId === command.sessionId &&
|
|
38
|
+
(attachments.get(command.attachment)?.watches ?? 0) > 0);
|
|
39
|
+
}
|
|
40
|
+
/** Authority lasts for one command, not model work that might wake later. */
|
|
41
|
+
export function runLocalUiCommand(attachment, sessionId, body) {
|
|
42
|
+
const command = { attachment, sessionId, available: true };
|
|
43
|
+
return commands.run(command, async () => {
|
|
44
|
+
let result;
|
|
45
|
+
try {
|
|
46
|
+
result = await body();
|
|
47
|
+
}
|
|
48
|
+
catch (cause) {
|
|
49
|
+
command.available = false;
|
|
50
|
+
await command.complete?.(false);
|
|
51
|
+
throw cause;
|
|
52
|
+
}
|
|
53
|
+
command.available = false;
|
|
54
|
+
return command.complete === undefined
|
|
55
|
+
? result
|
|
56
|
+
: command.complete(isAttached(command));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** Consume host authority before registering the post-commit continuation. */
|
|
60
|
+
export function consumeLocalUiCommand(sessionId) {
|
|
61
|
+
const command = commands.getStore();
|
|
62
|
+
if (command === undefined ||
|
|
63
|
+
!command.available ||
|
|
64
|
+
command.sessionId !== sessionId ||
|
|
65
|
+
!isAttached(command))
|
|
66
|
+
return undefined;
|
|
67
|
+
command.available = false;
|
|
68
|
+
return (complete) => {
|
|
69
|
+
command.complete = complete;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -8,6 +8,7 @@ export type Output = Pick<NodeJS.WritableStream, "write">;
|
|
|
8
8
|
export type OpenWorkerBatch = typeof openPrivateLocalBunShiitakeWorkerBatch;
|
|
9
9
|
export type PrivateLocalWorkerServerIo = Readonly<{
|
|
10
10
|
cwd: string;
|
|
11
|
+
home?: string;
|
|
11
12
|
stdout?: Output | undefined;
|
|
12
13
|
signals?: Signals | undefined;
|
|
13
14
|
}>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { logEvent } from "./log-event.js";
|
|
2
|
-
import {
|
|
2
|
+
import { capturePrivateLocalConfigRoot, } from "./private-local-config-root.js";
|
|
3
|
+
import { readFungiMcpSelection } from "./private-fungi-mcp-selection.js";
|
|
4
|
+
import { fungiMcpSelectionSchema } from "./private-fungi-mcp-selection-schema.js";
|
|
3
5
|
import { readFile } from "node:fs/promises";
|
|
4
6
|
import process from "node:process";
|
|
5
7
|
import { dirname, resolve } from "node:path";
|
|
@@ -37,7 +39,7 @@ function parseWorkers(value, filePath) {
|
|
|
37
39
|
},
|
|
38
40
|
}));
|
|
39
41
|
}
|
|
40
|
-
async function readWorkers(filePath, cwd) {
|
|
42
|
+
async function readWorkers(filePath, cwd, configRoot) {
|
|
41
43
|
const absolutePath = resolve(cwd, filePath);
|
|
42
44
|
let value;
|
|
43
45
|
try {
|
|
@@ -49,11 +51,16 @@ async function readWorkers(filePath, cwd) {
|
|
|
49
51
|
const workers = [];
|
|
50
52
|
for (const { name, owner } of parseWorkers(value, absolutePath)) {
|
|
51
53
|
const { fungiMcp, ...local } = owner;
|
|
52
|
-
let configured =
|
|
54
|
+
let configured = {
|
|
55
|
+
...local,
|
|
56
|
+
configRoot,
|
|
57
|
+
};
|
|
53
58
|
if (fungiMcp !== undefined)
|
|
54
59
|
configured = {
|
|
55
60
|
...configured,
|
|
56
|
-
fungiMcp: readFungiMcpSelection(fungiMcp
|
|
61
|
+
fungiMcp: readFungiMcpSelection(fungiMcp, {
|
|
62
|
+
credentialPath: configRoot.credentialPath,
|
|
63
|
+
}),
|
|
57
64
|
};
|
|
58
65
|
workers.push({ name, owner: configured });
|
|
59
66
|
}
|
|
@@ -78,7 +85,8 @@ export async function runPrivateLocalWorkerServer(workersFile, listen, io, openB
|
|
|
78
85
|
const { openPrivateLocalBunShiitakeWorkerBatch } = await import("./private-bun-shiitake-host.js");
|
|
79
86
|
return openPrivateLocalBunShiitakeWorkerBatch(...args);
|
|
80
87
|
}) {
|
|
81
|
-
const
|
|
88
|
+
const root = capturePrivateLocalConfigRoot(io.home);
|
|
89
|
+
const workers = await readWorkers(workersFile, io.cwd, root);
|
|
82
90
|
const stdout = io.stdout ?? process.stdout;
|
|
83
91
|
const signals = io.signals ?? process;
|
|
84
92
|
const stopped = Promise.withResolvers();
|