@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,6 +1,6 @@
|
|
|
1
1
|
import type { BirdDogMail } from "@fungi.computer/bird-dog";
|
|
2
|
-
import type { Accepted, CommandSettlement, VerifiedAgentReply } from "@fungi.computer/shiitake";
|
|
3
|
-
import type { AgentControlDirectory, AgentDelegationInput, AgentDelegationInvocation } from "@fungi.computer/shiitake/agent-control";
|
|
2
|
+
import type { Accepted, CancelResult, CommandSettlement, VerifiedAgentReply } from "@fungi.computer/shiitake";
|
|
3
|
+
import type { AgentControlDirectory, AgentDelegationInput, AgentDelegationInvocation, AgentRecallInput } from "@fungi.computer/shiitake/agent-control";
|
|
4
4
|
import { createFleetDelegation } from "@fungi.computer/shiitake/fleet";
|
|
5
5
|
/**
|
|
6
6
|
* The Local service's project mail routing, shared by every Agent it owns.
|
|
@@ -20,10 +20,17 @@ export type PrivateLocalMailRouting = Readonly<{
|
|
|
20
20
|
export declare function createPrivateLocalDelegation(input: Readonly<{
|
|
21
21
|
agentId: string;
|
|
22
22
|
storage: Parameters<typeof createFleetDelegation>[0]["storage"];
|
|
23
|
-
directory: AgentControlDirectory
|
|
23
|
+
directory: Pick<AgentControlDirectory, "list" | "open">;
|
|
24
24
|
routing: PrivateLocalMailRouting;
|
|
25
|
+
/** Current Team role of this physical store, projected by its enrolled link. */
|
|
26
|
+
meshAdmission?: ((recipientAgentId: string) => Promise<readonly {
|
|
27
|
+
agentId: string;
|
|
28
|
+
name: string;
|
|
29
|
+
}[] | undefined>) | undefined;
|
|
25
30
|
accept: (mail: BirdDogMail, meta?: BirdDogMail["meta"], reply?: VerifiedAgentReply) => Promise<Accepted>;
|
|
26
31
|
}>): {
|
|
27
32
|
send: (task: AgentDelegationInput, sourceSessionId: string, signal: AbortSignal, invocation?: AgentDelegationInvocation) => Promise<Accepted>;
|
|
33
|
+
recall: (task: AgentRecallInput, sourceSessionId: string, signal: AbortSignal) => Promise<CancelResult>;
|
|
28
34
|
receive: (mail: BirdDogMail) => Promise<Accepted>;
|
|
35
|
+
outstanding: () => Promise<import("@fungi.computer/shiitake/fleet").OutstandingDelegation | undefined>;
|
|
29
36
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { deterministicReplayKey } from "@fungi.computer/cairn";
|
|
2
|
+
import { AgentArchiveRefused } from "@fungi.computer/shiitake/agent-control";
|
|
2
3
|
import { createFleetDelegation, delegationProvenance, delegationReplyMeta, delegationSendMeta, } from "@fungi.computer/shiitake/fleet";
|
|
3
4
|
/** How long one Local delegation grant stays valid while it is admitted. */
|
|
4
5
|
const grantLifetimeMs = 60_000;
|
|
@@ -21,22 +22,50 @@ export function createPrivateLocalDelegation(input) {
|
|
|
21
22
|
name,
|
|
22
23
|
}));
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
async function send(task, sourceSessionId, signal, invocation) {
|
|
26
|
-
if (invocation === undefined)
|
|
27
|
-
throw new Error("Delegation requires a tool invocation");
|
|
25
|
+
async function authorizedMembers(agentId, signal) {
|
|
28
26
|
const agents = await members(signal);
|
|
29
|
-
if (!agents.some(({ agentId }) =>
|
|
27
|
+
if (!agents.some(({ agentId: memberId }) => memberId === agentId)) {
|
|
28
|
+
const archived = await input.directory.list(signal, { archived: true });
|
|
29
|
+
if (archived.some(({ agentId: memberId }) => memberId === agentId))
|
|
30
|
+
throw new AgentArchiveRefused("archived", `Agent ${agentId} is archived; unarchive it to send it work`);
|
|
30
31
|
throw new Error("Agent is outside this Local project");
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
}
|
|
33
|
+
return agents;
|
|
34
|
+
}
|
|
35
|
+
function requestKey(task, sourceSessionId) {
|
|
36
|
+
return deterministicReplayKey("local-agent-delegation", [
|
|
34
37
|
input.agentId,
|
|
35
38
|
sourceSessionId,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
task.agentId,
|
|
40
|
+
task.sessionId,
|
|
41
|
+
task.requestId,
|
|
39
42
|
]);
|
|
43
|
+
}
|
|
44
|
+
/** Send with one caller-owned identity; follow-up replies return here. */
|
|
45
|
+
async function send(task, sourceSessionId, signal, invocation) {
|
|
46
|
+
if (invocation === undefined)
|
|
47
|
+
throw new Error("Delegation requires a tool invocation");
|
|
48
|
+
const agents = await authorizedMembers(task.agentId, signal);
|
|
49
|
+
const requestId = await requestKey(task, sourceSessionId);
|
|
50
|
+
signal.throwIfAborted();
|
|
51
|
+
if (task.delivery === "steer") {
|
|
52
|
+
const opened = await input.directory.open(task.agentId, signal);
|
|
53
|
+
try {
|
|
54
|
+
return await opened.agent.session(task.sessionId).send(task.text, {
|
|
55
|
+
requestId,
|
|
56
|
+
delivery: "steer",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await opened.release();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const meta = delegationSendMeta({ id: input.agentId, name: input.agentId }, invocation.meta);
|
|
64
|
+
// The caller's request key owns replay, not the current model program's bytes.
|
|
65
|
+
const provenance = await delegationProvenance({
|
|
66
|
+
agentId: input.agentId,
|
|
67
|
+
sessionId: sourceSessionId,
|
|
68
|
+
});
|
|
40
69
|
signal.throwIfAborted();
|
|
41
70
|
return fleet.start({
|
|
42
71
|
agents: agents.map(({ agentId }) => agentId),
|
|
@@ -44,7 +73,32 @@ export function createPrivateLocalDelegation(input) {
|
|
|
44
73
|
expiresAt: Date.now() + grantLifetimeMs,
|
|
45
74
|
provenance,
|
|
46
75
|
meta,
|
|
47
|
-
}, {
|
|
76
|
+
}, {
|
|
77
|
+
agentId: task.agentId,
|
|
78
|
+
sessionId: task.sessionId,
|
|
79
|
+
text: task.text,
|
|
80
|
+
requestId,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** Resolve only this sender's receipt, then let the target own the pending guard. */
|
|
84
|
+
async function recall(task, sourceSessionId, signal) {
|
|
85
|
+
await authorizedMembers(task.agentId, signal);
|
|
86
|
+
const requestId = await requestKey(task, sourceSessionId);
|
|
87
|
+
signal.throwIfAborted();
|
|
88
|
+
const opened = await input.directory.open(task.agentId, signal);
|
|
89
|
+
try {
|
|
90
|
+
const session = opened.agent.session(task.sessionId);
|
|
91
|
+
const admitted = fleet.receipt(requestId) ?? (await session.withdraw(requestId));
|
|
92
|
+
return admitted.status === "accepted"
|
|
93
|
+
? await session.cancel({
|
|
94
|
+
commandId: admitted.commandId,
|
|
95
|
+
onlyIfPending: true,
|
|
96
|
+
})
|
|
97
|
+
: admitted;
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
await opened.release();
|
|
101
|
+
}
|
|
48
102
|
}
|
|
49
103
|
/**
|
|
50
104
|
* Admit one mail addressed to this Agent. New work must name its sender as
|
|
@@ -52,9 +106,10 @@ export function createPrivateLocalDelegation(input) {
|
|
|
52
106
|
* authenticated reply. Everything else is refused.
|
|
53
107
|
*/
|
|
54
108
|
async function receive(mail) {
|
|
55
|
-
|
|
109
|
+
const mesh = await input.meshAdmission?.(mail.to.agentId);
|
|
110
|
+
if (mail.to.agentId !== input.agentId && mesh === undefined)
|
|
56
111
|
throw new Error("Local addressed mail recipient is not authorized");
|
|
57
|
-
const agents = await members(new AbortController().signal);
|
|
112
|
+
const agents = mesh ?? (await members(new AbortController().signal));
|
|
58
113
|
if (!agents.some(({ agentId }) => agentId === mail.from.agentId))
|
|
59
114
|
throw new Error("Local mail sender is outside this project");
|
|
60
115
|
if (mail.replyTo.length > 0) {
|
|
@@ -74,5 +129,5 @@ export function createPrivateLocalDelegation(input) {
|
|
|
74
129
|
const meta = delegationReplyMeta(delegation, replier.name);
|
|
75
130
|
return input.accept({ ...mail, meta }, meta, delegation.reply);
|
|
76
131
|
}
|
|
77
|
-
return { send, receive };
|
|
132
|
+
return { send, recall, receive, outstanding: fleet.outstanding };
|
|
78
133
|
}
|
|
@@ -8,12 +8,20 @@ export declare const privateLocalEffectiveModelSchema: z.ZodObject<{
|
|
|
8
8
|
modelName: z.ZodString;
|
|
9
9
|
credential: z.ZodObject<{
|
|
10
10
|
kind: z.ZodEnum<{
|
|
11
|
-
api_key: "api_key";
|
|
12
11
|
oauth: "oauth";
|
|
12
|
+
api_key: "api_key";
|
|
13
13
|
missing: "missing";
|
|
14
14
|
}>;
|
|
15
15
|
source: z.ZodString;
|
|
16
16
|
}, z.core.$strict>;
|
|
17
|
+
reasoning: z.ZodOptional<z.ZodEnum<{
|
|
18
|
+
minimal: "minimal";
|
|
19
|
+
low: "low";
|
|
20
|
+
medium: "medium";
|
|
21
|
+
high: "high";
|
|
22
|
+
xhigh: "xhigh";
|
|
23
|
+
max: "max";
|
|
24
|
+
}>>;
|
|
17
25
|
label: z.ZodString;
|
|
18
26
|
}, z.core.$strict>;
|
|
19
27
|
export type PrivateLocalEffectiveModel = z.output<typeof privateLocalEffectiveModelSchema>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { thinkingLevelSchema } from "./private-fungi-model-catalog.js";
|
|
2
3
|
/** Secret-free model status published by the Local owner for one Session. */
|
|
3
4
|
export const privateLocalEffectiveModelSchema = z.strictObject({
|
|
4
5
|
agentName: z.literal("Shiitake"),
|
|
@@ -10,5 +11,7 @@ export const privateLocalEffectiveModelSchema = z.strictObject({
|
|
|
10
11
|
kind: z.enum(["oauth", "api_key", "missing"]),
|
|
11
12
|
source: z.string().min(1).max(160),
|
|
12
13
|
}),
|
|
14
|
+
/** The resolved thinking level a run would use; absent when the model has none. */
|
|
15
|
+
reasoning: thinkingLevelSchema.optional(),
|
|
13
16
|
label: z.string().min(1).max(640),
|
|
14
17
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only host variables a Local exec child receives besides its selected
|
|
3
|
+
* PATH and HOME. Exec runs as the host's own user, so that user's session
|
|
4
|
+
* locator variables are not a secret and are what `systemd-run --user`,
|
|
5
|
+
* `loginctl` and other user-service tools need to find the user manager.
|
|
6
|
+
* Every other host variable, credentials included, stays out.
|
|
7
|
+
*/
|
|
8
|
+
export type PrivateLocalExecEnvironment = {
|
|
9
|
+
PATH: string;
|
|
10
|
+
HOME: string;
|
|
11
|
+
XDG_RUNTIME_DIR?: string;
|
|
12
|
+
DBUS_SESSION_BUS_ADDRESS?: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function privateLocalExecEnvironment(executionPath: string, host: NodeJS.ProcessEnv & {
|
|
15
|
+
HOME: string;
|
|
16
|
+
}): PrivateLocalExecEnvironment;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function privateLocalExecEnvironment(executionPath, host) {
|
|
2
|
+
const environment = {
|
|
3
|
+
PATH: executionPath,
|
|
4
|
+
HOME: host.HOME,
|
|
5
|
+
};
|
|
6
|
+
if (host.XDG_RUNTIME_DIR)
|
|
7
|
+
environment.XDG_RUNTIME_DIR = host.XDG_RUNTIME_DIR;
|
|
8
|
+
if (host.DBUS_SESSION_BUS_ADDRESS)
|
|
9
|
+
environment.DBUS_SESSION_BUS_ADDRESS = host.DBUS_SESSION_BUS_ADDRESS;
|
|
10
|
+
return environment;
|
|
11
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type LocalSandboxPolicy } from "./private-local-sandbox-policy.js";
|
|
2
|
+
import type { LinuxSandboxPayload } from "./private-local-bwrap.js";
|
|
3
|
+
/** Ordinary upstream bwrap argv. There is no approval or bare-command path. */
|
|
4
|
+
export declare function linuxSandboxCommand(policy: LocalSandboxPolicy, payload: LinuxSandboxPayload, cwd: string, argv: readonly [string, ...string[]], host?: NodeJS.ProcessEnv): {
|
|
5
|
+
args: string[];
|
|
6
|
+
executable: string;
|
|
7
|
+
seccompFilter: Uint8Array;
|
|
8
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, realpathSync, statSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
import { protectedSandboxPaths, } from "./private-local-sandbox-policy.js";
|
|
4
|
+
/** Ordinary upstream bwrap argv. There is no approval or bare-command path. */
|
|
5
|
+
export function linuxSandboxCommand(policy, payload, cwd, argv, host = process.env) {
|
|
6
|
+
const root = policy.writableRoot;
|
|
7
|
+
// Bind sources must exist. Only the trusted host prepares the reserved paths.
|
|
8
|
+
mkdirSync(join(root, ".fungi"), { recursive: true });
|
|
9
|
+
for (const git of policy.immovable) {
|
|
10
|
+
mkdirSync(join(git, "hooks"), { recursive: true });
|
|
11
|
+
for (const config of ["config", "config.worktree"])
|
|
12
|
+
if (!existsSync(join(git, config)))
|
|
13
|
+
writeFileSync(join(git, config), "", { flag: "wx" });
|
|
14
|
+
}
|
|
15
|
+
const args = [
|
|
16
|
+
"--unshare-all",
|
|
17
|
+
"--die-with-parent",
|
|
18
|
+
"--new-session",
|
|
19
|
+
"--seccomp",
|
|
20
|
+
"3",
|
|
21
|
+
"--ro-bind",
|
|
22
|
+
"/",
|
|
23
|
+
"/",
|
|
24
|
+
// Private system mounts precede the folder bind so /tmp projects survive.
|
|
25
|
+
"--tmpfs",
|
|
26
|
+
"/tmp",
|
|
27
|
+
"--tmpfs",
|
|
28
|
+
"/run",
|
|
29
|
+
"--dev",
|
|
30
|
+
"/dev",
|
|
31
|
+
"--proc",
|
|
32
|
+
"/proc",
|
|
33
|
+
"--bind",
|
|
34
|
+
root,
|
|
35
|
+
root,
|
|
36
|
+
];
|
|
37
|
+
for (const path of policy.immovable)
|
|
38
|
+
args.push("--bind", path, path);
|
|
39
|
+
for (const path of protectedSandboxPaths(policy.readOnly))
|
|
40
|
+
args.push("--ro-bind", path, path);
|
|
41
|
+
if (existsSync("/var/run") && realpathSync("/var/run") !== "/run")
|
|
42
|
+
args.push("--tmpfs", realpathSync("/var/run"));
|
|
43
|
+
const runtime = host.XDG_RUNTIME_DIR;
|
|
44
|
+
if (runtime && isAbsolute(runtime) && existsSync(runtime))
|
|
45
|
+
args.push("--tmpfs", realpathSync(runtime));
|
|
46
|
+
// Stopgap masks, not an inventory of all possible host IPC. /run also
|
|
47
|
+
// covers the usual /var/run alias. These follow folder/protected binds so
|
|
48
|
+
// a selected socket inside the project is not exposed again by a later bind.
|
|
49
|
+
// Keep unusual selected SSH-agent paths
|
|
50
|
+
// and Docker Desktop sockets out without masking all of the user's HOME.
|
|
51
|
+
if (host.HOME && isAbsolute(host.HOME)) {
|
|
52
|
+
for (const path of [
|
|
53
|
+
join(host.HOME, ".docker", "run"),
|
|
54
|
+
join(host.HOME, ".docker", "desktop"),
|
|
55
|
+
])
|
|
56
|
+
if (existsSync(path) && statSync(path).isDirectory())
|
|
57
|
+
args.push("--tmpfs", realpathSync(path));
|
|
58
|
+
}
|
|
59
|
+
const agentSocket = host.SSH_AUTH_SOCK;
|
|
60
|
+
if (agentSocket &&
|
|
61
|
+
isAbsolute(agentSocket) &&
|
|
62
|
+
existsSync(agentSocket) &&
|
|
63
|
+
statSync(agentSocket).isSocket())
|
|
64
|
+
args.push("--ro-bind", "/dev/null", realpathSync(agentSocket));
|
|
65
|
+
args.push("--chdir", cwd, "--", ...argv);
|
|
66
|
+
return { ...payload, args };
|
|
67
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BirdDogMail } from "@fungi.computer/bird-dog";
|
|
2
2
|
import { type BirdDogEffect } from "@fungi.computer/bird-dog/effect";
|
|
3
|
-
import type {
|
|
3
|
+
import type { PrivateSqliteDatabase } from "./private-sqlite-owner.js";
|
|
4
4
|
import type { Accepted } from "@fungi.computer/shiitake";
|
|
5
5
|
export type PrivateLocalMailOptions = Readonly<{
|
|
6
6
|
/** Admit one reply at its addressed Agent; a rejection retries later. */
|
|
@@ -8,4 +8,6 @@ export type PrivateLocalMailOptions = Readonly<{
|
|
|
8
8
|
scheduleWake?: (at: number) => void;
|
|
9
9
|
now?: () => number;
|
|
10
10
|
}>;
|
|
11
|
-
|
|
11
|
+
/** Whether this Agent's outbox still owes any reply a delivery attempt. */
|
|
12
|
+
export declare function hasPrivateLocalPendingMail(database: PrivateSqliteDatabase): Promise<boolean>;
|
|
13
|
+
export declare function createPrivateLocalMailRoom(database: PrivateSqliteDatabase, options: PrivateLocalMailOptions): BirdDogEffect;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BirdDogError, make as makeBirdDog, } from "@fungi.computer/bird-dog/effect";
|
|
1
|
+
import { BirdDogError, hasPendingDelivery, make as makeBirdDog, } from "@fungi.computer/bird-dog/effect";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import { messageAuthor } from "@fungi.computer/agar/agents";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -7,41 +7,49 @@ const outboxRows = z.array(z.object({ key: z.string(), value: z.string() }).read
|
|
|
7
7
|
function storeFailure(cause) {
|
|
8
8
|
return new BirdDogError("local mail store is unavailable", { cause });
|
|
9
9
|
}
|
|
10
|
+
/** The Agent store's BirdDog outbox rows. */
|
|
11
|
+
function localOutbox(database) {
|
|
12
|
+
return {
|
|
13
|
+
read: (key) => Effect.try({
|
|
14
|
+
try: () => {
|
|
15
|
+
const statement = database.prepare("SELECT value FROM local_birddog_outbox WHERE key = ?");
|
|
16
|
+
const row = outboxValue.safeParse(statement.get(key));
|
|
17
|
+
return row.success ? row.data.value : undefined;
|
|
18
|
+
},
|
|
19
|
+
catch: storeFailure,
|
|
20
|
+
}),
|
|
21
|
+
put: (key, value) => Effect.try({
|
|
22
|
+
try: () => {
|
|
23
|
+
database
|
|
24
|
+
.prepare("INSERT INTO local_birddog_outbox (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value")
|
|
25
|
+
.run(key, value);
|
|
26
|
+
},
|
|
27
|
+
catch: storeFailure,
|
|
28
|
+
}),
|
|
29
|
+
list: (limit, afterKey) => Effect.try({
|
|
30
|
+
try: () => outboxRows.parse(database
|
|
31
|
+
.prepare("SELECT key, value FROM local_birddog_outbox WHERE (? IS NULL OR key > ?) ORDER BY key LIMIT ?")
|
|
32
|
+
.all(afterKey ?? null, afterKey ?? null, limit)),
|
|
33
|
+
catch: storeFailure,
|
|
34
|
+
}),
|
|
35
|
+
delete: (key) => Effect.try({
|
|
36
|
+
try: () => {
|
|
37
|
+
database
|
|
38
|
+
.prepare("DELETE FROM local_birddog_outbox WHERE key = ?")
|
|
39
|
+
.run(key);
|
|
40
|
+
},
|
|
41
|
+
catch: storeFailure,
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Whether this Agent's outbox still owes any reply a delivery attempt. */
|
|
46
|
+
export function hasPrivateLocalPendingMail(database) {
|
|
47
|
+
return Effect.runPromise(hasPendingDelivery(localOutbox(database)));
|
|
48
|
+
}
|
|
10
49
|
export function createPrivateLocalMailRoom(database, options) {
|
|
11
50
|
database.exec("CREATE TABLE IF NOT EXISTS local_birddog_outbox (key TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)");
|
|
12
51
|
const room = Effect.runSync(makeBirdDog({
|
|
13
|
-
outbox:
|
|
14
|
-
read: (key) => Effect.try({
|
|
15
|
-
try: () => {
|
|
16
|
-
const statement = database.prepare("SELECT value FROM local_birddog_outbox WHERE key = ?");
|
|
17
|
-
const row = outboxValue.safeParse(statement.get(key));
|
|
18
|
-
return row.success ? row.data.value : undefined;
|
|
19
|
-
},
|
|
20
|
-
catch: storeFailure,
|
|
21
|
-
}),
|
|
22
|
-
put: (key, value) => Effect.try({
|
|
23
|
-
try: () => {
|
|
24
|
-
database
|
|
25
|
-
.prepare("INSERT INTO local_birddog_outbox (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value")
|
|
26
|
-
.run(key, value);
|
|
27
|
-
},
|
|
28
|
-
catch: storeFailure,
|
|
29
|
-
}),
|
|
30
|
-
list: (limit, afterKey) => Effect.try({
|
|
31
|
-
try: () => outboxRows.parse(database
|
|
32
|
-
.prepare("SELECT key, value FROM local_birddog_outbox WHERE (? IS NULL OR key > ?) ORDER BY key LIMIT ?")
|
|
33
|
-
.all(afterKey ?? null, afterKey ?? null, limit)),
|
|
34
|
-
catch: storeFailure,
|
|
35
|
-
}),
|
|
36
|
-
delete: (key) => Effect.try({
|
|
37
|
-
try: () => {
|
|
38
|
-
database
|
|
39
|
-
.prepare("DELETE FROM local_birddog_outbox WHERE key = ?")
|
|
40
|
-
.run(key);
|
|
41
|
-
},
|
|
42
|
-
catch: storeFailure,
|
|
43
|
-
}),
|
|
44
|
-
},
|
|
52
|
+
outbox: localOutbox(database),
|
|
45
53
|
post: (mail) => Effect.tryPromise({
|
|
46
54
|
try: async () => {
|
|
47
55
|
await options.deliver(mail);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type LocalMachineEnrollmentAccount } from "./private-local-mesh-record.js";
|
|
3
|
+
export declare const localMeshJoinSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4
|
+
origin: z.ZodURL;
|
|
5
|
+
invocationRoot: z.ZodString;
|
|
6
|
+
machineLabel: z.ZodString;
|
|
7
|
+
agents: z.ZodArray<z.ZodString>;
|
|
8
|
+
kind: z.ZodLiteral<"oauth">;
|
|
9
|
+
teamId: z.ZodString;
|
|
10
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
11
|
+
origin: z.ZodURL;
|
|
12
|
+
invocationRoot: z.ZodString;
|
|
13
|
+
machineLabel: z.ZodString;
|
|
14
|
+
agents: z.ZodArray<z.ZodString>;
|
|
15
|
+
kind: z.ZodLiteral<"oyster-key">;
|
|
16
|
+
key: z.ZodString;
|
|
17
|
+
}, z.core.$strict>], "kind">;
|
|
18
|
+
/**
|
|
19
|
+
* Persist the device pin before authenticated enrollment I/O. This is enrollment
|
|
20
|
+
* only: it does not report a socket, command Accepted or connected mesh.
|
|
21
|
+
*/
|
|
22
|
+
export declare function enrollPrivateLocalMesh(raw: z.infer<typeof localMeshJoinSchema>, options: {
|
|
23
|
+
account: LocalMachineEnrollmentAccount;
|
|
24
|
+
credentialPath: string;
|
|
25
|
+
fetch?: typeof globalThis.fetch;
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
state: string;
|
|
29
|
+
deviceId: string;
|
|
30
|
+
agents: {
|
|
31
|
+
selection: number;
|
|
32
|
+
agentId: string;
|
|
33
|
+
}[];
|
|
34
|
+
}>;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
3
|
+
import { realpath } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, relative, isAbsolute, resolve } from "node:path";
|
|
5
|
+
import { defineIdFamily } from "@fungi.computer/cairn/id";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { readBoundedResponse } from "./private-bounded-response.js";
|
|
8
|
+
import { fungiAccountFetch } from "./private-fungi-account.js";
|
|
9
|
+
import { parseFungiMcpEndpoint } from "./private-fungi-mcp-endpoint.js";
|
|
10
|
+
import { listPrivateLocalAgentStores, readPrivateLocalAgentProfile, } from "./private-local-session-discovery.js";
|
|
11
|
+
import { machineBindingSchema, localMachineEnrollmentSchema, } from "./private-local-mesh-record.js";
|
|
12
|
+
const id = z.string().min(1).max(256);
|
|
13
|
+
const joinCommon = z.strictObject({
|
|
14
|
+
origin: z.url(),
|
|
15
|
+
invocationRoot: z.string().min(1).max(4096),
|
|
16
|
+
machineLabel: z.string().trim().min(1).max(256),
|
|
17
|
+
agents: z
|
|
18
|
+
.array(id)
|
|
19
|
+
.max(256)
|
|
20
|
+
.refine((agents) => new Set(agents).size === agents.length),
|
|
21
|
+
});
|
|
22
|
+
export const localMeshJoinSchema = z.discriminatedUnion("kind", [
|
|
23
|
+
joinCommon.extend({ kind: z.literal("oauth"), teamId: id }),
|
|
24
|
+
joinCommon.extend({
|
|
25
|
+
kind: z.literal("oyster-key"),
|
|
26
|
+
key: z.string().startsWith("oyk_").min(5).max(4096),
|
|
27
|
+
}),
|
|
28
|
+
]);
|
|
29
|
+
const responseSchema = z.strictObject({
|
|
30
|
+
kind: z.literal("joined"),
|
|
31
|
+
deviceId: id,
|
|
32
|
+
agents: z.array(machineBindingSchema).max(256),
|
|
33
|
+
});
|
|
34
|
+
const oysterResponseSchema = responseSchema.extend({ scopeId: id });
|
|
35
|
+
const requests = defineIdFamily({
|
|
36
|
+
family: "local-machine-enrollment",
|
|
37
|
+
version: 1,
|
|
38
|
+
kinds: { join: { prefix: "machine_join" } },
|
|
39
|
+
});
|
|
40
|
+
function deviceKey() {
|
|
41
|
+
const pair = generateKeyPairSync("ed25519");
|
|
42
|
+
const jwk = z
|
|
43
|
+
.object({ x: z.string() })
|
|
44
|
+
.parse(pair.publicKey.export({ format: "jwk" }));
|
|
45
|
+
return {
|
|
46
|
+
publicKey: Buffer.from(jwk.x, "base64url").toString("base64"),
|
|
47
|
+
privateKey: pair.privateKey
|
|
48
|
+
.export({ format: "der", type: "pkcs8" })
|
|
49
|
+
.toString("base64"),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Persist the device pin before authenticated enrollment I/O. This is enrollment
|
|
54
|
+
* only: it does not report a socket, command Accepted or connected mesh.
|
|
55
|
+
*/
|
|
56
|
+
export async function enrollPrivateLocalMesh(raw, options) {
|
|
57
|
+
const input = localMeshJoinSchema.parse(raw);
|
|
58
|
+
const endpoint = parseFungiMcpEndpoint(input.origin);
|
|
59
|
+
if (endpoint.pathname !== "/")
|
|
60
|
+
throw new Error("Mesh enrollment needs an origin");
|
|
61
|
+
const origin = endpoint.origin;
|
|
62
|
+
const authority = {
|
|
63
|
+
kind: input.kind === "oauth" ? "oauth" : "oyster",
|
|
64
|
+
origin,
|
|
65
|
+
};
|
|
66
|
+
if (input.kind === "oauth") {
|
|
67
|
+
const saved = await options.account.read();
|
|
68
|
+
if (!saved.some((record) => record.kind === "oauth" && record.origin === origin))
|
|
69
|
+
throw new Error("Sign in to this Fungi issuer before joining a machine");
|
|
70
|
+
}
|
|
71
|
+
const invocationRoot = await realpath(resolve(input.invocationRoot));
|
|
72
|
+
const stores = await listPrivateLocalAgentStores(invocationRoot);
|
|
73
|
+
const selected = input.agents.map((localAgentId) => {
|
|
74
|
+
const databasePath = stores.find((path) => basename(dirname(path)) === localAgentId);
|
|
75
|
+
if (databasePath === undefined)
|
|
76
|
+
throw new Error("The selected Agent is not active in this project");
|
|
77
|
+
const within = relative(invocationRoot, databasePath);
|
|
78
|
+
if (isAbsolute(within) || within === ".." || within.startsWith("../"))
|
|
79
|
+
throw new Error("The selected Agent is outside this project");
|
|
80
|
+
const profile = readPrivateLocalAgentProfile(databasePath);
|
|
81
|
+
return { localAgentId, databasePath, name: profile.name ?? localAgentId };
|
|
82
|
+
});
|
|
83
|
+
const proposed = {
|
|
84
|
+
state: "pending",
|
|
85
|
+
requestId: requests.join.generate(),
|
|
86
|
+
target: input.kind === "oauth"
|
|
87
|
+
? { kind: "oauth", teamId: input.teamId }
|
|
88
|
+
: { kind: "oyster" },
|
|
89
|
+
invocationRoot,
|
|
90
|
+
machineLabel: input.machineLabel,
|
|
91
|
+
...deviceKey(),
|
|
92
|
+
agents: selected,
|
|
93
|
+
};
|
|
94
|
+
let chosen;
|
|
95
|
+
await options.account.update(authority, (current) => {
|
|
96
|
+
const prior = current.find((record) => JSON.stringify(record.target) === JSON.stringify(proposed.target));
|
|
97
|
+
if (prior !== undefined) {
|
|
98
|
+
if (prior.invocationRoot !== invocationRoot ||
|
|
99
|
+
prior.machineLabel !== input.machineLabel ||
|
|
100
|
+
JSON.stringify(prior.agents) !== JSON.stringify(selected))
|
|
101
|
+
throw new Error("This scope already has another machine enrollment intent");
|
|
102
|
+
chosen = prior;
|
|
103
|
+
return [...current];
|
|
104
|
+
}
|
|
105
|
+
chosen = proposed;
|
|
106
|
+
return [...current, proposed];
|
|
107
|
+
});
|
|
108
|
+
if (chosen === undefined)
|
|
109
|
+
throw new Error("The credential owner did not persist an enrollment");
|
|
110
|
+
const record = chosen;
|
|
111
|
+
if (record.state === "enrolled")
|
|
112
|
+
return {
|
|
113
|
+
state: "enrolled",
|
|
114
|
+
deviceId: record.deviceId,
|
|
115
|
+
agents: record.bindings,
|
|
116
|
+
};
|
|
117
|
+
const url = new URL(input.kind === "oauth"
|
|
118
|
+
? `/api/teams/${encodeURIComponent(input.teamId)}/local/mesh/join`
|
|
119
|
+
: "/local/mesh/join", origin);
|
|
120
|
+
// Only OAuth owns refresh/retry. An Oyster key is ephemeral enrollment
|
|
121
|
+
// authority; neither the credential owner nor later Seam dials receive it.
|
|
122
|
+
const request = input.kind === "oauth"
|
|
123
|
+
? fungiAccountFetch(origin, options)
|
|
124
|
+
: (options.fetch ?? globalThis.fetch);
|
|
125
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
126
|
+
if (input.kind === "oyster-key")
|
|
127
|
+
headers.set("Authorization", `Bearer ${input.key}`);
|
|
128
|
+
const init = {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers,
|
|
131
|
+
redirect: "error",
|
|
132
|
+
body: JSON.stringify({
|
|
133
|
+
requestId: record.requestId,
|
|
134
|
+
machineLabel: record.machineLabel,
|
|
135
|
+
publicKey: record.publicKey,
|
|
136
|
+
// Local directory selectors and database paths remain private.
|
|
137
|
+
agents: record.agents.map(({ name }) => ({ name })),
|
|
138
|
+
}),
|
|
139
|
+
};
|
|
140
|
+
if (options.signal !== undefined)
|
|
141
|
+
init.signal = options.signal;
|
|
142
|
+
let response;
|
|
143
|
+
try {
|
|
144
|
+
response = await request(url, init);
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
if (input.kind === "oyster-key") {
|
|
148
|
+
// Release Local-owned references before decoding/storing the response.
|
|
149
|
+
// This cannot erase immutable string bytes or caller/fetch-owned copies.
|
|
150
|
+
headers.delete("Authorization");
|
|
151
|
+
input.key = "";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!response.ok)
|
|
155
|
+
throw new Error(`Machine enrollment refused with HTTP ${String(response.status)}`);
|
|
156
|
+
const payload = JSON.parse(await readBoundedResponse(response, 64 * 1024, "Machine enrollment"));
|
|
157
|
+
const result = input.kind === "oauth"
|
|
158
|
+
? { ...responseSchema.parse(payload), scopeId: input.teamId }
|
|
159
|
+
: oysterResponseSchema.parse(payload);
|
|
160
|
+
if (result.agents.length !== record.agents.length ||
|
|
161
|
+
result.agents.some((binding, index) => binding.selection !== index) ||
|
|
162
|
+
new Set(result.agents.map((binding) => binding.agentId)).size !==
|
|
163
|
+
result.agents.length)
|
|
164
|
+
throw new Error("Machine enrollment does not match the selected Agent set");
|
|
165
|
+
const enrolled = localMachineEnrollmentSchema.parse({
|
|
166
|
+
...record,
|
|
167
|
+
state: "enrolled",
|
|
168
|
+
deviceId: result.deviceId,
|
|
169
|
+
scopeId: result.scopeId,
|
|
170
|
+
bindings: result.agents,
|
|
171
|
+
});
|
|
172
|
+
await options.account.update(authority, (current) => {
|
|
173
|
+
const matching = current.find((candidate) => candidate.requestId === record.requestId);
|
|
174
|
+
if (matching === undefined)
|
|
175
|
+
throw new Error("Machine enrollment was removed before acknowledgment");
|
|
176
|
+
if (matching.state === "enrolled" &&
|
|
177
|
+
(matching.deviceId !== result.deviceId ||
|
|
178
|
+
matching.scopeId !== result.scopeId ||
|
|
179
|
+
JSON.stringify(matching.bindings) !== JSON.stringify(result.agents)))
|
|
180
|
+
throw new Error("Home changed an acknowledged machine enrollment");
|
|
181
|
+
return current.map((candidate) => candidate.requestId === record.requestId ? enrolled : candidate);
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
state: "enrolled",
|
|
185
|
+
deviceId: result.deviceId,
|
|
186
|
+
agents: result.agents,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PostEnvelope, PostDeliveryResult } from "@fungi.computer/carrier/post";
|
|
2
|
+
import type { LocalMachineEnrollment, MachineEnrollmentAuthority } from "./private-local-mesh-record.js";
|
|
3
|
+
export type EnrolledLocalMachine = Extract<LocalMachineEnrollment, {
|
|
4
|
+
state: "enrolled";
|
|
5
|
+
}> & {
|
|
6
|
+
authority: MachineEnrollmentAuthority;
|
|
7
|
+
};
|
|
8
|
+
export declare const localMeshLinkKey: (record: EnrolledLocalMachine) => string;
|
|
9
|
+
/**
|
|
10
|
+
* Service-owned native I/O only. The account owns pins, Carrier owns retry
|
|
11
|
+
* custody, and the addressed runtime owns acceptance. No mail is queued here.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createLocalMeshLinks(options: {
|
|
14
|
+
credentialPath: string;
|
|
15
|
+
receive: (record: EnrolledLocalMachine, envelope: PostEnvelope) => Promise<PostDeliveryResult>;
|
|
16
|
+
}): {
|
|
17
|
+
refresh: () => Promise<void>;
|
|
18
|
+
isCurrent: (record: EnrolledLocalMachine) => Promise<boolean>;
|
|
19
|
+
deliver: (record: EnrolledLocalMachine, envelope: PostEnvelope) => Promise<PostDeliveryResult>;
|
|
20
|
+
close: () => Promise<void>;
|
|
21
|
+
};
|