@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.
Files changed (174) hide show
  1. package/README.md +282 -34
  2. package/dist/auth-cli.d.ts +10 -0
  3. package/dist/auth-cli.js +36 -14
  4. package/dist/fungi-account-command.d.ts +1 -1
  5. package/dist/fungi-account-command.js +6 -3
  6. package/dist/index.d.ts +10 -2
  7. package/dist/index.js +8 -2
  8. package/dist/internal/image-fit-metafile.json +106 -0
  9. package/dist/internal/local-image-fit-worker.mjs +960 -0
  10. package/dist/internal/private-workerd-sandbox-worker.mjs +11 -12
  11. package/dist/internal/sandbox-metafile.json +7 -7
  12. package/dist/local-archive-handoff.d.ts +12 -0
  13. package/dist/local-archive-handoff.js +21 -0
  14. package/dist/local-extensions.d.ts +58 -12
  15. package/dist/local-extensions.js +195 -55
  16. package/dist/local-image-fit.d.ts +30 -0
  17. package/dist/local-image-fit.js +105 -0
  18. package/dist/local-models.d.ts +4 -2
  19. package/dist/local-models.js +11 -6
  20. package/dist/local-session-archives.d.ts +8 -0
  21. package/dist/local-session-archives.js +152 -0
  22. package/dist/local-skill-source.d.ts +18 -3
  23. package/dist/local-skill-source.js +216 -140
  24. package/dist/log-event.d.ts +7 -0
  25. package/dist/log-event.js +8 -4
  26. package/dist/private-bounded-response.d.ts +2 -0
  27. package/dist/private-bounded-response.js +41 -0
  28. package/dist/private-file-credential-store.d.ts +28 -3
  29. package/dist/private-file-credential-store.js +57 -9
  30. package/dist/private-files-patch.d.ts +2 -1
  31. package/dist/private-files-patch.js +21 -3
  32. package/dist/private-fungi-account.d.ts +40 -23
  33. package/dist/private-fungi-account.js +201 -21
  34. package/dist/private-fungi-mcp-connection.d.ts +4 -0
  35. package/dist/private-fungi-mcp-connection.js +25 -6
  36. package/dist/private-fungi-mcp-selection-schema.d.ts +7 -0
  37. package/dist/private-fungi-mcp-selection-schema.js +15 -0
  38. package/dist/private-fungi-mcp-selection.d.ts +6 -6
  39. package/dist/private-fungi-mcp-selection.js +29 -16
  40. package/dist/private-fungi-model-api.js +4 -4
  41. package/dist/private-fungi-model-catalog.d.ts +10 -0
  42. package/dist/private-fungi-model-catalog.js +10 -2
  43. package/dist/private-fungi-model-provider.d.ts +1 -1
  44. package/dist/private-fungi-model-provider.js +6 -1
  45. package/dist/private-fungi-pending-logouts.d.ts +1 -1
  46. package/dist/private-fungi-pending-logouts.js +1 -2
  47. package/dist/private-fungi-service-logout.d.ts +44 -0
  48. package/dist/private-fungi-service-logout.js +59 -0
  49. package/dist/private-invocation-computer.d.ts +24 -3
  50. package/dist/private-invocation-computer.js +101 -62
  51. package/dist/private-invocation-files.d.ts +16 -1
  52. package/dist/private-invocation-files.js +154 -83
  53. package/dist/private-local-acp-stdio.js +3 -0
  54. package/dist/private-local-admission-gate.d.ts +14 -0
  55. package/dist/private-local-admission-gate.js +52 -0
  56. package/dist/private-local-agent-control.d.ts +101 -0
  57. package/dist/private-local-agent-control.js +30 -0
  58. package/dist/private-local-agent-default-model.d.ts +12 -0
  59. package/dist/private-local-agent-default-model.js +31 -0
  60. package/dist/private-local-auth-rpc.d.ts +67 -7
  61. package/dist/private-local-auth-rpc.js +140 -17
  62. package/dist/private-local-bwrap.d.ts +13 -0
  63. package/dist/private-local-bwrap.js +70 -0
  64. package/dist/private-local-client-version.d.ts +5 -0
  65. package/dist/private-local-client-version.js +43 -0
  66. package/dist/private-local-computer-attach.d.ts +3 -3
  67. package/dist/private-local-computer-attach.js +17 -3
  68. package/dist/private-local-computer-processes.d.ts +6 -3
  69. package/dist/private-local-computer-processes.js +55 -31
  70. package/dist/private-local-config-root.d.ts +21 -0
  71. package/dist/private-local-config-root.js +19 -0
  72. package/dist/private-local-containment.d.ts +51 -0
  73. package/dist/private-local-containment.js +161 -0
  74. package/dist/private-local-delegation.d.ts +10 -3
  75. package/dist/private-local-delegation.js +70 -15
  76. package/dist/private-local-effective-model.d.ts +9 -1
  77. package/dist/private-local-effective-model.js +3 -0
  78. package/dist/private-local-exec-environment.d.ts +16 -0
  79. package/dist/private-local-exec-environment.js +11 -0
  80. package/dist/private-local-linux-sandbox.d.ts +8 -0
  81. package/dist/private-local-linux-sandbox.js +67 -0
  82. package/dist/private-local-mail.d.ts +4 -2
  83. package/dist/private-local-mail.js +41 -33
  84. package/dist/private-local-mesh-enrollment.d.ts +34 -0
  85. package/dist/private-local-mesh-enrollment.js +188 -0
  86. package/dist/private-local-mesh-links.d.ts +21 -0
  87. package/dist/private-local-mesh-links.js +229 -0
  88. package/dist/private-local-mesh-record.d.ts +120 -0
  89. package/dist/private-local-mesh-record.js +55 -0
  90. package/dist/private-local-process-identity.d.ts +20 -0
  91. package/dist/private-local-process-identity.js +40 -0
  92. package/dist/private-local-runtime-host.d.ts +64 -13
  93. package/dist/private-local-runtime-host.js +361 -172
  94. package/dist/private-local-sandbox-policy.d.ts +21 -0
  95. package/dist/private-local-sandbox-policy.js +121 -0
  96. package/dist/private-local-sandbox.d.ts +37 -0
  97. package/dist/private-local-sandbox.js +114 -0
  98. package/dist/private-local-seatbelt.d.ts +24 -0
  99. package/dist/private-local-seatbelt.js +68 -0
  100. package/dist/private-local-service-contract.d.ts +91 -11
  101. package/dist/private-local-service-discovery.d.ts +129 -13
  102. package/dist/private-local-service-discovery.js +148 -47
  103. package/dist/private-local-service-lease.d.ts +16 -0
  104. package/dist/private-local-service-lease.js +105 -0
  105. package/dist/private-local-service-listener.d.ts +9 -2
  106. package/dist/private-local-service-listener.js +260 -64
  107. package/dist/private-local-service-log.d.ts +128 -0
  108. package/dist/private-local-service-log.js +321 -0
  109. package/dist/private-local-service-owner.d.ts +87 -1519
  110. package/dist/private-local-service-owner.js +1069 -71
  111. package/dist/private-local-service-process.js +3 -1
  112. package/dist/private-local-service-termination.d.ts +7 -0
  113. package/dist/private-local-service-termination.js +67 -0
  114. package/dist/private-local-service.d.ts +31 -2
  115. package/dist/private-local-service.js +402 -126
  116. package/dist/private-local-session-discovery.d.ts +52 -5
  117. package/dist/private-local-session-discovery.js +132 -11
  118. package/dist/private-local-shiitake-host.js +2 -2
  119. package/dist/private-local-startup-model.d.ts +51 -0
  120. package/dist/private-local-startup-model.js +86 -0
  121. package/dist/private-local-stop-all.d.ts +27 -0
  122. package/dist/private-local-stop-all.js +26 -0
  123. package/dist/private-local-ui-attachment.d.ts +19 -0
  124. package/dist/private-local-ui-attachment.js +71 -0
  125. package/dist/private-local-worker-server.d.ts +1 -0
  126. package/dist/private-local-worker-server.js +13 -5
  127. package/dist/private-mcp-form-contract.d.ts +186 -0
  128. package/dist/private-mcp-form-contract.js +53 -0
  129. package/dist/private-mcp-form-elicitation.d.ts +18 -0
  130. package/dist/private-mcp-form-elicitation.js +97 -0
  131. package/dist/private-mcp-form-session.d.ts +172 -0
  132. package/dist/private-mcp-form-session.js +148 -0
  133. package/dist/private-openai-claims.d.ts +30 -0
  134. package/dist/private-openai-claims.js +69 -0
  135. package/dist/private-oyster-mcp-auth.d.ts +13 -0
  136. package/dist/private-oyster-mcp-auth.js +57 -0
  137. package/dist/private-provider-rejection.d.ts +15 -0
  138. package/dist/private-provider-rejection.js +24 -0
  139. package/dist/private-sqlite-owner.d.ts +34 -2
  140. package/dist/private-sqlite-owner.js +103 -37
  141. package/dist/private-team-computers.d.ts +4 -4
  142. package/dist/private-team-computers.js +11 -2
  143. package/dist/seatbelt/safe-base.sbpl +79 -0
  144. package/dist/subscription-activation.d.ts +9 -0
  145. package/dist/subscription-activation.js +62 -0
  146. package/dist/subscription-identity.d.ts +23 -0
  147. package/dist/subscription-identity.js +124 -0
  148. package/dist/subscription-turn.d.ts +17 -0
  149. package/dist/subscription-turn.js +143 -0
  150. package/dist/team-apps.d.ts +2 -2
  151. package/dist/team-apps.js +7 -2
  152. package/native/bin/LICENSE.bubblewrap +502 -0
  153. package/native/bin/LICENSE.libcap +112 -0
  154. package/native/bin/LICENSE.sandbox-runtime +201 -0
  155. package/native/bin/SOURCE.bubblewrap.tar.xz +0 -0
  156. package/native/bin/bwrap-build-source/Dockerfile +48 -0
  157. package/native/bin/bwrap-build-source/README.md +96 -0
  158. package/native/bin/bwrap-build-source/build.mjs +52 -0
  159. package/native/bin/bwrap-build-source/linux-arm64.ini +14 -0
  160. package/native/bin/bwrap-build-source/pin.mjs +21 -0
  161. package/native/bin/bwrap-build-source/record.mjs +74 -0
  162. package/native/bin/bwrap-build-source/vendor/LICENSE.sandbox-runtime +201 -0
  163. package/native/bin/bwrap-build-source/vendor/README.md +23 -0
  164. package/native/bin/bwrap-build-source/vendor/seccomp-unix-block.c +280 -0
  165. package/native/bin/bwrap-build.json +44 -0
  166. package/native/bin/fungi-bwrap +0 -0
  167. package/native/bin/fungi-bwrap.sha256 +1 -0
  168. package/native/bin/fungi-seccomp.bpf +0 -0
  169. package/native/bin/fungi-seccomp.bpf.sha256 +1 -0
  170. package/package.json +44 -31
  171. package/dist/private-local-model-settings.d.ts +0 -5
  172. package/dist/private-local-model-settings.js +0 -31
  173. package/dist/private-local-subagents.d.ts +0 -27
  174. package/dist/private-local-subagents.js +0 -77
