@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,45 +1,54 @@
|
|
|
1
1
|
import { createAssistantMessageEventStream } from "@earendil-works/pi-ai/utils/event-stream";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { observeProviderStatus } from "@fungi.computer/mule";
|
|
3
|
+
import { modelFailureMessage, modelFailureRetryable, } from "./private-provider-rejection.js";
|
|
4
4
|
import { logEvent } from "./log-event.js";
|
|
5
|
-
import {
|
|
5
|
+
import { localArchiveHandoff } from "./local-archive-handoff.js";
|
|
6
|
+
import { createMcpFormSession } from "./private-mcp-form-session.js";
|
|
7
|
+
import { localExtensions, } from "./local-extensions.js";
|
|
6
8
|
import { lstat, mkdir, realpath, stat } from "node:fs/promises";
|
|
7
9
|
import { lstatSync, mkdirSync, realpathSync } from "node:fs";
|
|
8
10
|
import { basename, dirname, isAbsolute, join, relative, resolve, } from "node:path";
|
|
9
|
-
import { DatabaseSync } from "node:sqlite";
|
|
10
11
|
import { createHash } from "node:crypto";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { cleanupSessionResources, } from "@earendil-works/pi-ai";
|
|
14
|
+
import { parseModelPreference, } from "@fungi.computer/shiitake/model-preference";
|
|
13
15
|
import { createAgentControlSource, } from "@fungi.computer/shiitake/agent-control";
|
|
14
16
|
import { defineIdFamily } from "@fungi.computer/cairn";
|
|
17
|
+
import { words } from "@fungi.computer/cairn/words";
|
|
15
18
|
import { botanicalExtensionsWithSkills } from "@fungi.computer/shiitake-extensions/roster";
|
|
16
19
|
import { homeExtension, homeGrant, } from "@fungi.computer/shiitake-extensions/home/host";
|
|
17
20
|
import { Knapsack } from "@fungi.computer/knapsack";
|
|
18
21
|
import { makeHost, Shiitake, ShiitakeError, } from "@fungi.computer/shiitake";
|
|
19
22
|
import { createPrivateModels } from "./local-models.js";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
23
|
+
import { capturePrivateLocalConfigRoot, } from "./private-local-config-root.js";
|
|
24
|
+
import { acquireSubscriptionTurn } from "./subscription-turn.js";
|
|
25
|
+
import { listOfferedLocalModels } from "./private-local-default-model.js";
|
|
26
|
+
import { ensureAgentDefaultModelTable, readAgentDefaultModel, seedAgentDefaultModel, writeAgentDefaultModel, } from "./private-local-agent-default-model.js";
|
|
27
|
+
import { defaultLocalReasoning, resolveKnownLocalStartupChoice, resolveLocalStartupChoice, startupReasoning, validateLocalReasoning, } from "./private-local-startup-model.js";
|
|
22
28
|
import { assertFungiModelsEnabled } from "./private-fungi-model-api.js";
|
|
23
29
|
import { privateLocalEffectiveModelSchema, } from "./private-local-effective-model.js";
|
|
24
|
-
import { createPrivateInvocationComputer } from "./private-invocation-computer.js";
|
|
30
|
+
import { createPrivateInvocationComputer, } from "./private-invocation-computer.js";
|
|
25
31
|
import { createPrivateLocalComputerProcesses, } from "./private-local-computer-processes.js";
|
|
26
32
|
import { capturePrivateLocalExecPath, parsePrivateLocalExecPath, } from "./private-local-exec-path.js";
|
|
27
33
|
import { ensurePrivateLocalGitExclude } from "./private-local-git-exclude.js";
|
|
34
|
+
import { createLocalSandboxControls, } from "./private-local-sandbox.js";
|
|
35
|
+
import { localAgentFolderSchema, resolveAgentFolder, } from "./private-local-sandbox-policy.js";
|
|
36
|
+
import { LocalStoreSchemaError } from "./private-local-session-discovery.js";
|
|
28
37
|
import { acquireNodeStoreLease, nodeRuntimeHost, } from "@fungi.computer/shiitake/node";
|
|
29
|
-
import { createPrivateInvocationFilesPort } from "./private-invocation-files.js";
|
|
38
|
+
import { createPrivateInvocationFilesPort, } from "./private-invocation-files.js";
|
|
30
39
|
import { createLocalSkillSource } from "./local-skill-source.js";
|
|
31
40
|
import { createPrivateWorkerdSandbox } from "./private-workerd-sandbox.js";
|
|
32
41
|
import { createPrivateFungiMcpConnection, } from "./private-fungi-mcp-connection.js";
|
|
33
|
-
import { createPrivateSqliteOwner } from "./private-sqlite-owner.js";
|
|
34
|
-
import { createPrivateLocalMailRoom } from "./private-local-mail.js";
|
|
42
|
+
import { createPrivateSqliteOwner, openPrivateWritableSqliteDatabase, } from "./private-sqlite-owner.js";
|
|
43
|
+
import { createPrivateLocalMailRoom, hasPrivateLocalPendingMail, } from "./private-local-mail.js";
|
|
35
44
|
import { createPrivateLocalDelegation, } from "./private-local-delegation.js";
|
|
36
|
-
import {
|
|
45
|
+
import { createPrivateLocalAgentControl, } from "./private-local-agent-control.js";
|
|
37
46
|
import { subagentsExtension, subagentsGrant, } from "@fungi.computer/shiitake-extensions/subagents/host";
|
|
38
47
|
const initialSessionId = "main";
|
|
39
48
|
const maximumInstructionReadBytes = 128 * 1_024;
|
|
40
49
|
const maximumInstructionSnapshotBytes = 32_000;
|
|
41
|
-
const instructionRevisionVersion = "local-host-instructions-
|
|
42
|
-
const authoredInstructions = "You are the local coding agent. Explain durable changes clearly.
|
|
50
|
+
const instructionRevisionVersion = "local-host-instructions-v3";
|
|
51
|
+
const authoredInstructions = "You are the local coding agent. Explain durable changes clearly. After a run fails, each new user message is a new instruction: retry or continue the failed task only when the user explicitly asks, and ask what they want when the message is ambiguous.";
|
|
43
52
|
const utf8 = new TextEncoder();
|
|
44
53
|
const localHostIds = defineIdFamily({
|
|
45
54
|
family: "local-host",
|
|
@@ -55,30 +64,51 @@ function isErrnoException(error) {
|
|
|
55
64
|
function sidecarPathFor(databasePath) {
|
|
56
65
|
return `${databasePath}.owner.sqlite`;
|
|
57
66
|
}
|
|
58
|
-
function ensureLocalAgentId(database, pathAgentId) {
|
|
67
|
+
function ensureLocalAgentId(database, databasePath, pathAgentId, name, createdBy, folder, requireSandbox) {
|
|
59
68
|
database.exec("BEGIN IMMEDIATE");
|
|
60
69
|
try {
|
|
61
70
|
database.exec(`
|
|
62
71
|
CREATE TABLE IF NOT EXISTS private_local_identity (
|
|
63
72
|
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
64
|
-
agent_id TEXT NOT NULL UNIQUE
|
|
73
|
+
agent_id TEXT NOT NULL UNIQUE,
|
|
74
|
+
name TEXT,
|
|
75
|
+
created_by TEXT,
|
|
76
|
+
archived INTEGER NOT NULL DEFAULT 0 CHECK (archived IN (0, 1)),
|
|
77
|
+
folder TEXT NOT NULL DEFAULT '.',
|
|
78
|
+
require_sandbox INTEGER NOT NULL DEFAULT 0 CHECK (require_sandbox IN (0, 1))
|
|
65
79
|
)
|
|
66
80
|
`);
|
|
81
|
+
const columns = database
|
|
82
|
+
.prepare("PRAGMA table_info(private_local_identity)")
|
|
83
|
+
.all();
|
|
84
|
+
if (!["name", "created_by", "archived", "folder", "require_sandbox"].every((required) => columns.some((column) => column.name === required)))
|
|
85
|
+
throw new LocalStoreSchemaError(databasePath);
|
|
67
86
|
const row = database
|
|
68
|
-
.prepare("SELECT agent_id FROM private_local_identity WHERE singleton = 1")
|
|
87
|
+
.prepare("SELECT agent_id, created_by, folder FROM private_local_identity WHERE singleton = 1")
|
|
69
88
|
.get();
|
|
70
89
|
if (row !== undefined) {
|
|
71
90
|
const parsed = localHostIds.store.schema.safeParse(row.agent_id);
|
|
72
91
|
if (!parsed.success ||
|
|
73
92
|
(pathAgentId !== undefined && parsed.data !== pathAgentId))
|
|
74
93
|
throw new Error("Local Agent identity does not match its store");
|
|
94
|
+
if (createdBy !== undefined && row.created_by !== createdBy)
|
|
95
|
+
throw new Error("Local Agent creation identity conflicts with its store");
|
|
96
|
+
const storedFolder = localAgentFolderSchema.parse(row.folder);
|
|
97
|
+
if (folder !== undefined &&
|
|
98
|
+
localAgentFolderSchema.parse(folder) !== storedFolder)
|
|
99
|
+
throw new Error("Local Agent folder conflicts with its store");
|
|
100
|
+
if (requireSandbox !== undefined)
|
|
101
|
+
database
|
|
102
|
+
.prepare("UPDATE private_local_identity SET require_sandbox = ? WHERE singleton = 1")
|
|
103
|
+
.run(requireSandbox ? 1 : 0);
|
|
75
104
|
database.exec("COMMIT");
|
|
76
105
|
return parsed.data;
|
|
77
106
|
}
|
|
78
107
|
const agentId = pathAgentId ?? localHostIds.store.generate();
|
|
79
108
|
database
|
|
80
|
-
.prepare("INSERT INTO private_local_identity (singleton, agent_id) VALUES (1, ?)")
|
|
81
|
-
.
|
|
109
|
+
.prepare("INSERT INTO private_local_identity (singleton, agent_id, name, created_by, folder, require_sandbox) VALUES (1, ?, ?, ?, ?, ?)")
|
|
110
|
+
// An unnamed Agent gets a Cairn candidate word name so it never shows its id.
|
|
111
|
+
.run(agentId, name ?? words(), createdBy ?? null, localAgentFolderSchema.parse(folder ?? "."), requireSandbox === true ? 1 : 0);
|
|
82
112
|
database.exec("COMMIT");
|
|
83
113
|
return agentId;
|
|
84
114
|
}
|
|
@@ -119,11 +149,15 @@ async function ensurePrivateLocalDirectory(path) {
|
|
|
119
149
|
canonical !== path)
|
|
120
150
|
throw new Error("Local Agent create directory is not project owned");
|
|
121
151
|
}
|
|
122
|
-
async function assertPrivateLocalReceiptFile(path) {
|
|
152
|
+
async function assertPrivateLocalReceiptFile(path, kind) {
|
|
123
153
|
// A concurrent create's commit deletes the rollback journal at any point
|
|
124
154
|
// between these reads; a file that is gone has nothing left to check.
|
|
125
155
|
try {
|
|
126
156
|
const metadata = await lstat(path);
|
|
157
|
+
// SQLite can unlink the journal between the kernel's lookup and getattr.
|
|
158
|
+
// Its now-unlinked regular inode is gone, just as with ENOENT below.
|
|
159
|
+
if (kind === "journal" && metadata.isFile() && metadata.nlink === 0)
|
|
160
|
+
return;
|
|
127
161
|
if (!metadata.isFile() ||
|
|
128
162
|
metadata.isSymbolicLink() ||
|
|
129
163
|
metadata.nlink !== 1 ||
|
|
@@ -148,18 +182,17 @@ export async function resolvePrivateLocalCreateStore(root, requestId) {
|
|
|
148
182
|
await ensurePrivateLocalDirectory(fungiDirectory);
|
|
149
183
|
await ensurePrivateLocalDirectory(agentsDirectory);
|
|
150
184
|
const receiptPath = join(agentsDirectory, localCreateReceiptFile);
|
|
151
|
-
await assertPrivateLocalReceiptFile(receiptPath);
|
|
152
|
-
await assertPrivateLocalReceiptFile(`${receiptPath}-journal
|
|
153
|
-
const database =
|
|
154
|
-
timeout: receiptBusyTimeoutMs,
|
|
155
|
-
});
|
|
185
|
+
await assertPrivateLocalReceiptFile(receiptPath, "receipt");
|
|
186
|
+
await assertPrivateLocalReceiptFile(`${receiptPath}-journal`, "journal");
|
|
187
|
+
const database = openPrivateWritableSqliteDatabase(receiptPath, receiptBusyTimeoutMs);
|
|
156
188
|
try {
|
|
157
189
|
database.exec(`PRAGMA busy_timeout = ${String(receiptBusyTimeoutMs)}`);
|
|
158
190
|
database.exec("BEGIN IMMEDIATE");
|
|
159
191
|
database.exec(`
|
|
160
192
|
CREATE TABLE IF NOT EXISTS ${localCreateReceiptTable} (
|
|
161
193
|
request_id TEXT PRIMARY KEY NOT NULL,
|
|
162
|
-
agent_id TEXT NOT NULL UNIQUE
|
|
194
|
+
agent_id TEXT NOT NULL UNIQUE,
|
|
195
|
+
name TEXT
|
|
163
196
|
) WITHOUT ROWID
|
|
164
197
|
`);
|
|
165
198
|
const existing = database
|
|
@@ -346,6 +379,8 @@ export function createPrivateLocalInstructionSource(port) {
|
|
|
346
379
|
deadlineAt: null,
|
|
347
380
|
signal,
|
|
348
381
|
});
|
|
382
|
+
if ("image" in page)
|
|
383
|
+
throw new Error("Root AGENTS.md is an image, not instructions");
|
|
349
384
|
if (page.truncated)
|
|
350
385
|
throw new Error("Root AGENTS.md exceeds the 128 KiB instruction bound");
|
|
351
386
|
agents = page.content;
|
|
@@ -372,26 +407,6 @@ export function createPrivateLocalInstructionSource(port) {
|
|
|
372
407
|
},
|
|
373
408
|
});
|
|
374
409
|
}
|
|
375
|
-
/** Validate the selected registered model before resolving credentials. */
|
|
376
|
-
function validateLocalReasoning(model, requested) {
|
|
377
|
-
if (requested === undefined)
|
|
378
|
-
return;
|
|
379
|
-
const supported = getSupportedThinkingLevels(model).filter((level) => level !== "off");
|
|
380
|
-
if (!supported.includes(requested))
|
|
381
|
-
throw new Error("Reasoning level " +
|
|
382
|
-
requested +
|
|
383
|
-
" is not supported by " +
|
|
384
|
-
model.provider +
|
|
385
|
-
"/" +
|
|
386
|
-
model.id +
|
|
387
|
-
"; supported levels: " +
|
|
388
|
-
(supported.join(", ") || "none"));
|
|
389
|
-
}
|
|
390
|
-
/** Local's default is medium, clamped through Pi's model capabilities. */
|
|
391
|
-
function defaultLocalReasoning(model) {
|
|
392
|
-
const level = clampThinkingLevel(model, "medium");
|
|
393
|
-
return level === "off" ? undefined : level;
|
|
394
|
-
}
|
|
395
410
|
export function createPrivateLocalModelSource(models, providerId, modelId, providerCacheKey, selectedOwnerReasoning, defaults) {
|
|
396
411
|
const model = modelId === undefined || providerId === undefined
|
|
397
412
|
? undefined
|
|
@@ -402,25 +417,23 @@ export function createPrivateLocalModelSource(models, providerId, modelId, provi
|
|
|
402
417
|
validateLocalReasoning(model, selectedOwnerReasoning);
|
|
403
418
|
const ownerReasoning = selectedOwnerReasoning ??
|
|
404
419
|
(model === undefined ? undefined : defaultLocalReasoning(model));
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const
|
|
420
|
+
const agentPreference = () => defaults?.agent?.() ??
|
|
421
|
+
(providerId === undefined
|
|
422
|
+
? null
|
|
423
|
+
: {
|
|
424
|
+
providerId,
|
|
425
|
+
modelId: modelId ?? "@fungi/luna",
|
|
426
|
+
});
|
|
427
|
+
const prepareSelection = async (preference, signal) => {
|
|
428
|
+
const startup = await resolveLocalStartupChoice({
|
|
429
|
+
models,
|
|
414
430
|
session: preference,
|
|
415
|
-
agent,
|
|
416
|
-
|
|
431
|
+
agent: agentPreference(),
|
|
432
|
+
signal,
|
|
417
433
|
});
|
|
418
|
-
|
|
419
|
-
session: explicit,
|
|
420
|
-
available: explicit === null ? await defaultLocalModel(models, signal) : null,
|
|
421
|
-
});
|
|
422
|
-
if (choice === null)
|
|
434
|
+
if (startup === null)
|
|
423
435
|
throw new ShiitakeError("model", "rejected", "No signed-in model provider is available. Run /login, then choose a model with /model.");
|
|
436
|
+
const choice = startup.preference;
|
|
424
437
|
const selectedProvider = choice.providerId;
|
|
425
438
|
if (selectedProvider === "fungi") {
|
|
426
439
|
try {
|
|
@@ -445,11 +458,7 @@ export function createPrivateLocalModelSource(models, providerId, modelId, provi
|
|
|
445
458
|
const selected = models.getModel(selectedProvider, selectedId);
|
|
446
459
|
if (selected === undefined)
|
|
447
460
|
throw new ShiitakeError("model", "rejected", `Local model is not registered: ${selectedProvider}/${selectedId}`);
|
|
448
|
-
const reasoning = choice
|
|
449
|
-
(ownerReasoning !== undefined &&
|
|
450
|
-
getSupportedThinkingLevels(selected).includes(ownerReasoning)
|
|
451
|
-
? ownerReasoning
|
|
452
|
-
: defaultLocalReasoning(selected));
|
|
461
|
+
const reasoning = startupReasoning(selected, choice, ownerReasoning);
|
|
453
462
|
validateLocalReasoning(selected, reasoning);
|
|
454
463
|
return { selected, reasoning };
|
|
455
464
|
};
|
|
@@ -467,14 +476,14 @@ export function createPrivateLocalModelSource(models, providerId, modelId, provi
|
|
|
467
476
|
};
|
|
468
477
|
return Object.freeze({
|
|
469
478
|
assertReady: async (preference, signal) => {
|
|
470
|
-
const { selected } = await
|
|
479
|
+
const { selected } = await prepareSelection(preference, signal);
|
|
471
480
|
await assertAuthenticated(selected.provider, signal);
|
|
472
481
|
},
|
|
473
482
|
effective: async (preference, signal) => {
|
|
474
483
|
signal.throwIfAborted();
|
|
475
484
|
let selection;
|
|
476
485
|
try {
|
|
477
|
-
selection = await
|
|
486
|
+
selection = await prepareSelection(preference, signal);
|
|
478
487
|
}
|
|
479
488
|
catch (cause) {
|
|
480
489
|
if (signal.aborted ||
|
|
@@ -514,38 +523,61 @@ export function createPrivateLocalModelSource(models, providerId, modelId, provi
|
|
|
514
523
|
modelId: selected.id,
|
|
515
524
|
modelName: selected.name,
|
|
516
525
|
credential: { kind: auth?.type ?? "missing", source },
|
|
526
|
+
reasoning,
|
|
517
527
|
label: `Shiitake · ${selected.name} (${reasoning ?? "no reasoning"}) · ${source}`,
|
|
518
528
|
});
|
|
519
529
|
},
|
|
520
|
-
//
|
|
521
|
-
|
|
530
|
+
// Observe the same explicit layers against last-known registry facts.
|
|
531
|
+
// Catalog presence alone cannot identify the signed-in fallback: Pi's
|
|
532
|
+
// availability queries check auth. Leave that choice unknown here.
|
|
533
|
+
describe: ({ signal, preference }) => {
|
|
522
534
|
signal.throwIfAborted();
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
};
|
|
535
|
+
const choice = resolveKnownLocalStartupChoice({
|
|
536
|
+
session: preference,
|
|
537
|
+
agent: agentPreference(),
|
|
538
|
+
});
|
|
539
|
+
const selected = choice === null
|
|
540
|
+
? undefined
|
|
541
|
+
: models.getModel(choice.preference.providerId, choice.preference.modelId);
|
|
542
|
+
const contextWindow = selected?.contextWindow;
|
|
543
|
+
return Promise.resolve({
|
|
544
|
+
contextWindow: contextWindow !== undefined && contextWindow > 0
|
|
545
|
+
? contextWindow
|
|
546
|
+
: null,
|
|
547
|
+
});
|
|
527
548
|
},
|
|
528
549
|
acquire: async ({ signal, preference }) => {
|
|
529
550
|
signal.throwIfAborted();
|
|
530
|
-
const { selected, reasoning: selectedReasoning } = await
|
|
551
|
+
const { selected, reasoning: selectedReasoning } = await prepareSelection(preference, signal);
|
|
531
552
|
await assertAuthenticated(selected.provider, signal);
|
|
553
|
+
const turn = await acquireSubscriptionTurn(models, selected.provider, signal);
|
|
554
|
+
// One acquired door owns at most its latest failure. Identity ties the
|
|
555
|
+
// description to the exact returned message, not to a provider name or
|
|
556
|
+
// prose that any SDK can supply.
|
|
557
|
+
let modelFailure;
|
|
532
558
|
return Promise.resolve(Object.freeze({
|
|
533
559
|
model: selected,
|
|
560
|
+
retryable: (message) => !signal.aborted &&
|
|
561
|
+
message.stopReason === "error" &&
|
|
562
|
+
modelFailure?.message === message &&
|
|
563
|
+
modelFailure.retryable,
|
|
564
|
+
failureReason: (message) => modelFailure?.message === message ? modelFailure.reason : undefined,
|
|
534
565
|
stream: (selected, context, options) => {
|
|
566
|
+
modelFailure = undefined;
|
|
535
567
|
const streamOptions = { ...options };
|
|
536
568
|
const reasoning = options?.reasoning ?? selectedReasoning;
|
|
537
569
|
if (reasoning !== undefined)
|
|
538
570
|
streamOptions.reasoning = reasoning;
|
|
571
|
+
const provider = observeProviderStatus(streamOptions);
|
|
539
572
|
const events = createAssistantMessageEventStream();
|
|
540
573
|
const failure = (usage, aborted, upstream) => {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
return {
|
|
574
|
+
const evidence = {
|
|
575
|
+
refusal: turn.refusal(),
|
|
576
|
+
upstream,
|
|
577
|
+
rejection: provider.rejection(),
|
|
578
|
+
};
|
|
579
|
+
const message = modelFailureMessage(selected.provider, evidence);
|
|
580
|
+
const result = {
|
|
549
581
|
role: "assistant",
|
|
550
582
|
content: [],
|
|
551
583
|
api: selected.api,
|
|
@@ -556,11 +588,20 @@ export function createPrivateLocalModelSource(models, providerId, modelId, provi
|
|
|
556
588
|
timestamp: Date.now(),
|
|
557
589
|
errorMessage: aborted ? "Run cancelled" : message,
|
|
558
590
|
};
|
|
591
|
+
if (!aborted)
|
|
592
|
+
modelFailure = {
|
|
593
|
+
message: result,
|
|
594
|
+
reason: message,
|
|
595
|
+
retryable: modelFailureRetryable(evidence),
|
|
596
|
+
};
|
|
597
|
+
return result;
|
|
559
598
|
};
|
|
560
599
|
void (async () => {
|
|
561
600
|
try {
|
|
562
|
-
const upstream = models.streamSimple(selected, context, {
|
|
601
|
+
const upstream = turn.models.streamSimple(selected, context, {
|
|
563
602
|
...streamOptions,
|
|
603
|
+
fetch: provider.fetch,
|
|
604
|
+
onProviderStreamEvent: provider.onProviderStreamEvent,
|
|
564
605
|
sessionId: providerCacheKey,
|
|
565
606
|
transport: options?.transport ?? "auto",
|
|
566
607
|
});
|
|
@@ -601,13 +642,13 @@ export function createPrivateLocalModelSource(models, providerId, modelId, provi
|
|
|
601
642
|
})();
|
|
602
643
|
return events;
|
|
603
644
|
},
|
|
604
|
-
release:
|
|
645
|
+
release: turn.release,
|
|
605
646
|
}));
|
|
606
647
|
},
|
|
607
648
|
});
|
|
608
649
|
}
|
|
609
650
|
/** One local Computer source, resolving its PATH and process scope per Session. */
|
|
610
|
-
function createPrivateLocalComputerSource(root, port, sessionPath, processes) {
|
|
651
|
+
function createPrivateLocalComputerSource(root, port, sessionPath, processes, home, sandbox) {
|
|
611
652
|
const id = "local-computer";
|
|
612
653
|
return {
|
|
613
654
|
id,
|
|
@@ -617,7 +658,7 @@ function createPrivateLocalComputerSource(root, port, sessionPath, processes) {
|
|
|
617
658
|
throw new Error("Local Computer requires a Session");
|
|
618
659
|
const path = sessionPath(sessionId) ??
|
|
619
660
|
capturePrivateLocalExecPath(process.env.PATH ?? "/usr/bin:/bin", process.cwd());
|
|
620
|
-
const selected = await createPrivateInvocationComputer(root, path, processes.scope(sessionId));
|
|
661
|
+
const selected = await createPrivateInvocationComputer(root, path, processes.scope(sessionId), home, sandbox);
|
|
621
662
|
return Knapsack.computer({
|
|
622
663
|
id,
|
|
623
664
|
name: "local",
|
|
@@ -642,10 +683,11 @@ storeDirectory,
|
|
|
642
683
|
* This Agent's sub-agent record: its Session-bound Agent-control tools and
|
|
643
684
|
* the view's facts. Present when it can direct other Agents.
|
|
644
685
|
*/
|
|
645
|
-
subagents) {
|
|
686
|
+
subagents, configuration, configRoot = capturePrivateLocalConfigRoot(), sandbox) {
|
|
687
|
+
const forms = createMcpFormSession();
|
|
646
688
|
const root = await realpath(resolve(invocationRoot));
|
|
647
|
-
const port = await createPrivateInvocationFilesPort(root);
|
|
648
|
-
const processes = createPrivateLocalComputerProcesses(root);
|
|
689
|
+
const port = await createPrivateInvocationFilesPort(root, undefined, sandbox);
|
|
690
|
+
const processes = createPrivateLocalComputerProcesses(root, configRoot.home);
|
|
649
691
|
const computerSources = sessionPath === undefined
|
|
650
692
|
? [
|
|
651
693
|
Knapsack.computer({
|
|
@@ -656,60 +698,110 @@ subagents) {
|
|
|
656
698
|
port,
|
|
657
699
|
pathInstructions: localPathInstructions(root),
|
|
658
700
|
},
|
|
659
|
-
computer: await createPrivateInvocationComputer(root, undefined, processes.scope("invocation")),
|
|
701
|
+
computer: await createPrivateInvocationComputer(root, undefined, processes.scope("invocation"), configRoot.home, sandbox?.launch),
|
|
660
702
|
}),
|
|
661
703
|
]
|
|
662
|
-
: [
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
704
|
+
: [
|
|
705
|
+
createPrivateLocalComputerSource(root, port, sessionPath, processes, configRoot.home, sandbox?.launch),
|
|
706
|
+
];
|
|
707
|
+
const installed = configuration?.installed ??
|
|
708
|
+
(storeDirectory === undefined
|
|
709
|
+
? { roster: [], grants: [], skills: [], diagnostics: [] }
|
|
710
|
+
: await localExtensions({ home: configRoot.home }).agent(storeDirectory, root));
|
|
711
|
+
function configure(installed, connection, clientPath) {
|
|
712
|
+
const base = botanicalExtensionsWithSkills(createLocalSkillSource({
|
|
713
|
+
cwd: root,
|
|
714
|
+
home: configRoot.home,
|
|
715
|
+
extensions: installed.skills,
|
|
716
|
+
}));
|
|
717
|
+
for (const diagnostic of installed.diagnostics)
|
|
718
|
+
logEvent("local-extension-load", diagnostic.error);
|
|
719
|
+
const archive = storeDirectory === undefined
|
|
720
|
+
? { roster: [], grants: [] }
|
|
721
|
+
: localArchiveHandoff(storeDirectory, installed.grants);
|
|
722
|
+
const extensions = {
|
|
723
|
+
roster: [
|
|
724
|
+
...base.roster,
|
|
725
|
+
forms.extension,
|
|
726
|
+
homeExtension({
|
|
727
|
+
computers: {
|
|
728
|
+
read: () => [
|
|
729
|
+
{ name: "local", attachmentId: "local-computer", port },
|
|
730
|
+
],
|
|
731
|
+
},
|
|
732
|
+
}),
|
|
733
|
+
...(subagents === undefined
|
|
734
|
+
? []
|
|
735
|
+
: [subagentsExtension({ source: subagents.source })]),
|
|
736
|
+
...archive.roster,
|
|
737
|
+
...installed.roster,
|
|
738
|
+
],
|
|
739
|
+
grants: [
|
|
740
|
+
...base.grants,
|
|
741
|
+
{
|
|
742
|
+
id: forms.extension.manifest.id,
|
|
743
|
+
capabilities: forms.extension.manifest.requests,
|
|
675
744
|
},
|
|
745
|
+
homeGrant,
|
|
746
|
+
...(subagents === undefined ? [] : [subagentsGrant]),
|
|
747
|
+
...archive.grants,
|
|
748
|
+
...installed.grants,
|
|
749
|
+
],
|
|
750
|
+
};
|
|
751
|
+
return Object.freeze({
|
|
752
|
+
extensions,
|
|
753
|
+
tools: Object.freeze({
|
|
754
|
+
execution: Object.freeze({ timeoutMs: 900_000 }),
|
|
755
|
+
sources: Object.freeze([
|
|
756
|
+
...(agentControl === undefined
|
|
757
|
+
? []
|
|
758
|
+
: [subagents?.control ?? createAgentControlSource(agentControl)]),
|
|
759
|
+
...(clientPath === undefined
|
|
760
|
+
? computerSources
|
|
761
|
+
: [
|
|
762
|
+
createPrivateLocalComputerSource(root, port, () => clientPath, processes, configRoot.home, sandbox?.launch),
|
|
763
|
+
]),
|
|
764
|
+
...(connection === undefined
|
|
765
|
+
? []
|
|
766
|
+
: [createPrivateFungiMcpConnection(connection)]),
|
|
767
|
+
]),
|
|
768
|
+
sandbox: createPrivateWorkerdSandbox(),
|
|
676
769
|
}),
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
...(subagents === undefined ? [] : [subagentsGrant]),
|
|
686
|
-
...installed.grants,
|
|
687
|
-
],
|
|
688
|
-
};
|
|
689
|
-
return Object.freeze({
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
const initial = configure(installed, configuration === undefined ? fungiMcp : configuration.fungiMcp, configuration?.clientPath);
|
|
773
|
+
if (initial.tools === undefined)
|
|
774
|
+
throw new Error("Local tools are unavailable");
|
|
775
|
+
return {
|
|
776
|
+
...initial,
|
|
777
|
+
tools: initial.tools,
|
|
690
778
|
port,
|
|
691
|
-
extensions,
|
|
692
779
|
processes,
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
? []
|
|
698
|
-
: [subagents?.control ?? createAgentControlSource(agentControl)]),
|
|
699
|
-
...computerSources,
|
|
700
|
-
...(fungiMcp === undefined
|
|
701
|
-
? []
|
|
702
|
-
: [createPrivateFungiMcpConnection(fungiMcp)]),
|
|
703
|
-
]),
|
|
704
|
-
sandbox: createPrivateWorkerdSandbox(),
|
|
705
|
-
}),
|
|
706
|
-
});
|
|
780
|
+
configure,
|
|
781
|
+
bindSession: forms.bindSession,
|
|
782
|
+
presentApproval: forms.presentApproval,
|
|
783
|
+
};
|
|
707
784
|
}
|
|
708
|
-
/** Shiitake's public Node runtime host, reporting failed passes to local logs. */
|
|
709
785
|
function localRuntimeOwner() {
|
|
710
|
-
|
|
786
|
+
const host = nodeRuntimeHost((cause) => {
|
|
711
787
|
logEvent("shiitake_reconciliation_failed", cause);
|
|
712
788
|
});
|
|
789
|
+
let pendingWork;
|
|
790
|
+
return Object.freeze({
|
|
791
|
+
...host,
|
|
792
|
+
runtime: Object.freeze({
|
|
793
|
+
bind: (input) => {
|
|
794
|
+
pendingWork = input.pendingWork;
|
|
795
|
+
return host.runtime.bind(input);
|
|
796
|
+
},
|
|
797
|
+
}),
|
|
798
|
+
settled: async () => {
|
|
799
|
+
if (pendingWork === undefined)
|
|
800
|
+
return false;
|
|
801
|
+
const work = await pendingWork();
|
|
802
|
+
return !work.pending && work.nextWakeAt === null;
|
|
803
|
+
},
|
|
804
|
+
});
|
|
713
805
|
}
|
|
714
806
|
/** Event-loop adaptation for the one local Shiitake owner; it has no queue policy. */
|
|
715
807
|
export function createPrivateLocalRuntimeHost() {
|
|
@@ -756,6 +848,11 @@ export async function closePrivateLocalResources(resources, initiatingFailure) {
|
|
|
756
848
|
if (failure !== undefined)
|
|
757
849
|
failures.push(failure);
|
|
758
850
|
}
|
|
851
|
+
if (resources.sandbox !== undefined) {
|
|
852
|
+
const failure = await captureCloseFailure(() => Promise.resolve().then(() => resources.sandbox?.close()), "Local sandbox close failed");
|
|
853
|
+
if (failure !== undefined)
|
|
854
|
+
failures.push(failure);
|
|
855
|
+
}
|
|
759
856
|
const runtimeFailure = await captureCloseFailure(resources.runtime.close, "Local runtime close failed");
|
|
760
857
|
if (runtimeFailure !== undefined) {
|
|
761
858
|
failures.push(runtimeFailure);
|
|
@@ -784,13 +881,23 @@ export async function closePrivateLocalResources(resources, initiatingFailure) {
|
|
|
784
881
|
if (failures.length > 0)
|
|
785
882
|
throw aggregateLocalFailures(failures, "Local host close failed");
|
|
786
883
|
}
|
|
884
|
+
/** Adapt Core's store refusal without reclassifying other opening failures. */
|
|
885
|
+
function localHostOpeningFailure(cause, databasePath) {
|
|
886
|
+
if (cause instanceof ShiitakeError &&
|
|
887
|
+
cause.boundary === "store" &&
|
|
888
|
+
cause.cause === "incompatible")
|
|
889
|
+
return new LocalStoreSchemaError(databasePath, { cause });
|
|
890
|
+
return asLocalFailure(cause, "Local host opening failed");
|
|
891
|
+
}
|
|
787
892
|
/**
|
|
788
893
|
* Open one private file-backed local Shiitake host and its fixed Session.
|
|
789
894
|
*
|
|
790
895
|
* The returned close owns agent, main database, and sidecar release in that
|
|
791
896
|
* order. It is the only lifecycle the current terminal caller needs to learn.
|
|
792
897
|
*/
|
|
793
|
-
export async function openPrivateLocalRuntimeHost(input,
|
|
898
|
+
export async function openPrivateLocalRuntimeHost(input, suppliedModels) {
|
|
899
|
+
const configRoot = input.configRoot ?? capturePrivateLocalConfigRoot();
|
|
900
|
+
const models = suppliedModels ?? createPrivateModels(configRoot.credentialPath);
|
|
794
901
|
const root = await realpath(resolve(input.invocationRoot));
|
|
795
902
|
await ensurePrivateLocalGitExclude(root);
|
|
796
903
|
const selectedProviderId = input.providerId;
|
|
@@ -810,18 +917,43 @@ export async function openPrivateLocalRuntimeHost(input, models = createPrivateM
|
|
|
810
917
|
let database;
|
|
811
918
|
let agent;
|
|
812
919
|
let processes;
|
|
920
|
+
let sandbox;
|
|
813
921
|
const sessionPaths = new Map();
|
|
922
|
+
let usesServiceConfiguration = input.configuration !== undefined;
|
|
814
923
|
try {
|
|
815
924
|
lease = acquireNodeStoreLease(paths.sidecarPath);
|
|
816
925
|
// Claim only after the lease: a rejected opener must not clean up its live owner.
|
|
817
926
|
providerCacheKey = createHash("sha256")
|
|
818
927
|
.update(JSON.stringify(["fungi-local", paths.databasePath, initialSessionId]))
|
|
819
928
|
.digest("hex");
|
|
820
|
-
database =
|
|
929
|
+
database = openPrivateWritableSqliteDatabase(paths.databasePath, 0);
|
|
821
930
|
database.exec("PRAGMA foreign_keys = ON");
|
|
822
|
-
const agentId = ensureLocalAgentId(database, paths.agentId);
|
|
931
|
+
const agentId = ensureLocalAgentId(database, paths.databasePath, paths.agentId, input.name, input.createdBy, input.folder, input.requireSandbox);
|
|
932
|
+
const sandboxDatabase = database;
|
|
933
|
+
const readSandboxRequirement = () => z
|
|
934
|
+
.union([z.literal(0), z.literal(1)])
|
|
935
|
+
.parse(sandboxDatabase
|
|
936
|
+
.prepare("SELECT require_sandbox FROM private_local_identity WHERE singleton = 1")
|
|
937
|
+
.get()?.require_sandbox) === 1;
|
|
938
|
+
const selectedFolder = resolveAgentFolder(root, localAgentFolderSchema.parse(database
|
|
939
|
+
.prepare("SELECT folder FROM private_local_identity WHERE singleton = 1")
|
|
940
|
+
.get()?.folder));
|
|
941
|
+
const sandboxOwner = createLocalSandboxControls({
|
|
942
|
+
project: root,
|
|
943
|
+
folder: selectedFolder.folder,
|
|
944
|
+
required: readSandboxRequirement,
|
|
945
|
+
});
|
|
946
|
+
sandbox = sandboxOwner;
|
|
947
|
+
// Both constructors below can resume stored commands before returning.
|
|
948
|
+
// Admission must precede tools, configuration hooks and Agent construction.
|
|
949
|
+
const name = z
|
|
950
|
+
.string()
|
|
951
|
+
.nullable()
|
|
952
|
+
.parse(database
|
|
953
|
+
.prepare("SELECT name FROM private_local_identity WHERE singleton = 1")
|
|
954
|
+
.get()?.name) ?? agentId;
|
|
823
955
|
const modelDatabase = database;
|
|
824
|
-
modelDatabase
|
|
956
|
+
ensureAgentDefaultModelTable(modelDatabase);
|
|
825
957
|
let initialModel = selectedProviderId === undefined || selectedModelId === undefined
|
|
826
958
|
? null
|
|
827
959
|
: parseModelPreference({
|
|
@@ -833,16 +965,8 @@ export async function openPrivateLocalRuntimeHost(input, models = createPrivateM
|
|
|
833
965
|
...initialModel,
|
|
834
966
|
reasoning: input.reasoning,
|
|
835
967
|
});
|
|
836
|
-
modelDatabase
|
|
837
|
-
|
|
838
|
-
.run(JSON.stringify(initialModel));
|
|
839
|
-
const readAgentModel = () => {
|
|
840
|
-
const row = modelDatabase
|
|
841
|
-
.prepare("SELECT model FROM private_local_model_settings WHERE singleton = 1")
|
|
842
|
-
.get();
|
|
843
|
-
return parseModelPreferenceJson(row?.model);
|
|
844
|
-
};
|
|
845
|
-
const modelSettings = createLocalModelSettings(input.modelSettingsPath);
|
|
968
|
+
seedAgentDefaultModel(modelDatabase, initialModel);
|
|
969
|
+
const readAgentModel = () => readAgentDefaultModel(modelDatabase);
|
|
846
970
|
const store = createPrivateSqliteOwner(database);
|
|
847
971
|
let acceptMail;
|
|
848
972
|
let readSettlement;
|
|
@@ -855,28 +979,39 @@ export async function openPrivateLocalRuntimeHost(input, models = createPrivateM
|
|
|
855
979
|
storage: store,
|
|
856
980
|
directory: input.agentControl,
|
|
857
981
|
routing: input.mail,
|
|
982
|
+
meshAdmission: input.meshAdmission === undefined
|
|
983
|
+
? undefined
|
|
984
|
+
: async (recipientAgentId) => input.meshAdmission?.(agentId, recipientAgentId),
|
|
858
985
|
accept: (mail, meta, reply) => {
|
|
859
986
|
if (acceptMail === undefined)
|
|
860
987
|
return Promise.reject(new Error("Local mail ingress is unavailable"));
|
|
861
988
|
return acceptMail(mail, meta, reply);
|
|
862
989
|
},
|
|
863
990
|
});
|
|
864
|
-
let
|
|
991
|
+
let projectDirectory;
|
|
865
992
|
if (input.agentControl !== undefined) {
|
|
866
|
-
|
|
993
|
+
projectDirectory = {
|
|
867
994
|
...input.agentControl,
|
|
868
995
|
models: (signal) => listOfferedLocalModels(models, signal),
|
|
869
996
|
};
|
|
870
997
|
if (delegation !== undefined)
|
|
871
|
-
|
|
998
|
+
projectDirectory = {
|
|
999
|
+
...projectDirectory,
|
|
1000
|
+
send: delegation.send,
|
|
1001
|
+
recall: delegation.recall,
|
|
1002
|
+
};
|
|
872
1003
|
}
|
|
873
|
-
const subagents =
|
|
1004
|
+
const subagents = projectDirectory === undefined
|
|
874
1005
|
? undefined
|
|
875
|
-
:
|
|
876
|
-
|
|
1006
|
+
: createPrivateLocalAgentControl({
|
|
1007
|
+
agentId,
|
|
1008
|
+
directory: projectDirectory,
|
|
1009
|
+
storage: store,
|
|
1010
|
+
});
|
|
1011
|
+
const localTools = await createPrivateLocalTools(selectedFolder.absolute, input.fungiMcp, subagents?.directory, (sessionId) => sessionPaths.get(sessionId), dirname(paths.databasePath), subagents, await input.configuration?.(dirname(paths.databasePath)), configRoot, sandboxOwner);
|
|
877
1012
|
processes = localTools.processes;
|
|
878
1013
|
const sessionProcesses = processes;
|
|
879
|
-
const modelSource = createPrivateLocalModelSource(models, selectedProviderId, selectedModelId, providerCacheKey, input.reasoning, { agent: readAgentModel
|
|
1014
|
+
const modelSource = createPrivateLocalModelSource(models, selectedProviderId, selectedModelId, providerCacheKey, input.reasoning, { agent: readAgentModel });
|
|
880
1015
|
const hostOptions = {
|
|
881
1016
|
store,
|
|
882
1017
|
runtime: runtime.runtime,
|
|
@@ -899,17 +1034,27 @@ export async function openPrivateLocalRuntimeHost(input, models = createPrivateM
|
|
|
899
1034
|
readSettlement = host.readSettlement;
|
|
900
1035
|
}
|
|
901
1036
|
const sessionOwner = agent;
|
|
902
|
-
const
|
|
903
|
-
const
|
|
1037
|
+
const setAgentModel = async (input, signal) => {
|
|
1038
|
+
const preference = parseModelPreference(input);
|
|
1039
|
+
if (preference === null)
|
|
1040
|
+
throw new ShiitakeError("model", "invalid", "An Agent model is required");
|
|
1041
|
+
await modelSource.assertReady(preference, signal);
|
|
1042
|
+
signal.throwIfAborted();
|
|
1043
|
+
writeAgentDefaultModel(modelDatabase, preference);
|
|
1044
|
+
};
|
|
1045
|
+
// /model in a chat sets this Agent's default and its thinking level; a
|
|
1046
|
+
// Session override is cleared so the running Session follows the default.
|
|
1047
|
+
const getSession = (sessionId, attachment) => {
|
|
1048
|
+
const session = localTools.bindSession(sessionId, sessionOwner.session(sessionId), attachment);
|
|
904
1049
|
return {
|
|
905
1050
|
...session,
|
|
906
1051
|
modelPreference: {
|
|
907
|
-
read: session.modelPreference.read,
|
|
1052
|
+
read: async () => (await session.modelPreference.read()) ?? readAgentModel(),
|
|
908
1053
|
set: async (input) => {
|
|
909
1054
|
const preference = parseModelPreference(input);
|
|
910
1055
|
if (preference !== null)
|
|
911
|
-
await
|
|
912
|
-
await session.modelPreference.set(
|
|
1056
|
+
await setAgentModel(preference, new AbortController().signal);
|
|
1057
|
+
await session.modelPreference.set(null);
|
|
913
1058
|
},
|
|
914
1059
|
},
|
|
915
1060
|
};
|
|
@@ -918,18 +1063,23 @@ export async function openPrivateLocalRuntimeHost(input, models = createPrivateM
|
|
|
918
1063
|
let closing;
|
|
919
1064
|
return Object.freeze({
|
|
920
1065
|
agentId,
|
|
1066
|
+
name,
|
|
921
1067
|
databasePath: paths.databasePath,
|
|
922
1068
|
session,
|
|
923
1069
|
getSession,
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1070
|
+
presentApproval: localTools.presentApproval,
|
|
1071
|
+
setModel: setAgentModel,
|
|
1072
|
+
setRequireSandbox: (required) => {
|
|
1073
|
+
sandboxDatabase
|
|
1074
|
+
.prepare("UPDATE private_local_identity SET require_sandbox = ? WHERE singleton = 1")
|
|
1075
|
+
.run(required ? 1 : 0);
|
|
1076
|
+
sandboxOwner.assertReady();
|
|
1077
|
+
},
|
|
1078
|
+
sandboxStatus: sandboxOwner.status,
|
|
1079
|
+
setArchived: (archived) => {
|
|
930
1080
|
modelDatabase
|
|
931
|
-
.prepare("UPDATE
|
|
932
|
-
.run(
|
|
1081
|
+
.prepare("UPDATE private_local_identity SET archived = ? WHERE singleton = 1")
|
|
1082
|
+
.run(archived ? 1 : 0);
|
|
933
1083
|
},
|
|
934
1084
|
assertModelReady: async (sessionId, signal) => {
|
|
935
1085
|
const preference = await sessionOwner
|
|
@@ -939,6 +1089,8 @@ export async function openPrivateLocalRuntimeHost(input, models = createPrivateM
|
|
|
939
1089
|
},
|
|
940
1090
|
bindSessionPath: (sessionId, path) => {
|
|
941
1091
|
const selected = parsePrivateLocalExecPath(path);
|
|
1092
|
+
if (usesServiceConfiguration)
|
|
1093
|
+
return true;
|
|
942
1094
|
const previous = sessionPaths.get(sessionId);
|
|
943
1095
|
if (previous !== undefined && previous !== selected)
|
|
944
1096
|
return false;
|
|
@@ -961,16 +1113,53 @@ export async function openPrivateLocalRuntimeHost(input, models = createPrivateM
|
|
|
961
1113
|
return deleted;
|
|
962
1114
|
},
|
|
963
1115
|
extensions: sessionOwner.extensions,
|
|
1116
|
+
prepareReload: async (configuration) => {
|
|
1117
|
+
const prepared = await sessionOwner.prepareConfiguration(localTools.configure(configuration.installed, configuration.fungiMcp, configuration.clientPath));
|
|
1118
|
+
return {
|
|
1119
|
+
discard: prepared.discard,
|
|
1120
|
+
commit: () => {
|
|
1121
|
+
prepared.commit();
|
|
1122
|
+
usesServiceConfiguration = true;
|
|
1123
|
+
},
|
|
1124
|
+
};
|
|
1125
|
+
},
|
|
964
1126
|
acceptMail: (mail) => delegation === undefined
|
|
965
1127
|
? Promise.reject(new Error("Local mail ingress is unavailable"))
|
|
966
1128
|
: delegation.receive(mail),
|
|
1129
|
+
pendingDelegation: () => delegation === undefined
|
|
1130
|
+
? Promise.resolve(undefined)
|
|
1131
|
+
: delegation.outstanding(),
|
|
967
1132
|
readSettlement: (sessionId, commandId) => readSettlement === undefined
|
|
968
1133
|
? Promise.reject(new Error("Local settlement reads are unavailable"))
|
|
969
1134
|
: readSettlement(sessionId, commandId),
|
|
1135
|
+
idle: async () => {
|
|
1136
|
+
if (sessionProcesses.running())
|
|
1137
|
+
return false;
|
|
1138
|
+
if (!(await runtime.settled()))
|
|
1139
|
+
return false;
|
|
1140
|
+
// Delegated work still owed to this Agent is not read here: that read
|
|
1141
|
+
// reopens each child, and the child's reply mail reopens this Agent.
|
|
1142
|
+
let after;
|
|
1143
|
+
do {
|
|
1144
|
+
const page = await sessionOwner.listSessions(after);
|
|
1145
|
+
for (const row of page.sessions)
|
|
1146
|
+
if (row.state === "running" || row.state === "needs_you")
|
|
1147
|
+
return false;
|
|
1148
|
+
after = page.next;
|
|
1149
|
+
} while (after !== undefined);
|
|
1150
|
+
// Reconciliation enqueues replies before advancing its settled cursor.
|
|
1151
|
+
// Observe mail last: an earlier empty outbox can become retryable mail
|
|
1152
|
+
// while we await settlement or Session reads, especially during stop
|
|
1153
|
+
// when delivery cannot reopen a parent. The owner rechecks admissions
|
|
1154
|
+
// and uses after this observation before forgetting the Agent.
|
|
1155
|
+
return (input.mail === undefined ||
|
|
1156
|
+
!(await hasPrivateLocalPendingMail(modelDatabase)));
|
|
1157
|
+
},
|
|
970
1158
|
close: () => {
|
|
971
1159
|
closing ??= closePrivateLocalResources({
|
|
972
1160
|
agent,
|
|
973
1161
|
processes,
|
|
1162
|
+
sandbox,
|
|
974
1163
|
runtime,
|
|
975
1164
|
database,
|
|
976
1165
|
lease,
|
|
@@ -981,8 +1170,8 @@ export async function openPrivateLocalRuntimeHost(input, models = createPrivateM
|
|
|
981
1170
|
});
|
|
982
1171
|
}
|
|
983
1172
|
catch (cause) {
|
|
984
|
-
const openingFailure =
|
|
985
|
-
await closePrivateLocalResources({ agent, processes, runtime, database, lease, providerCacheKey }, openingFailure);
|
|
1173
|
+
const openingFailure = localHostOpeningFailure(cause, paths.databasePath);
|
|
1174
|
+
await closePrivateLocalResources({ agent, processes, sandbox, runtime, database, lease, providerCacheKey }, openingFailure);
|
|
986
1175
|
throw openingFailure;
|
|
987
1176
|
}
|
|
988
1177
|
}
|