glove-foundry 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +27 -15
  2. package/dist/{chunk-KBMD4RYU.js → chunk-6Q2LGPSC.js} +49 -198
  3. package/dist/{chunk-GKZLWD5M.js → chunk-7F7QVO6I.js} +4 -1
  4. package/dist/{chunk-SGF3CFUR.js → chunk-D2AHSYOB.js} +71 -8
  5. package/dist/{chunk-TH5ETCG2.js → chunk-MABLKZVO.js} +13 -4
  6. package/dist/{chunk-LW5PJ2FN.js → chunk-NHV64WNO.js} +1 -1
  7. package/dist/{chunk-5NFLGHOC.js → chunk-PVOKR4CN.js} +211 -29
  8. package/dist/cli.js +7 -5
  9. package/dist/{client-DcsnudMI.d.ts → client-CLTds1uV.d.ts} +13 -1
  10. package/dist/client.d.ts +1 -1
  11. package/dist/client.js +1 -1
  12. package/dist/execution-agent.js +4 -4
  13. package/dist/execution-daemon.js +1 -1
  14. package/dist/execution-loader.js +4 -4
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.js +14 -14
  17. package/docs/building-with-foundry.md +4 -2
  18. package/docs/release-verification.md +1 -1
  19. package/package.json +6 -6
  20. package/templates/minimal/README.md +34 -8
  21. package/templates/minimal/agents/assistant/agent.ts +24 -10
  22. package/templates/minimal/foundry.application.ts +11 -3
  23. package/templates/minimal/gitignore +1 -0
  24. package/templates/minimal/lib/file-store.ts +95 -0
  25. package/templates/travel-concierge/README.md +46 -12
  26. package/templates/travel-concierge/agents/concierge/agent.ts +10 -4
  27. package/templates/travel-concierge/agents/concierge/composition.ts +9 -2
  28. package/templates/travel-concierge/agents/concierge/flights.ts +43 -0
  29. package/templates/travel-concierge/agents/concierge/instances.ts +42 -0
  30. package/templates/travel-concierge/agents/concierge/memory/traveller.memory.ts +13 -8
  31. package/templates/travel-concierge/agents/concierge/tools/convert-currency.tool.ts +34 -0
  32. package/templates/travel-concierge/agents/concierge/workbench.ts +4 -1
  33. package/templates/travel-concierge/foundry.application.ts +15 -6
  34. package/templates/travel-concierge/gitignore +1 -0
  35. package/templates/travel-concierge/lib/demo-model.ts +89 -25
  36. package/templates/travel-concierge/lib/file-context.ts +60 -0
  37. package/templates/travel-concierge/lib/file-store.ts +95 -0
  38. package/templates/minimal/agents/assistant/tools/current-time.tool.ts +0 -17
  39. package/templates/travel-concierge/agents/concierge/tools/find-flights.tool.ts +0 -49
@@ -8,16 +8,13 @@ import {
8
8
  EMPTY_AGENT_COMPOSITION,
9
9
  EMPTY_CAPABILITY_REGISTRY,
10
10
  EMPTY_FOUNDRY_APPLICATION,
11
- EMPTY_NATIVE_REGISTRY,
12
11
  FOUNDRY_AGENT_APPLICATION_BRAND,
13
12
  FOUNDRY_APPROVAL_DIRECTORY_ENV,
14
13
  FOUNDRY_CORE_COMMAND_DIRECTORY_ENV,
15
14
  FOUNDRY_CORE_COMMAND_EVENT,
16
- FOUNDRY_LAYER_BRAND,
17
15
  FOUNDRY_MCP_BRAND,
18
16
  FOUNDRY_MEMORY_BRAND,
19
17
  FOUNDRY_SHARED_TOOL_BRAND,
20
- FOUNDRY_SUBSCRIBER_BRAND,
21
18
  MemoryFoundryDataAdapter,
22
19
  claimFoundryCoreCommandRequest,
23
20
  createAgentInstance,
@@ -38,13 +35,17 @@ import {
38
35
  resolveFoundryApproval,
39
36
  settleFoundryCoreCommandRequest,
40
37
  transmissionPredicate
41
- } from "./chunk-KBMD4RYU.js";
38
+ } from "./chunk-6Q2LGPSC.js";
42
39
  import {
40
+ EMPTY_NATIVE_REGISTRY,
43
41
  FOUNDRY_APPLICATION_ENV,
44
42
  FOUNDRY_EVENT_PREFIX,
45
43
  FOUNDRY_EXECUTION_MARKER,
44
+ FOUNDRY_LAYER_BRAND,
45
+ FOUNDRY_RUN_PROCESS_EVENT,
46
+ FOUNDRY_SUBSCRIBER_BRAND,
46
47
  bindFileIdentity
47
- } from "./chunk-5NFLGHOC.js";
48
+ } from "./chunk-PVOKR4CN.js";
48
49
 
