@fungi.computer/truffle 0.1.1 → 0.2.1
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,38 +1,112 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
2
|
+
export { sandboxStatusSchema } from "./private-local-sandbox.js";
|
|
3
|
+
/**
|
|
4
|
+
* The client-service wire contract. Bump it with any change an older client
|
|
5
|
+
* cannot read; a running client then asks its window to restart.
|
|
6
|
+
*/
|
|
7
|
+
export declare const privateLocalServiceProtocol = 3;
|
|
8
|
+
/** This build's identity: the packed CLI's content hash, or `source` for a source run. */
|
|
9
|
+
export declare function privateLocalServiceVersion(): string;
|
|
10
|
+
/**
|
|
11
|
+
* The fields every protocol keeps in `service.json`, so any client can
|
|
12
|
+
* identify, replace or stop a service whatever protocol it speaks.
|
|
13
|
+
*/
|
|
14
|
+
declare const serviceLifecycleSchema: z.ZodObject<{
|
|
15
|
+
protocol: z.ZodNumber;
|
|
16
|
+
version: z.ZodString;
|
|
3
17
|
serviceId: z.ZodString & z.ZodType<import("@fungi.computer/cairn").CairnId<"local-service", "service", 1>, string, z.core.$ZodTypeInternals<import("@fungi.computer/cairn").CairnId<"local-service", "service", 1>, string>>;
|
|
18
|
+
pid: z.ZodNumber;
|
|
19
|
+
startedAt: z.ZodString;
|
|
20
|
+
}, z.core.$loose>;
|
|
21
|
+
declare const serviceRecordSchema: z.ZodObject<{
|
|
22
|
+
protocol: z.ZodLiteral<3>;
|
|
4
23
|
baseUrl: z.ZodURL;
|
|
5
|
-
|
|
6
|
-
declare const serviceStatusSchema: z.ZodObject<{
|
|
24
|
+
version: z.ZodString;
|
|
7
25
|
serviceId: z.ZodString & z.ZodType<import("@fungi.computer/cairn").CairnId<"local-service", "service", 1>, string, z.core.$ZodTypeInternals<import("@fungi.computer/cairn").CairnId<"local-service", "service", 1>, string>>;
|
|
8
|
-
|
|
9
|
-
|
|
26
|
+
pid: z.ZodNumber;
|
|
27
|
+
startedAt: z.ZodString;
|
|
10
28
|
}, z.core.$strict>;
|
|
11
29
|
export type ServiceRecord = z.infer<typeof serviceRecordSchema>;
|
|
30
|
+
/** Mandatory live diagnostics; never written into the registration pointer. */
|
|
31
|
+
export declare const liveServiceStatusSchema: z.ZodObject<{
|
|
32
|
+
protocol: z.ZodLiteral<3>;
|
|
33
|
+
baseUrl: z.ZodURL;
|
|
34
|
+
version: z.ZodString;
|
|
35
|
+
serviceId: z.ZodString & z.ZodType<import("@fungi.computer/cairn").CairnId<"local-service", "service", 1>, string, z.core.$ZodTypeInternals<import("@fungi.computer/cairn").CairnId<"local-service", "service", 1>, string>>;
|
|
36
|
+
pid: z.ZodNumber;
|
|
37
|
+
startedAt: z.ZodString;
|
|
38
|
+
diagnostics: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
39
|
+
state: z.ZodLiteral<"active">;
|
|
40
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
41
|
+
state: z.ZodLiteral<"failed">;
|
|
42
|
+
code: z.ZodEnum<{
|
|
43
|
+
"sink-open-failed": "sink-open-failed";
|
|
44
|
+
"sink-write-failed": "sink-write-failed";
|
|
45
|
+
"sink-close-failed": "sink-close-failed";
|
|
46
|
+
}>;
|
|
47
|
+
since: z.ZodISODateTime;
|
|
48
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
49
|
+
state: z.ZodLiteral<"recovery-disabled">;
|
|
50
|
+
code: z.ZodEnum<{
|
|
51
|
+
"read-failed": "read-failed";
|
|
52
|
+
"decode-failed": "decode-failed";
|
|
53
|
+
}>;
|
|
54
|
+
since: z.ZodISODateTime;
|
|
55
|
+
}, z.core.$strict>], "state">;
|
|
56
|
+
}, z.core.$strict>;
|
|
57
|
+
export type LiveServiceStatus = z.infer<typeof liveServiceStatusSchema>;
|
|
58
|
+
/**
|
|
59
|
+
* What `service.json` names: a service of this protocol, a service of another
|
|
60
|
+
* protocol (identified only by its lifecycle fields), or a file no protocol
|
|
61
|
+
* wrote.
|
|
62
|
+
*/
|
|
63
|
+
export type ServiceRegistration = Readonly<{
|
|
64
|
+
kind: "current";
|
|
65
|
+
record: ServiceRecord;
|
|
66
|
+
}> | Readonly<{
|
|
67
|
+
kind: "other-protocol";
|
|
68
|
+
lifecycle: z.infer<typeof serviceLifecycleSchema>;
|
|
69
|
+
}> | Readonly<{
|
|
70
|
+
kind: "unidentified";
|
|
71
|
+
}>;
|
|
12
72
|
export declare const selectionSchema: z.ZodObject<{
|
|
73
|
+
uiAttachmentLifetime: z.ZodOptional<z.ZodEnum<{
|
|
74
|
+
watch: "watch";
|
|
75
|
+
connection: "connection";
|
|
76
|
+
}>>;
|
|
13
77
|
invocationRoot: z.ZodString;
|
|
14
78
|
clientPath: z.ZodString;
|
|
79
|
+
requireSandbox: z.ZodOptional<z.ZodBoolean>;
|
|
15
80
|
databasePath: z.ZodOptional<z.ZodString>;
|
|
16
81
|
sessionId: z.ZodOptional<z.ZodString>;
|
|
17
82
|
fungiMcp: z.ZodOptional<z.ZodObject<{
|
|
18
83
|
endpoint: z.ZodPipe<z.ZodURL, z.ZodString>;
|
|
84
|
+
authentication: z.ZodOptional<z.ZodLiteral<"oyster-key">>;
|
|
19
85
|
}, z.core.$strict>>;
|
|
20
86
|
}, z.core.$strict>;
|
|
21
87
|
export declare const sessionCreationSchema: z.ZodObject<{
|
|
88
|
+
uiAttachmentLifetime: z.ZodOptional<z.ZodEnum<{
|
|
89
|
+
watch: "watch";
|
|
90
|
+
connection: "connection";
|
|
91
|
+
}>>;
|
|
22
92
|
invocationRoot: z.ZodString;
|
|
23
93
|
clientPath: z.ZodString;
|
|
94
|
+
requireSandbox: z.ZodOptional<z.ZodBoolean>;
|
|
24
95
|
databasePath: z.ZodString;
|
|
25
96
|
requestId: z.ZodString;
|
|
26
97
|
fungiMcp: z.ZodOptional<z.ZodObject<{
|
|
27
98
|
endpoint: z.ZodPipe<z.ZodURL, z.ZodString>;
|
|
99
|
+
authentication: z.ZodOptional<z.ZodLiteral<"oyster-key">>;
|
|
28
100
|
}, z.core.$strict>>;
|
|
29
101
|
}, z.core.$strict>;
|
|
30
102
|
export declare const sessionInspectionSchema: z.ZodObject<{
|
|
103
|
+
databasePath: z.ZodOptional<z.ZodString>;
|
|
104
|
+
invocationRoot: z.ZodString;
|
|
31
105
|
fungiMcp: z.ZodOptional<z.ZodObject<{
|
|
32
106
|
endpoint: z.ZodPipe<z.ZodURL, z.ZodString>;
|
|
107
|
+
authentication: z.ZodOptional<z.ZodLiteral<"oyster-key">>;
|
|
33
108
|
}, z.core.$strict>>;
|
|
34
|
-
|
|
35
|
-
databasePath: z.ZodOptional<z.ZodString>;
|
|
109
|
+
requireSandbox: z.ZodOptional<z.ZodBoolean>;
|
|
36
110
|
sessionId: z.ZodString;
|
|
37
111
|
limit: z.ZodNumber;
|
|
38
112
|
}, z.core.$strict>;
|
|
@@ -40,11 +114,41 @@ export declare const selectedSchema: z.ZodObject<{
|
|
|
40
114
|
agentId: z.ZodString;
|
|
41
115
|
databasePath: z.ZodString;
|
|
42
116
|
sessionId: z.ZodString;
|
|
43
|
-
routePath: z.
|
|
117
|
+
routePath: z.ZodTemplateLiteral<`/agents/${string}`>;
|
|
118
|
+
}, z.core.$strict>;
|
|
119
|
+
export declare const discoverRequestSchema: z.ZodObject<{
|
|
120
|
+
invocationRoot: z.ZodString;
|
|
121
|
+
archived: z.ZodDefault<z.ZodBoolean>;
|
|
122
|
+
}, z.core.$strict>;
|
|
123
|
+
export declare const archiveRequestSchema: z.ZodObject<{
|
|
124
|
+
invocationRoot: z.ZodString;
|
|
125
|
+
agentId: z.ZodString;
|
|
126
|
+
archived: z.ZodBoolean;
|
|
127
|
+
}, z.core.$strict>;
|
|
128
|
+
/** The 409 answer to an archive the Local service refuses. */
|
|
129
|
+
export declare const archiveRefusalSchema: z.ZodObject<{
|
|
130
|
+
code: z.ZodLiteral<"agent_archive_refused">;
|
|
131
|
+
reason: z.ZodEnum<{
|
|
132
|
+
pending: "pending";
|
|
133
|
+
running: "running";
|
|
134
|
+
archived: "archived";
|
|
135
|
+
not_creator: "not_creator";
|
|
136
|
+
self: "self";
|
|
137
|
+
unreadable: "unreadable";
|
|
138
|
+
}>;
|
|
139
|
+
error: z.ZodString;
|
|
44
140
|
}, z.core.$strict>;
|
|
45
141
|
export declare const sessionDiscoverySchema: z.ZodObject<{
|
|
46
142
|
root: z.ZodString;
|
|
47
|
-
|
|
143
|
+
agents: z.ZodArray<z.ZodObject<{
|
|
144
|
+
agentId: z.ZodString;
|
|
145
|
+
name: z.ZodOptional<z.ZodString>;
|
|
146
|
+
createdBy: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
147
|
+
unreadable: z.ZodOptional<z.ZodString>;
|
|
148
|
+
databasePath: z.ZodString;
|
|
149
|
+
sessionCount: z.ZodNumber;
|
|
150
|
+
updatedAt: z.ZodOptional<z.ZodNumber>;
|
|
151
|
+
}, z.core.$strict>>;
|
|
48
152
|
sessions: z.ZodArray<z.ZodObject<{
|
|
49
153
|
databasePath: z.ZodString;
|
|
50
154
|
sessionId: z.ZodString;
|
|
@@ -55,8 +159,8 @@ export declare const sessionDiscoverySchema: z.ZodObject<{
|
|
|
55
159
|
problems: z.ZodArray<z.ZodObject<{
|
|
56
160
|
kind: z.ZodEnum<{
|
|
57
161
|
corrupt: "corrupt";
|
|
58
|
-
in_use: "in_use";
|
|
59
162
|
unreadable: "unreadable";
|
|
163
|
+
in_use: "in_use";
|
|
60
164
|
}>;
|
|
61
165
|
}, z.core.$strict>>;
|
|
62
166
|
}, z.core.$strict>;
|
|
@@ -74,11 +178,18 @@ export declare const modelChoicesSchema: z.ZodArray<z.ZodObject<{
|
|
|
74
178
|
max: "max";
|
|
75
179
|
}>>;
|
|
76
180
|
}, z.core.$strict>>;
|
|
181
|
+
/** Resolve a read target without creating/chmodding a directory. */
|
|
182
|
+
export declare function serviceDirectoryPath(directory?: string): string;
|
|
77
183
|
export declare function serviceDirectory(directory?: string): Promise<string>;
|
|
78
184
|
export declare const recordPath: (directory: string) => string;
|
|
185
|
+
/** Read `service.json` by its lifecycle fields first, so any protocol's record is named. */
|
|
186
|
+
export declare function readServiceRegistration(directory: string): Promise<ServiceRegistration | undefined>;
|
|
187
|
+
/** The record of a service speaking this client's protocol, if one is registered. */
|
|
79
188
|
export declare function readServiceRecord(directory: string): Promise<ServiceRecord | undefined>;
|
|
80
189
|
/** An older listener must not erase its successor's discovery record. */
|
|
81
190
|
export declare function removeOwnedServiceRecord(directory: string, serviceId: string): Promise<void>;
|
|
191
|
+
/** Remove a `service.json` no protocol wrote; only call it once no owner is live. */
|
|
192
|
+
export declare function removeUnidentifiedServiceRecord(directory: string): Promise<void>;
|
|
82
193
|
/** Legacy owners have no SQLite lease, so their file lock must not be stolen. */
|
|
83
194
|
export declare function assertNoUntrackedServiceOwner(directory: string): Promise<void>;
|
|
84
195
|
/** Startup and shutdown serialize with the native listener and lifetime owner lock. */
|
|
@@ -87,10 +198,10 @@ export declare function acquireServiceLock(directory: string, name: "startup" |
|
|
|
87
198
|
* Whether a service process holds this directory's lifetime lease. A held
|
|
88
199
|
* lease is a live owner even when it is too busy to answer its handshake.
|
|
89
200
|
*/
|
|
90
|
-
export declare function serviceOwnerIsLive(directory: string): boolean
|
|
201
|
+
export declare function serviceOwnerIsLive(directory: string): Promise<boolean>;
|
|
91
202
|
export type ServiceProbe = Readonly<{
|
|
92
203
|
kind: "running";
|
|
93
|
-
service:
|
|
204
|
+
service: LiveServiceStatus;
|
|
94
205
|
}> | Readonly<{
|
|
95
206
|
kind: "not-running";
|
|
96
207
|
}> | Readonly<{
|
|
@@ -98,4 +209,9 @@ export type ServiceProbe = Readonly<{
|
|
|
98
209
|
}>;
|
|
99
210
|
export declare function readLiveServiceStatus(record: ServiceRecord, timeoutMs?: number): Promise<ServiceProbe>;
|
|
100
211
|
export declare function handshake(record: ServiceRecord): Promise<boolean>;
|
|
101
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Clear the lifetime lock a killed owner left behind. The lease's process is
|
|
214
|
+
* gone, so acquiring the owner lock removes the marked directory;
|
|
215
|
+
* releasing it at once leaves nothing behind. A live owner makes this throw.
|
|
216
|
+
*/
|
|
217
|
+
export declare function clearAbandonedServiceOwner(directory: string): Promise<void>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { fungiMcpSelectionSchema } from "./private-fungi-mcp-selection.js";
|
|
1
|
+
import { fungiMcpSelectionSchema } from "./private-fungi-mcp-selection-schema.js";
|
|
2
|
+
import { localUiAttachmentLifetimeSchema } from "./private-local-ui-attachment.js";
|
|
2
3
|
import { privateLocalExecPathSchema } from "./private-local-exec-path.js";
|
|
3
4
|
import { chmod, mkdir, readFile, realpath, rm, rmdir, stat, writeFile, } from "node:fs/promises";
|
|
4
5
|
import { join, resolve } from "node:path";
|
|
@@ -6,10 +7,43 @@ import { homedir } from "node:os";
|
|
|
6
7
|
import lockfile from "proper-lockfile";
|
|
7
8
|
import { z } from "zod";
|
|
8
9
|
import { localServiceIds } from "./private-local-service-ids.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
10
|
+
import { acquireServiceOwnerLease, readServiceLeaseOwner, } from "./private-local-service-lease.js";
|
|
11
|
+
import { modelReasoningLevels } from "@fungi.computer/shiitake/model-preference";
|
|
12
|
+
import { archiveRefusalReasons } from "@fungi.computer/shiitake/agent-control";
|
|
13
|
+
import { serviceDiagnosticsStatusSchema } from "./private-local-service-log.js";
|
|
14
|
+
export { sandboxStatusSchema } from "./private-local-sandbox.js";
|
|
15
|
+
/**
|
|
16
|
+
* The client-service wire contract. Bump it with any change an older client
|
|
17
|
+
* cannot read; a running client then asks its window to restart.
|
|
18
|
+
*/
|
|
19
|
+
export const privateLocalServiceProtocol = 3;
|
|
20
|
+
/** This build's identity: the packed CLI's content hash, or `source` for a source run. */
|
|
21
|
+
export function privateLocalServiceVersion() {
|
|
22
|
+
return (z
|
|
23
|
+
.string()
|
|
24
|
+
.regex(/^[a-f0-9]{64}$/u)
|
|
25
|
+
.optional()
|
|
26
|
+
.parse(process.env.FUNGI_BUNDLED_BUILD_ID) ?? "source");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The fields every protocol keeps in `service.json`, so any client can
|
|
30
|
+
* identify, replace or stop a service whatever protocol it speaks.
|
|
31
|
+
*/
|
|
32
|
+
const serviceLifecycleSchema = z.looseObject({
|
|
33
|
+
protocol: z.number().int().positive(),
|
|
34
|
+
version: z.string().min(1),
|
|
12
35
|
serviceId: z.string().refine((value) => localServiceIds.service.is(value)),
|
|
36
|
+
/** The listener process. */
|
|
37
|
+
pid: z.number().int().positive(),
|
|
38
|
+
/**
|
|
39
|
+
* Pointer to the owner identity; signal authority comes from the lease row,
|
|
40
|
+
* not these file fields.
|
|
41
|
+
*/
|
|
42
|
+
startedAt: z.string().min(1),
|
|
43
|
+
});
|
|
44
|
+
const serviceRecordSchema = z.strictObject({
|
|
45
|
+
...serviceLifecycleSchema.shape,
|
|
46
|
+
protocol: z.literal(privateLocalServiceProtocol),
|
|
13
47
|
baseUrl: z.url().refine((value) => {
|
|
14
48
|
const url = new URL(value);
|
|
15
49
|
return (url.protocol === "http:" &&
|
|
@@ -22,28 +56,30 @@ const serviceRecordSchema = z.strictObject({
|
|
|
22
56
|
url.hash === "");
|
|
23
57
|
}),
|
|
24
58
|
});
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
.regex(/^[a-f0-9]{64}$/u)
|
|
29
|
-
.optional(),
|
|
59
|
+
/** Mandatory live diagnostics; never written into the registration pointer. */
|
|
60
|
+
export const liveServiceStatusSchema = serviceRecordSchema.extend({
|
|
61
|
+
diagnostics: serviceDiagnosticsStatusSchema,
|
|
30
62
|
});
|
|
31
63
|
export const selectionSchema = z.strictObject({
|
|
64
|
+
uiAttachmentLifetime: localUiAttachmentLifetimeSchema.optional(),
|
|
32
65
|
invocationRoot: z.string().min(1),
|
|
33
66
|
clientPath: privateLocalExecPathSchema,
|
|
67
|
+
requireSandbox: z.boolean().optional(),
|
|
34
68
|
databasePath: z.string().min(1).optional(),
|
|
35
69
|
sessionId: z.string().min(1).optional(),
|
|
36
70
|
fungiMcp: fungiMcpSelectionSchema.optional(),
|
|
37
71
|
});
|
|
38
72
|
export const sessionCreationSchema = z.strictObject({
|
|
73
|
+
uiAttachmentLifetime: localUiAttachmentLifetimeSchema.optional(),
|
|
39
74
|
invocationRoot: z.string().min(1),
|
|
40
75
|
clientPath: privateLocalExecPathSchema,
|
|
76
|
+
requireSandbox: z.boolean().optional(),
|
|
41
77
|
databasePath: z.string().min(1),
|
|
42
78
|
requestId: z.string().min(1),
|
|
43
79
|
fungiMcp: fungiMcpSelectionSchema.optional(),
|
|
44
80
|
});
|
|
45
81
|
export const sessionInspectionSchema = selectionSchema
|
|
46
|
-
.omit({ clientPath: true })
|
|
82
|
+
.omit({ clientPath: true, uiAttachmentLifetime: true })
|
|
47
83
|
.extend({
|
|
48
84
|
sessionId: z.string().min(1),
|
|
49
85
|
limit: z.number().int().min(1).max(200),
|
|
@@ -52,11 +88,34 @@ export const selectedSchema = z.strictObject({
|
|
|
52
88
|
agentId: z.string().min(1),
|
|
53
89
|
databasePath: z.string().min(1),
|
|
54
90
|
sessionId: z.string().min(1),
|
|
55
|
-
routePath: z.
|
|
91
|
+
routePath: z.templateLiteral(["/agents/", z.uuid({ version: "v4" })]),
|
|
92
|
+
});
|
|
93
|
+
export const discoverRequestSchema = z.strictObject({
|
|
94
|
+
invocationRoot: z.string().min(1),
|
|
95
|
+
archived: z.boolean().default(false),
|
|
96
|
+
});
|
|
97
|
+
export const archiveRequestSchema = z.strictObject({
|
|
98
|
+
invocationRoot: z.string().min(1),
|
|
99
|
+
agentId: z.string().min(1),
|
|
100
|
+
archived: z.boolean(),
|
|
101
|
+
});
|
|
102
|
+
/** The 409 answer to an archive the Local service refuses. */
|
|
103
|
+
export const archiveRefusalSchema = z.strictObject({
|
|
104
|
+
code: z.literal("agent_archive_refused"),
|
|
105
|
+
reason: z.enum(archiveRefusalReasons),
|
|
106
|
+
error: z.string(),
|
|
56
107
|
});
|
|
57
108
|
export const sessionDiscoverySchema = z.strictObject({
|
|
58
109
|
root: z.string().min(1),
|
|
59
|
-
|
|
110
|
+
agents: z.array(z.strictObject({
|
|
111
|
+
agentId: z.string().min(1),
|
|
112
|
+
name: z.string().optional(),
|
|
113
|
+
createdBy: z.string().nullable().optional(),
|
|
114
|
+
unreadable: z.string().optional(),
|
|
115
|
+
databasePath: z.string().min(1),
|
|
116
|
+
sessionCount: z.number().int().nonnegative(),
|
|
117
|
+
updatedAt: z.number().optional(),
|
|
118
|
+
})),
|
|
60
119
|
sessions: z.array(z.strictObject({
|
|
61
120
|
databasePath: z.string().min(1),
|
|
62
121
|
sessionId: z.string().min(1),
|
|
@@ -66,41 +125,84 @@ export const sessionDiscoverySchema = z.strictObject({
|
|
|
66
125
|
})),
|
|
67
126
|
problems: z.array(z.strictObject({ kind: z.enum(["corrupt", "in_use", "unreadable"]) })),
|
|
68
127
|
});
|
|
69
|
-
const reasoningLevels = [
|
|
70
|
-
"minimal",
|
|
71
|
-
"low",
|
|
72
|
-
"medium",
|
|
73
|
-
"high",
|
|
74
|
-
"xhigh",
|
|
75
|
-
"max",
|
|
76
|
-
];
|
|
77
128
|
export const modelChoicesSchema = z.array(z.strictObject({
|
|
78
129
|
providerId: z.string().min(1),
|
|
79
130
|
providerName: z.string().min(1),
|
|
80
131
|
id: z.string().min(1),
|
|
81
132
|
name: z.string().min(1),
|
|
82
|
-
reasoning: z.array(z.enum(
|
|
133
|
+
reasoning: z.array(z.enum(modelReasoningLevels)),
|
|
83
134
|
}));
|
|
135
|
+
/** Resolve a read target without creating/chmodding a directory. */
|
|
136
|
+
export function serviceDirectoryPath(directory) {
|
|
137
|
+
return resolve(directory ?? join(homedir(), ".fungi", "service"));
|
|
138
|
+
}
|
|
84
139
|
export async function serviceDirectory(directory) {
|
|
85
|
-
const path =
|
|
140
|
+
const path = serviceDirectoryPath(directory);
|
|
86
141
|
await mkdir(path, { recursive: true, mode: 0o700 });
|
|
87
142
|
await chmod(path, 0o700);
|
|
88
143
|
return realpath(path);
|
|
89
144
|
}
|
|
90
145
|
export const recordPath = (directory) => join(directory, "service.json");
|
|
91
|
-
|
|
146
|
+
/** Read `service.json` by its lifecycle fields first, so any protocol's record is named. */
|
|
147
|
+
export async function readServiceRegistration(directory) {
|
|
148
|
+
let text;
|
|
92
149
|
try {
|
|
93
|
-
|
|
150
|
+
text = await readFile(recordPath(directory), "utf8");
|
|
94
151
|
}
|
|
95
152
|
catch (cause) {
|
|
96
153
|
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
|
|
97
154
|
return undefined;
|
|
98
155
|
throw cause;
|
|
99
156
|
}
|
|
157
|
+
let json;
|
|
158
|
+
try {
|
|
159
|
+
json = JSON.parse(text);
|
|
160
|
+
}
|
|
161
|
+
catch (cause) {
|
|
162
|
+
if (cause instanceof SyntaxError)
|
|
163
|
+
return { kind: "unidentified" };
|
|
164
|
+
throw cause;
|
|
165
|
+
}
|
|
166
|
+
const lifecycle = serviceLifecycleSchema.safeParse(json);
|
|
167
|
+
if (!lifecycle.success) {
|
|
168
|
+
// A service from before the PID was recorded cannot be signalled; name it
|
|
169
|
+
// and its own stop route instead of treating it as unidentified.
|
|
170
|
+
const older = serviceRecordSchema
|
|
171
|
+
.pick({ serviceId: true, baseUrl: true })
|
|
172
|
+
.safeParse(json);
|
|
173
|
+
if (older.success)
|
|
174
|
+
throw new LocalServiceRecordWithoutPid(directory, older.data);
|
|
175
|
+
return { kind: "unidentified" };
|
|
176
|
+
}
|
|
177
|
+
if (lifecycle.data.protocol !== privateLocalServiceProtocol)
|
|
178
|
+
return { kind: "other-protocol", lifecycle: lifecycle.data };
|
|
179
|
+
return { kind: "current", record: serviceRecordSchema.parse(json) };
|
|
180
|
+
}
|
|
181
|
+
/** `service.json` was written by a service too old to record its PID. */
|
|
182
|
+
class LocalServiceRecordWithoutPid extends Error {
|
|
183
|
+
name = "LocalServiceRecordWithoutPid";
|
|
184
|
+
constructor(directory, service) {
|
|
185
|
+
const stopUrl = new URL(`/service/${service.serviceId}/stop`, service.baseUrl);
|
|
186
|
+
super(`An older Fungi service (${service.serviceId} on port ${new URL(service.baseUrl).port}) is registered in ${recordPath(directory)}, and this fungi cannot stop it. Stop it with \`curl -X POST ${stopUrl.href}\`; if it keeps running, end the process whose command line contains \`--internal-service ${directory}\`. If it is no longer running, delete ${recordPath(directory)}. Then retry; saved Sessions are kept.`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** The record of a service speaking this client's protocol, if one is registered. */
|
|
190
|
+
export async function readServiceRecord(directory) {
|
|
191
|
+
const registration = await readServiceRegistration(directory);
|
|
192
|
+
return registration?.kind === "current" ? registration.record : undefined;
|
|
100
193
|
}
|
|
101
194
|
/** An older listener must not erase its successor's discovery record. */
|
|
102
195
|
export async function removeOwnedServiceRecord(directory, serviceId) {
|
|
103
|
-
|
|
196
|
+
const registration = await readServiceRegistration(directory);
|
|
197
|
+
if ((registration?.kind === "current" &&
|
|
198
|
+
registration.record.serviceId === serviceId) ||
|
|
199
|
+
(registration?.kind === "other-protocol" &&
|
|
200
|
+
registration.lifecycle.serviceId === serviceId))
|
|
201
|
+
await rm(recordPath(directory), { force: true });
|
|
202
|
+
}
|
|
203
|
+
/** Remove a `service.json` no protocol wrote; only call it once no owner is live. */
|
|
204
|
+
export async function removeUnidentifiedServiceRecord(directory) {
|
|
205
|
+
if ((await readServiceRegistration(directory))?.kind === "unidentified")
|
|
104
206
|
await rm(recordPath(directory), { force: true });
|
|
105
207
|
}
|
|
106
208
|
const ownerLeaseMarker = (directory) => join(directory, "owner.lock", "sqlite-owner");
|
|
@@ -132,16 +234,13 @@ export async function acquireServiceLock(directory, name) {
|
|
|
132
234
|
throw error;
|
|
133
235
|
},
|
|
134
236
|
});
|
|
135
|
-
|
|
136
|
-
// a directory containing the marker: writing the marker changes its mtime
|
|
137
|
-
// and makes its own heartbeat declare the live lock compromised.
|
|
138
|
-
const lease = acquireNodeStoreLease(join(directory, "owner.sqlite"));
|
|
237
|
+
const releaseLease = await acquireServiceOwnerLease(directory);
|
|
139
238
|
const lockPath = join(directory, "owner.lock");
|
|
140
239
|
let created = false;
|
|
141
240
|
try {
|
|
142
241
|
await assertNoUntrackedServiceOwner(directory);
|
|
143
|
-
// A dead
|
|
144
|
-
//
|
|
242
|
+
// A dead owner can leave a marked directory behind. Its process claim has
|
|
243
|
+
// already been replaced transactionally, without trusting a heartbeat.
|
|
145
244
|
if (await pathExists(ownerLeaseMarker(directory)))
|
|
146
245
|
await rm(lockPath, { recursive: true });
|
|
147
246
|
await mkdir(lockPath);
|
|
@@ -157,7 +256,7 @@ export async function acquireServiceLock(directory, name) {
|
|
|
157
256
|
await rm(lockPath, { recursive: true });
|
|
158
257
|
}
|
|
159
258
|
finally {
|
|
160
|
-
|
|
259
|
+
releaseLease();
|
|
161
260
|
}
|
|
162
261
|
throw cause;
|
|
163
262
|
}
|
|
@@ -167,7 +266,7 @@ export async function acquireServiceLock(directory, name) {
|
|
|
167
266
|
await rmdir(lockPath);
|
|
168
267
|
}
|
|
169
268
|
finally {
|
|
170
|
-
|
|
269
|
+
releaseLease();
|
|
171
270
|
}
|
|
172
271
|
};
|
|
173
272
|
}
|
|
@@ -175,18 +274,8 @@ export async function acquireServiceLock(directory, name) {
|
|
|
175
274
|
* Whether a service process holds this directory's lifetime lease. A held
|
|
176
275
|
* lease is a live owner even when it is too busy to answer its handshake.
|
|
177
276
|
*/
|
|
178
|
-
export function serviceOwnerIsLive(directory) {
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
lease = acquireNodeStoreLease(join(directory, "owner.sqlite"));
|
|
182
|
-
}
|
|
183
|
-
catch (cause) {
|
|
184
|
-
if (privateSqliteFailureCode(cause) === "busy")
|
|
185
|
-
return true;
|
|
186
|
-
throw cause;
|
|
187
|
-
}
|
|
188
|
-
lease.release();
|
|
189
|
-
return false;
|
|
277
|
+
export async function serviceOwnerIsLive(directory) {
|
|
278
|
+
return (await readServiceLeaseOwner(directory)) !== undefined;
|
|
190
279
|
}
|
|
191
280
|
const transportFailureSchema = z.looseObject({
|
|
192
281
|
code: z.string().optional(),
|
|
@@ -200,9 +289,12 @@ export async function readLiveServiceStatus(record, timeoutMs = 2000) {
|
|
|
200
289
|
});
|
|
201
290
|
if (!response.ok)
|
|
202
291
|
return { kind: "unavailable" };
|
|
203
|
-
const actual =
|
|
292
|
+
const actual = liveServiceStatusSchema.parse(await response.json());
|
|
204
293
|
return actual.serviceId === record.serviceId &&
|
|
205
|
-
actual.baseUrl === record.baseUrl
|
|
294
|
+
actual.baseUrl === record.baseUrl &&
|
|
295
|
+
actual.pid === record.pid &&
|
|
296
|
+
actual.startedAt === record.startedAt &&
|
|
297
|
+
actual.version === record.version
|
|
206
298
|
? { kind: "running", service: actual }
|
|
207
299
|
: { kind: "unavailable" };
|
|
208
300
|
}
|
|
@@ -219,3 +311,12 @@ export async function readLiveServiceStatus(record, timeoutMs = 2000) {
|
|
|
219
311
|
export async function handshake(record) {
|
|
220
312
|
return (await readLiveServiceStatus(record)).kind === "running";
|
|
221
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Clear the lifetime lock a killed owner left behind. The lease's process is
|
|
316
|
+
* gone, so acquiring the owner lock removes the marked directory;
|
|
317
|
+
* releasing it at once leaves nothing behind. A live owner makes this throw.
|
|
318
|
+
*/
|
|
319
|
+
export async function clearAbandonedServiceOwner(directory) {
|
|
320
|
+
const release = await acquireServiceLock(directory, "owner");
|
|
321
|
+
await release();
|
|
322
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const ownerSchema: z.ZodObject<{
|
|
3
|
+
pid: z.ZodNumber;
|
|
4
|
+
startedAt: z.ZodString;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
export type ServiceLeaseOwner = z.infer<typeof ownerSchema>;
|
|
7
|
+
/** The live claim's identity comes from SQLite, never from service.json. */
|
|
8
|
+
export declare function readServiceLeaseOwner(directory: string): Promise<ServiceLeaseOwner | undefined>;
|
|
9
|
+
/**
|
|
10
|
+
* Acquire the one process-owned row and publish its identity in the same
|
|
11
|
+
* transaction. This is a process-lifetime claim, not a heartbeat/timeout lease:
|
|
12
|
+
* only release or proof that the recorded process is gone permits replacement.
|
|
13
|
+
* Committing makes the identity readable by clients while the owner runs.
|
|
14
|
+
*/
|
|
15
|
+
export declare function acquireServiceOwnerLease(directory: string): Promise<() => void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { openPrivateWritableSqliteDatabase, } from "./private-sqlite-owner.js";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { readOwnProcessStart, readProcessStart, } from "./private-local-process-identity.js";
|
|
5
|
+
const ownerSchema = z.object({
|
|
6
|
+
pid: z.number().int().positive(),
|
|
7
|
+
startedAt: z.string().min(1),
|
|
8
|
+
});
|
|
9
|
+
function openLease(directory) {
|
|
10
|
+
const database = openPrivateWritableSqliteDatabase(join(directory, "owner.sqlite"), 0);
|
|
11
|
+
try {
|
|
12
|
+
database.exec("CREATE TABLE IF NOT EXISTS service_owner (id INTEGER PRIMARY KEY CHECK (id = 1), pid INTEGER NOT NULL, startedAt TEXT NOT NULL)");
|
|
13
|
+
return database;
|
|
14
|
+
}
|
|
15
|
+
catch (cause) {
|
|
16
|
+
database.close();
|
|
17
|
+
throw cause;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function readOwner(database) {
|
|
21
|
+
const row = database
|
|
22
|
+
.prepare("SELECT pid, startedAt FROM service_owner WHERE id = 1")
|
|
23
|
+
.get();
|
|
24
|
+
return row === undefined ? undefined : ownerSchema.parse(row);
|
|
25
|
+
}
|
|
26
|
+
async function ownerIsLive(owner) {
|
|
27
|
+
const current = await readProcessStart(owner.pid);
|
|
28
|
+
if (current.kind !== "unverifiable")
|
|
29
|
+
return current.kind === "running" && current.start === owner.startedAt;
|
|
30
|
+
// Without precise identity we may retain a stale claim, but never steal a
|
|
31
|
+
// possibly live one. Signal 0 checks existence; it does not terminate it.
|
|
32
|
+
try {
|
|
33
|
+
process.kill(owner.pid, 0);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch (cause) {
|
|
37
|
+
if (cause instanceof Error && "code" in cause) {
|
|
38
|
+
if (cause.code === "ESRCH")
|
|
39
|
+
return false;
|
|
40
|
+
if (cause.code === "EPERM")
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
throw cause;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** The live claim's identity comes from SQLite, never from service.json. */
|
|
47
|
+
export async function readServiceLeaseOwner(directory) {
|
|
48
|
+
const database = openLease(directory);
|
|
49
|
+
try {
|
|
50
|
+
const owner = readOwner(database);
|
|
51
|
+
return owner !== undefined && (await ownerIsLive(owner))
|
|
52
|
+
? owner
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
database.close();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Acquire the one process-owned row and publish its identity in the same
|
|
61
|
+
* transaction. This is a process-lifetime claim, not a heartbeat/timeout lease:
|
|
62
|
+
* only release or proof that the recorded process is gone permits replacement.
|
|
63
|
+
* Committing makes the identity readable by clients while the owner runs.
|
|
64
|
+
*/
|
|
65
|
+
export async function acquireServiceOwnerLease(directory) {
|
|
66
|
+
const own = { pid: process.pid, startedAt: await readOwnProcessStart() };
|
|
67
|
+
const database = openLease(directory);
|
|
68
|
+
let transaction = false;
|
|
69
|
+
try {
|
|
70
|
+
database.exec("BEGIN IMMEDIATE");
|
|
71
|
+
transaction = true;
|
|
72
|
+
const owner = readOwner(database);
|
|
73
|
+
if (owner !== undefined && (await ownerIsLive(owner)))
|
|
74
|
+
throw new Error("The Fungi service owner database is locked by a live process");
|
|
75
|
+
database
|
|
76
|
+
.prepare("INSERT OR REPLACE INTO service_owner (id, pid, startedAt) VALUES (1, ?, ?)")
|
|
77
|
+
.run(own.pid, own.startedAt);
|
|
78
|
+
database.exec("COMMIT");
|
|
79
|
+
transaction = false;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
try {
|
|
83
|
+
if (transaction)
|
|
84
|
+
database.exec("ROLLBACK");
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
database.close();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
let released = false;
|
|
91
|
+
return () => {
|
|
92
|
+
if (released)
|
|
93
|
+
return;
|
|
94
|
+
const database = openLease(directory);
|
|
95
|
+
try {
|
|
96
|
+
database
|
|
97
|
+
.prepare("DELETE FROM service_owner WHERE id = 1 AND pid = ? AND startedAt = ?")
|
|
98
|
+
.run(own.pid, own.startedAt);
|
|
99
|
+
released = true;
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
database.close();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
+
import { type ServiceDrainPhase } from "./private-local-service-log.js";
|
|
1
2
|
import { runLocalAuthCli } from "./auth-cli.js";
|
|
3
|
+
import { type PrivateLocalConfigRoot } from "./private-local-config-root.js";
|
|
2
4
|
import { listPrivateModels } from "./local-models.js";
|
|
3
5
|
import { type OpenLocalServiceRuntime } from "./private-local-service-owner.js";
|
|
4
6
|
export declare function createPrivateLocalServiceShutdown(input: {
|
|
5
7
|
closeOwners: () => Promise<void>;
|
|
6
8
|
closeServer: () => Promise<void>;
|
|
9
|
+
closeDiagnostics?: () => void;
|
|
7
10
|
removeRecord: () => Promise<void>;
|
|
8
11
|
releaseOwner: () => Promise<void>;
|
|
9
12
|
}): {
|
|
10
|
-
drain: () => Promise<void>;
|
|
11
13
|
finish: () => Promise<void>;
|
|
12
14
|
isDraining: () => boolean;
|
|
15
|
+
phase: () => ServiceDrainPhase;
|
|
13
16
|
};
|
|
14
17
|
/** Native Bun listener retained from TUI; service owns both listener and stores. */
|
|
15
|
-
export declare function runPrivateLocalService(directory: string, openRuntime?: OpenLocalServiceRuntime, listModels?: typeof listPrivateModels, runAuth?: typeof runLocalAuthCli
|
|
18
|
+
export declare function runPrivateLocalService(directory: string, openRuntime?: OpenLocalServiceRuntime, listModels?: typeof listPrivateModels, runAuth?: typeof runLocalAuthCli,
|
|
19
|
+
/** How often the service releases Agents nothing uses. */
|
|
20
|
+
idleSweepMs?: number,
|
|
21
|
+
/** Narrow manifest I/O port, independent of the Agent-runtime opener. */
|
|
22
|
+
readRecoveryManifest?: (path: string) => Promise<string>, root?: PrivateLocalConfigRoot): Promise<void>;
|