glove-foundry 0.3.3 → 0.4.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.
@@ -186,6 +186,10 @@ var CONVENTION_EXPORTS = [
186
186
  "skills",
187
187
  "subagents",
188
188
  "memory",
189
+ "goals",
190
+ "facts",
191
+ "forms",
192
+ "contextProviders",
189
193
  "inboxes",
190
194
  "subscribers",
191
195
  "layers",
@@ -227,6 +231,11 @@ function defineSubagent(options) {
227
231
  description: options.description,
228
232
  factory: async ({ parentStore, parentControls, prompt }) => {
229
233
  const store = await parentStore.createSubAgentStore?.(options.name, options.durable ?? false) ?? void 0;
234
+ const tools = typeof options.tools === "function" ? await resolveResolvable(options.tools({
235
+ name: options.name,
236
+ prompt,
237
+ parent: parentControls.glove
238
+ })) : options.tools ?? [];
230
239
  const glove = new Glove({
231
240
  ...store ? { store } : {},
232
241
  model: options.model ?? parentControls.glove.model,
@@ -242,7 +251,7 @@ function defineSubagent(options) {
242
251
  },
243
252
  ...options.enableToolResultSummary !== void 0 ? { enableToolResultSummary: options.enableToolResultSummary } : {}
244
253
  }).build();
245
- for (const tool of options.tools ?? []) glove.fold(tool);
254
+ for (const tool of tools) glove.fold(tool);
246
255
  for (const hook of options.hooks ?? []) glove.defineHook(hook.name, hook.handler);
247
256
  for (const skill of options.skills ?? []) glove.defineSkill(skill);
248
257
  for (const subagent of options.subagents ?? []) glove.defineSubAgent(subagent);
@@ -285,6 +294,125 @@ function defineRoutes(routes) {
285
294
  return Object.freeze({ ...routes });
286
295
  }
287
296
 
297
+ // src/guidance.ts
298
+ import { createHash } from "node:crypto";
299
+ import { FactPreparation, FactStore, useFacts } from "glove-facts";
300
+ import { useGoalRunner, useFormRunner } from "glove-memory/tools";
301
+ var defineFacts = (options) => Object.freeze({ ...options });
302
+ var defineGoals = (options) => Object.freeze({ ...options });
303
+ var defineForms = (options) => Object.freeze({ ...options });
304
+ function foundryGuidanceSubject(context, scope = "conversation") {
305
+ return `foundry:${scope}:${createHash("sha256").update(JSON.stringify([
306
+ context.workspaceId,
307
+ context.agentId,
308
+ ...scope === "conversation" ? [context.conversationId] : []
309
+ ])).digest("hex")}`;
310
+ }
311
+ async function mountFoundryGuidance(glove, context, options) {
312
+ const { facts, goals, forms } = options;
313
+ const subject = (scope) => typeof scope === "object" ? scope.subject : foundryGuidanceSubject(context, scope);
314
+ const handles = {};
315
+ let preparer;
316
+ if (facts) {
317
+ handles.facts = new FactStore(facts.adapter, {
318
+ scope: typeof facts.scope === "object" ? facts.scope : { subject: subject(facts.scope), context: "conversation-evidence" },
319
+ maxRevisions: facts.maxRevisions,
320
+ onUrgent: facts.onUrgent
321
+ });
322
+ if (facts.preparationAgent) {
323
+ if (facts.preparationAgent === glove) throw new Error("Fact preparation requires a dedicated agent.");
324
+ preparer = new FactPreparation(handles.facts, { agent: facts.preparationAgent });
325
+ }
326
+ useFacts(glove, handles.facts, facts.source ?? (() => ({
327
+ source: { kind: "message", id: context.message.id ?? context.runId },
328
+ operationId: context.message.id ?? context.runId
329
+ })));
330
+ }
331
+ if ((goals?.preparation || forms?.preparation) && !preparer) {
332
+ throw new Error("Guidance preparation requires facts with a dedicated preparationAgent.");
333
+ }
334
+ if (goals) {
335
+ const scope = typeof goals.scope === "object" ? goals.scope : {
336
+ subject: subject(goals.scope),
337
+ key: goals.program?.key ?? "conversation"
338
+ };
339
+ if (goals.preparation && preparer.facts.scope().subject !== scope.subject) throw new Error("Goals and facts must share the same subject.");
340
+ handles.goals = useGoalRunner(glove, goals.adapter, {
341
+ ...goals,
342
+ scope,
343
+ preparation: goals.preparation ? { ...goals.preparation, preparer } : void 0
344
+ }).runner;
345
+ if (goals.program) await handles.goals.start(goals.program, "Initialize definition-provided goals; preserve existing progress.");
346
+ }
347
+ if (forms) {
348
+ if (forms.preparation && preparer.facts.scope().subject !== subject(forms.scope)) throw new Error("Forms and facts must share the same subject.");
349
+ handles.forms = useFormRunner(glove, forms.adapter, {
350
+ ...forms,
351
+ subject: subject(forms.scope),
352
+ preparation: forms.preparation ? { ...forms.preparation, preparer } : void 0
353
+ }).runner;
354
+ }
355
+ let previous = "";
356
+ const snapshot = async () => {
357
+ if (!facts && !goals && !forms && options.contextProviders.length === 0) return;
358
+ const goalState = await handles.goals?.status();
359
+ const factState = await handles.facts?.inspect();
360
+ const formState = forms ? await forms.adapter.findInstances({ subject: subject(forms.scope) }) : void 0;
361
+ const state = {
362
+ goals: goalState ? {
363
+ version: goalState.version,
364
+ status: goalState.status,
365
+ activeGoal: goalState.activeGoal,
366
+ items: goalState.goals.map((g) => ({
367
+ key: g.definition.key,
368
+ title: g.definition.title,
369
+ status: g.status,
370
+ completed: g.items.filter((i) => i.state?.done).length,
371
+ total: g.items.length
372
+ }))
373
+ } : null,
374
+ facts: factState ? {
375
+ version: factState.version,
376
+ revisions: factState.facts.length,
377
+ claims: factState.claims.length,
378
+ urgent: factState.facts.filter((f) => f.urgent).length
379
+ } : null,
380
+ forms: formState?.map((f) => ({
381
+ id: f.id,
382
+ definitionId: f.defId,
383
+ version: f.version,
384
+ status: f.status,
385
+ answered: Object.values(f.entries).filter((e) => e.cursor >= 0).length,
386
+ pendingHooks: Object.keys(f.pendingHooks ?? {}).length,
387
+ blockedOn: f.blockedOn ?? null
388
+ })) ?? null,
389
+ contextProviders: options.contextProviders.length
390
+ };
391
+ const encoded = JSON.stringify(state);
392
+ if (encoded !== previous) {
393
+ context.controls.emit({ type: "foundry.guidance.state", data: state });
394
+ previous = encoded;
395
+ }
396
+ };
397
+ const cleanups = [];
398
+ try {
399
+ for (const provider of options.contextProviders) cleanups.push(glove.addContextProvider(provider));
400
+ if (facts || goals || forms) {
401
+ await snapshot();
402
+ cleanups.push(glove.addContextProvider(async () => {
403
+ await snapshot();
404
+ return null;
405
+ }));
406
+ }
407
+ return { handles, snapshot, dispose: () => {
408
+ for (const remove of cleanups.reverse()) remove();
409
+ } };
410
+ } catch (error) {
411
+ for (const remove of cleanups.reverse()) remove();
412
+ throw error;
413
+ }
414
+ }
415
+
288
416
  // src/capabilities.ts
289
417
  import { Effect as Effect2 } from "effect";
290
418
  import { mountMcp } from "glove-mcp";
@@ -410,6 +538,10 @@ function defineAgentApplication(options) {
410
538
  }, "application", id2));
411
539
  }
412
540
  var defineApp = defineAgentApplication;