49
50
  // src/codegen.ts
50
51
  import { mkdir, writeFile } from "node:fs/promises";
@@ -1038,6 +1039,7 @@ var ApplicationConnectionSupervisor = class {
1038
1039
  }
1039
1040
  running = /* @__PURE__ */ new Map();
1040
1041
  states = /* @__PURE__ */ new Map();
1042
+ closed = false;
1041
1043
  list() {
1042
1044
  return [...this.states.values()].map((state) => ({
1043
1045
  ...state,
@@ -1063,10 +1065,18 @@ var ApplicationConnectionSupervisor = class {
1063
1065
  await this.stop(id2);
1064
1066
  this.start(current);
1065
1067
  }
1068
+ /**
1069
+ * Stop every connection for good. A reconcile still in flight (a run's
1070
+ * playbook sync, an installation change) must not start a connection after
1071
+ * this: nothing would stop it, and a provider connection runs until it is
1072
+ * interrupted, so the process could never exit.
1073
+ */
1066
1074
  async stopAll() {
1075
+ this.closed = true;
1067
1076
  await Promise.all([...this.running.keys()].map((id2) => this.stop(id2)));
1068
1077
  }
1069
1078
  start(desired) {
1079
+ if (this.closed) return;
1070
1080
  const controller = new AbortController();
1071
1081
  const task = this.run(desired, controller.signal).finally(() => {
1072
1082
  const current = this.running.get(desired.id);
@@ -1444,6 +1454,9 @@ function promiseEffect(operation, evaluate) {
1444
1454
  catch: (cause) => runtimeFailure(operation, cause)
1445
1455
  }).pipe(Effect5.withSpan(`foundry.${operation}`));
1446
1456
  }
1457
+ var RUN_PROCESS_RELEASE_TIMEOUT_MS = 1e4;
1458
+ var RUN_PROCESS_REPORT_GRACE_MS = 1e3;
1459
+ var RUN_PROCESS_RETENTION_MS = 6e4;
1447
1460
  var FoundryRuntime = class _FoundryRuntime {
1448
1461
  rootDir;
1449
1462
  agents;
@@ -1472,6 +1485,8 @@ var FoundryRuntime = class _FoundryRuntime {
1472
1485
  activationTransitions = Effect5.unsafeMakeSemaphore(1);
1473
1486
  activationDispatches = /* @__PURE__ */ new Map();
1474
1487
  activationTimers = /* @__PURE__ */ new Map();
1488
+ /** Whether each run's child process still holds its conversation, from FOUNDRY_RUN_PROCESS_EVENT. */
1489
+ runProcesses = /* @__PURE__ */ new Map();
1475
1490
  approvalDirectory = join2(
1476
1491
  tmpdir2(),
1477
1492
  "glove-foundry-approvals",
@@ -1545,6 +1560,11 @@ var FoundryRuntime = class _FoundryRuntime {
1545
1560
  this.observability,
1546
1561
  (name) => this.routeBySignalName.get(name) ?? name,
1547
1562
  (event) => {
1563
+ if (event.type === FOUNDRY_RUN_PROCESS_EVENT) {
1564
+ const state = event.data?.state;
1565
+ if (state === "acquired" || state === "released") this.trackRunProcess(event.run.id, state);
1566
+ return;
1567
+ }
1548
1568
  if (event.type === FOUNDRY_CORE_COMMAND_EVENT) {
1549
1569
  void this.executeCoreCommand(event.data, event.run.id).catch((cause) => {
1550
1570
  this.observability.append({
@@ -2049,6 +2069,42 @@ var FoundryRuntime = class _FoundryRuntime {
2049
2069
  cancel(runId) {
2050
2070
  return this.signalRunner.cancel(runId);
2051
2071
  }
2072
+ trackRunProcess(runId, state) {
2073
+ this.runProcesses.set(runId, state);
2074
+ if (state !== "released") return;
2075
+ const forget = setTimeout(() => {
2076
+ if (this.runProcesses.get(runId) === "released") this.runProcesses.delete(runId);
2077
+ }, RUN_PROCESS_RETENTION_MS);
2078
+ forget.unref?.();
2079
+ }
2080
+ /**
2081
+ * Wait until a cancelled run's process has let go of its conversation. A run
2082
+ * that never started has no process. A process that dies before it reports
2083
+ * (SIGTERM during startup, or Station's SIGKILL five seconds after the
2084
+ * cancel) cannot report, so the wait is bounded by that kill.
2085
+ */
2086
+ async waitForRunProcessRelease(run, agent) {
2087
+ if (!run.startedAt) return;
2088
+ const started = Date.now();
2089
+ for (; ; ) {
2090
+ const state = this.runProcesses.get(run.id);
2091
+ const waited = Date.now() - started;
2092
+ if (state === "released") return;
2093
+ if (state === void 0 && waited >= RUN_PROCESS_REPORT_GRACE_MS) return;
2094
+ if (waited >= RUN_PROCESS_RELEASE_TIMEOUT_MS) {
2095
+ this.runProcesses.delete(run.id);
2096
+ this.observability.append({
2097
+ type: "run.steer.release-timeout",
2098
+ category: "system",
2099
+ agent,
2100
+ runId: run.id,
2101
+ data: { waitedMs: waited }
2102
+ });
2103
+ return;
2104
+ }
2105
+ await new Promise((resolveWait) => setTimeout(resolveWait, 25));
2106
+ }
2107
+ }
2052
2108
  /**
2053
2109
  * Change course at a run boundary.
2054
2110
  *
@@ -2073,6 +2129,7 @@ var FoundryRuntime = class _FoundryRuntime {
2073
2129
  if (!terminal || (terminal.status === "pending" || terminal.status === "running")) {
2074
2130
  throw new Error(`Foundry could not reach a safe steering boundary for run "${runId}".`);
2075
2131
  }
2132
+ await this.waitForRunProcessRelease(terminal, source.agent);
2076
2133
  }
2077
2134
  const run = await this.send(source.agentId, source.conversationId, message, {
2078
2135
  context: {
@@ -3771,7 +3828,8 @@ function skeleton(rows){
3771
3828
  async function api(url,options){
3772
3829
  const response=await fetch(url,options);let body;
3773
3830
  try{body=await response.json()}catch{body={error:"The runtime returned an unreadable response."}}
3774
- if(!response.ok)throw new Error(body.error||("Request failed ("+response.status+")"));return body;
3831
+ /* /health answers 503 with the same body when a subsystem is down; the header shows it. */
3832
+ if(!response.ok&&!(url==="/health"&&response.status===503))throw new Error(body.error||("Request failed ("+response.status+")"));return body;
3775
3833
  }
3776
3834
  function toast(message){const node=$("toast");node.textContent=message;node.classList.add("show");setTimeout(()=>node.classList.remove("show"),2400)}
3777
3835
  function route(){const parts=location.pathname.split("/").filter(Boolean).map(decodeURIComponent);return{parts,section:parts[0]||"overview",id:parts.slice(1).join("/")}}
@@ -4772,11 +4830,15 @@ function installationFrom(value) {
4772
4830
  if (typeof body.id !== "string" || !body.id) {
4773
4831
  throw new RequestError(400, "id is required.");
4774
4832
  }
4833
+ if (body.accountId !== void 0 && (typeof body.accountId !== "string" || !body.accountId)) {
4834
+ throw new RequestError(400, "accountId must be a non-empty string.");
4835
+ }
4775
4836
  return {
4776
4837
  agentId: body.agentId,
4777
4838
  installation: {
4778
4839
  kind: body.kind,
4779
4840
  id: body.id,
4841
+ ...body.accountId !== void 0 ? { accountId: body.accountId } : {},
4780
4842
  ...body.config !== void 0 ? { config: body.config } : {}
4781
4843
  }
4782
4844
  };
@@ -5097,12 +5159,13 @@ var FoundryServer = class {
5097
5159
  return;
5098
5160
  }
5099
5161
  if (method === "GET" && url.pathname === "/health") {
5100
- json(response, 200, await this.runtime.health());
5162
+ const health = await this.runtime.health();
5163
+ json(response, health.ok ? 200 : 503, health);
5101
5164
  return;
5102
5165
  }
5103
5166
  if (method === "GET" && url.pathname === "/health/detailed") {
5104
5167
  const health = await this.runtime.health();
5105
- json(response, 200, {
5168
+ json(response, health.ok ? 200 : 503, {
5106
5169
  ...health,
5107
5170
  connections: this.runtime.listApplicationConnections(),
5108
5171
  runs: await this.runtime.listRuns()
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  mountFoundryGuidance,
3
3
  mountFoundryWorkbench
4
- } from "./chunk-LW5PJ2FN.js";
4
+ } from "./chunk-NHV64WNO.js";
5
5
  import {
6
6
  EMPTY_CAPABILITY_REGISTRY,
7
7
  EMPTY_FOUNDRY_APPLICATION,
@@ -23,20 +23,21 @@ import {
23
23
  isInboxCapableStore,
24
24
  materializeComposedPlaybook,
25
25
  mountAgentDefinitionMemory,
26
- mountFoundrySurfaces,
27
26
  normalizeScheduleTiming,
28
27
  toGloveMessage,
29
28
  toGloveRequestInput,
30
29
  withFoundryPermissions
31
- } from "./chunk-KBMD4RYU.js";
30
+ } from "./chunk-6Q2LGPSC.js";
32
31
  import {
33
32
  FOUNDRY_APPLICATION_ENV,
34
33
  FOUNDRY_EVENT_PREFIX,
35
34
  FOUNDRY_EXECUTION_MARKER,
35
+ FOUNDRY_RUN_PROCESS_EVENT,
36
36
  defineAgentFromModule,
37
37
  internalAgentName,
38
+ mountFoundrySurfaces,
38
39
  resolveResolvable
39
- } from "./chunk-5NFLGHOC.js";
40
+ } from "./chunk-PVOKR4CN.js";
40
41
 
41
42
  // src/agent-runtime.ts
42
43
  import { pathToFileURL } from "node:url";
@@ -173,6 +174,14 @@ async function executeSpawned(spawned, message, signal2) {
173
174
  return spawned;
174
175
  }
175
176
  async function runDefinition(definition, id, runtimeValue) {
177
+ await writeAgentEvent(FOUNDRY_RUN_PROCESS_EVENT, { state: "acquired" }).catch(() => void 0);
178
+ try {
179
+ return await executeDefinition(definition, id, runtimeValue);
180
+ } finally {
181
+ await writeAgentEvent(FOUNDRY_RUN_PROCESS_EVENT, { state: "released" }).catch(() => void 0);
182
+ }
183
+ }
184
+ async function executeDefinition(definition, id, runtimeValue) {
176
185
  const envelope = runtimeValue;
177
186
  const request = envelope.request;
178
187
  const input = request;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveResolvable
3
- } from "./chunk-5NFLGHOC.js";
3
+ } from "./chunk-PVOKR4CN.js";
4
4
 
5
5
  // src/guidance.ts
6
6
  import { createHash } from "node:crypto";
@@ -1,5 +1,4 @@
1
- // src/definition.ts
2
- import { Displaymanager, Glove } from "glove-core";
1
+ // src/surfaces.ts
3
2
  import { Effect } from "effect";
4
3
 
5
4
  // src/identity.ts
@@ -54,12 +53,157 @@ function fileDefinitionLabel(value) {
54
53
  return cell?.id ?? `<${cell?.kind ?? "definition"} file route>`;
55
54
  }
56
55
 
56
+ // src/surfaces.ts
57
+ var FOUNDRY_LAYER_BRAND = /* @__PURE__ */ Symbol.for("glove-foundry-layer");
58
+ var FOUNDRY_SUBSCRIBER_BRAND = /* @__PURE__ */ Symbol.for(
59
+ "glove-foundry-subscriber"
60
+ );
61
+ var SURFACE_ID = /^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)*$/;
62
+ function assertSurfaceId(id, label) {
63
+ if (!SURFACE_ID.test(id)) {
64
+ throw new Error(
65
+ `Invalid Foundry ${label} id "${id}". Use lowercase path segments containing letters, digits, and hyphens.`
66
+ );
67
+ }
68
+ }
69
+ function defineLayer(options) {
70
+ if (options.id) assertSurfaceId(options.id, "layer");
71
+ const { id, ...definition } = options;
72
+ return Object.freeze(fileIdentified({
73
+ ...definition,
74
+ [FOUNDRY_LAYER_BRAND]: true
75
+ }, "layer", id));
76
+ }
77
+ function configureLayer(layer, config) {
78
+ return Object.freeze({ layer, config });
79
+ }
80
+ function defineSubscriber(options) {
81
+ if (options.id) assertSurfaceId(options.id, "subscriber");
82
+ const { id, ...definition } = options;
83
+ return Object.freeze(fileIdentified({
84
+ ...definition,
85
+ [FOUNDRY_SUBSCRIBER_BRAND]: true
86
+ }, "subscriber", id));
87
+ }
88
+ var EMPTY_NATIVE_REGISTRY = Object.freeze({
89
+ layers: Object.freeze([]),
90
+ subscribers: Object.freeze([])
91
+ });
92
+ function indexById(values, label) {
93
+ const indexed = /* @__PURE__ */ new Map();
94
+ for (const value of values) {
95
+ if (indexed.has(value.id)) {
96
+ throw new Error(`Duplicate Foundry ${label} id "${value.id}".`);
97
+ }
98
+ indexed.set(value.id, value);
99
+ }
100
+ return indexed;
101
+ }
102
+ async function mountFoundrySurfaces(options) {
103
+ const layers = indexById(options.registry.layers, "layer");
104
+ const subscribers = indexById(options.registry.subscribers, "subscriber");
105
+ const cleanup = [];
106
+ const disposeAll = async () => {
107
+ const failures = [];
108
+ for (const dispose of cleanup.reverse()) {
109
+ try {
110
+ const result = dispose();
111
+ if (Effect.isEffect(result)) await Effect.runPromise(result);
112
+ else await Promise.resolve(result);
113
+ } catch (cause) {
114
+ failures.push(cause);
115
+ }
116
+ }
117
+ if (failures.length > 0) {
118
+ throw new AggregateError(failures, "Foundry surface cleanup failed.");
119
+ }
120
+ };
121
+ try {
122
+ for (const selected of options.subscribers ?? []) {
123
+ let definition = selected;
124
+ if (typeof definition === "object" && definition !== null && FOUNDRY_SUBSCRIBER_BRAND in definition) {
125
+ const registered = subscribers.get(definition.id);
126
+ if (!registered) {
127
+ throw new Error(
128
+ `Agent "${options.context.agentId}" references unknown subscriber "${definition.id}".`
129
+ );
130
+ }
131
+ definition = registered;
132
+ }
133
+ let subscriber;
134
+ let subscriberId = "inline";
135
+ if (typeof definition === "object" && definition !== null && definition[FOUNDRY_SUBSCRIBER_BRAND] === true) {
136
+ const foundrySubscriber = definition;
137
+ const created = typeof foundrySubscriber.create === "function" ? foundrySubscriber.create(options.context) : foundrySubscriber.create;
138
+ subscriber = Effect.isEffect(created) ? await Effect.runPromise(created) : await Promise.resolve(created);
139
+ subscriberId = foundrySubscriber.id;
140
+ } else {
141
+ subscriber = definition;
142
+ }
143
+ options.context.glove.addSubscriber(subscriber);
144
+ cleanup.push(() => options.context.glove.removeSubscriber(subscriber));
145
+ options.context.emit({
146
+ type: "foundry.subscriber.mounted",
147
+ data: { id: subscriberId }
148
+ });
149
+ }
150
+ for (const selected of options.layers ?? []) {
151
+ const reference = FOUNDRY_LAYER_BRAND in selected ? { layer: selected } : selected;
152
+ const definition = layers.get(reference.layer.id);
153
+ if (!definition) {
154
+ throw new Error(
155
+ `Agent "${options.context.agentId}" references unknown layer "${reference.layer.id}".`
156
+ );
157
+ }
158
+ const referenceConfig = "config" in reference ? reference.config : void 0;
159
+ const parsed = definition.config ? definition.config.safeParse(referenceConfig ?? {}) : { success: true, data: referenceConfig };
160
+ if (!parsed.success) {
161
+ throw new Error(
162
+ `Invalid config for layer "${definition.id}": ${parsed.error.message}`
163
+ );
164
+ }
165
+ const dispose = await Effect.runPromise(
166
+ definition.setup({ ...options.context, config: parsed.data })
167
+ );
168
+ if (dispose) cleanup.push(dispose);
169
+ options.context.emit({
170
+ type: "foundry.layer.mounted",
171
+ data: { id: definition.id }
172
+ });
173
+ }
174
+ } catch (cause) {
175
+ try {
176
+ await disposeAll();
177
+ } catch (cleanupCause) {
178
+ throw new AggregateError(
179
+ [cause, cleanupCause],
180
+ "Foundry surface mount and cleanup failed."
181
+ );
182
+ }
183
+ throw cause;
184
+ }
185
+ return disposeAll;
186
+ }
187
+ function isFoundryLayer(value) {
188
+ return Boolean(
189
+ value && typeof value === "object" && value[FOUNDRY_LAYER_BRAND] === true
190
+ );
191
+ }
192
+ function isFoundrySubscriber(value) {
193
+ return Boolean(
194
+ value && typeof value === "object" && value[FOUNDRY_SUBSCRIBER_BRAND] === true
195
+ );
196
+ }
197
+
57
198
  // src/definition.ts
199
+ import { Displaymanager, Glove } from "glove-core";
200
+ import { Effect as Effect2 } from "effect";
58
201
  var FOUNDRY_AGENT_DEFINITION_BRAND = /* @__PURE__ */ Symbol.for(
59
202
  "glove-foundry-agent-definition"
60
203
  );
61
204
  var FOUNDRY_AGENT_BRAND = FOUNDRY_AGENT_DEFINITION_BRAND;
62
205
  var FOUNDRY_EVENT_PREFIX = "__GLOVE_FOUNDRY_EVENT__";
206
+ var FOUNDRY_RUN_PROCESS_EVENT = "foundry.run.process";
63
207
  var FOUNDRY_APPLICATION_ENV = "GLOVE_FOUNDRY_APPLICATION_FILE";
64
208
  var FOUNDRY_AGENT_ROUTE_ENV = "GLOVE_FOUNDRY_AGENT_ROUTE";
65
209
  var FOUNDRY_AGENT_FILE_ENV = "GLOVE_FOUNDRY_AGENT_FILE";
@@ -217,39 +361,67 @@ function defineSubagent(options) {
217
361
  for (const hook of options.hooks ?? []) glove.defineHook(hook.name, hook.handler);
218
362
  for (const skill of options.skills ?? []) glove.defineSkill(skill);
219
363
  for (const subagent of options.subagents ?? []) glove.defineSubAgent(subagent);
220
- if (options.configure) {
221
- const message = { sender: "user", text: prompt };
222
- const request = {
223
- agentId: options.name,
224
- conversationId: `subagent:${options.name}`,
225
- workspaceId: "subagent",
226
- message: prompt,
227
- source: { kind: "spawn" }
228
- };
229
- await resolveResolvable(options.configure({
230
- definitionId: options.name,
231
- agentId: options.name,
232
- conversationId: request.conversationId,
233
- workspaceId: request.workspaceId,
234
- runId: `subagent:${options.name}`,
235
- input: prompt,
236
- request,
237
- message,
238
- messageInput: prompt,
239
- messageText: prompt,
240
- history: [],
241
- messages: [message],
242
- glove,
243
- signal: new AbortController().signal,
244
- emit: () => void 0
245
- }));
364
+ const message = { sender: "user", text: prompt };
365
+ const request = {
366
+ agentId: options.name,
367
+ conversationId: `subagent:${options.name}`,
368
+ workspaceId: "subagent",
369
+ message: prompt,
370
+ source: { kind: "spawn" }
371
+ };
372
+ const context = {
373
+ definitionId: options.name,
374
+ agentId: options.name,
375
+ conversationId: request.conversationId,
376
+ workspaceId: request.workspaceId,
377
+ runId: `subagent:${options.name}`,
378
+ input: prompt,
379
+ request,
380
+ message,
381
+ messageInput: prompt,
382
+ messageText: prompt,
383
+ history: [],
384
+ messages: [message],
385
+ glove,
386
+ signal: new AbortController().signal,
387
+ emit: () => void 0
388
+ };
389
+ const disposeSurfaces = await mountFoundrySurfaces({
390
+ registry: {
391
+ layers: uniqueById((options.layers ?? []).map((selected) => "layer" in selected ? selected.layer : selected)),
392
+ subscribers: uniqueById((options.subscribers ?? []).filter(isFoundrySubscriber))
393
+ },
394
+ layers: options.layers ?? [],
395
+ subscribers: options.subscribers ?? [],
396
+ context
397
+ });
398
+ try {
399
+ if (options.configure) await resolveResolvable(options.configure(context));
400
+ } catch (cause) {
401
+ await disposeSurfaces();
402
+ throw cause;
246
403
  }
404
+ const processRequest = glove.processRequest.bind(glove);
405
+ let disposed = false;
406
+ glove.processRequest = async (...args) => {
407
+ try {
408
+ return await processRequest(...args);
409
+ } finally {
410
+ if (!disposed) {
411
+ disposed = true;
412
+ await disposeSurfaces();
413
+ }
414
+ }
415
+ };
247
416
  return glove;
248
417
  }
249
418
  });
250
419
  }
420
+ function uniqueById(values) {
421
+ return [...new Map(values.map((value) => [value.id, value])).values()];
422
+ }
251
423
  async function resolveResolvable(value) {
252
- if (Effect.isEffect(value)) return Effect.runPromise(value);
424
+ if (Effect2.isEffect(value)) return Effect2.runPromise(value);
253
425
  return Promise.resolve(value);
254
426
  }
255
427
  function defineRoutes(routes) {
@@ -261,9 +433,19 @@ export {
261
433
  bindFileIdentity,
262
434
  fileDefinitionKey,
263
435
  fileDefinitionLabel,
436
+ FOUNDRY_LAYER_BRAND,
437
+ FOUNDRY_SUBSCRIBER_BRAND,
438
+ defineLayer,
439
+ configureLayer,
440
+ defineSubscriber,
441
+ EMPTY_NATIVE_REGISTRY,
442
+ mountFoundrySurfaces,
443
+ isFoundryLayer,
444
+ isFoundrySubscriber,
264
445
  FOUNDRY_AGENT_DEFINITION_BRAND,
265
446
  FOUNDRY_AGENT_BRAND,
266
447
  FOUNDRY_EVENT_PREFIX,
448
+ FOUNDRY_RUN_PROCESS_EVENT,
267
449
  FOUNDRY_APPLICATION_ENV,
268
450
  FOUNDRY_AGENT_ROUTE_ENV,
269
451
  FOUNDRY_AGENT_FILE_ENV,
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  FoundryRuntime,
4
4
  FoundryServer,
5
5
  writeGeneratedTypes
6
- } from "./chunk-SGF3CFUR.js";
6
+ } from "./chunk-D2AHSYOB.js";
7
7
  import {
8
8
  DEFAULT_FOUNDRY_CONFIG
9
9
  } from "./chunk-ZFNMFE3T.js";
@@ -11,8 +11,8 @@ import "./chunk-J7ZBJM54.js";
11
11
  import {
12
12
  EMPTY_FOUNDRY_APPLICATION,
13
13
  isFoundryApplication
14
- } from "./chunk-KBMD4RYU.js";
15
- import "./chunk-5NFLGHOC.js";
14
+ } from "./chunk-6Q2LGPSC.js";
15
+ import "./chunk-PVOKR4CN.js";
16
16
 
17
17
  // src/cli.ts
18
18
  import { watch } from "node:fs";
@@ -147,7 +147,7 @@ var TEMPLATE_IMPORTS = Object.freeze({
147
147
  "glove-working-environment",
148
148
  "zod"
149
149
  ],
150
- minimal: ["effect", "glove-core", "zod"]
150
+ minimal: ["glove-core", "zod"]
151
151
  });
152
152
  var TEMPLATE_AGENT_ROUTE = Object.freeze({
153
153
  "travel-concierge": "concierge",
@@ -312,6 +312,7 @@ export default defineConfig({
312
312
  });
313
313
  `
314
314
  );
315
+ files.set(`${foundryDir}/.gitignore`, ".data\n");
315
316
  files.set(
316
317
  `${foundryDir}/package.json`,
317
318
  `${JSON.stringify(
@@ -495,7 +496,8 @@ function nextReadme(projectName, route, placeholders) {
495
496
  "| --- | --- |",
496
497
  `| \`foundry/agents/${route}/agent.ts\` | The agent. The file path is its id. |`,
497
498
  "| `foundry/foundry.application.ts` | Data adapter, accounts, routes |",
498
- "| `foundry.config.ts` | Points the runtime at `foundry/agents` |",
499
+ "| `foundry.config.mts` | Points the runtime at `foundry/agents` |",
500
+ "| `foundry/.data/` | Runtime data, transcripts and memory. Ignored by git; delete it to start fresh |",
499
501
  "| `lib/foundry.ts` | Typed HTTP client for your app to import |",
500
502
  `| \`app/api/${route}/route.ts\` | An example Next.js route handler |`,
501
503
  "| `.foundry/routes.d.ts` | Generated. Do not edit |",
@@ -1734,7 +1734,8 @@ interface DefineFoundrySubagentOptions {
1734
1734
  readonly hooks?: ReadonlyArray<FoundryHookDefinition>;
1735
1735
  readonly skills?: ReadonlyArray<DefineSkillArgs>;
1736
1736
  readonly subagents?: ReadonlyArray<DefineSubAgentArgs>;
1737
- readonly layers?: ReadonlyArray<FoundryLayer<any>>;
1737
+ /** Mounted on each invocation and disposed when the subagent's request ends. */
1738
+ readonly layers?: ReadonlyArray<FoundryLayerSelection>;
1738
1739
  readonly subscribers?: ReadonlyArray<FoundrySubscriber | SubscriberAdapter>;
1739
1740
  readonly configure?: (context: FoundrySurfaceContext<string>) => Resolvable<void>;
1740
1741
  }
@@ -2200,6 +2201,8 @@ declare class FoundryRuntime {
2200
2201
  private readonly activationTransitions;
2201
2202
  private readonly activationDispatches;
2202
2203
  private readonly activationTimers;
2204
+ /** Whether each run's child process still holds its conversation, from FOUNDRY_RUN_PROCESS_EVENT. */
2205
+ private readonly runProcesses;
2203
2206
  private readonly approvalDirectory;
2204
2207
  private readonly coreCommandDirectory;
2205
2208
  private started;
@@ -2267,6 +2270,14 @@ declare class FoundryRuntime {
2267
2270
  timeoutMs?: number;
2268
2271
  }): Promise<FoundryRun<TOutput> | null>;
2269
2272
  cancel(runId: string): Promise<boolean>;
2273
+ private trackRunProcess;
2274
+ /**
2275
+ * Wait until a cancelled run's process has let go of its conversation. A run
2276
+ * that never started has no process. A process that dies before it reports
2277
+ * (SIGTERM during startup, or Station's SIGKILL five seconds after the
2278
+ * cancel) cannot report, so the wait is bounded by that kill.
2279
+ */
2280
+ private waitForRunProcessRelease;
2270
2281
  /**
2271
2282
  * Change course at a run boundary.
2272
2283
  *
@@ -2440,6 +2451,7 @@ declare class FoundryClient<TRoutes extends FoundryRouteMap> {
2440
2451
  private readonly baseUrl;
2441
2452
  private readonly fetcher;
2442
2453
  constructor(options?: FoundryClientOptions);
2454
+ /** Resolves with `ok: false` when a subsystem is down (the server answers 503). */
2443
2455
  health(): Promise<FoundryHealth>;
2444
2456
  agent<TRoute extends Extract<keyof TRoutes, string>>(route: TRoute): {
2445
2457
  create(options?: CreateAgentInstanceOptions): Promise<AgentInstance>;
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { bf as FoundryClient, bg as FoundryClientAuthorizationAdapter, bh as FoundryClientOptions, bu as FoundryHealth, c7 as FoundryRunHandle, ce as FoundrySteerHandleResult, db as WaitOptions, dk as createFoundryClient } from './client-DcsnudMI.js';
1
+ export { bf as FoundryClient, bg as FoundryClientAuthorizationAdapter, bh as FoundryClientOptions, bu as FoundryHealth, c7 as FoundryRunHandle, ce as FoundrySteerHandleResult, db as WaitOptions, dk as createFoundryClient } from './client-CLTds1uV.js';
2
2
  import 'glove-core';
3
3
  import 'effect';
4
4
  import 'zod';
package/dist/client.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  FoundryClient,
3
3
  FoundryRunHandle,
4
4
  createFoundryClient
5
- } from "./chunk-GKZLWD5M.js";
5
+ } from "./chunk-7F7QVO6I.js";
6
6
  export {
7
7
  FoundryClient,
8
8
  FoundryRunHandle,
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  compileAgentModule
3
- } from "./chunk-TH5ETCG2.js";
4
- import "./chunk-LW5PJ2FN.js";
3
+ } from "./chunk-MABLKZVO.js";
4
+ import "./chunk-NHV64WNO.js";
5
5
  import {
6
6
  bindAgentLocalDefinitions
7
- } from "./chunk-KBMD4RYU.js";
7
+ } from "./chunk-6Q2LGPSC.js";
8
8
  import {
9
9
  FOUNDRY_AGENT_FILE_ENV,
10
10
  FOUNDRY_AGENT_ROUTE_ENV
11
- } from "./chunk-5NFLGHOC.js";
11
+ } from "./chunk-PVOKR4CN.js";
12
12
 
13
13
  // src/execution-agent.ts
14
14
  import { pathToFileURL } from "node:url";
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  FOUNDRY_APPLICATION_ENV,
6
6
  internalAgentName
7
- } from "./chunk-5NFLGHOC.js";
7
+ } from "./chunk-PVOKR4CN.js";
8
8
 
9
9
  // src/execution-daemon.ts
10
10
  import { randomBytes } from "node:crypto";
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  compileAgentModule
3
- } from "./chunk-TH5ETCG2.js";
4
- import "./chunk-LW5PJ2FN.js";
3
+ } from "./chunk-MABLKZVO.js";
4
+ import "./chunk-NHV64WNO.js";
5
5
  import {
6
6
  bindAgentLocalDefinitions
7
- } from "./chunk-KBMD4RYU.js";
8
- import "./chunk-5NFLGHOC.js";
7
+ } from "./chunk-6Q2LGPSC.js";
8
+ import "./chunk-PVOKR4CN.js";
9
9
 
10
10
  // src/execution-loader.ts
11
11
  import { dirname } from "node:path";