@@ -86,12 +86,14 @@ function localDirectory(manager, databasePath, invocationRoot) {
86
86
  const selected = await manager.connect({
87
87
  invocationRoot: input.cwd,
88
88
  databasePath,
89
+ uiAttachmentLifetime: "connection",
89
90
  }, { signal: input.signal });
90
91
  try {
91
92
  const created = await manager.createSession({
92
93
  invocationRoot: input.cwd,
93
94
  databasePath: selected.databasePath,
94
95
  requestId: createPrivateLocalSessionRequestId(),
96
+ uiAttachmentLifetime: "connection",
95
97
  }, { signal: input.signal });
96
98
  return binding(created);
97
99
  }
@@ -105,6 +107,7 @@ function localDirectory(manager, databasePath, invocationRoot) {
105
107
  invocationRoot: input.cwd,
106
108
  databasePath,
107
109
  sessionId: input.sessionId,
110
+ uiAttachmentLifetime: "connection",
108
111
  }, { signal: input.signal });
109
112
  return binding(connected);
110
113
  },
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Admissions of work to one Agent share it as short leases; an archive takes the
3
+ * Agent exclusively. So an archive sees every command admitted before it, and
4
+ * work offered after it finds the Agent archived: nothing runs in between.
5
+ */
6
+ export declare class AdmissionGate {
7
+ #private;
8
+ /** Nothing holds or waits for this gate, so it can be forgotten. */
9
+ get idle(): boolean;
10
+ /** Hold a shared lease; the returned release may be called more than once. */
11
+ lease(): Promise<() => void>;
12
+ /** Run alone: after every held lease is released and before any new one. */
13
+ exclusive<Value>(run: () => Promise<Value>): Promise<Value>;
14
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Admissions of work to one Agent share it as short leases; an archive takes the
3
+ * Agent exclusively. So an archive sees every command admitted before it, and
4
+ * work offered after it finds the Agent archived: nothing runs in between.
5
+ */
6
+ export class AdmissionGate {
7
+ #leases = 0;
8
+ #exclusive = false;
9
+ #waiters = [];
10
+ #changed() {
11
+ for (const wake of this.#waiters.splice(0))
12
+ wake();
13
+ }
14
+ #change() {
15
+ return new Promise((resolve) => {
16
+ this.#waiters.push(resolve);
17
+ });
18
+ }
19
+ /** Nothing holds or waits for this gate, so it can be forgotten. */
20
+ get idle() {
21
+ return this.#leases === 0 && !this.#exclusive && this.#waiters.length === 0;
22
+ }
23
+ /** Hold a shared lease; the returned release may be called more than once. */
24
+ async lease() {
25
+ while (this.#exclusive)
26
+ await this.#change();
27
+ this.#leases += 1;
28
+ let held = true;
29
+ return () => {
30
+ if (!held)
31
+ return;
32
+ held = false;
33
+ this.#leases -= 1;
34
+ this.#changed();
35
+ };
36
+ }
37
+ /** Run alone: after every held lease is released and before any new one. */
38
+ async exclusive(run) {
39
+ while (this.#exclusive)
40
+ await this.#change();
41
+ this.#exclusive = true;
42
+ try {
43
+ while (this.#leases > 0)
44
+ await this.#change();
45
+ return await run();
46
+ }
47
+ finally {
48
+ this.#exclusive = false;
49
+ this.#changed();
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,101 @@
1
+ import { type AgentControlAgent, type AgentControlDirectory } from "@fungi.computer/shiitake/agent-control";
2
+ import { createConversationAgents } from "@fungi.computer/shiitake-extensions/roster/conversation-agents";
3
+ /** One Agent creating an Agent, as the project's Agent directory receives it. */
4
+ export type LocalCreateRequest = Parameters<NonNullable<AgentControlDirectory["create"]>>[0] & Readonly<{
5
+ /** The Agent creating, resolved by the creator's own host, never by the caller. */
6
+ createdBy: string;
7
+ }>;
8
+ /**
9
+ * One ordinary Agent as the project's directory lists it. `unreadable` is
10
+ * set when this Agent's own store cannot be read (for
11
+ * example one written before the current schema); only that row is affected.
12
+ */
13
+ export type LocalAgentControlAgent = AgentControlAgent & Readonly<{
14
+ unreadable?: string;
15
+ }>;
16
+ /**
17
+ * Who archives or restores an Agent: an Agent, named by its own host and never
18
+ * by the caller, or a person at the CLI or TUI. A person may archive any
19
+ * Agent; an Agent only one it created.
20
+ */
21
+ export type LocalArchiver = Readonly<{
22
+ kind: "agent";
23
+ agentId: string;
24
+ }> | Readonly<{
25
+ kind: "human";
26
+ }>;
27
+ /** One archive or restore request, as the project's Agent directory receives it. */
28
+ export type LocalArchiveRequest = Readonly<{
29
+ agentId: string;
30
+ by: LocalArchiver;
31
+ }>;
32
+ /**
33
+ * The project's Agent directory a Local host receives. Its create names the
34
+ * creating Agent, so the directory owner can hold the created Agent to what
35
+ * the creator holds; its archive names who archives.
36
+ */
37
+ export type LocalAgentControlDirectory = Omit<AgentControlDirectory, "create" | "list" | "archive" | "unarchive"> & Readonly<{
38
+ list: (signal: AbortSignal, options?: Readonly<{
39
+ archived?: boolean;
40
+ }>) => Promise<readonly LocalAgentControlAgent[]>;
41
+ create: (input: LocalCreateRequest, signal: AbortSignal) => Promise<AgentControlAgent>;
42
+ archive: (input: LocalArchiveRequest, signal: AbortSignal) => Promise<void>;
43
+ unarchive: (input: LocalArchiveRequest, signal: AbortSignal) => Promise<void>;
44
+ }>;
45
+ /**
46
+ * Ordinary Agent controls bound to this authenticated creating Agent; each
47
+ * conversation's accepted creates and sends are recorded in this Agent's own
48
+ * store for its footer.
49
+ */
50
+ export declare function createPrivateLocalAgentControl(input: Readonly<{
51
+ agentId: string;
52
+ directory: LocalAgentControlDirectory;
53
+ storage: Parameters<typeof createConversationAgents>[0];
54
+ }>): {
55
+ control: Readonly<{
56
+ id: string;
57
+ acquire: (context: import("@fungi.computer/mycelium").CapabilitySourceContext) => Promise<import("@fungi.computer/mycelium").CapabilitySourceAcquisition>;
58
+ }>;
59
+ source: Readonly<{
60
+ read: (sessionId: string, limit: number) => Promise<Readonly<{
61
+ agents: readonly import("@fungi.computer/shiitake-extensions/subagents/host").SubagentRecord[];
62
+ more: number;
63
+ }>>;
64
+ open: (agentId: string, signal: AbortSignal) => Promise<Readonly<{
65
+ agent: import("@fungi.computer/shiitake-extensions/subagents/host").SubagentAgent;
66
+ release: () => Promise<void>;
67
+ }>>;
68
+ }>;
69
+ directory: Readonly<{
70
+ list: (signal: AbortSignal, options?: Readonly<{
71
+ archived?: boolean;
72
+ }>) => Promise<readonly AgentControlAgent[]>;
73
+ send?: (input: import("@fungi.computer/shiitake/agent-control").AgentDelegationInput, sourceSessionId: string, signal: AbortSignal, invocation?: import("@fungi.computer/shiitake/agent-control").AgentDelegationInvocation) => Promise<import("@fungi.computer/shiitake").Accepted>;
74
+ recall?: (input: import("@fungi.computer/shiitake/agent-control").AgentRecallInput, sourceSessionId: string, signal: AbortSignal) => Promise<import("@fungi.computer/shiitake").CancelResult>;
75
+ create?: (input: Readonly<{
76
+ requestId: string;
77
+ name?: string;
78
+ model?: import("@fungi.computer/shiitake").ModelPreference;
79
+ extensions?: readonly string[];
80
+ }>, signal: AbortSignal) => Promise<AgentControlAgent>;
81
+ setModel?: (input: Readonly<{
82
+ agentId: string;
83
+ model: import("@fungi.computer/shiitake").ModelPreference;
84
+ }>, signal: AbortSignal) => Promise<void>;
85
+ archive?: (input: Readonly<{
86
+ agentId: string;
87
+ }>, signal: AbortSignal) => Promise<void>;
88
+ unarchive?: (input: Readonly<{
89
+ agentId: string;
90
+ }>, signal: AbortSignal) => Promise<void>;
91
+ models?: (signal: AbortSignal) => Promise<readonly import("@fungi.computer/shiitake/agent-control").AgentControlModel[]>;
92
+ checkModel?: (input: Readonly<{
93
+ agentId: string;
94
+ sessionId: string;
95
+ }>, signal: AbortSignal) => Promise<void>;
96
+ open: (agentId: string, signal: AbortSignal) => Promise<Readonly<{
97
+ agent: import("@fungi.computer/shiitake").ShiitakeAgent;
98
+ release: () => Promise<void>;
99
+ }>>;
100
+ }>;
101
+ };
@@ -0,0 +1,30 @@
1
+ import { AgentCreateRefused, } from "@fungi.computer/shiitake/agent-control";
2
+ import { createConversationAgents } from "@fungi.computer/shiitake-extensions/roster/conversation-agents";
3
+ /**
4
+ * Ordinary Agent controls bound to this authenticated creating Agent; each
5
+ * conversation's accepted creates and sends are recorded in this Agent's own
6
+ * store for its footer.
7
+ */
8
+ export function createPrivateLocalAgentControl(input) {
9
+ const conversations = createConversationAgents(input.storage);
10
+ function bound(parentSessionId) {
11
+ const { create, archive, unarchive } = input.directory;
12
+ const by = { kind: "agent", agentId: input.agentId };
13
+ return {
14
+ ...input.directory,
15
+ archive: ({ agentId }, signal) => archive({ agentId, by }, signal),
16
+ unarchive: ({ agentId }, signal) => unarchive({ agentId, by }, signal),
17
+ create: async (request, signal) => {
18
+ if (parentSessionId === undefined)
19
+ throw new AgentCreateRefused("no_creator", "creation requires an invoking Session");
20
+ return create({ ...request, createdBy: input.agentId }, signal);
21
+ },
22
+ };
23
+ }
24
+ const control = conversations.control(bound);
25
+ return {
26
+ control,
27
+ source: conversations.source(input.directory),
28
+ directory: conversations.recording(undefined, bound(undefined)),
29
+ };
30
+ }
@@ -0,0 +1,12 @@
1
+ import type { PrivateSqliteDatabase } from "./private-sqlite-owner.js";
2
+ import { type ModelPreference } from "@fungi.computer/shiitake/model-preference";
3
+ /** Create the Agent default's table in a store this host owns. */
4
+ export declare function ensureAgentDefaultModelTable(database: PrivateSqliteDatabase): void;
5
+ /** Keep an Agent's first default; later changes go through the writer. */
6
+ export declare function seedAgentDefaultModel(database: PrivateSqliteDatabase, model: ModelPreference | null): void;
7
+ /** The stored default as written (JSON text); undefined when the row is missing. */
8
+ export declare function readAgentDefaultModelJson(database: PrivateSqliteDatabase): string | undefined;
9
+ /** Read the Agent default a store holds; null when none is set. */
10
+ export declare function readAgentDefaultModel(database: PrivateSqliteDatabase): ModelPreference | null;
11
+ /** Replace the Agent default, thinking level included. */
12
+ export declare function writeAgentDefaultModel(database: PrivateSqliteDatabase, model: ModelPreference): void;
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ import { parseModelPreferenceJson, } from "@fungi.computer/shiitake/model-preference";
3
+ /** The one module that owns the Agent default's table SQL. */
4
+ const settingsTable = "private_local_model_settings";
5
+ const storedModelSchema = z.string().optional();
6
+ /** Create the Agent default's table in a store this host owns. */
7
+ export function ensureAgentDefaultModelTable(database) {
8
+ database.exec(`CREATE TABLE IF NOT EXISTS ${settingsTable} (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), model TEXT NOT NULL) STRICT`);
9
+ }
10
+ /** Keep an Agent's first default; later changes go through the writer. */
11
+ export function seedAgentDefaultModel(database, model) {
12
+ database
13
+ .prepare(`INSERT OR IGNORE INTO ${settingsTable} VALUES (1, ?)`)
14
+ .run(JSON.stringify(model));
15
+ }
16
+ /** The stored default as written (JSON text); undefined when the row is missing. */
17
+ export function readAgentDefaultModelJson(database) {
18
+ return storedModelSchema.parse(database
19
+ .prepare(`SELECT model FROM ${settingsTable} WHERE singleton = 1`)
20
+ .get()?.model);
21
+ }
22
+ /** Read the Agent default a store holds; null when none is set. */
23
+ export function readAgentDefaultModel(database) {
24
+ return parseModelPreferenceJson(readAgentDefaultModelJson(database));
25
+ }
26
+ /** Replace the Agent default, thinking level included. */
27
+ export function writeAgentDefaultModel(database, model) {
28
+ database
29
+ .prepare(`UPDATE ${settingsTable} SET model = ? WHERE singleton = 1`)
30
+ .run(JSON.stringify(model));
31
+ }
@@ -1,6 +1,7 @@
1
1
  import type { AuthInteraction, AuthEvent } from "@earendil-works/pi-ai";
2
2
  import { RpcTarget, type RpcStub } from "@fungi.computer/capnweb";
3
3
  import { z } from "zod";
4
+ import { type FungiAccountLogoutRequest, type FungiAccountLogoutResult } from "./private-fungi-service-logout.js";
4
5
  import { runLocalAuthCli, type LocalAuthResult } from "./auth-cli.js";
5
6
  declare const requestSchema: z.ZodObject<{
6
7
  command: z.ZodEnum<{
@@ -10,8 +11,8 @@ declare const requestSchema: z.ZodObject<{
10
11
  }>;
11
12
  provider: z.ZodOptional<z.ZodString>;
12
13
  authType: z.ZodOptional<z.ZodEnum<{
13
- api_key: "api_key";
14
14
  oauth: "oauth";
15
+ api_key: "api_key";
15
16
  }>>;
16
17
  method: z.ZodOptional<z.ZodString>;
17
18
  }, z.core.$strip>;
@@ -38,6 +39,58 @@ declare const promptSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
38
39
  }, z.core.$strip>>;
39
40
  }, z.core.$strip>], "type">;
40
41
  export type AuthPromptRequest = z.infer<typeof promptSchema>;
42
+ /** What one auth RPC operation returns: a result or a typed failure. */
43
+ export type LocalAuthWireResult = z.output<typeof wireResultSchema>;
44
+ declare const wireResultSchema: z.ZodUnion<readonly [z.ZodDiscriminatedUnion<[z.ZodObject<{
45
+ kind: z.ZodLiteral<"completed">;
46
+ }, z.core.$strip>, z.ZodObject<{
47
+ kind: z.ZodLiteral<"status">;
48
+ providers: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
49
+ providerId: z.ZodString;
50
+ providerName: z.ZodString;
51
+ state: z.ZodLiteral<"unconfigured">;
52
+ }, z.core.$strip>, z.ZodObject<{
53
+ providerId: z.ZodString;
54
+ providerName: z.ZodString;
55
+ state: z.ZodLiteral<"configured">;
56
+ type: z.ZodEnum<{
57
+ oauth: "oauth";
58
+ api_key: "api_key";
59
+ }>;
60
+ }, z.core.$strip>], "state">>;
61
+ }, z.core.$strip>], "kind">, z.ZodObject<{
62
+ kind: z.ZodLiteral<"failed">;
63
+ failure: z.ZodDiscriminatedUnion<[z.ZodObject<{
64
+ category: z.ZodLiteral<"identity-unavailable">;
65
+ detail: z.ZodOptional<z.ZodObject<{
66
+ claim: z.ZodEnum<{
67
+ access_token: "access_token";
68
+ "access_token.iss": "access_token.iss";
69
+ "access_token.sub": "access_token.sub";
70
+ }>;
71
+ problem: z.ZodEnum<{
72
+ absent: "absent";
73
+ malformed: "malformed";
74
+ invalid: "invalid";
75
+ }>;
76
+ claimNames: z.ZodArray<z.ZodString>;
77
+ authClaimNames: z.ZodOptional<z.ZodArray<z.ZodString>>;
78
+ }, z.core.$strict>>;
79
+ }, z.core.$strict>, z.ZodObject<{
80
+ category: z.ZodLiteral<"callback-port-in-use">;
81
+ }, z.core.$strict>, z.ZodObject<{
82
+ category: z.ZodLiteral<"oauth-state-mismatch">;
83
+ }, z.core.$strict>, z.ZodObject<{
84
+ category: z.ZodLiteral<"token-exchange-rejected">;
85
+ status: z.ZodNumber;
86
+ }, z.core.$strict>, z.ZodObject<{
87
+ category: z.ZodLiteral<"cancelled">;
88
+ }, z.core.$strict>, z.ZodObject<{
89
+ category: z.ZodLiteral<"no-key-entered">;
90
+ }, z.core.$strict>, z.ZodObject<{
91
+ category: z.ZodLiteral<"unknown">;
92
+ }, z.core.$strict>], "category">;
93
+ }, z.core.$strict>]>;
41
94
  declare class AuthPromptTarget extends RpcTarget {
42
95
  #private;
43
96
  constructor(prompt: AuthPromptRequest, interaction: AuthInteraction);
@@ -54,16 +107,23 @@ declare class AuthPresenterTarget extends RpcTarget {
54
107
  /** The auth-only RPC door shared by its producer and remote client. */
55
108
  export type PrivateLocalAuthEndpoint = RpcTarget & {
56
109
  cancel(): void;
57
- run(input: PrivateLocalAuthRequest, presenter: RpcStub<AuthPresenterTarget>): Promise<LocalAuthResult>;
110
+ signOutFungi(input: FungiAccountLogoutRequest): Promise<FungiAccountLogoutResult>;
111
+ run(input: PrivateLocalAuthRequest, presenter: RpcStub<AuthPresenterTarget>): Promise<LocalAuthWireResult>;
58
112
  };
59
- /** One transient connection runs one operation through the existing Pi owner. */
113
+ /** One transient connection runs one provider or Fungi-account operation. */
60
114
  export declare class PrivateLocalAuthTarget extends RpcTarget implements PrivateLocalAuthEndpoint {
61
115
  #private;
62
- constructor(run?: typeof runLocalAuthCli);
116
+ constructor(run: typeof runLocalAuthCli, signOutAccount: (input: FungiAccountLogoutRequest) => Promise<FungiAccountLogoutResult>);
117
+ signOutFungi(input: FungiAccountLogoutRequest): Promise<FungiAccountLogoutResult>;
63
118
  cancel(): void;
64
119
  [Symbol.dispose](): void;
65
- run(input: PrivateLocalAuthRequest, presenter: RpcStub<AuthPresenterTarget>): Promise<LocalAuthResult>;
120
+ run(input: PrivateLocalAuthRequest, presenter: RpcStub<AuthPresenterTarget>): Promise<LocalAuthWireResult>;
66
121
  }
67
- /** Takes ownership of a dedicated auth RPC connection, never the Session client. */
68
- export declare function runPrivateLocalAuthRemote(remote: RpcStub<PrivateLocalAuthEndpoint>, request: PrivateLocalAuthRequest, interaction: AuthInteraction): Promise<LocalAuthResult>;
122
+ /**
123
+ * Takes ownership of a dedicated auth RPC connection, never the Session client.
124
+ * A cancel the caller asked for is awaited before the connection is released,
125
+ * so an ordinary cancel is never cut off by its own teardown; a cancel that
126
+ * genuinely fails is handed to `reportCleanupFailure` with its cause.
127
+ */
128
+ export declare function runPrivateLocalAuthRemote(remote: RpcStub<PrivateLocalAuthEndpoint>, request: PrivateLocalAuthRequest, interaction: AuthInteraction, reportCleanupFailure: (cause: unknown) => void): Promise<LocalAuthResult>;
69
129
  export type { AuthPresenterTarget, AuthPromptTarget };
@@ -1,6 +1,10 @@
1
1
  import { RpcTarget } from "@fungi.computer/capnweb";
2
2
  import { z } from "zod";
3
- import { localAuthInputLimit, runLocalAuthCli, } from "./auth-cli.js";
3
+ import { fungiAccountLogoutRequestSchema, fungiAccountLogoutResultSchema, } from "./private-fungi-service-logout.js";
4
+ import { LocalAuthRefusal, localAuthInputLimit, runLocalAuthCli, } from "./auth-cli.js";
5
+ import { openAIIdentityDetailSchema } from "./private-openai-claims.js";
6
+ import { CredentialActivationError } from "./subscription-activation.js";
7
+ import { OpenAIIdentityUnavailable } from "./subscription-identity.js";
4
8
  const requestSchema = z.object({
5
9
  command: z.enum(["login", "logout", "status"]),
6
10
  provider: z.string().optional(),
@@ -76,6 +80,103 @@ const eventSchema = z.discriminatedUnion("type", [
76
80
  }),
77
81
  ]);
78
82
  const answerSchema = z.string().max(localAuthInputLimit);
83
+ /**
84
+ * The closed set of failures that cross the service. Provider errors can
85
+ * contain credential material, so no free text, cause or provider message
86
+ * crosses: only a fixed category, HTTP status, or missing-person claim NAMES.
87
+ */
88
+ const localAuthFailureSchema = z.discriminatedUnion("category", [
89
+ z.strictObject({
90
+ category: z.literal("identity-unavailable"),
91
+ detail: openAIIdentityDetailSchema.optional(),
92
+ }),
93
+ z.strictObject({ category: z.literal("callback-port-in-use") }),
94
+ z.strictObject({ category: z.literal("oauth-state-mismatch") }),
95
+ z.strictObject({
96
+ category: z.literal("token-exchange-rejected"),
97
+ status: z.number().int().min(100).max(599),
98
+ }),
99
+ z.strictObject({ category: z.literal("cancelled") }),
100
+ z.strictObject({ category: z.literal("no-key-entered") }),
101
+ z.strictObject({ category: z.literal("unknown") }),
102
+ ]);
103
+ const wireResultSchema = z.union([
104
+ authResultSchema,
105
+ z.strictObject({
106
+ kind: z.literal("failed"),
107
+ failure: localAuthFailureSchema,
108
+ }),
109
+ ]);
110
+ /** Pi 1.3.0's fixed error prefixes; never retain a provider body suffix. */
111
+ const callbackPortInUse = /^Port \d+ is in use\b/u;
112
+ const oauthStateMismatch = "OAuth state mismatch";
113
+ const openAITokenRequestFailed = /^OpenAI OAuth token request failed \(([1-5]\d{2})\)/u;
114
+ function classifyFailure(cause, aborted) {
115
+ if (aborted)
116
+ return { category: "cancelled" };
117
+ if (cause instanceof LocalAuthRefusal)
118
+ return { category: "no-key-entered" };
119
+ if (cause instanceof OpenAIIdentityUnavailable)
120
+ return { category: "identity-unavailable", detail: cause.detail };
121
+ if (cause instanceof CredentialActivationError &&
122
+ cause.category === "identity-unavailable")
123
+ return { category: "identity-unavailable" };
124
+ if (cause instanceof Error) {
125
+ const tokenRequest = openAITokenRequestFailed.exec(cause.message);
126
+ if (tokenRequest !== null)
127
+ return {
128
+ category: "token-exchange-rejected",
129
+ status: Number(tokenRequest[1]),
130
+ };
131
+ }
132
+ if (cause instanceof Error && callbackPortInUse.test(cause.message))
133
+ return { category: "callback-port-in-use" };
134
+ if (cause instanceof Error && cause.message === oauthStateMismatch)
135
+ return { category: "oauth-state-mismatch" };
136
+ return { category: "unknown" };
137
+ }
138
+ /** Fixed wording per category; the dialog shows exactly this. */
139
+ function failureText(failure) {
140
+ switch (failure.category) {
141
+ case "identity-unavailable": {
142
+ const detail = failure.detail;
143
+ if (detail === undefined)
144
+ return "Sign-in could not establish the account identity (identity-unavailable). Nothing was saved.";
145
+ const auth = detail.authClaimNames === undefined
146
+ ? ""
147
+ : "; auth claims: " + (detail.authClaimNames.join(", ") || "none");
148
+ return ("OpenAI sign-in did not identify the person (identity-unavailable: " +
149
+ detail.claim +
150
+ " " +
151
+ detail.problem +
152
+ "; claims: " +
153
+ (detail.claimNames.join(", ") || "none") +
154
+ auth +
155
+ "). Nothing was saved.");
156
+ }
157
+ case "callback-port-in-use":
158
+ return "The sign-in callback port is in use, probably by another sign-in or the Codex CLI. Close it and try again.";
159
+ case "oauth-state-mismatch":
160
+ return "The sign-in redirect did not belong to this attempt. Start the sign-in again.";
161
+ case "token-exchange-rejected":
162
+ return `OpenAI refused the sign-in token exchange (HTTP ${String(failure.status)}).`;
163
+ case "cancelled":
164
+ return "Authentication cancelled";
165
+ case "no-key-entered":
166
+ return new LocalAuthRefusal().message;
167
+ case "unknown":
168
+ return "Local authentication failed";
169
+ }
170
+ }
171
+ /** The client-side error for one typed failure; its message is fixed text. */
172
+ class LocalAuthFailure extends Error {
173
+ failure;
174
+ constructor(failure) {
175
+ super(failureText(failure));
176
+ this.failure = failure;
177
+ this.name = "LocalAuthFailure";
178
+ }
179
+ }
79
180
  class AuthPromptTarget extends RpcTarget {
80
181
  #controller = new AbortController();
81
182
  #result;
@@ -167,14 +268,22 @@ class AuthPresenterTarget extends RpcTarget {
167
268
  this.#interaction.notify(decoded);
168
269
  }
169
270
  }
170
- /** One transient connection runs one operation through the existing Pi owner. */
271
+ /** One transient connection runs one provider or Fungi-account operation. */
171
272
  export class PrivateLocalAuthTarget extends RpcTarget {
172
273
  #controller = new AbortController();
173
274
  #used = false;
174
275
  #run;
175
- constructor(run = runLocalAuthCli) {
276
+ #signOutAccount;
277
+ constructor(run, signOutAccount) {
176
278
  super();
177
279
  this.#run = run;
280
+ this.#signOutAccount = signOutAccount;
281
+ }
282
+ async signOutFungi(input) {
283
+ if (this.#used)
284
+ throw new Error("Authentication connection already used");
285
+ this.#used = true;
286
+ return fungiAccountLogoutResultSchema.parse(await this.#signOutAccount(fungiAccountLogoutRequestSchema.parse(input)));
178
287
  }
179
288
  cancel() {
180
289
  this.#controller.abort();
@@ -235,39 +344,53 @@ export class PrivateLocalAuthTarget extends RpcTarget {
235
344
  });
236
345
  return authResultSchema.parse(result);
237
346
  }
238
- catch {
239
- // Provider errors can contain credential material; do not serialize them.
240
- throw new Error(signal.aborted
241
- ? "Authentication cancelled"
242
- : "Local authentication failed");
347
+ catch (cause) {
348
+ return {
349
+ kind: "failed",
350
+ failure: classifyFailure(cause, signal.aborted),
351
+ };
243
352
  }
244
353
  finally {
245
354
  this.cancel();
246
355
  }
247
356
  }
248
357
  }
249
- /** Takes ownership of a dedicated auth RPC connection, never the Session client. */
250
- export async function runPrivateLocalAuthRemote(remote, request, interaction) {
358
+ /**
359
+ * Takes ownership of a dedicated auth RPC connection, never the Session client.
360
+ * A cancel the caller asked for is awaited before the connection is released,
361
+ * so an ordinary cancel is never cut off by its own teardown; a cancel that
362
+ * genuinely fails is handed to `reportCleanupFailure` with its cause.
363
+ */
364
+ export async function runPrivateLocalAuthRemote(remote, request, interaction, reportCleanupFailure) {
251
365
  const controller = new AbortController();
252
366
  const signal = interaction.signal === undefined
253
367
  ? controller.signal
254
368
  : AbortSignal.any([controller.signal, interaction.signal]);
255
369
  const presenter = new AuthPresenterTarget({ ...interaction, signal });
370
+ let cancellation;
256
371
  const cancel = () => {
257
- void remote.cancel().catch((cause) => {
258
- console.warn("Local authentication cancellation failed", cause instanceof Error ? cause.name : "NonError");
259
- remote[Symbol.dispose]();
260
- });
372
+ cancellation = (async () => {
373
+ await remote.cancel();
374
+ })();
261
375
  };
262
376
  signal.addEventListener("abort", cancel, { once: true });
263
377
  try {
264
378
  signal.throwIfAborted();
265
- const result = await remote.run(request, presenter);
266
- return authResultSchema.parse(result);
379
+ const result = wireResultSchema.parse(await remote.run(request, presenter));
380
+ if (result.kind === "failed")
381
+ throw new LocalAuthFailure(result.failure);
382
+ return result;
267
383
  }
268
384
  finally {
269
385
  signal.removeEventListener("abort", cancel);
270
386
  controller.abort();
271
- remote[Symbol.dispose]();
387
+ try {
388
+ const [cancelled] = await Promise.allSettled([cancellation]);
389
+ if (cancelled.status === "rejected")
390
+ reportCleanupFailure(cancelled.reason);
391
+ }
392
+ finally {
393
+ remote[Symbol.dispose]();
394
+ }
272
395
  }
273
396
  }
@@ -0,0 +1,13 @@
1
+ export declare class LocalBwrapUnavailable extends Error {
2
+ readonly reason: "missing" | "invalid-digest" | "digest-mismatch" | "not-executable";
3
+ readonly name = "LocalBwrapUnavailable";
4
+ constructor(reason: "missing" | "invalid-digest" | "digest-mismatch" | "not-executable", message: string, options?: ErrorOptions);
5
+ }
6
+ export type LinuxSandboxPayload = {
7
+ readonly executable: string;
8
+ readonly seccompFilter: Uint8Array;
9
+ };
10
+ /** Check both packaged artifacts on every use; never cache verification. */
11
+ export declare function verifyLocalLinuxSandboxPayload(executable: string): LinuxSandboxPayload;
12
+ /** Same optional native-package lookup as apply_patch, with no PATH fallback. */
13
+ export declare function localLinuxSandboxPayload(): LinuxSandboxPayload;