541
+ function identifyMcpEntry(entry, id2) {
542
+ if (entry.transport) return { ...entry, id: id2 };
543
+ return { ...entry, id: id2, url: entry.url };
544
+ }
413
545
  function defineMcp(options) {
414
546
  if (options.id) assertCapabilityId(options.id, "MCP");
415
547
  const { id: id2, ...definition } = options;
@@ -585,7 +717,19 @@ function installRegistry(options) {
585
717
  const contribution = application.install ? yield* application.install(headlessContext) : void 0;
586
718
  for (const tool of contribution?.tools ?? []) options.context.glove.fold(tool);
587
719
  } else {
588
- installedMcp.push(definition);
720
+ const mcpDefinition = definition;
721
+ const headlessContext = (() => {
722
+ const { glove: _glove, store: _store, ...headless } = context;
723
+ return headless;
724
+ })();
725
+ const source = typeof mcpDefinition.entry === "function" ? mcpDefinition.entry(headlessContext) : mcpDefinition.entry;
726
+ const entry = Effect2.isEffect(source) ? yield* source : source instanceof Promise ? yield* Effect2.tryPromise({ try: () => source, catch: (cause) => cause }) : source;
727
+ installedMcp.push({
728
+ definition: mcpDefinition,
729
+ installation,
730
+ config,
731
+ entry: identifyMcpEntry(entry, mcpDefinition.id)
732
+ });
589
733
  }
590
734
  installed.push(installation);
591
735
  options.context.emit({
@@ -601,24 +745,23 @@ function installRegistry(options) {
601
745
  }
602
746
  const adapter = yield* options.mcpAdapter({
603
747
  ...options.context,
604
- installed: installedMcp
748
+ installed: installedMcp.map(({ definition }) => definition),
749
+ resolved: installedMcp
605
750
  });
606
- const selectedIds = new Set(installedMcp.map((entry) => entry.id));
751
+ const selectedIds = new Set(installedMcp.map(({ definition }) => definition.id));
607
752
  const scopedAdapter = {
608
753
  identifier: adapter.identifier,
609
754
  getActive: async () => [...selectedIds],
610
755
  activate: (id2) => adapter.activate(id2),
611
756
  deactivate: (id2) => adapter.deactivate(id2),
612
757
  ...adapter.getAccessToken ? { getAccessToken: (id2) => adapter.getAccessToken(id2) } : {},
613
- ...adapter.getAuthHeaders ? { getAuthHeaders: (id2) => adapter.getAuthHeaders(id2) } : {}
758
+ ...adapter.getAuthHeaders ? { getAuthHeaders: (id2) => adapter.getAuthHeaders(id2) } : {},
759
+ ...adapter.getStdioEnvironment ? { getStdioEnvironment: (id2) => adapter.getStdioEnvironment(id2) } : {}
614
760
  };
615
761
  yield* Effect2.tryPromise({
616
762
  try: () => mountMcp(options.context.glove, {
617
763
  adapter: scopedAdapter,
618
- entries: installedMcp.map((definition) => ({
619
- ...definition.entry,
620
- id: definition.id
621
- })),
764
+ entries: installedMcp.map(({ entry }) => entry),
622
765
  ambiguityPolicy: { type: "auto-pick-best" }
623
766
  }),
624
767
  catch: (cause) => cause
@@ -777,7 +920,7 @@ function isFoundrySubscriber(value) {
777
920
  }
778
921
 
779
922
  // src/playbook.ts
780
- import { createHash } from "node:crypto";
923
+ import { createHash as createHash2 } from "node:crypto";
781
924
  var ID = /^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)*$/;
782
925
  function assertData(value, path, seen = /* @__PURE__ */ new Set()) {
783
926
  if (value === void 0) throw new Error(`Playbook ${path} cannot contain undefined.`);
@@ -921,12 +1064,12 @@ function reconstructPlaybook(playbook) {
921
1064
  var FOUNDRY_PLAYBOOK_ACTION_BRAND = /* @__PURE__ */ Symbol.for("glove-foundry-playbook-action");
922
1065
  var FOUNDRY_COMPOSED_PLAYBOOK_BRAND = /* @__PURE__ */ Symbol.for("glove-foundry-composed-playbook");
923
1066
  function agentPlaybookId(definitionId, agentId, name) {
924
- return `playbook-${createHash("sha256").update(`${definitionId}\0${agentId}\0${name}`).digest("hex").slice(0, 24)}`;
1067
+ return `playbook-${createHash2("sha256").update(`${definitionId}\0${agentId}\0${name}`).digest("hex").slice(0, 24)}`;
925
1068
  }
926
1069
  function composedPlaybookRevision(playbook) {
927
1070
  const materialized = materializeComposedPlaybook(playbook, "playbook-revision", "pending");
928
1071
  const { id: _id, definitionRevision: _revision, ...data } = materialized;
929
- return createHash("sha256").update(JSON.stringify(data)).digest("hex");
1072
+ return createHash2("sha256").update(JSON.stringify(data)).digest("hex");
930
1073
  }
931
1074
  function definePlaybookAction(options = {}) {
932
1075
  if (options.id && !ID.test(options.id)) {
@@ -981,9 +1124,15 @@ function definePlaybookSubscription(options) {
981
1124
  return Object.freeze(authored);
982
1125
  }
983
1126
  function reconstructPlaybookSubscription(subscription) {
1127
+ const { playbook, ...data } = subscription;
984
1128
  return freezeData2({
985
- ...structuredClone(subscription),
986
- playbook: reconstructPlaybook(subscription.playbook)
1129
+ ...structuredClone(data),
1130
+ // A file-routed subscription owns exactly one playbook, so its route is
1131
+ // also the stable persisted playbook identity when the author supplied a
1132
+ // composed policy without a runtime id.
1133
+ playbook: reconstructPlaybook(
1134
+ playbook.id ? playbook : { ...playbook, id: subscription.id }
1135
+ )
987
1136
  });
988
1137
  }
989
1138
 
@@ -1190,6 +1339,18 @@ var MemoryFoundryDataAdapter = class {
1190
1339
  this.workspace.set(`${entry.workspaceId}:${entry.key}`, Object.freeze({ ...entry }));
1191
1340
  });
1192
1341
  }
1342
+ compareAndSetWorkspaceEntry(entry, expectedUpdatedAt) {
1343
+ return Effect4.sync(() => {
1344
+ const key = `${entry.workspaceId}:${entry.key}`;
1345
+ const current = this.workspace.get(key);
1346
+ if ((current?.updatedAt ?? null) !== expectedUpdatedAt) return false;
1347
+ if (!Number.isFinite(Date.parse(entry.updatedAt)) || current && Date.parse(entry.updatedAt) <= Date.parse(current.updatedAt)) {
1348
+ throw new Error("An atomic workspace update must advance updatedAt.");
1349
+ }
1350
+ this.workspace.set(key, freezeInstanceData(structuredClone(entry)));
1351
+ return true;
1352
+ });
1353
+ }
1193
1354
  listWorkspaceEntries(workspaceId) {
1194
1355
  return Effect4.succeed([...this.workspace.values()].filter((item) => item.workspaceId === workspaceId));
1195
1356
  }
@@ -1321,7 +1482,7 @@ function id(prefix) {
1321
1482
  }
1322
1483
 
1323
1484
  // src/schedule.ts
1324
- import { createHash as createHash2 } from "node:crypto";
1485
+ import { createHash as createHash3 } from "node:crypto";
1325
1486
  import { Duration } from "effect";
1326
1487
  var FOUNDRY_SCHEDULE_BRAND = /* @__PURE__ */ Symbol.for("glove-foundry-schedule");
1327
1488
  var COMPACT_DURATION = /^(\d+)\s*(ms|s|m|h|d|w)$/i;
@@ -1365,11 +1526,11 @@ function isFoundrySchedule(value) {
1365
1526
  return Boolean(value && typeof value === "object" && value[FOUNDRY_SCHEDULE_BRAND] === true);
1366
1527
  }
1367
1528
  function agentScheduleActivationId(definitionId, agentId, name) {
1368
- const digest = createHash2("sha256").update(`${definitionId}\0${agentId}\0${name}`).digest("hex").slice(0, 24);
1529
+ const digest = createHash3("sha256").update(`${definitionId}\0${agentId}\0${name}`).digest("hex").slice(0, 24);
1369
1530
  return `activation_${digest}`;
1370
1531
  }
1371
1532
  function agentScheduleRevision(schedule) {
1372
- return createHash2("sha256").update(JSON.stringify({
1533
+ return createHash3("sha256").update(JSON.stringify({
1373
1534
  message: schedule.message,
1374
1535
  payload: schedule.payload,
1375
1536
  timing: schedule.timing,
@@ -1381,10 +1542,194 @@ function agentScheduleRevision(schedule) {
1381
1542
  import { randomUUID as randomUUID2 } from "node:crypto";
1382
1543
  import { Effect as Effect5, JSONSchema, Schema } from "effect";
1383
1544
  import { z } from "zod";
1545
+
1546
+ // src/core-command-result.ts
1547
+ import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
1548
+ import { join } from "node:path";
1549
+ var FOUNDRY_CORE_COMMAND_DIRECTORY_ENV = "GLOVE_FOUNDRY_CORE_COMMAND_DIRECTORY";
1550
+ var REQUEST_SUFFIX = ".request.json";
1551
+ var CLAIM_SUFFIX = ".claim";
1552
+ var CANCELLATION_SUFFIX = ".cancellation.json";
1553
+ var RESULT_SUFFIX = ".result.json";
1554
+ var COMMAND_ID = /^command_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1555
+ function commandPath(directory, id2, suffix) {
1556
+ if (!COMMAND_ID.test(id2)) throw new Error("Invalid Foundry core command id.");
1557
+ return join(directory, `${id2}${suffix}`);
1558
+ }
1559
+ async function readJson(path) {
1560
+ try {
1561
+ return JSON.parse(await readFile(path, "utf8"));
1562
+ } catch (cause) {
1563
+ if (cause.code === "ENOENT") return null;
1564
+ throw cause;
1565
+ }
1566
+ }
1567
+ function requestRecord(value) {
1568
+ if (!value || typeof value !== "object") return null;
1569
+ const item = value;
1570
+ const valid = typeof item.id === "string" && COMMAND_ID.test(item.id) && typeof item.runId === "string" && item.type === "transmit" && typeof item.requestedAt === "string" && typeof item.expiresAt === "string";
1571
+ if (!valid) return null;
1572
+ if (item.command !== void 0) {
1573
+ const command = item.command;
1574
+ if (!command || typeof command !== "object" || command.type !== "transmit" || command.id !== item.id || typeof command.definitionId !== "string" || typeof command.agentId !== "string" || typeof command.conversationId !== "string" || typeof command.workspaceId !== "string" || typeof command.routeId !== "string" || !("payload" in command)) return null;
1575
+ }
1576
+ return item;
1577
+ }
1578
+ function resultRecord(value) {
1579
+ if (!value || typeof value !== "object") return null;
1580
+ const item = value;
1581
+ if (typeof item.id !== "string" || !COMMAND_ID.test(item.id) || typeof item.resolvedAt !== "string") return null;
1582
+ if (item.status === "success") return item;
1583
+ return item.status === "error" && typeof item.error === "string" ? item : null;
1584
+ }
1585
+ function cancellationRecord(value) {
1586
+ if (!value || typeof value !== "object") return null;
1587
+ const item = value;
1588
+ return typeof item.id === "string" && COMMAND_ID.test(item.id) && (item.status === "cancelled" || item.status === "expired") && typeof item.cancelledAt === "string" ? item : null;
1589
+ }
1590
+ function jsonValue(value) {
1591
+ if (value === void 0) return void 0;
1592
+ return JSON.parse(JSON.stringify(value));
1593
+ }
1594
+ async function createFoundryCoreCommandRequest(directory, input) {
1595
+ await mkdir(directory, { recursive: true, mode: 448 });
1596
+ const requestedAt = /* @__PURE__ */ new Date();
1597
+ const timeoutMs = Math.min(Math.max(input.timeoutMs ?? 3e5, 1e3), 6e5);
1598
+ const request = Object.freeze({
1599
+ id: input.id,
1600
+ runId: input.runId,
1601
+ type: input.type,
1602
+ requestedAt: requestedAt.toISOString(),
1603
+ expiresAt: new Date(requestedAt.getTime() + timeoutMs).toISOString(),
1604
+ ...input.command ? { command: jsonValue(input.command) } : {}
1605
+ });
1606
+ await writeFile(
1607
+ commandPath(directory, request.id, REQUEST_SUFFIX),
1608
+ `${JSON.stringify(request)}
1609
+ `,
1610
+ { encoding: "utf8", flag: "wx", mode: 384 }
1611
+ );
1612
+ return request;
1613
+ }
1614
+ async function getFoundryCoreCommandRequest(directory, id2) {
1615
+ return requestRecord(await readJson(commandPath(directory, id2, REQUEST_SUFFIX)));
1616
+ }
1617
+ async function claimFoundryCoreCommandRequest(directory, id2) {
1618
+ try {
1619
+ await writeFile(
1620
+ commandPath(directory, id2, CLAIM_SUFFIX),
1621
+ `${(/* @__PURE__ */ new Date()).toISOString()}
1622
+ `,
1623
+ { encoding: "utf8", flag: "wx", mode: 384 }
1624
+ );
1625
+ return true;
1626
+ } catch (cause) {
1627
+ if (cause.code === "EEXIST") return false;
1628
+ throw cause;
1629
+ }
1630
+ }
1631
+ async function settleFoundryCoreCommandRequest(directory, id2, outcome) {
1632
+ const result = outcome.status === "success" ? Object.freeze({
1633
+ id: id2,
1634
+ status: "success",
1635
+ ...outcome.output !== void 0 ? { output: jsonValue(outcome.output) } : {},
1636
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
1637
+ }) : Object.freeze({
1638
+ id: id2,
1639
+ status: "error",
1640
+ error: outcome.error,
1641
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
1642
+ });
1643
+ try {
1644
+ await writeFile(
1645
+ commandPath(directory, id2, RESULT_SUFFIX),
1646
+ `${JSON.stringify(result)}
1647
+ `,
1648
+ { encoding: "utf8", flag: "wx", mode: 384 }
1649
+ );
1650
+ } catch (cause) {
1651
+ if (cause.code !== "EEXIST") throw cause;
1652
+ }
1653
+ }
1654
+ async function cancelFoundryCoreCommandRequest(directory, id2, status) {
1655
+ const cancellation = Object.freeze({
1656
+ id: id2,
1657
+ status,
1658
+ cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
1659
+ });
1660
+ try {
1661
+ await writeFile(
1662
+ commandPath(directory, id2, CANCELLATION_SUFFIX),
1663
+ `${JSON.stringify(cancellation)}
1664
+ `,
1665
+ { encoding: "utf8", flag: "wx", mode: 384 }
1666
+ );
1667
+ } catch (cause) {
1668
+ if (cause.code !== "EEXIST") throw cause;
1669
+ }
1670
+ }
1671
+ async function monitorFoundryCoreCommandRequest(directory, request, delivery, stopped) {
1672
+ while (!stopped.aborted && !delivery.signal.aborted) {
1673
+ const cancellation = cancellationRecord(
1674
+ await readJson(commandPath(directory, request.id, CANCELLATION_SUFFIX))
1675
+ );
1676
+ if (cancellation) {
1677
+ delivery.abort(new Error(
1678
+ cancellation.status === "cancelled" ? "Foundry transmission call was cancelled." : "Foundry transmission call timed out."
1679
+ ));
1680
+ return;
1681
+ }
1682
+ if (Date.parse(request.expiresAt) <= Date.now()) {
1683
+ delivery.abort(new Error("Foundry transmission call timed out."));
1684
+ return;
1685
+ }
1686
+ await new Promise((resolveWait) => setTimeout(resolveWait, 50));
1687
+ }
1688
+ }
1689
+ async function waitForFoundryCoreCommandResult(directory, request, signal2, options = {}) {
1690
+ for (; ; ) {
1691
+ const result = resultRecord(
1692
+ await readJson(commandPath(directory, request.id, RESULT_SUFFIX))
1693
+ );
1694
+ if (result) {
1695
+ if (options.cleanup) await cleanupFoundryCoreCommandRequest(directory, request.id);
1696
+ if (result.status === "error") throw new Error(result.error);
1697
+ return result.output;
1698
+ }
1699
+ if (signal2.aborted) {
1700
+ await cancelFoundryCoreCommandRequest(directory, request.id, "cancelled");
1701
+ throw new Error("Foundry transmission call was cancelled.");
1702
+ }
1703
+ if (Date.parse(request.expiresAt) <= Date.now()) {
1704
+ await cancelFoundryCoreCommandRequest(directory, request.id, "expired");
1705
+ throw new Error("Foundry transmission call timed out.");
1706
+ }
1707
+ await new Promise((resolveWait) => setTimeout(resolveWait, 50));
1708
+ }
1709
+ }
1710
+ async function cleanupFoundryCoreCommandRequest(directory, id2) {
1711
+ await Promise.all([
1712
+ REQUEST_SUFFIX,
1713
+ CLAIM_SUFFIX,
1714
+ RESULT_SUFFIX,
1715
+ CANCELLATION_SUFFIX
1716
+ ].map(async (suffix) => {
1717
+ try {
1718
+ await unlink(commandPath(directory, id2, suffix));
1719
+ } catch (cause) {
1720
+ if (cause.code !== "ENOENT") throw cause;
1721
+ }
1722
+ }));
1723
+ }
1724
+
1725
+ // src/core-tools.ts
1384
1726
  var FOUNDRY_CORE_COMMAND_EVENT = "foundry.core.command";
1385
1727
  function commandId() {
1386
1728
  return `command_${randomUUID2()}`;
1387
1729
  }
1730
+ function privateTransmitEvent(command) {
1731
+ return Object.freeze({ ...command, payload: { privateRequest: true } });
1732
+ }
1388
1733
  function durationSchema(description) {
1389
1734
  return z.string().describe(description).refine((value) => {
1390
1735
  try {
@@ -1407,6 +1752,9 @@ var timingInputSchema = z.discriminatedUnion("kind", [
1407
1752
  function toolSegment(value) {
1408
1753
  return value.replaceAll("/", "__").replaceAll("-", "_");
1409
1754
  }
1755
+ function installedApplicationTransmissionToolName(application, transmission) {
1756
+ return `glove_app_${toolSegment(application.id)}__${toolSegment(transmission.id)}_send`;
1757
+ }
1410
1758
  function outboundToolSchema(transmission, routeIds) {
1411
1759
  const document = JSONSchema.make(transmission.outbound.input);
1412
1760
  const { $schema: _schema, $defs, ...payload } = document;
@@ -1429,10 +1777,9 @@ function createInstalledApplicationTransmissionTools(context, applications, inst
1429
1777
  const installed = new Set(
1430
1778
  installations.filter((item) => item.kind === "application").map((item) => item.id)
1431
1779
  );
1432
- const emit = (command) => {
1780
+ const emit = (command, eventCommand = command) => {
1433
1781
  context.controls.commands.push(command);
1434
- context.controls.emit({ type: FOUNDRY_CORE_COMMAND_EVENT, data: command });
1435
- return success(command);
1782
+ context.controls.emit({ type: FOUNDRY_CORE_COMMAND_EVENT, data: eventCommand });
1436
1783
  };
1437
1784
  const tools = [];
1438
1785
  for (const application of applications) {
@@ -1448,13 +1795,25 @@ function createInstalledApplicationTransmissionTools(context, applications, inst
1448
1795
  ).map((outbound) => outbound.routeId)
1449
1796
  )
1450
1797
  )].sort();
1798
+ const requiresPermission = transmission.outbound.requiresPermission;
1451
1799
  tools.push({
1452
- name: `glove_app_${toolSegment(application.id)}__${toolSegment(transmission.id)}_send`,
1800
+ name: installedApplicationTransmissionToolName(application, transmission),
1453
1801
  description: [
1454
1802
  `Send through the ${transmission.name} outbound transmission installed by the ${application.id} application.`,
1455
1803
  routeIds.length > 0 ? `Allowed playbook routes: ${routeIds.join(", ")}.` : "Supply a route authorized for this agent run."
1456
1804
  ].join(" "),
1457
1805
  jsonSchema: outboundToolSchema(transmission, routeIds),
1806
+ ...requiresPermission !== void 0 ? {
1807
+ requiresPermission: (input) => {
1808
+ if (routeIds.length > 0 && !routeIds.includes(input.routeId)) return false;
1809
+ try {
1810
+ const payload = Schema.decodeUnknownSync(transmission.outbound.input)(input.payload);
1811
+ return typeof requiresPermission === "function" ? requiresPermission(payload) : requiresPermission;
1812
+ } catch {
1813
+ return false;
1814
+ }
1815
+ }
1816
+ } : {},
1458
1817
  async do(input) {
1459
1818
  if (routeIds.length > 0 && !routeIds.includes(input.routeId)) {
1460
1819
  return {
@@ -1467,7 +1826,13 @@ function createInstalledApplicationTransmissionTools(context, applications, inst
1467
1826
  const payload = await Schema.decodeUnknownPromise(
1468
1827
  transmission.outbound.input
1469
1828
  )(input.payload);
1470
- return emit({
1829
+ const directory = process.env[FOUNDRY_CORE_COMMAND_DIRECTORY_ENV];
1830
+ if (!directory) {
1831
+ throw new Error(
1832
+ "Foundry cannot call an application transmission without its parent runtime."
1833
+ );
1834
+ }
1835
+ const command = {
1471
1836
  id: commandId(),
1472
1837
  type: "transmit",
1473
1838
  definitionId: context.definitionId,
@@ -1476,9 +1841,31 @@ function createInstalledApplicationTransmissionTools(context, applications, inst
1476
1841
  workspaceId: context.workspaceId,
1477
1842
  routeId: input.routeId,
1478
1843
  payload,
1844
+ ...transmission.outbound.observe ? { observability: transmission.outbound.observe(payload) } : {},
1479
1845
  applicationId: application.id,
1480
1846
  transmissionId: transmission.id
1847
+ };
1848
+ const request = await createFoundryCoreCommandRequest(directory, {
1849
+ id: command.id,
1850
+ runId: context.runId,
1851
+ type: command.type,
1852
+ command
1481
1853
  });
1854
+ emit(command, privateTransmitEvent(command));
1855
+ const output = await waitForFoundryCoreCommandResult(
1856
+ directory,
1857
+ request,
1858
+ context.controls.signal,
1859
+ { cleanup: true }
1860
+ );
1861
+ const decoded = await Schema.decodeUnknownPromise(
1862
+ transmission.outbound.output
1863
+ )(output);
1864
+ return {
1865
+ status: "success",
1866
+ data: transmission.outbound.project ? transmission.outbound.project(decoded) : decoded,
1867
+ ...transmission.outbound.render ? { renderData: transmission.outbound.render(decoded) } : {}
1868
+ };
1482
1869
  } catch (cause) {
1483
1870
  return {
1484
1871
  status: "error",
@@ -1511,9 +1898,9 @@ function createFoundryCoreTools(context, desiredSchedules = []) {
1511
1898
  context.controls.emit({ type: FOUNDRY_CORE_COMMAND_EVENT, data: command });
1512
1899
  return success(command);
1513
1900
  };
1514
- const scheduleView = () => {
1901
+ const scheduleView = async () => {
1515
1902
  const records = new Map(
1516
- context.activations.filter((item) => item.kind === "scheduled").map((item) => [item.id, item])
1903
+ [...context.activations, ...await Effect5.runPromise(context.data.listActivations(context.workspaceId))].filter((item) => item.kind === "scheduled" && item.agentId === context.agentId).map((item) => [item.id, item])
1517
1904
  );
1518
1905
  const now = (/* @__PURE__ */ new Date()).toISOString();
1519
1906
  for (const desired of desiredSchedules) {
@@ -1561,6 +1948,20 @@ function createFoundryCoreTools(context, desiredSchedules = []) {
1561
1948
  if (current) records.set(command.activationId, {
1562
1949
  ...current,
1563
1950
  ...command.patch,
1951
+ status: current.status === "paused" ? "paused" : "pending",
1952
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1953
+ });
1954
+ } else if (command.type === "schedule.pause") {
1955
+ const current = records.get(command.activationId);
1956
+ if (current) records.set(command.activationId, {
1957
+ ...current,
1958
+ status: "paused",
1959
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1960
+ });
1961
+ } else if (command.type === "schedule.resume") {
1962
+ const current = records.get(command.activationId);
1963
+ if (current) records.set(command.activationId, {
1964
+ ...current,
1564
1965
  status: "pending",
1565
1966
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1566
1967
  });
@@ -1626,11 +2027,11 @@ function createFoundryCoreTools(context, desiredSchedules = []) {
1626
2027
  },
1627
2028
  {
1628
2029
  name: "glove_foundry_schedules",
1629
- description: "List, update, or cancel scheduled triggers owned by this agent instance. Use the activation id returned by list.",
2030
+ description: "List, update, pause, resume, or cancel scheduled triggers owned by this agent instance. Use the activation id returned by list.",
1630
2031
  inputSchema: z.discriminatedUnion("action", [
1631
2032
  z.object({
1632
2033
  action: z.literal("list"),
1633
- status: z.enum(["active", "completed", "cancelled", "all"]).default("active")
2034
+ status: z.enum(["active", "paused", "completed", "cancelled", "all"]).default("active")
1634
2035
  }),
1635
2036
  z.object({
1636
2037
  action: z.literal("update"),
@@ -1639,16 +2040,18 @@ function createFoundryCoreTools(context, desiredSchedules = []) {
1639
2040
  payload: z.unknown().optional(),
1640
2041
  timing: timingInputSchema.optional()
1641
2042
  }),
2043
+ z.object({ action: z.literal("pause"), activationId: z.string().min(1) }),
2044
+ z.object({ action: z.literal("resume"), activationId: z.string().min(1) }),
1642
2045
  z.object({ action: z.literal("cancel"), activationId: z.string().min(1) })
1643
2046
  ]),
1644
2047
  async do(input) {
2048
+ const scheduled = await scheduleView();
1645
2049
  if (input.action === "list") {
1646
- const scheduled = scheduleView();
1647
2050
  const filter = input.status ?? "active";
1648
2051
  const filtered = filter === "all" ? scheduled : filter === "active" ? scheduled.filter((item) => item.status === "pending" || item.status === "active") : scheduled.filter((item) => item.status === filter);
1649
2052
  return { status: "success", data: filtered };
1650
2053
  }
1651
- if (!scheduleView().some((item) => item.id === input.activationId && item.agentId === context.agentId && item.kind === "scheduled")) {
2054
+ if (!scheduled.some((item) => item.id === input.activationId && item.agentId === context.agentId && item.kind === "scheduled")) {
1652
2055
  return { status: "error", data: null, message: `Schedule "${input.activationId}" is not owned by this agent instance.` };
1653
2056
  }
1654
2057
  if (input.action === "cancel") {
@@ -1662,6 +2065,35 @@ function createFoundryCoreTools(context, desiredSchedules = []) {
1662
2065
  workspaceId: context.workspaceId
1663
2066
  });
1664
2067
  }
2068
+ if (input.action === "pause" || input.action === "resume") {
2069
+ const current = scheduled.find((item) => item.id === input.activationId);
2070
+ if (input.action === "pause" && current.status === "paused") {
2071
+ return { status: "success", data: current };
2072
+ }
2073
+ if (input.action === "resume" && current.status !== "paused") {
2074
+ return {
2075
+ status: "error",
2076
+ data: null,
2077
+ message: `Schedule "${input.activationId}" is not paused.`
2078
+ };
2079
+ }
2080
+ if (input.action === "pause" && (current.status === "completed" || current.status === "cancelled")) {
2081
+ return {
2082
+ status: "error",
2083
+ data: null,
2084
+ message: `Schedule "${input.activationId}" cannot be paused from status "${current.status}".`
2085
+ };
2086
+ }
2087
+ return emit({
2088
+ id: commandId(),
2089
+ type: input.action === "pause" ? "schedule.pause" : "schedule.resume",
2090
+ activationId: input.activationId,
2091
+ definitionId: context.definitionId,
2092
+ agentId: context.agentId,
2093
+ conversationId: context.conversationId,
2094
+ workspaceId: context.workspaceId
2095
+ });
2096
+ }
1665
2097
  const patch = {};
1666
2098
  if (input.message !== void 0) patch.message = input.message;
1667
2099
  if (input.payload !== void 0) patch.payload = input.payload;
@@ -1740,7 +2172,7 @@ function createFoundryCoreTools(context, desiredSchedules = []) {
1740
2172
  payload: z.unknown()
1741
2173
  }),
1742
2174
  async do(input) {
1743
- return emit({
2175
+ const command = {
1744
2176
  id: commandId(),
1745
2177
  type: "transmit",
1746
2178
  definitionId: context.definitionId,
@@ -1749,7 +2181,36 @@ function createFoundryCoreTools(context, desiredSchedules = []) {
1749
2181
  workspaceId: context.workspaceId,
1750
2182
  routeId: input.routeId,
1751
2183
  payload: input.payload
2184
+ };
2185
+ const directory = process.env[FOUNDRY_CORE_COMMAND_DIRECTORY_ENV];
2186
+ if (!directory) {
2187
+ return emit(command);
2188
+ }
2189
+ const request = await createFoundryCoreCommandRequest(directory, {
2190
+ id: command.id,
2191
+ runId: context.runId,
2192
+ type: command.type,
2193
+ command
1752
2194
  });
2195
+ context.controls.commands.push(command);
2196
+ context.controls.emit({ type: FOUNDRY_CORE_COMMAND_EVENT, data: privateTransmitEvent(command) });
2197
+ try {
2198
+ return {
2199
+ status: "success",
2200
+ data: await waitForFoundryCoreCommandResult(
2201
+ directory,
2202
+ request,
2203
+ context.controls.signal,
2204
+ { cleanup: true }
2205
+ )
2206
+ };
2207
+ } catch (cause) {
2208
+ return {
2209
+ status: "error",
2210
+ data: null,
2211
+ message: cause instanceof Error ? cause.message : String(cause)
2212
+ };
2213
+ }
1753
2214
  }
1754
2215
  },
1755
2216
  {
@@ -1888,6 +2349,9 @@ function createFoundryCoreTools(context, desiredSchedules = []) {
1888
2349
 
1889
2350
  // src/workbench.ts
1890
2351
  import { Effect as Effect6 } from "effect";
2352
+ import {
2353
+ getToolJsonSchema
2354
+ } from "glove-core";
1891
2355
  import {
1892
2356
  JsSession,
1893
2357
  mountJs
@@ -1976,6 +2440,153 @@ function defineRepl(options) {
1976
2440
  [FOUNDRY_REPL_BRAND]: true
1977
2441
  });
1978
2442
  }
2443
+ function isProgrammaticSelection(value) {
2444
+ return typeof value === "object" && value !== null && "tool" in value;
2445
+ }
2446
+ function parseProgrammaticToolData(data) {
2447
+ if (typeof data !== "string") return data;
2448
+ const text = data.trim();
2449
+ if (!text.startsWith("{") && !text.startsWith("[")) return data;
2450
+ try {
2451
+ return JSON.parse(text);
2452
+ } catch {
2453
+ return data;
2454
+ }
2455
+ }
2456
+ async function runProgrammaticTool(tool, input, signal2) {
2457
+ if (tool.unAbortable || !signal2) return tool.run(input, void 0, signal2);
2458
+ if (signal2.aborted) throw signal2.reason ?? new DOMException("Aborted", "AbortError");
2459
+ return new Promise((resolve2, reject) => {
2460
+ const abort = () => reject(signal2.reason ?? new DOMException("Aborted", "AbortError"));
2461
+ signal2.addEventListener("abort", abort, { once: true });
2462
+ tool.run(input, void 0, signal2).then(
2463
+ (value) => {
2464
+ signal2.removeEventListener("abort", abort);
2465
+ resolve2(value);
2466
+ },
2467
+ (cause) => {
2468
+ signal2.removeEventListener("abort", abort);
2469
+ reject(cause);
2470
+ }
2471
+ );
2472
+ });
2473
+ }
2474
+ function programmaticToolFunction(selection, options) {
2475
+ const decorated = isProgrammaticSelection(selection) ? selection : { tool: selection };
2476
+ const tool = decorated.tool;
2477
+ if (!options.available.has(tool)) {
2478
+ throw new Error(
2479
+ `Programmatic tool "${tool.name}" is not an exact reference from the assembled agent tool registry.`
2480
+ );
2481
+ }
2482
+ const name = decorated.name ?? tool.name;
2483
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
2484
+ throw new Error(
2485
+ `Programmatic tool name "${name}" is not a valid JavaScript/Python/Lisp identifier; provide a valid name override.`
2486
+ );
2487
+ }
2488
+ return {
2489
+ name,
2490
+ description: tool.description,
2491
+ inputSchema: getToolJsonSchema(tool),
2492
+ ...decorated.readOnly !== void 0 ? { readOnlyHint: decorated.readOnly } : {},
2493
+ ...decorated.server !== void 0 ? { server: decorated.server } : {},
2494
+ ...decorated.serverDescription !== void 0 ? { serverDescription: decorated.serverDescription } : {},
2495
+ ...decorated.resultShape !== void 0 ? { resultShape: decorated.resultShape } : {},
2496
+ async call(input, fnContext = {}) {
2497
+ if (options.budget.used >= options.budget.max) {
2498
+ options.context.controls.emit({
2499
+ type: "foundry.repl.programmatic-tool.limit",
2500
+ data: { tool: tool.name, function: name, maxCalls: options.budget.max }
2501
+ });
2502
+ throw new Error(
2503
+ `Programmatic tool-call limit (${options.budget.max}) reached for this run. Return the result already computed or continue in a new run.`
2504
+ );
2505
+ }
2506
+ options.budget.used += 1;
2507
+ const parsed = tool.input_schema?.safeParse(input);
2508
+ if (parsed && !parsed.success) {
2509
+ throw new Error(
2510
+ parsed.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`).join("; ")
2511
+ );
2512
+ }
2513
+ const validated = parsed?.success ? parsed.data : input;
2514
+ const permission = typeof tool.requiresPermission === "function" ? tool.requiresPermission(validated) : Boolean(tool.requiresPermission);
2515
+ if (permission) {
2516
+ options.context.controls.emit({
2517
+ type: "foundry.repl.programmatic-tool.denied",
2518
+ data: { tool: tool.name, function: name, reason: "interactive-approval-required" }
2519
+ });
2520
+ throw new Error(
2521
+ `Tool "${tool.name}" requires interactive approval and cannot run inside a programmatic workflow. Call it directly so the approval can be shown.`
2522
+ );
2523
+ }
2524
+ const started = Date.now();
2525
+ options.context.controls.emit({
2526
+ type: "foundry.repl.programmatic-tool.started",
2527
+ data: { tool: tool.name, function: name, call: options.budget.used }
2528
+ });
2529
+ try {
2530
+ const result = await runProgrammaticTool(tool, validated, fnContext.signal);
2531
+ if (result.status !== "success") {
2532
+ throw new Error(result.message ?? `Tool "${tool.name}" failed.`);
2533
+ }
2534
+ options.context.controls.emit({
2535
+ type: "foundry.repl.programmatic-tool.completed",
2536
+ data: { tool: tool.name, function: name, durationMs: Date.now() - started }
2537
+ });
2538
+ return parseProgrammaticToolData(result.data);
2539
+ } catch (cause) {
2540
+ options.context.controls.emit({
2541
+ type: "foundry.repl.programmatic-tool.failed",
2542
+ data: {
2543
+ tool: tool.name,
2544
+ function: name,
2545
+ durationMs: Date.now() - started,
2546
+ outcome: "error"
2547
+ }
2548
+ });
2549
+ throw cause;
2550
+ }
2551
+ }
2552
+ };
2553
+ }
2554
+ async function registerProgrammaticTools(options) {
2555
+ const config = options.repl.programmaticTools;
2556
+ if (!config) return 0;
2557
+ const max = config.maxCalls ?? 50;
2558
+ if (!Number.isInteger(max) || max < 1 || max > 1e3) {
2559
+ throw new Error("Foundry programmaticTools.maxCalls must be an integer from 1 to 1000.");
2560
+ }
2561
+ const tools = Object.freeze([...options.glove.tools]);
2562
+ const selected = await resolveResolvable(config.select({
2563
+ assembly: options.context,
2564
+ glove: options.glove,
2565
+ tools,
2566
+ ...options.workingEnvironment ? { workingEnvironment: options.workingEnvironment } : {},
2567
+ ...options.vfs ? { vfs: options.vfs } : {}
2568
+ }));
2569
+ const available = new Set(tools);
2570
+ const budget = { used: 0, max };
2571
+ const functions = selected.map(
2572
+ (selection) => programmaticToolFunction(selection, { available, budget, context: options.context })
2573
+ );
2574
+ const names = /* @__PURE__ */ new Set();
2575
+ for (const fn of functions) {
2576
+ if (names.has(fn.name)) throw new Error(`Duplicate programmatic function name "${fn.name}".`);
2577
+ names.add(fn.name);
2578
+ }
2579
+ switch (options.repl.language) {
2580
+ case "javascript":
2581
+ case "python":
2582
+ options.repl.session.registerAll(functions);
2583
+ break;
2584
+ case "lisp":
2585
+ options.repl.session.registerFns(functions);
2586
+ break;
2587
+ }
2588
+ return functions.length;
2589
+ }
1979
2590
  function persistenceContext(context) {
1980
2591
  return {
1981
2592
  definitionId: context.definitionId,
@@ -2018,6 +2629,7 @@ async function mountFoundryWorkbench(options) {
2018
2629
  let environment;
2019
2630
  let persistence;
2020
2631
  let mountedRepl;
2632
+ let replMountPromise;
2021
2633
  const dispose = async () => {
2022
2634
  if (!environment || !options.workingEnvironment) return;
2023
2635
  const failures = [];
@@ -2052,6 +2664,54 @@ async function mountFoundryWorkbench(options) {
2052
2664
  );
2053
2665
  }
2054
2666
  };
2667
+ const mountRepl = () => {
2668
+ const repl = options.repl;
2669
+ if (!repl) return Promise.resolve();
2670
+ if (replMountPromise) return replMountPromise;
2671
+ replMountPromise = (async () => {
2672
+ if (repl[FOUNDRY_REPL_BRAND] !== true) {
2673
+ throw new Error("Agent repl must be created with defineRepl(...).");
2674
+ }
2675
+ const programmaticTools = await registerProgrammaticTools({
2676
+ repl,
2677
+ glove: options.glove,
2678
+ context: options.context,
2679
+ ...environment ? { workingEnvironment: environment, vfs: environment.fs } : {}
2680
+ });
2681
+ switch (repl.language) {
2682
+ case "javascript":
2683
+ mountJs(options.glove, {
2684
+ session: repl.session,
2685
+ ...repl.mount ?? {},
2686
+ exclusive: false
2687
+ });
2688
+ break;
2689
+ case "python":
2690
+ mountPy(options.glove, {
2691
+ session: repl.session,
2692
+ ...repl.mount ?? {},
2693
+ exclusive: false
2694
+ });
2695
+ break;
2696
+ case "lisp":
2697
+ mountLisp(options.glove, {
2698
+ session: repl.session,
2699
+ ...repl.mount ?? {},
2700
+ exclusive: false
2701
+ });
2702
+ break;
2703
+ }
2704
+ options.context.controls.emit({
2705
+ type: "foundry.repl.mounted",
2706
+ data: {
2707
+ language: repl.language,
2708
+ frame: repl.mount?.frame ?? "repl",
2709
+ programmaticTools
2710
+ }
2711
+ });
2712
+ })();
2713
+ return replMountPromise;
2714
+ };
2055
2715
  try {
2056
2716
  if (options.workingEnvironment) {
2057
2717
  const created = await createEnvironment(
@@ -2075,52 +2735,13 @@ async function mountFoundryWorkbench(options) {
2075
2735
  });
2076
2736
  }
2077
2737
  if (options.repl) {
2078
- if (options.repl[FOUNDRY_REPL_BRAND] !== true) {
2079
- throw new Error("Agent repl must be created with defineRepl(...).");
2080
- }
2081
- switch (options.repl.language) {
2082
- case "javascript":
2083
- mountJs(options.glove, {
2084
- session: options.repl.session,
2085
- ...options.repl.mount ?? {}
2086
- });
2087
- mountedRepl = Object.freeze({
2088
- language: "javascript",
2089
- session: options.repl.session
2090
- });
2091
- break;
2092
- case "python":
2093
- mountPy(options.glove, {
2094
- session: options.repl.session,
2095
- ...options.repl.mount ?? {}
2096
- });
2097
- mountedRepl = Object.freeze({
2098
- language: "python",
2099
- session: options.repl.session
2100
- });
2101
- break;
2102
- case "lisp":
2103
- mountLisp(options.glove, {
2104
- session: options.repl.session,
2105
- ...options.repl.mount ?? {}
2106
- });
2107
- mountedRepl = Object.freeze({
2108
- language: "lisp",
2109
- session: options.repl.session
2110
- });
2111
- break;
2112
- }
2113
- options.context.controls.emit({
2114
- type: "foundry.repl.mounted",
2115
- data: {
2116
- language: options.repl.language,
2117
- frame: options.repl.mount?.frame ?? "repl"
2118
- }
2119
- });
2738
+ mountedRepl = options.repl.language === "javascript" ? Object.freeze({ language: "javascript", session: options.repl.session }) : options.repl.language === "python" ? Object.freeze({ language: "python", session: options.repl.session }) : Object.freeze({ language: "lisp", session: options.repl.session });
2739
+ if (!options.deferRepl) await mountRepl();
2120
2740
  }
2121
2741
  return {
2122
2742
  ...environment ? { workingEnvironment: environment, vfs: environment.fs } : {},
2123
2743
  ...mountedRepl ? { repl: mountedRepl } : {},
2744
+ mountRepl,
2124
2745
  dispose
2125
2746
  };
2126
2747
  } catch (cause) {
@@ -2136,9 +2757,220 @@ async function mountFoundryWorkbench(options) {
2136
2757
  }
2137
2758
  }
2138
2759
 
2760
+ // src/approval.ts
2761
+ import { randomUUID as randomUUID3 } from "node:crypto";
2762
+ import { mkdir as mkdir2, readFile as readFile2, readdir, writeFile as writeFile2 } from "node:fs/promises";
2763
+ import { join as join2 } from "node:path";
2764
+ import {
2765
+ Displaymanager as Displaymanager2,
2766
+ permissionKey
2767
+ } from "glove-core";
2768
+ var FOUNDRY_APPROVAL_DIRECTORY_ENV = "GLOVE_FOUNDRY_APPROVAL_DIRECTORY";
2769
+ var REQUEST_SUFFIX2 = ".request.json";
2770
+ var RESOLUTION_SUFFIX = ".resolution.json";
2771
+ var APPROVAL_ID = /^approval_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
2772
+ function approvalPath(directory, id2, suffix) {
2773
+ if (!APPROVAL_ID.test(id2)) throw new Error("Invalid Foundry approval id.");
2774
+ return join2(directory, `${id2}${suffix}`);
2775
+ }
2776
+ function serializable(value) {
2777
+ try {
2778
+ return JSON.parse(JSON.stringify(value));
2779
+ } catch {
2780
+ return String(value);
2781
+ }
2782
+ }
2783
+ async function readJson2(path) {
2784
+ try {
2785
+ return JSON.parse(await readFile2(path, "utf8"));
2786
+ } catch (cause) {
2787
+ if (cause.code === "ENOENT") return null;
2788
+ throw cause;
2789
+ }
2790
+ }
2791
+ function requestRecord2(value) {
2792
+ if (!value || typeof value !== "object") return null;
2793
+ const item = value;
2794
+ return typeof item.id === "string" && APPROVAL_ID.test(item.id) && typeof item.runId === "string" && typeof item.definitionId === "string" && typeof item.agentId === "string" && typeof item.conversationId === "string" && typeof item.workspaceId === "string" && typeof item.toolName === "string" && typeof item.requestedAt === "string" && typeof item.expiresAt === "string" ? item : null;
2795
+ }
2796
+ function resolutionRecord(value) {
2797
+ if (!value || typeof value !== "object") return null;
2798
+ const item = value;
2799
+ return typeof item.id === "string" && APPROVAL_ID.test(item.id) && ["approved", "denied", "expired", "cancelled"].includes(item.status ?? "") && typeof item.resolvedAt === "string" ? item : null;
2800
+ }
2801
+ async function writeResolution(directory, id2, status, rejectExisting = false) {
2802
+ const resolution = Object.freeze({ id: id2, status, resolvedAt: (/* @__PURE__ */ new Date()).toISOString() });
2803
+ try {
2804
+ await writeFile2(
2805
+ approvalPath(directory, id2, RESOLUTION_SUFFIX),
2806
+ `${JSON.stringify(resolution)}
2807
+ `,
2808
+ { encoding: "utf8", flag: "wx", mode: 384 }
2809
+ );
2810
+ return resolution;
2811
+ } catch (cause) {
2812
+ if (cause.code !== "EEXIST") throw cause;
2813
+ const existing = resolutionRecord(
2814
+ await readJson2(approvalPath(directory, id2, RESOLUTION_SUFFIX))
2815
+ );
2816
+ if (!existing) throw new Error(`Foundry approval "${id2}" has an invalid resolution.`);
2817
+ if (rejectExisting) {
2818
+ throw new Error(`Foundry approval "${id2}" is already ${existing.status}.`);
2819
+ }
2820
+ return existing;
2821
+ }
2822
+ }
2823
+ async function createFoundryApproval(directory, input) {
2824
+ await mkdir2(directory, { recursive: true, mode: 448 });
2825
+ const requestedAt = /* @__PURE__ */ new Date();
2826
+ const timeoutMs = Math.min(Math.max(input.timeoutMs ?? 3e5, 1e3), 6e5);
2827
+ const request = Object.freeze({
2828
+ id: `approval_${randomUUID3()}`,
2829
+ runId: input.runId,
2830
+ definitionId: input.definitionId,
2831
+ agentId: input.agentId,
2832
+ conversationId: input.conversationId,
2833
+ workspaceId: input.workspaceId,
2834
+ toolName: input.toolName,
2835
+ toolInput: serializable(input.toolInput),
2836
+ requestedAt: requestedAt.toISOString(),
2837
+ expiresAt: new Date(requestedAt.getTime() + timeoutMs).toISOString()
2838
+ });
2839
+ await writeFile2(
2840
+ approvalPath(directory, request.id, REQUEST_SUFFIX2),
2841
+ `${JSON.stringify(request)}
2842
+ `,
2843
+ { encoding: "utf8", flag: "wx", mode: 384 }
2844
+ );
2845
+ return Object.freeze({ ...request, status: "pending" });
2846
+ }
2847
+ async function listFoundryApprovals(directory, filter = {}) {
2848
+ let names;
2849
+ try {
2850
+ names = await readdir(directory);
2851
+ } catch (cause) {
2852
+ if (cause.code === "ENOENT") return [];
2853
+ throw cause;
2854
+ }
2855
+ const approvals = [];
2856
+ for (const name of names.filter((item) => item.endsWith(REQUEST_SUFFIX2)).sort()) {
2857
+ const request = requestRecord2(await readJson2(join2(directory, name)));
2858
+ if (!request) continue;
2859
+ const resolution = resolutionRecord(
2860
+ await readJson2(approvalPath(directory, request.id, RESOLUTION_SUFFIX))
2861
+ );
2862
+ const status = resolution?.status ?? (Date.parse(request.expiresAt) <= Date.now() ? "expired" : "pending");
2863
+ const approval = Object.freeze({
2864
+ ...request,
2865
+ status,
2866
+ ...resolution ? { resolvedAt: resolution.resolvedAt } : {}
2867
+ });
2868
+ if (filter.runId && approval.runId !== filter.runId) continue;
2869
+ if (filter.status && approval.status !== filter.status) continue;
2870
+ approvals.push(approval);
2871
+ }
2872
+ return approvals.sort((a, b) => a.requestedAt.localeCompare(b.requestedAt));
2873
+ }
2874
+ async function resolveFoundryApproval(directory, id2, decision) {
2875
+ const request = requestRecord2(await readJson2(approvalPath(directory, id2, REQUEST_SUFFIX2)));
2876
+ if (!request) throw new Error(`Foundry approval "${id2}" was not found.`);
2877
+ const current = (await listFoundryApprovals(directory)).find((item) => item.id === id2);
2878
+ if (!current || current.status !== "pending") {
2879
+ throw new Error(`Foundry approval "${id2}" is already ${current?.status ?? "unavailable"}.`);
2880
+ }
2881
+ const resolution = await writeResolution(
2882
+ directory,
2883
+ id2,
2884
+ decision === "approve" ? "approved" : "denied",
2885
+ true
2886
+ );
2887
+ return Object.freeze({ ...request, status: resolution.status, resolvedAt: resolution.resolvedAt });
2888
+ }
2889
+ async function waitForResolution(directory, approval, signal2) {
2890
+ for (; ; ) {
2891
+ const resolved = resolutionRecord(
2892
+ await readJson2(approvalPath(directory, approval.id, RESOLUTION_SUFFIX))
2893
+ );
2894
+ if (resolved) return resolved;
2895
+ if (signal2.aborted) return writeResolution(directory, approval.id, "cancelled");
2896
+ if (Date.parse(approval.expiresAt) <= Date.now()) {
2897
+ return writeResolution(directory, approval.id, "expired");
2898
+ }
2899
+ await new Promise((resolveWait) => setTimeout(resolveWait, 100));
2900
+ }
2901
+ }
2902
+ var FoundryApprovalDisplayManager = class extends Displaymanager2 {
2903
+ constructor(context) {
2904
+ super();
2905
+ this.context = context;
2906
+ }
2907
+ async pushAndWait(slot) {
2908
+ if (slot.renderer !== "permission_request") {
2909
+ return super.pushAndWait(slot);
2910
+ }
2911
+ const request = slot.input && typeof slot.input === "object" ? slot.input : {};
2912
+ const toolName = typeof request.toolName === "string" ? request.toolName : void 0;
2913
+ if (!this.context.directory || !toolName) {
2914
+ this.context.emit({
2915
+ type: "foundry.approval.unavailable",
2916
+ data: { toolName: toolName ?? "unknown", reason: "approval-channel-unavailable" }
2917
+ });
2918
+ return false;
2919
+ }
2920
+ try {
2921
+ const approval = await createFoundryApproval(this.context.directory, {
2922
+ runId: this.context.runId,
2923
+ definitionId: this.context.definitionId,
2924
+ agentId: this.context.agentId,
2925
+ conversationId: this.context.conversationId,
2926
+ workspaceId: this.context.workspaceId,
2927
+ toolName,
2928
+ toolInput: request.toolInput
2929
+ });
2930
+ this.context.emit({ type: "foundry.approval.requested", data: approval });
2931
+ const resolution = await waitForResolution(
2932
+ this.context.directory,
2933
+ approval,
2934
+ this.context.signal
2935
+ );
2936
+ this.context.emit({
2937
+ type: "foundry.approval.settled",
2938
+ data: { approvalId: approval.id, toolName, status: resolution.status }
2939
+ });
2940
+ return resolution.status === "approved";
2941
+ } catch (cause) {
2942
+ this.context.emit({
2943
+ type: "foundry.approval.failed",
2944
+ data: { toolName, error: cause instanceof Error ? cause.message : String(cause) }
2945
+ });
2946
+ return false;
2947
+ }
2948
+ }
2949
+ };
2950
+ function withFoundryPermissions(store) {
2951
+ if (store.getPermission && store.setPermission) return store;
2952
+ const permissions = /* @__PURE__ */ new Map();
2953
+ return new Proxy(store, {
2954
+ get(target, property, receiver) {
2955
+ if (property === "getPermission") {
2956
+ return async (toolName, input) => permissions.get(permissionKey(toolName, input)) ?? "unset";
2957
+ }
2958
+ if (property === "setPermission") {
2959
+ return async (toolName, status, input) => {
2960
+ const key = permissionKey(toolName, input);
2961
+ if (status === "unset") permissions.delete(key);
2962
+ else permissions.set(key, status);
2963
+ };
2964
+ }
2965
+ const value = Reflect.get(target, property, receiver);
2966
+ return typeof value === "function" ? value.bind(target) : value;
2967
+ }
2968
+ });
2969
+ }
2970
+
2139
2971
  // src/agent-runtime.ts
2140
2972
  import { pathToFileURL } from "node:url";
2141
- import { Displaymanager as Displaymanager2, Glove as Glove2 } from "glove-core";
2973
+ import { Glove as Glove2 } from "glove-core";
2142
2974
  import { mountMesh } from "glove-mesh";
2143
2975
  import { Effect as Effect7 } from "effect";
2144
2976
  import { signal } from "station-signal";
@@ -2191,7 +3023,7 @@ function extractOutput(result) {
2191
3023
  }
2192
3024
  return "text" in object ? object.text : result;
2193
3025
  }
2194
- function serializable(value) {
3026
+ function serializable2(value) {
2195
3027
  try {
2196
3028
  return JSON.parse(JSON.stringify(value));
2197
3029
  } catch {
@@ -2201,7 +3033,7 @@ function serializable(value) {
2201
3033
  function writeAgentEvent(type, data) {
2202
3034
  const line = `${FOUNDRY_EVENT_PREFIX}${JSON.stringify({
2203
3035
  type,
2204
- data: serializable(data),
3036
+ data: serializable2(data),
2205
3037
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2206
3038
  })}
2207
3039
  `;
@@ -2289,8 +3121,9 @@ async function runDefinition(definition, id2, runtimeValue) {
2289
3121
  conversationId: request.conversationId,
2290
3122
  workspaceId: request.workspaceId
2291
3123
  }) : null;
3124
+ const runtimeStore = store ? withFoundryPermissions(store) : null;
2292
3125
  const history = Object.freeze(
2293
- (store ? await store.getMessages() : []).map(freezeGloveMessage)
3126
+ (runtimeStore ? await runtimeStore.getMessages() : []).map(freezeGloveMessage)
2294
3127
  );
2295
3128
  const message = toGloveMessage(request.message);
2296
3129
  const messages = Object.freeze([...history, message]);
@@ -2333,7 +3166,7 @@ async function runDefinition(definition, id2, runtimeValue) {
2333
3166
  history,
2334
3167
  messages,
2335
3168
  installations,
2336
- store,
3169
+ store: runtimeStore,
2337
3170
  subscriber,
2338
3171
  controls
2339
3172
  };
@@ -2347,13 +3180,26 @@ async function runDefinition(definition, id2, runtimeValue) {
2347
3180
  const [model, systemPrompt, displayManager, compactionLimit, compactionInstructions, maxTurns] = await Promise.all([
2348
3181
  resolveOptional("model", definition.model, HANDLER_ONLY_MODEL),
2349
3182
  resolveOptional("systemPrompt", definition.systemPrompt, ""),
2350
- resolveOptional("displayManager", definition.displayManager, new Displaymanager2()),
3183
+ resolveOptional(
3184
+ "displayManager",
3185
+ definition.displayManager,
3186
+ new FoundryApprovalDisplayManager({
3187
+ directory: process.env[FOUNDRY_APPROVAL_DIRECTORY_ENV],
3188
+ runId,
3189
+ definitionId: id2,
3190
+ agentId: request.agentId,
3191
+ conversationId: request.conversationId,
3192
+ workspaceId: request.workspaceId,
3193
+ signal: abortController.signal,
3194
+ emit: controls.emit
3195
+ })
3196
+ ),
2351
3197
  resolveOptional("compactionLimit", definition.compactionLimit, void 0),
2352
3198
  resolveOptional("compactionInstructions", definition.compactionInstructions, "Preserve goals, decisions, unresolved work, tool results, and pending inbox items."),
2353
3199
  resolveOptional("maxTurns", definition.maxTurns, void 0)
2354
3200
  ]);
2355
3201
  const base = new Glove2({
2356
- ...store ? { store } : {},
3202
+ ...runtimeStore ? { store: runtimeStore } : {},
2357
3203
  model,
2358
3204
  displayManager,
2359
3205
  systemPrompt,
@@ -2481,7 +3327,8 @@ async function runDefinition(definition, id2, runtimeValue) {
2481
3327
  glove: base,
2482
3328
  context: assemblyContext,
2483
3329
  ...workingEnvironment ? { workingEnvironment } : {},
2484
- ...repl ? { repl } : {}
3330
+ ...repl ? { repl } : {},
3331
+ deferRepl: Boolean(repl?.programmaticTools)
2485
3332
  });
2486
3333
  cleanups.push(workbench.dispose);
2487
3334
  const callByName = /* @__PURE__ */ new Map();
@@ -2511,8 +3358,22 @@ async function runDefinition(definition, id2, runtimeValue) {
2511
3358
  signal: abortController.signal,
2512
3359
  emit: controls.emit
2513
3360
  };
3361
+ const guidance = await mountFoundryGuidance(base, assemblyContext, {
3362
+ facts: await resolveOptional("facts", definition.facts, void 0),
3363
+ goals: await resolveOptional("goals", definition.goals, void 0),
3364
+ forms: await resolveOptional("forms", definition.forms, void 0),
3365
+ contextProviders: await resolveOptional("contextProviders", definition.contextProviders, [])
3366
+ });
3367
+ cleanups.push(async () => {
3368
+ try {
3369
+ await guidance.snapshot();
3370
+ } finally {
3371
+ guidance.dispose();
3372
+ }
3373
+ });
2514
3374
  const callContext = {
2515
3375
  ...surfaceContext,
3376
+ ...guidance.handles,
2516
3377
  installations: effectiveInstallations,
2517
3378
  invoke
2518
3379
  };
@@ -2616,11 +3477,12 @@ async function runDefinition(definition, id2, runtimeValue) {
2616
3477
  if (definition.configure) {
2617
3478
  await resolveResolvable(definition.configure(base, callContext));
2618
3479
  }
3480
+ await workbench.mountRepl();
2619
3481
  const glove = definition.build ? await resolveResolvable(definition.build(base, assemblyContext)) ?? base : base;
2620
3482
  const runtimeContext = { ...callContext, glove };
2621
- const defaultRun = async () => extractOutput(
3483
+ const defaultRun = async (messageInput = assemblyContext.messageInput) => extractOutput(
2622
3484
  await glove.processRequest(
2623
- toGloveRequestInput(assemblyContext.messageInput),
3485
+ toGloveRequestInput(messageInput),
2624
3486
  abortController.signal
2625
3487
  )
2626
3488
  );
@@ -2628,7 +3490,7 @@ async function runDefinition(definition, id2, runtimeValue) {
2628
3490
  await resolveResolvable(definition.spawn(glove, runtimeContext, messageInput)),
2629
3491
  messageInput,
2630
3492
  abortController.signal
2631
- ) : defaultRun();
3493
+ ) : defaultRun(messageInput);
2632
3494
  const handlerContext = {
2633
3495
  ...runtimeContext,
2634
3496
  defaultRun,
@@ -2636,12 +3498,13 @@ async function runDefinition(definition, id2, runtimeValue) {
2636
3498
  spawn
2637
3499
  };
2638
3500
  const result = definition.run ? await resolveResolvable(definition.run(glove, handlerContext)) : definition.handler ? await resolveResolvable(definition.handler(handlerContext)) : definition.spawn ? await spawn() : await defaultRun();
3501
+ await guidance.snapshot();
2639
3502
  const sleep = [...controls.commands].reverse().find(
2640
3503
  (command) => command.type === "sleep"
2641
3504
  );
2642
3505
  return {
2643
3506
  status: sleep ? "suspended" : "completed",
2644
- value: serializable(result),
3507
+ value: serializable2(result),
2645
3508
  agentId: request.agentId,
2646
3509
  conversationId: request.conversationId,
2647
3510
  workspaceId: request.workspaceId,
@@ -2669,6 +3532,7 @@ function compileAgentDefinition(definition, route) {
2669
3532
  const contentPartSchema = z2.object({
2670
3533
  type: z2.enum(["text", "image", "video", "document"]),
2671
3534
  text: z2.string().optional(),
3535
+ name: z2.string().optional(),
2672
3536
  source: z2.object({
2673
3537
  type: z2.enum(["base64", "url"]),
2674
3538
  media_type: z2.string(),
@@ -2764,7 +3628,7 @@ function compileAgentDefinition(definition, route) {
2764
3628
  origin: z2.enum(["agent-definition", "agent-tool"]),
2765
3629
  scheduleName: z2.string().optional(),
2766
3630
  definitionRevision: z2.string().optional(),
2767
- status: z2.enum(["pending", "active", "completed", "cancelled"]),
3631
+ status: z2.enum(["pending", "active", "paused", "completed", "cancelled"]),
2768
3632
  createdByRunId: z2.string(),
2769
3633
  lastRunId: z2.string().optional(),
2770
3634
  createdAt: z2.string(),
@@ -2788,6 +3652,115 @@ function compileAgentModule(route, module) {
2788
3652
  return compileAgentDefinition(defineAgentFromModule(route, module), route);
2789
3653
  }
2790
3654
 
3655
+ // src/composition.ts
3656
+ var composedToolBodies = /* @__PURE__ */ new WeakMap();
3657
+ function isToolBody(value) {
3658
+ if (!value || typeof value !== "object") return false;
3659
+ const body = value;
3660
+ return typeof body.description === "string" && typeof body.do === "function" && (body.inputSchema !== void 0 || body.jsonSchema !== void 0);
3661
+ }
3662
+ function toolFromBody(body) {
3663
+ const existing = composedToolBodies.get(body);
3664
+ if (existing) return existing;
3665
+ let definition;
3666
+ const tool = { ...body };
3667
+ Object.defineProperty(tool, "name", {
3668
+ enumerable: true,
3669
+ get: () => definition.id.replaceAll("/", "__").replaceAll("-", "_")
3670
+ });
3671
+ definition = defineSharedTool({
3672
+ description: body.description,
3673
+ tool
3674
+ });
3675
+ composedToolBodies.set(body, definition);
3676
+ return definition;
3677
+ }
3678
+ function composedToolDefinition(body) {
3679
+ if (!isToolBody(body)) return void 0;
3680
+ return composedToolBodies.get(body) ?? toolFromBody(body);
3681
+ }
3682
+ function addUnique(values, value, kind) {
3683
+ const key = fileDefinitionKey(value);
3684
+ if (values.some((candidate) => fileDefinitionKey(candidate) === key)) {
3685
+ throw new Error(`Duplicate agent-local ${kind} "${fileDefinitionLabel(value)}".`);
3686
+ }
3687
+ values.push(value);
3688
+ }
3689
+ function composeAgent(...sources) {
3690
+ const capabilities = {
3691
+ tools: [...EMPTY_CAPABILITY_REGISTRY.tools],
3692
+ applications: [...EMPTY_CAPABILITY_REGISTRY.applications],
3693
+ mcp: [...EMPTY_CAPABILITY_REGISTRY.mcp],
3694
+ memory: [...EMPTY_CAPABILITY_REGISTRY.memory]
3695
+ };
3696
+ const native = {
3697
+ layers: [...EMPTY_NATIVE_REGISTRY.layers],
3698
+ subscribers: [...EMPTY_NATIVE_REGISTRY.subscribers]
3699
+ };
3700
+ const visit = (source) => {
3701
+ if (!source) return;
3702
+ if (typeof source === "function") {
3703
+ visit(source());
3704
+ return;
3705
+ }
3706
+ if (Array.isArray(source)) {
3707
+ for (const child of source) visit(child);
3708
+ return;
3709
+ }
3710
+ if ("capabilities" in source && "native" in source) {
3711
+ visit([
3712
+ ...source.capabilities.tools,
3713
+ ...source.capabilities.applications,
3714
+ ...source.capabilities.mcp,
3715
+ ...source.capabilities.memory,
3716
+ ...source.native.layers,
3717
+ ...source.native.subscribers
3718
+ ]);
3719
+ return;
3720
+ }
3721
+ const branded = source;
3722
+ if (branded[FOUNDRY_SHARED_TOOL_BRAND] === true) {
3723
+ addUnique(capabilities.tools, branded, "tool");
3724
+ } else if (branded[FOUNDRY_AGENT_APPLICATION_BRAND] === true) {
3725
+ addUnique(
3726
+ capabilities.applications,
3727
+ branded,
3728
+ "application"
3729
+ );
3730
+ } else if (branded[FOUNDRY_MCP_BRAND] === true) {
3731
+ addUnique(capabilities.mcp, branded, "MCP");
3732
+ } else if (branded[FOUNDRY_MEMORY_BRAND] === true) {
3733
+ addUnique(
3734
+ capabilities.memory,
3735
+ branded,
3736
+ "memory"
3737
+ );
3738
+ } else if (branded[FOUNDRY_LAYER_BRAND] === true) {
3739
+ addUnique(native.layers, branded, "layer");
3740
+ } else if (branded[FOUNDRY_SUBSCRIBER_BRAND] === true) {
3741
+ addUnique(native.subscribers, branded, "subscriber");
3742
+ } else if (isToolBody(source)) {
3743
+ addUnique(capabilities.tools, toolFromBody(source), "tool");
3744
+ } else {
3745
+ throw new Error("composeAgent received an unrecognized Foundry definition.");
3746
+ }
3747
+ };
3748
+ for (const source of sources) visit(source);
3749
+ return Object.freeze({
3750
+ capabilities: Object.freeze({
3751
+ tools: Object.freeze(capabilities.tools),
3752
+ applications: Object.freeze(capabilities.applications),
3753
+ mcp: Object.freeze(capabilities.mcp),
3754
+ memory: Object.freeze(capabilities.memory)
3755
+ }),
3756
+ native: Object.freeze({
3757
+ layers: Object.freeze(native.layers),
3758
+ subscribers: Object.freeze(native.subscribers)
3759
+ })
3760
+ });
3761
+ }
3762
+ var EMPTY_AGENT_COMPOSITION = composeAgent();
3763
+
2791
3764
  // src/connection.ts
2792
3765
  var FOUNDRY_CONNECTION_BRAND = /* @__PURE__ */ Symbol.for(
2793
3766
  "glove-foundry-application-connection"
@@ -2976,12 +3949,13 @@ function defineTransmission(options) {
2976
3949
  predicates: Object.freeze([...options.inbound.predicates ?? []])
2977
3950
  })
2978
3951
  } : {},
3952
+ ...options.outbound ? { outbound: Object.freeze({ ...options.outbound }) } : {},
2979
3953
  [FOUNDRY_TRANSMISSION_BRAND]: true
2980
3954
  }, "transmission", id2));
2981
3955
  }
2982
3956
 
2983
3957
  // src/discovery.ts
2984
- import { readdir } from "node:fs/promises";
3958
+ import { readdir as readdir2 } from "node:fs/promises";
2985
3959
  import { dirname, extname, relative, resolve, sep } from "node:path";
2986
3960
  import { pathToFileURL as pathToFileURL2 } from "node:url";
2987
3961
  var AGENT_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".js", ".mjs"]);
@@ -3019,7 +3993,7 @@ function localDefinitionRoute(agentDirectory, filePath, suffix) {
3019
3993
  async function bindAgentLocalDefinitions(agentDirectory) {
3020
3994
  let entries;
3021
3995
  try {
3022
- entries = await readdir(agentDirectory, { recursive: true });
3996
+ entries = await readdir2(agentDirectory, { recursive: true });
3023
3997
  } catch {
3024
3998
  return;
3025
3999
  }
@@ -3034,10 +4008,10 @@ async function bindAgentLocalDefinitions(agentDirectory) {
3034
4008
  const filePath = resolve(agentDirectory, entry);
3035
4009
  const url = pathToFileURL2(filePath);
3036
4010
  const imported = await import(url.href);
3037
- const value = imported.default;
4011
+ const value = matched.kind === "tool" ? imported.default && typeof imported.default === "object" && imported.default[matched.brand] === true ? imported.default : composedToolDefinition(imported.default ?? imported) : imported.default;
3038
4012
  if (!value || typeof value !== "object" || value[matched.brand] !== true) {
3039
4013
  throw new Error(
3040
- `${normalizePath(relative(agentDirectory, filePath))} must default-export its Foundry ${matched.kind} definition so the file can own its identity.`
4014
+ `${normalizePath(relative(agentDirectory, filePath))} must ${matched.kind === "tool" ? "export a Glove tool body or default-export" : "default-export"} its Foundry ${matched.kind} definition so the file can own its identity.`
3041
4015
  );
3042
4016
  }
3043
4017
  const route = localDefinitionRoute(agentDirectory, filePath, matched.suffix);
@@ -3063,7 +4037,7 @@ async function findAgentFiles(agentsDir) {
3063
4037
  const absolute = resolve(agentsDir);
3064
4038
  let entries;
3065
4039
  try {
3066
- entries = await readdir(absolute, { recursive: true });
4040
+ entries = await readdir2(absolute, { recursive: true });
3067
4041
  } catch (error) {
3068
4042
  const message = error instanceof Error ? error.message : String(error);
3069
4043
  throw new Error(`Cannot read Foundry agents directory ${absolute}: ${message}`);
@@ -3164,6 +4138,10 @@ function createManifest(agents) {
3164
4138
  "skills",
3165
4139
  "subagents",
3166
4140
  "memory",
4141
+ "goals",
4142
+ "facts",
4143
+ "forms",
4144
+ "contextProviders",
3167
4145
  "inboxes",
3168
4146
  "subscribers",
3169
4147
  "layers",
@@ -3184,8 +4162,6 @@ export {
3184
4162
  defineApplication,
3185
4163
  EMPTY_FOUNDRY_APPLICATION,
3186
4164
  bindFileIdentity,
3187
- fileDefinitionKey,
3188
- fileDefinitionLabel,
3189
4165
  FOUNDRY_AGENT_DEFINITION_BRAND,
3190
4166
  FOUNDRY_AGENT_BRAND,
3191
4167
  FOUNDRY_EVENT_PREFIX,
@@ -3202,6 +4178,10 @@ export {
3202
4178
  defineAgentFromModule,
3203
4179
  defineSubagent,
3204
4180
  defineRoutes,
4181
+ defineFacts,
4182
+ defineGoals,
4183
+ defineForms,
4184
+ foundryGuidanceSubject,
3205
4185
  FOUNDRY_SHARED_TOOL_BRAND,
3206
4186
  FOUNDRY_AGENT_APPLICATION_BRAND,
3207
4187
  FOUNDRY_MCP_BRAND,
@@ -3236,6 +4216,7 @@ export {
3236
4216
  definePlaybookSubscription,
3237
4217
  reconstructPlaybookSubscription,
3238
4218
  toGloveMessage,
4219
+ freezeGloveMessage,
3239
4220
  toGloveRequestInput,
3240
4221
  MemoryFoundryDataAdapter,
3241
4222
  createAgentInstance,
@@ -3247,7 +4228,13 @@ export {
3247
4228
  FOUNDRY_SCHEDULE_BRAND,
3248
4229
  defineSchedule,
3249
4230
  isFoundrySchedule,
4231
+ FOUNDRY_CORE_COMMAND_DIRECTORY_ENV,
4232
+ getFoundryCoreCommandRequest,
4233
+ claimFoundryCoreCommandRequest,
4234
+ settleFoundryCoreCommandRequest,
4235
+ monitorFoundryCoreCommandRequest,
3250
4236
  FOUNDRY_CORE_COMMAND_EVENT,
4237
+ installedApplicationTransmissionToolName,
3251
4238
  createInstalledApplicationTransmissionTools,
3252
4239
  createFoundryCoreTools,
3253
4240
  FOUNDRY_WORKING_ENVIRONMENT_BRAND,
@@ -3255,8 +4242,16 @@ export {
3255
4242
  defineWorkingEnvironment,
3256
4243
  foundryDataEnvironmentPersistence,
3257
4244
  defineRepl,
4245
+ FOUNDRY_APPROVAL_DIRECTORY_ENV,
4246
+ createFoundryApproval,
4247
+ listFoundryApprovals,
4248
+ resolveFoundryApproval,
4249
+ FoundryApprovalDisplayManager,
4250
+ withFoundryPermissions,
3258
4251
  compileAgentDefinition,
3259
4252
  compileAgentModule,
4253
+ composeAgent,
4254
+ EMPTY_AGENT_COMPOSITION,
3260
4255
  FOUNDRY_CONNECTION_BRAND,
3261
4256
  defineConnection,
3262
4257
  FOUNDRY_TRANSMISSION_BRAND,