@fungi.computer/agar 0.6.1 → 0.7.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 CHANGED
@@ -9,7 +9,7 @@ methods, connects its frontend to a host through typed capabilities, and gives
9
9
  App builders selected context, host-rendered permission requests and explicit
10
10
  cleanup. The host and resource owners authorize the work; the same App runs on
11
11
  Fungi's hosted Teams or on any host that composes
12
- [Oyster](https://oyster.shiit.app).
12
+ [Oyster](https://github.com/fungi-computer/oyster).
13
13
 
14
14
  ## Install or set up
15
15
 
package/dist/agents.d.ts CHANGED
@@ -56,14 +56,31 @@ export declare const appAgentListResultSchema: z.ZodLazy<z.ZodObject<{
56
56
  name: z.ZodString;
57
57
  }, z.core.$strict>>;
58
58
  }, z.core.$strict>>;
59
- /** Computer provisioning is a Team creation choice, separate from Home policy. */
60
- export declare const appAgentComputerSchema: Schema.Literals<readonly ["new", "none"]>;
59
+ /**
60
+ * Computer provisioning is a Team creation choice, separate from Home policy:
61
+ * a new Computer, none, or one of the Team's existing Computers.
62
+ */
63
+ export declare const appAgentComputerSchema: Schema.Union<readonly [Schema.Literals<readonly ["new", "none"]>, Schema.Struct<{
64
+ readonly computerId: Schema.String;
65
+ }>]>;
66
+ /** The creation-time Computer choice for a new Agent. */
67
+ export type AppAgentComputer = Schema.Schema.Type<typeof appAgentComputerSchema>;
68
+ /** One existing Team Computer a new Agent may start on. */
69
+ export declare const appAgentComputerChoiceSchema: Schema.Struct<{
70
+ readonly computerId: Schema.String;
71
+ readonly name: Schema.String;
72
+ readonly createdAt: Schema.Number;
73
+ }>;
74
+ /** An existing Team Computer offered when creating an Agent. */
75
+ export type AppAgentComputerChoice = Schema.Schema.Type<typeof appAgentComputerChoiceSchema>;
61
76
  /** Validates Agent creation input: a name, a caller-chosen request id, an
62
77
  * optional Computer provisioning choice and an optional chosen avatar. */
63
78
  export declare const appAgentCreateInputSchema: Schema.Struct<{
64
79
  readonly name: Schema.String;
65
80
  readonly requestId: Schema.String;
66
- readonly computer: Schema.optionalKey<Schema.Literals<readonly ["new", "none"]>>;
81
+ readonly computer: Schema.optionalKey<Schema.Union<readonly [Schema.Literals<readonly ["new", "none"]>, Schema.Struct<{
82
+ readonly computerId: Schema.String;
83
+ }>]>>;
67
84
  readonly avatar: Schema.optionalKey<Schema.declare<{
68
85
  v: 1;
69
86
  character: "mushroom" | "cat" | "burger";
@@ -102,6 +119,8 @@ export type AppAgentListHandle = {
102
119
  export type AppAgentCreateHandle = {
103
120
  /** Create a new Agent with the given name and request id. */
104
121
  create(input: AppAgentCreateInput): Promise<AppAgent>;
122
+ /** The Team Computers a new Agent may use; null when the host offers no choice. */
123
+ computers(): Promise<readonly AppAgentComputerChoice[] | null>;
105
124
  };
106
125
  /**
107
126
  * Live capability handle for listing and creating Agents in a Team or
@@ -118,7 +137,7 @@ export type AppAgentDirectoryHandle = AppAgentListHandle & AppAgentCreateHandle
118
137
  /** Capability to list the Agents visible to this App in read-only form. */
119
138
  export declare const agentList: CapabilityContract<AppAgentListHandle>;
120
139
  /** Capability to create new Agents and open transports to them. */
121
- export declare const agentManage: CapabilityContract<Pick<AppAgentDirectoryHandle, "create" | "open">>;
140
+ export declare const agentManage: CapabilityContract<Pick<AppAgentDirectoryHandle, "create" | "computers" | "open">>;
122
141
  /** Validates Agent rename input: the target Agent and its new display name. */
123
142
  export declare const appAgentRenameInputSchema: Schema.Struct<{
124
143
  readonly agentId: Schema.String;
@@ -217,6 +236,11 @@ export declare const agentSessionWrite: CapabilityContract<Pick<AppAgentDirector
217
236
  export declare function listAppAgents(capabilities: CapabilityDirectory): Promise<readonly AppAgent[]>;
218
237
  /** Create an Agent through the Team directory selected by the host. */
219
238
  export declare function createAppAgent(capabilities: CapabilityDirectory, input: AppAgentCreateInput): Promise<AppAgent>;
239
+ /**
240
+ * The Team Computers a new Agent may use, newest first, or null when the host
241
+ * makes no creation-time Computer choice.
242
+ */
243
+ export declare function listAppAgentComputers(capabilities: CapabilityDirectory): Promise<readonly AppAgentComputerChoice[] | null>;
220
244
  /**
221
245
  * A Fetch-based transport to one Agent's session endpoint, scoped to
222
246
  * this App and safe to hand to an HTTP client adapter.
package/dist/agents.js CHANGED
@@ -28,8 +28,26 @@ export const appAgentSchema = Schema.Struct({
28
28
  export const appAgentListResultSchema = z.lazy(() => z.strictObject({
29
29
  agents: z.array(z.strictObject({ agentId: z.string(), name: z.string() })),
30
30
  }));
31
- /** Computer provisioning is a Team creation choice, separate from Home policy. */
32
- export const appAgentComputerSchema = Schema.Literals(["new", "none"]);
31
+ /**
32
+ * Computer provisioning is a Team creation choice, separate from Home policy:
33
+ * a new Computer, none, or one of the Team's existing Computers.
34
+ */
35
+ export const appAgentComputerSchema = Schema.Union([
36
+ Schema.Literals(["new", "none"]),
37
+ Schema.Struct({ computerId: boundedIdentity }),
38
+ ]);
39
+ /** One existing Team Computer a new Agent may start on. */
40
+ export const appAgentComputerChoiceSchema = Schema.Struct({
41
+ computerId: boundedIdentity,
42
+ name: boundedIdentity,
43
+ createdAt: Schema.Number.pipe(Schema.check(Schema.isInt())),
44
+ });
45
+ /**
46
+ * The host's existing Computers for a new Agent, or null when the host makes
47
+ * no creation-time Computer choice at all.
48
+ */
49
+ const appAgentComputerChoicesSchema = Schema.NullOr(Schema.Array(appAgentComputerChoiceSchema));
50
+ const decodeComputerChoices = Schema.decodeUnknownSync(appAgentComputerChoicesSchema, { onExcessProperty: "error" });
33
51
  /** Validates Agent creation input: a name, a caller-chosen request id, an
34
52
  * optional Computer provisioning choice and an optional chosen avatar. */
35
53
  export const appAgentCreateInputSchema = Schema.Struct({
@@ -51,6 +69,7 @@ export const agentList = defineCapability("agent.list", 1, z.lazy(() => rpcHandl
51
69
  /** Capability to create new Agents and open transports to them. */
52
70
  export const agentManage = defineCapability("agent.manage", 1, z.lazy(() => rpcHandleSchema({
53
71
  open: rpcMethod(rpcFetchHandleSchema),
72
+ computers: rpcMethod(rpcDataSchema(z.unknown().transform((value) => decodeComputerChoices(value)))),
54
73
  create: rpcMethod(rpcDataSchema(z
55
74
  .unknown()
56
75
  .transform((value) => Schema.decodeUnknownSync(appAgentSchema)(value)))),
@@ -127,6 +146,16 @@ export async function createAppAgent(capabilities, input) {
127
146
  const agent = await capabilities.require(agentManage).create(request);
128
147
  return Schema.decodeUnknownSync(appAgentSchema)(agent);
129
148
  }
149
+ /**
150
+ * The Team Computers a new Agent may use, newest first, or null when the host
151
+ * makes no creation-time Computer choice.
152
+ */
153
+ export async function listAppAgentComputers(capabilities) {
154
+ const choices = decodeComputerChoices(await capabilities.require(agentManage).computers());
155
+ return choices === null
156
+ ? null
157
+ : [...choices].sort((a, b) => b.createdAt - a.createdAt);
158
+ }
130
159
  /** Keep read and write handles separate while owning their shared connection lifetime. */
131
160
  async function openOperationHandles(openRead, openWrite) {
132
161
  const read = await openRead();
@@ -20,7 +20,7 @@ export declare const computerList: CapabilityContract<{
20
20
  }): Promise<ComputerListing>;
21
21
  }>;
22
22
  export declare const computerRuntimeStateSchema: Schema.Union<readonly [Schema.Struct<{
23
- readonly state: Schema.Literals<readonly ["running", "sleeping", "starting"]>;
23
+ readonly state: Schema.Literals<readonly ["running", "sleeping", "starting", "start_failed"]>;
24
24
  }>, Schema.Struct<{
25
25
  readonly state: Schema.Literal<"queued">;
26
26
  readonly position: Schema.Number;
@@ -36,7 +36,7 @@ export declare const computerInstrumentSnapshotSchema: Schema.Struct<{
36
36
  readonly computerId: Schema.NonEmptyString;
37
37
  readonly name: Schema.NonEmptyString;
38
38
  readonly runtime: Schema.Union<readonly [Schema.Struct<{
39
- readonly state: Schema.Literals<readonly ["running", "sleeping", "starting"]>;
39
+ readonly state: Schema.Literals<readonly ["running", "sleeping", "starting", "start_failed"]>;
40
40
  }>, Schema.Struct<{
41
41
  readonly state: Schema.Literal<"queued">;
42
42
  readonly position: Schema.Number;
package/dist/computer.js CHANGED
@@ -22,7 +22,8 @@ export const computerList = defineCapability("computer.list", 1, z.lazy(() => rp
22
22
  })));
23
23
  export const computerRuntimeStateSchema = Schema.Union([
24
24
  Schema.Struct({
25
- state: Schema.Literals(["running", "sleeping", "starting"]),
25
+ // start_failed: a queued start ended without running; start again.
26
+ state: Schema.Literals(["running", "sleeping", "starting", "start_failed"]),
26
27
  }),
27
28
  Schema.Struct({
28
29
  state: Schema.Literal("queued"),
package/dist/inbox.d.ts CHANGED
@@ -78,8 +78,8 @@ export declare function watchMemberInbox(options: Readonly<{
78
78
  export declare function readTeamInboxNoticePage(transport: Pick<AppAgentTransport, "baseUrl" | "fetch">, after?: string | null): Promise<{
79
79
  readonly items: readonly ({
80
80
  readonly appId: import("@fungi.computer/puffball").AppId;
81
- readonly displayName: string;
82
81
  readonly createdAt: number;
82
+ readonly displayName: string;
83
83
  readonly dispositionId: string;
84
84
  readonly releaseIds: readonly import("@fungi.computer/puffball").ReleaseId[];
85
85
  readonly reason?: string | undefined;
package/dist/index.d.ts CHANGED
@@ -6,8 +6,8 @@ export type { Pending } from "./pending.js";
6
6
  export type { CapabilityContract, CapabilityDirectory, CapabilityRegistry, } from "./capabilities.js";
7
7
  export { agentAvatar, agentList, agentManage, agentRename, agentSessionRead, agentSessionWrite, agentToolPolicy, createAppAgent, renameAppAgent, setAppAgentAvatar, appAgentAvatarInputSchema, appAgentAvatarSchema, appAgentCreateInputSchema, appAgentRenameInputSchema, appAgentSchema, appAgentToolPolicyResultSchema, appAgentToolPolicyStatusSchema, listAppAgents, openAppAgent, } from "./agents.js";
8
8
  export type { AppAgent, AppAgentAvatarHandle, AppAgentAvatarInput, AppAgentCreateHandle, AppAgentCreateInput, AppAgentDirectoryHandle, AppAgentListHandle, AppAgentRenameHandle, AppAgentRenameInput, AppAgentToolPolicyHandle, AppAgentToolPolicyResult, AppAgentToolPolicyStatus, AppAgentTransport, AppAgentTransportHandle, } from "./agents.js";
9
- export { mediaGeneration, mediaGenerationFailureSchema, mediaGenerationReferenceSchema, mediaGenerationSelectionSchema, mediaModelSchema, openMediaGeneration, } from "./media.js";
10
- export type { MediaGenerationHandle, MediaGenerationAttempt, MediaGenerationAttemptHandle, MediaGenerationFailure, MediaGenerationInput, MediaGenerationOutcome, MediaGenerationReference, MediaGenerationResult, MediaGenerationSelection, MediaModel, MediaOutputFormat, MediaGenerationSession, } from "./media.js";
9
+ export { mediaGeneration, mediaGenerationFailureSchema, mediaGenerationReferenceSchema, mediaGenerationSelectionSchema, mediaModelSchema, mediaShare, mediaShareInputSchema, mediaShareStatusSchema, openMediaGeneration, } from "./media.js";
10
+ export type { MediaShareHandle, MediaShareInput, MediaShareStatus, MediaGenerationHandle, MediaGenerationAttempt, MediaGenerationAttemptHandle, MediaGenerationFailure, MediaGenerationInput, MediaGenerationOutcome, MediaGenerationReference, MediaGenerationResult, MediaGenerationSelection, MediaModel, MediaOutputFormat, MediaGenerationSession, } from "./media.js";
11
11
  export type { AppCommand, AppCommandHandler, AppCommandInvocation, AppCommands, } from "./commands.js";
12
12
  export { registerCatalog } from "./commands.js";
13
13
  export { connectApp } from "./connect.js";
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ export { FungiAuthorSchema, FungiDelegationReplySchema, FungiMessageMetaSchema,
2
2
  export { createCapabilityRegistry, defineCapability } from "./capabilities.js";
3
3
  export { pending, pendingSchema } from "./pending.js";
4
4
  export { agentAvatar, agentList, agentManage, agentRename, agentSessionRead, agentSessionWrite, agentToolPolicy, createAppAgent, renameAppAgent, setAppAgentAvatar, appAgentAvatarInputSchema, appAgentAvatarSchema, appAgentCreateInputSchema, appAgentRenameInputSchema, appAgentSchema, appAgentToolPolicyResultSchema, appAgentToolPolicyStatusSchema, listAppAgents, openAppAgent, } from "./agents.js";
5
- export { mediaGeneration, mediaGenerationFailureSchema, mediaGenerationReferenceSchema, mediaGenerationSelectionSchema, mediaModelSchema, openMediaGeneration, } from "./media.js";
5
+ export { mediaGeneration, mediaGenerationFailureSchema, mediaGenerationReferenceSchema, mediaGenerationSelectionSchema, mediaModelSchema, mediaShare, mediaShareInputSchema, mediaShareStatusSchema, openMediaGeneration, } from "./media.js";
6
6
  export { registerCatalog } from "./commands.js";
7
7
  export { connectApp } from "./connect.js";
8
8
  /** Data exchanged by iframe and host before the RPC MessagePort takes over. */
package/dist/media.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as z from "zod";
1
2
  import * as Schema from "effect/Schema";
2
3
  import type { MediaProviderDiagnostic } from "./errors.js";
3
4
  export { mediaLibraryRequestSchema, mediaLibraryPathSchema, mediaLibraryResponseSchema, } from "./media-library.js";
@@ -371,6 +372,38 @@ export declare const mediaGenerationFailureSchema: Schema.Struct<{
371
372
  * declare it to request access; openMediaGeneration uses it to obtain
372
373
  * the host's handle. */
373
374
  export declare const mediaGeneration: CapabilityContract<MediaGenerationHandle>;
375
+ /** Whether one generated Print has a public link, and what that link shows. */
376
+ export declare const mediaShareStatusSchema: z.ZodUnion<readonly [z.ZodObject<{
377
+ shared: z.ZodLiteral<false>;
378
+ }, z.core.$strict>, z.ZodObject<{
379
+ shared: z.ZodLiteral<true>;
380
+ url: z.ZodURL;
381
+ showPrompt: z.ZodBoolean;
382
+ }, z.core.$strict>]>;
383
+ /** A Print's share state as the host reports it. */
384
+ export type MediaShareStatus = z.output<typeof mediaShareStatusSchema>;
385
+ /** Turn a public link on for one generated Print. */
386
+ export declare const mediaShareInputSchema: z.ZodObject<{
387
+ requestId: z.ZodString;
388
+ showPrompt: z.ZodBoolean;
389
+ }, z.core.$strict>;
390
+ /** What to share and whether to show its prompt. */
391
+ export type MediaShareInput = z.output<typeof mediaShareInputSchema>;
392
+ /** Host handle behind the `media.share` capability. */
393
+ export type MediaShareHandle = Readonly<{
394
+ /** Read whether this Print has a live public link. */
395
+ status(requestId: string): Promise<MediaShareStatus>;
396
+ /** Turn a public link on; repeating it returns the live link unchanged. */
397
+ share(input: MediaShareInput): Promise<MediaShareStatus>;
398
+ /** Stop sharing; the link stops working at once. */
399
+ stop(requestId: string): Promise<MediaShareStatus>;
400
+ }>;
401
+ /**
402
+ * Capability contract for public links to generated Media. Off by default
403
+ * per output; the host keeps the link, the bytes it serves and who may change
404
+ * it.
405
+ */
406
+ export declare const mediaShare: CapabilityContract<MediaShareHandle>;
374
407
  /** Decode and normalize a typed input envelope; storage parses the envelope first. */
375
408
  export declare function parseMediaGenerationInput(input: Schema.Schema.Type<typeof mediaGenerationInputWireSchema>): MediaGenerationInput;
376
409
  /** Discover the exact retained model catalog surfaced by the owner. */
package/dist/media.js CHANGED
@@ -357,6 +357,33 @@ export const mediaGeneration = defineCapability("media.generation", 5, z.lazy(()
357
357
  restoreAttempt: rpcMethod(mediaAttemptHandleSchema),
358
358
  cancelAttempt: rpcVoid,
359
359
  })));
360
+ /** Whether one generated Print has a public link, and what that link shows. */
361
+ export const mediaShareStatusSchema = z.union([
362
+ z.strictObject({ shared: z.literal(false) }),
363
+ z.strictObject({
364
+ shared: z.literal(true),
365
+ /** The public, read-only page; anyone with it can see this one Print. */
366
+ url: z.url(),
367
+ /** The page shows the prompt only when its owner chose to. */
368
+ showPrompt: z.boolean(),
369
+ }),
370
+ ]);
371
+ /** Turn a public link on for one generated Print. */
372
+ export const mediaShareInputSchema = z.strictObject({
373
+ /** The generation's request identity (a Print's `printId`). */
374
+ requestId: z.string().min(1).max(256),
375
+ showPrompt: z.boolean(),
376
+ });
377
+ /**
378
+ * Capability contract for public links to generated Media. Off by default
379
+ * per output; the host keeps the link, the bytes it serves and who may change
380
+ * it.
381
+ */
382
+ export const mediaShare = defineCapability("media.share", 1, z.lazy(() => rpcHandleSchema({
383
+ status: rpcMethod(rpcDataSchema(mediaShareStatusSchema)),
384
+ share: rpcMethod(rpcDataSchema(mediaShareStatusSchema)),
385
+ stop: rpcMethod(rpcDataSchema(mediaShareStatusSchema)),
386
+ })));
360
387
  /** Parse a value received from the host and turn schema failures into the
361
388
  * transport-safe Agar error used by the other capability helpers. */
362
389
  function decode(schema, value) {
package/dist/query.d.ts CHANGED
@@ -37,8 +37,8 @@ export declare function useMemberInbox(app: QueryApp | null): {
37
37
  readonly agentId: string;
38
38
  readonly sessionId: string;
39
39
  readonly title: string;
40
- readonly state: "running" | "done" | "needs_you" | "errored";
41
40
  readonly createdAt: number;
41
+ readonly state: "running" | "done" | "needs_you" | "errored";
42
42
  readonly lastActivityAt: number;
43
43
  readonly triggerId?: string | undefined;
44
44
  readonly settledAt?: number | undefined;
@@ -13,50 +13,41 @@ export function realGit() {
13
13
  const info = (name) => existsSync(join(root, `${name}.git`))
14
14
  ? { name, defaultBranch: "main", lastPushAt: null, source: null }
15
15
  : null;
16
+ const gitOutput = (name, ...args) => execFileSync("git", [`--git-dir=${join(root, `${name}.git`)}`, ...args], {
17
+ encoding: "utf8",
18
+ // A thousand-commit log page is over execFileSync's 1 MiB default.
19
+ maxBuffer: 256 * 1024 * 1024,
20
+ });
16
21
  // A full object id names an immutable commit, so one read serves every
17
22
  // later ask; refs and abbreviations move and are never cached.
18
23
  const commits = new Map();
19
24
  const immutable = (hash) => /^[0-9a-f]{40,64}$/u.test(hash);
20
- const readCommit = (name, hash) => {
25
+ const cacheKey = (name, hash) => {
21
26
  // The repository's directory identity keeps a deleted-and-recreated name
22
27
  // from serving the old repository's commits, however it was removed.
23
28
  const dir = statSync(join(root, `${name}.git`), {
24
29
  bigint: true,
25
30
  throwIfNoEntry: false,
26
31
  });
27
- const key = `${name}\0${String(dir?.ino)}\0${String(dir?.birthtimeNs)}\0${hash}`;
28
- const cached = immutable(hash) ? commits.get(key) : undefined;
32
+ return `${name}\0${String(dir?.ino)}\0${String(dir?.birthtimeNs)}\0${hash}`;
33
+ };
34
+ const remember = (name, commit) => {
35
+ commits.set(cacheKey(name, commit.hash), structuredClone(commit));
36
+ };
37
+ const readCommit = (name, hash) => {
38
+ const cached = immutable(hash)
39
+ ? commits.get(cacheKey(name, hash))
40
+ : undefined;
29
41
  if (cached !== undefined)
30
42
  return structuredClone(cached);
31
43
  const found = spawnSync("git", [`--git-dir=${join(root, `${name}.git`)}`, "cat-file", "-t", hash], { encoding: "utf8" });
32
44
  if (found.status !== 0 || found.stdout.trim() !== "commit")
33
45
  return null;
34
- const [oid, treeHash, parents, authorName, authorEmail, committerName, committerEmail, authoredAt, committedAt, message,] = z
35
- .tuple([
36
- z.string(),
37
- z.string(),
38
- z.string(),
39
- z.string(),
40
- z.string(),
41
- z.string(),
42
- z.string(),
43
- z.string(),
44
- z.string(),
45
- z.string(),
46
- ])
47
- .parse(run(name, "show", "-s", "--format=%H%x00%T%x00%P%x00%an%x00%ae%x00%cn%x00%ce%x00%at%x00%ct%x00%B", hash).split("\0"));
48
- const commit = {
49
- hash: oid,
50
- treeHash,
51
- parents: parents ? parents.split(" ") : [],
52
- author: { name: authorName, email: authorEmail },
53
- committer: { name: committerName, email: committerEmail },
54
- authoredAt: Number(authoredAt),
55
- committedAt: Number(committedAt),
56
- message,
57
- };
46
+ const commit = parseCommits(gitOutput(name, "show", "-s", "-z", `--format=${COMMIT_FORMAT}`, hash)).at(0);
47
+ if (!commit)
48
+ throw new Error("Commit unreadable");
58
49
  if (immutable(hash))
59
- commits.set(key, structuredClone(commit));
50
+ remember(name, commit);
60
51
  return commit;
61
52
  };
62
53
  const git = {
@@ -93,15 +84,11 @@ export function realGit() {
93
84
  log: (name, { ref, limit = 50, offset = 0 }) => {
94
85
  if (!readCommit(name, ref))
95
86
  return Promise.resolve([]);
96
- return Promise.resolve(run(name, "rev-list", "--first-parent", `--max-count=${String(limit)}`, `--skip=${String(offset)}`, ref)
97
- .split("\n")
98
- .filter(Boolean)
99
- .map((hash) => {
100
- const commit = readCommit(name, hash);
101
- if (!commit)
102
- throw new Error("Commit disappeared");
103
- return commit;
104
- }));
87
+ // One walk with the same rev-list flags reads every page commit.
88
+ const page = parseCommits(gitOutput(name, "log", "-z", `--format=${COMMIT_FORMAT}`, "--first-parent", `--max-count=${String(limit)}`, `--skip=${String(offset)}`, ref));
89
+ for (const commit of page)
90
+ remember(name, commit);
91
+ return Promise.resolve(page);
105
92
  },
106
93
  readTree: (name, hash) => Promise.resolve(run(name, "ls-tree", "-z", hash)
107
94
  .split("\0")
@@ -189,3 +176,42 @@ export function realGit() {
189
176
  },
190
177
  };
191
178
  }
179
+ // Ten NUL-separated fields per commit; `-z` ends each commit with NUL too.
180
+ const COMMIT_FORMAT = "%H%x00%T%x00%P%x00%an%x00%ae%x00%cn%x00%ce%x00%at%x00%ct%x00%B";
181
+ const COMMIT_FIELDS = 10;
182
+ const commitFields = z.tuple([
183
+ z.string(),
184
+ z.string(),
185
+ z.string(),
186
+ z.string(),
187
+ z.string(),
188
+ z.string(),
189
+ z.string(),
190
+ z.string(),
191
+ z.string(),
192
+ z.string(),
193
+ ]);
194
+ function parseCommits(output) {
195
+ const fields = output.split("\0");
196
+ // The final commit's NUL terminator leaves one empty trailing field.
197
+ if (fields.at(-1) === "")
198
+ fields.pop();
199
+ if (fields.length % COMMIT_FIELDS !== 0)
200
+ throw new Error("Unexpected git commit format");
201
+ const parsed = [];
202
+ for (let start = 0; start < fields.length; start += COMMIT_FIELDS) {
203
+ const [oid, treeHash, parents, authorName, authorEmail, committerName, committerEmail, authoredAt, committedAt, message,] = commitFields.parse(fields.slice(start, start + COMMIT_FIELDS));
204
+ parsed.push({
205
+ hash: oid,
206
+ treeHash,
207
+ parents: parents ? parents.split(" ") : [],
208
+ author: { name: authorName, email: authorEmail },
209
+ committer: { name: committerName, email: committerEmail },
210
+ authoredAt: Number(authoredAt),
211
+ committedAt: Number(committedAt),
212
+ // %B keeps the raw message's trailing newline; readers see it trimmed.
213
+ message: message.trimEnd(),
214
+ });
215
+ }
216
+ return parsed;
217
+ }
@@ -228,9 +228,9 @@ Define a typed method once; neither discovery nor builds execute its handler.
228
228
 
229
229
  #### Parameters
230
230
 
231
- | Parameter | Type |
232
- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
233
- | `method` | `Omit`\<`Readonly`\<\{ `description`: `string`; `effect`: `"external"` \| `"read"` \| `"write"`; `requires`: readonly ( \| `"agent.list"` \| `"agent.manage"` \| `"agent.read"` \| `"agent.send"` \| `"computer.list"` \| `"computer.readFile"` \| `"computer.writeFile"` \| `"computer.execute"` \| `"agent.rename"` \| `"agent.avatar"` \| `"agent.toolPolicy"` \| `"approvals"` \| `"apps.open"` \| `"apps.call"` \| `"llm.generation"` \| `"media.generation"` \| `"games:servers"` \| `"git.repository"` \| `"app.storage"` \| `"app.events.emit"` \| `"team.workbenchHost"` \| `"marketplace.browse"` \| `"marketplace.purchase"` \| `"workflow.run"` \| `"workflow.publish"` \| `"workflow.arm"` \| `"world"` \| `"channels.read"` \| `"channels.personal.manage"`)[]; `hidden?`: `true`; `pending?`: `true`; `present?`: `Readonly`\<\{ `verb`: `string`; \}\>; \}\>, `"present"`\> & \{ input: I; output: O; present?: Readonly\<\{ verb: string; row: (call: Readonly\<\{ input: output\<I\>; output: output\<O\>; \}\>) =\> \{ readonly id: string; readonly title: string; ... 4 more ...; readonly media?: \{ ...; \} \| undefined; \}; rows: Readonly\<...\>; \}\> \| undefined; \} & (\{ ...; \} \| \{ ...; \}) |
231
+ | Parameter | Type |
232
+ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
233
+ | `method` | `Omit`\<`Readonly`\<\{ `description`: `string`; `effect`: `"external"` \| `"read"` \| `"write"`; `requires`: readonly ( \| `"agent.list"` \| `"agent.manage"` \| `"agent.read"` \| `"agent.send"` \| `"computer.list"` \| `"computer.readFile"` \| `"computer.writeFile"` \| `"computer.execute"` \| `"agent.rename"` \| `"agent.avatar"` \| `"agent.toolPolicy"` \| `"approvals"` \| `"apps.open"` \| `"apps.call"` \| `"llm.generation"` \| `"media.generation"` \| `"media.share"` \| `"games:servers"` \| `"git.repository"` \| `"app.storage"` \| `"app.events.emit"` \| `"team.workbenchHost"` \| `"marketplace.browse"` \| `"marketplace.purchase"` \| `"workflow.run"` \| `"workflow.publish"` \| `"workflow.arm"` \| `"world"` \| `"channels.read"` \| `"channels.personal.manage"`)[]; `hidden?`: `true`; `pending?`: `true`; `present?`: `Readonly`\<\{ `verb`: `string`; \}\>; \}\>, `"present"`\> & \{ input: I; output: O; present?: Readonly\<\{ verb: string; row: (call: Readonly\<\{ input: output\<I\>; output: output\<O\>; \}\>) =\> \{ readonly id: string; readonly title: string; ... 4 more ...; readonly media?: \{ ...; \} \| undefined; \}; rows: Readonly\<...\>; \}\> \| undefined; \} & (\{ ...; \} \| \{ ...; \}) |
234
234
 
235
235
  #### Returns
236
236
 
package/docs/api/index.md CHANGED
@@ -235,6 +235,18 @@ Create a new Agent with the given name and request id.
235
235
  `"crt"`; `watercolor`: `"watercolor"`; \}\>; \}; `sessionOperations`: readonly
236
236
  (`"agent.manage"` \| `"agent.read"` \| `"agent.send"`)[]; \}\>
237
237
 
238
+ ##### computers()
239
+
240
+ ```ts
241
+ computers(): Promise<readonly object[] | null>;
242
+ ```
243
+
244
+ The Team Computers a new Agent may use; null when the host offers no choice.
245
+
246
+ ###### Returns
247
+
248
+ `Promise`\<readonly `object`[] \| `null`\>
249
+
238
250
  ---
239
251
 
240
252
  ### AppAgentDirectoryHandle
@@ -1243,6 +1255,67 @@ and Computer-relative reference paths.
1243
1255
 
1244
1256
  ---
1245
1257
 
1258
+ ### MediaShareStatus
1259
+
1260
+ ```ts
1261
+ type MediaShareStatus = z.output<typeof mediaShareStatusSchema>;
1262
+ ```
1263
+
1264
+ A Print's share state as the host reports it.
1265
+
1266
+ ---
1267
+
1268
+ ### MediaShareInput
1269
+
1270
+ ```ts
1271
+ type MediaShareInput = z.output<typeof mediaShareInputSchema>;
1272
+ ```
1273
+
1274
+ What to share and whether to show its prompt.
1275
+
1276
+ ---
1277
+
1278
+ ### MediaShareHandle
1279
+
1280
+ ```ts
1281
+ type MediaShareHandle = Readonly<{
1282
+ status: Promise<
1283
+ | {
1284
+ shared: false;
1285
+ }
1286
+ | {
1287
+ shared: true;
1288
+ url: string;
1289
+ showPrompt: boolean;
1290
+ }
1291
+ >;
1292
+ share: Promise<
1293
+ | {
1294
+ shared: false;
1295
+ }
1296
+ | {
1297
+ shared: true;
1298
+ url: string;
1299
+ showPrompt: boolean;
1300
+ }
1301
+ >;
1302
+ stop: Promise<
1303
+ | {
1304
+ shared: false;
1305
+ }
1306
+ | {
1307
+ shared: true;
1308
+ url: string;
1309
+ showPrompt: boolean;
1310
+ }
1311
+ >;
1312
+ }>;
1313
+ ```
1314
+
1315
+ Host handle behind the `media.share` capability.
1316
+
1317
+ ---
1318
+
1246
1319
  ### Pending
1247
1320
 
1248
1321
  ```ts
@@ -2246,7 +2319,16 @@ Agent has an avatar: the one its owner chose, else Sprite's
2246
2319
  const appAgentCreateInputSchema: Struct<{
2247
2320
  name: String;
2248
2321
  requestId: String;
2249
- computer: optionalKey<Literals<readonly ["new", "none"]>>;
2322
+ computer: optionalKey<
2323
+ Union<
2324
+ readonly [
2325
+ Literals<readonly ["new", "none"]>,
2326
+ Struct<{
2327
+ computerId: String;
2328
+ }>,
2329
+ ]
2330
+ >
2331
+ >;
2250
2332
  avatar: optionalKey<
2251
2333
  declare<
2252
2334
  {
@@ -2309,7 +2391,7 @@ Capability to list the Agents visible to this App in read-only form.
2309
2391
 
2310
2392
  ```ts
2311
2393
  const agentManage: CapabilityContract<
2312
- Pick<AppAgentDirectoryHandle, "create" | "open">
2394
+ Pick<AppAgentDirectoryHandle, "create" | "computers" | "open">
2313
2395
  >;
2314
2396
  ```
2315
2397
 
@@ -2808,6 +2890,60 @@ request access; openMediaGeneration uses it to obtain the host's handle.
2808
2890
 
2809
2891
  ---
2810
2892
 
2893
+ ### mediaShareStatusSchema
2894
+
2895
+ ```ts
2896
+ const mediaShareStatusSchema: ZodUnion<
2897
+ readonly [
2898
+ ZodObject<
2899
+ {
2900
+ shared: ZodLiteral<false>;
2901
+ },
2902
+ $strict
2903
+ >,
2904
+ ZodObject<
2905
+ {
2906
+ shared: ZodLiteral<true>;
2907
+ url: ZodURL;
2908
+ showPrompt: ZodBoolean;
2909
+ },
2910
+ $strict
2911
+ >,
2912
+ ]
2913
+ >;
2914
+ ```
2915
+
2916
+ Whether one generated Print has a public link, and what that link shows.
2917
+
2918
+ ---
2919
+
2920
+ ### mediaShareInputSchema
2921
+
2922
+ ```ts
2923
+ const mediaShareInputSchema: ZodObject<
2924
+ {
2925
+ requestId: ZodString;
2926
+ showPrompt: ZodBoolean;
2927
+ },
2928
+ $strict
2929
+ >;
2930
+ ```
2931
+
2932
+ Turn a public link on for one generated Print.
2933
+
2934
+ ---
2935
+
2936
+ ### mediaShare
2937
+
2938
+ ```ts
2939
+ const mediaShare: CapabilityContract<MediaShareHandle>;
2940
+ ```
2941
+
2942
+ Capability contract for public links to generated Media. Off by default per
2943
+ output; the host keeps the link, the bytes it serves and who may change it.
2944
+
2945
+ ---
2946
+
2811
2947
  ### pendingSchema
2812
2948
 
2813
2949
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fungi.computer/agar",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "author": "Levi <levi@technomancy.dev>",
5
5
  "description": "App contract for fungi.computer: typed backend methods, host capabilities and the App frontend connection",
6
6
  "type": "module",
@@ -168,7 +168,7 @@
168
168
  }
169
169
  },
170
170
  "dependencies": {
171
- "@fungi.computer/puffball": "0.4.0",
171
+ "@fungi.computer/puffball": "0.5.0",
172
172
  "@fungi.computer/whistle": "0.3.0",
173
173
  "@opentui/keymap": "0.5.10",
174
174
  "@fungi.computer/capnweb": "0.12.0",