glove-foundry 0.0.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.
@@ -0,0 +1,4211 @@
1
+ import {
2
+ DEFAULT_FOUNDRY_CONFIG
3
+ } from "./chunk-ZFNMFE3T.js";
4
+ import {
5
+ EMPTY_CAPABILITY_REGISTRY,
6
+ EMPTY_FOUNDRY_APPLICATION,
7
+ EMPTY_NATIVE_REGISTRY,
8
+ FOUNDRY_AGENT_APPLICATION_BRAND,
9
+ FOUNDRY_AGENT_FILE_ENV,
10
+ FOUNDRY_AGENT_ROUTE_ENV,
11
+ FOUNDRY_APPLICATION_ENV,
12
+ FOUNDRY_CORE_COMMAND_EVENT,
13
+ FOUNDRY_EVENT_PREFIX,
14
+ FOUNDRY_EXECUTION_MARKER,
15
+ FOUNDRY_LAYER_BRAND,
16
+ FOUNDRY_MCP_BRAND,
17
+ FOUNDRY_MEMORY_BRAND,
18
+ FOUNDRY_SHARED_TOOL_BRAND,
19
+ FOUNDRY_SUBSCRIBER_BRAND,
20
+ MemoryFoundryDataAdapter,
21
+ bindFileIdentity,
22
+ compileAgentDefinition,
23
+ createAgentInstance,
24
+ createConversation,
25
+ createManifest,
26
+ definePlaybookSubscription,
27
+ defineSharedTool,
28
+ discoverAgents,
29
+ fileDefinitionKey,
30
+ fileDefinitionLabel,
31
+ id,
32
+ installationKey,
33
+ normalizeAgentInstallations,
34
+ reconstructPlaybook,
35
+ reconstructPlaybookSubscription,
36
+ routeFromInternalAgentName,
37
+ transmissionPredicate
38
+ } from "./chunk-CRWY7M66.js";
39
+
40
+ // src/codegen.ts
41
+ import { mkdir, writeFile } from "node:fs/promises";
42
+ import { dirname, relative, resolve, sep } from "node:path";
43
+ function moduleSpecifier(fromFile, targetFile) {
44
+ let specifier = relative(dirname(fromFile), targetFile).split(sep).join("/");
45
+ if (!specifier.startsWith(".")) specifier = `./${specifier}`;
46
+ return specifier.replace(/\.(?:tsx?|mts|mjs|js)$/, ".js");
47
+ }
48
+ async function writeGeneratedTypes(options) {
49
+ const generatedDir = resolve(options.rootDir, ".foundry");
50
+ const routesFile = resolve(generatedDir, "routes.d.ts");
51
+ const manifestFile = resolve(generatedDir, "manifest.json");
52
+ await mkdir(generatedDir, { recursive: true });
53
+ const routes = options.agents.map(
54
+ ({ route, filePath }) => ` readonly ${JSON.stringify(route)}: typeof import(${JSON.stringify(moduleSpecifier(routesFile, filePath))});`
55
+ ).join("\n");
56
+ const source = `// Generated by Glove Foundry. Do not edit.
57
+ export type FoundryRoutes = {
58
+ ${routes}
59
+ };
60
+ `;
61
+ await writeFile(routesFile, source, "utf8");
62
+ await writeFile(manifestFile, `${JSON.stringify(options.manifest, null, 2)}
63
+ `, "utf8");
64
+ return { routesFile, manifestFile };
65
+ }
66
+
67
+ // src/composition.ts
68
+ function addUnique(values, value, kind) {
69
+ const key = fileDefinitionKey(value);
70
+ if (values.some((candidate) => fileDefinitionKey(candidate) === key)) {
71
+ throw new Error(`Duplicate agent-local ${kind} "${fileDefinitionLabel(value)}".`);
72
+ }
73
+ values.push(value);
74
+ }
75
+ function composeAgent(...sources) {
76
+ const capabilities = {
77
+ tools: [...EMPTY_CAPABILITY_REGISTRY.tools],
78
+ applications: [...EMPTY_CAPABILITY_REGISTRY.applications],
79
+ mcp: [...EMPTY_CAPABILITY_REGISTRY.mcp],
80
+ memory: [...EMPTY_CAPABILITY_REGISTRY.memory]
81
+ };
82
+ const native = {
83
+ layers: [...EMPTY_NATIVE_REGISTRY.layers],
84
+ subscribers: [...EMPTY_NATIVE_REGISTRY.subscribers]
85
+ };
86
+ const visit = (source) => {
87
+ if (!source) return;
88
+ if (typeof source === "function") {
89
+ visit(source());
90
+ return;
91
+ }
92
+ if (Array.isArray(source)) {
93
+ for (const child of source) visit(child);
94
+ return;
95
+ }
96
+ if ("capabilities" in source && "native" in source) {
97
+ visit([
98
+ ...source.capabilities.tools,
99
+ ...source.capabilities.applications,
100
+ ...source.capabilities.mcp,
101
+ ...source.capabilities.memory,
102
+ ...source.native.layers,
103
+ ...source.native.subscribers
104
+ ]);
105
+ return;
106
+ }
107
+ const branded = source;
108
+ if (branded[FOUNDRY_SHARED_TOOL_BRAND] === true) {
109
+ addUnique(capabilities.tools, branded, "tool");
110
+ } else if (branded[FOUNDRY_AGENT_APPLICATION_BRAND] === true) {
111
+ addUnique(
112
+ capabilities.applications,
113
+ branded,
114
+ "application"
115
+ );
116
+ } else if (branded[FOUNDRY_MCP_BRAND] === true) {
117
+ addUnique(capabilities.mcp, branded, "MCP");
118
+ } else if (branded[FOUNDRY_MEMORY_BRAND] === true) {
119
+ addUnique(
120
+ capabilities.memory,
121
+ branded,
122
+ "memory"
123
+ );
124
+ } else if (branded[FOUNDRY_LAYER_BRAND] === true) {
125
+ addUnique(native.layers, branded, "layer");
126
+ } else if (branded[FOUNDRY_SUBSCRIBER_BRAND] === true) {
127
+ addUnique(native.subscribers, branded, "subscriber");
128
+ } else {
129
+ throw new Error("composeAgent received an unrecognized Foundry definition.");
130
+ }
131
+ };
132
+ for (const source of sources) visit(source);
133
+ return Object.freeze({
134
+ capabilities: Object.freeze({
135
+ tools: Object.freeze(capabilities.tools),
136
+ applications: Object.freeze(capabilities.applications),
137
+ mcp: Object.freeze(capabilities.mcp),
138
+ memory: Object.freeze(capabilities.memory)
139
+ }),
140
+ native: Object.freeze({
141
+ layers: Object.freeze(native.layers),
142
+ subscribers: Object.freeze(native.subscribers)
143
+ })
144
+ });
145
+ }
146
+ var EMPTY_AGENT_COMPOSITION = composeAgent();
147
+
148
+ // src/domain.ts
149
+ import { Schema } from "effect";
150
+ var Slug = Schema.String.pipe(
151
+ Schema.pattern(/^[a-z][a-z0-9-]*$/),
152
+ Schema.annotations({
153
+ description: "A lowercase, hyphen-delimited Foundry identifier"
154
+ })
155
+ );
156
+ var RoutePath = Schema.String.pipe(
157
+ Schema.pattern(/^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)*$/),
158
+ Schema.annotations({ description: "A file-routed Foundry agent identifier" })
159
+ );
160
+ var TransmissionId = Slug.pipe(Schema.brand("FoundryTransmissionId"));
161
+ var AgentDefinitionId = RoutePath.pipe(
162
+ Schema.brand("FoundryAgentDefinitionId")
163
+ );
164
+ var AgentId = Schema.NonEmptyTrimmedString.pipe(Schema.brand("FoundryAgentId"));
165
+ var AccountId = Schema.NonEmptyTrimmedString.pipe(
166
+ Schema.brand("FoundryAccountId")
167
+ );
168
+ var RouteId = Schema.NonEmptyTrimmedString.pipe(
169
+ Schema.brand("FoundryRouteId")
170
+ );
171
+ var BindingId = Schema.NonEmptyTrimmedString.pipe(
172
+ Schema.brand("FoundryBindingId")
173
+ );
174
+ var EventId = Schema.NonEmptyTrimmedString.pipe(
175
+ Schema.brand("FoundryEventId")
176
+ );
177
+ var RunId = Schema.NonEmptyTrimmedString.pipe(
178
+ Schema.brand("FoundryRunId")
179
+ );
180
+ var CapabilityId = Schema.String.pipe(
181
+ Schema.pattern(/^[a-z][a-z0-9-]*(?::[a-z][a-z0-9-]*)?$/),
182
+ Schema.brand("FoundryCapabilityId")
183
+ );
184
+ var StringRecord = Schema.Record({
185
+ key: Schema.String,
186
+ value: Schema.Unknown
187
+ });
188
+ var AccountReference = Schema.Struct({
189
+ id: AccountId,
190
+ transmissionId: TransmissionId,
191
+ externalAccountId: Schema.NonEmptyTrimmedString,
192
+ label: Schema.optional(Schema.NonEmptyTrimmedString),
193
+ accessRef: Schema.NonEmptyTrimmedString,
194
+ metadata: StringRecord
195
+ });
196
+ var AccountSummary = Schema.Struct({
197
+ id: AccountId,
198
+ transmissionId: TransmissionId,
199
+ externalAccountId: Schema.NonEmptyTrimmedString,
200
+ label: Schema.optional(Schema.NonEmptyTrimmedString),
201
+ metadata: StringRecord
202
+ });
203
+ var RouteFields = {
204
+ id: RouteId,
205
+ transmissionId: TransmissionId,
206
+ accountId: Schema.optional(AccountId),
207
+ visibility: Schema.Literal("private", "workspace"),
208
+ enabled: Schema.Boolean,
209
+ config: StringRecord
210
+ };
211
+ var InboundRoute = Schema.Struct({
212
+ ...RouteFields,
213
+ direction: Schema.Literal("inbound")
214
+ });
215
+ var OutboundRoute = Schema.Struct({
216
+ ...RouteFields,
217
+ direction: Schema.Literal("outbound")
218
+ });
219
+ var Route = Schema.Union(InboundRoute, OutboundRoute);
220
+ var ReplyPolicy = Schema.Union(
221
+ Schema.Struct({ mode: Schema.Literal("none") }),
222
+ Schema.Struct({ mode: Schema.Literal("origin") }),
223
+ Schema.Struct({ mode: Schema.Literal("route"), routeId: RouteId })
224
+ );
225
+ var AgentBinding = Schema.Struct({
226
+ id: BindingId,
227
+ agentId: AgentId,
228
+ transmissionId: TransmissionId,
229
+ accountId: Schema.optional(AccountId),
230
+ routeId: Schema.optional(RouteId),
231
+ capabilities: Schema.Array(CapabilityId),
232
+ reply: Schema.optional(ReplyPolicy),
233
+ enabled: Schema.Boolean
234
+ });
235
+ var RunGrant = Schema.Struct({
236
+ runId: RunId,
237
+ agentId: AgentId,
238
+ accountIds: Schema.Array(AccountId),
239
+ outboundRouteIds: Schema.Array(RouteId),
240
+ capabilities: Schema.Array(CapabilityId),
241
+ reply: ReplyPolicy
242
+ });
243
+ var EventReference = Schema.Struct({
244
+ id: EventId,
245
+ transmissionId: TransmissionId,
246
+ routeId: RouteId,
247
+ accountId: Schema.optional(AccountId),
248
+ externalEventId: Schema.NonEmptyTrimmedString,
249
+ threadKey: Schema.NonEmptyTrimmedString,
250
+ emittedAt: Schema.NonEmptyTrimmedString,
251
+ payloadRef: Schema.NonEmptyTrimmedString
252
+ });
253
+ var AccountNotFound = class extends Schema.TaggedError(
254
+ "AccountNotFound"
255
+ )("AccountNotFound", { accountId: AccountId }) {
256
+ get message() {
257
+ return `Foundry account "${this.accountId}" was not found.`;
258
+ }
259
+ };
260
+ var RouteNotFound = class extends Schema.TaggedError(
261
+ "RouteNotFound"
262
+ )("RouteNotFound", { routeId: RouteId }) {
263
+ get message() {
264
+ return `Foundry route "${this.routeId}" was not found.`;
265
+ }
266
+ };
267
+ var BindingNotFound = class extends Schema.TaggedError(
268
+ "BindingNotFound"
269
+ )("BindingNotFound", { bindingId: BindingId }) {
270
+ get message() {
271
+ return `Foundry binding "${this.bindingId}" was not found.`;
272
+ }
273
+ };
274
+ var EventNotFound = class extends Schema.TaggedError(
275
+ "EventNotFound"
276
+ )("EventNotFound", { eventId: EventId }) {
277
+ get message() {
278
+ return `Foundry event "${this.eventId}" was not found.`;
279
+ }
280
+ };
281
+ var TopologyConflict = class extends Schema.TaggedError(
282
+ "TopologyConflict"
283
+ )("TopologyConflict", {
284
+ resource: Schema.Literal("route", "binding"),
285
+ id: Schema.NonEmptyTrimmedString,
286
+ reason: Schema.NonEmptyTrimmedString
287
+ }) {
288
+ };
289
+ var AccountSessionUnavailable = class extends Schema.TaggedError(
290
+ "AccountSessionUnavailable"
291
+ )("AccountSessionUnavailable", {
292
+ accountId: AccountId,
293
+ operation: Schema.NonEmptyTrimmedString,
294
+ reason: Schema.NonEmptyTrimmedString
295
+ }) {
296
+ get message() {
297
+ return `Account session for "${this.accountId}" is unavailable during ${this.operation}: ${this.reason}`;
298
+ }
299
+ };
300
+ var GrantResolutionError = class extends Schema.TaggedError(
301
+ "GrantResolutionError"
302
+ )("GrantResolutionError", {
303
+ runId: RunId,
304
+ agentId: AgentId,
305
+ reason: Schema.NonEmptyTrimmedString
306
+ }) {
307
+ };
308
+
309
+ // src/services.ts
310
+ import { Context, Effect, Layer, Option, Ref } from "effect";
311
+ var AccountDirectory = class extends Context.Tag(
312
+ "@glove-foundry/AccountDirectory"
313
+ )() {
314
+ };
315
+ var TopologyStore = class extends Context.Tag("@glove-foundry/TopologyStore")() {
316
+ };
317
+ var EventStore = class extends Context.Tag("@glove-foundry/EventStore")() {
318
+ };
319
+ function routeMatches(route, filter) {
320
+ return (filter.transmissionId === void 0 || route.transmissionId === filter.transmissionId) && (filter.accountId === void 0 || route.accountId === filter.accountId) && (filter.direction === void 0 || route.direction === filter.direction) && (filter.enabled === void 0 || route.enabled === filter.enabled);
321
+ }
322
+ function bindingMatches(binding, filter) {
323
+ return (filter.agentId === void 0 || binding.agentId === filter.agentId) && (filter.transmissionId === void 0 || binding.transmissionId === filter.transmissionId) && (filter.accountId === void 0 || binding.accountId === filter.accountId) && (filter.routeId === void 0 || binding.routeId === filter.routeId) && (filter.enabled === void 0 || binding.enabled === filter.enabled);
324
+ }
325
+ function memoryAccountDirectory(accounts) {
326
+ return Layer.succeed(AccountDirectory, {
327
+ get: (id2) => {
328
+ const account = accounts.find((candidate) => candidate.id === id2);
329
+ return account ? Effect.succeed(account) : Effect.fail(new AccountNotFound({ accountId: id2 }));
330
+ },
331
+ list: (filter = {}) => Effect.succeed(
332
+ accounts.filter(
333
+ (account) => filter.transmissionId === void 0 || account.transmissionId === filter.transmissionId
334
+ )
335
+ )
336
+ });
337
+ }
338
+ var memoryTopologyStore = Layer.effect(
339
+ TopologyStore,
340
+ Effect.gen(function* () {
341
+ const routes = yield* Ref.make(/* @__PURE__ */ new Map());
342
+ const bindings = yield* Ref.make(/* @__PURE__ */ new Map());
343
+ return {
344
+ getRoute: (id2) => Ref.get(routes).pipe(
345
+ Effect.flatMap(
346
+ (state) => Option.fromNullable(state.get(id2)).pipe(
347
+ Option.match({
348
+ onNone: () => Effect.fail(new RouteNotFound({ routeId: id2 })),
349
+ onSome: Effect.succeed
350
+ })
351
+ )
352
+ )
353
+ ),
354
+ listRoutes: (filter = {}) => Ref.get(routes).pipe(
355
+ Effect.map(
356
+ (state) => [...state.values()].filter((route) => routeMatches(route, filter))
357
+ )
358
+ ),
359
+ putRoute: (route) => Ref.update(routes, (state) => {
360
+ const next = new Map(state);
361
+ next.set(route.id, route);
362
+ return next;
363
+ }).pipe(Effect.as(route)),
364
+ removeRoute: (id2) => Ref.modify(routes, (state) => {
365
+ if (!state.has(id2)) {
366
+ return [false, state];
367
+ }
368
+ const next = new Map(state);
369
+ next.delete(id2);
370
+ return [true, next];
371
+ }).pipe(
372
+ Effect.flatMap(
373
+ (removed) => removed ? Effect.void : Effect.fail(new RouteNotFound({ routeId: id2 }))
374
+ )
375
+ ),
376
+ getBinding: (id2) => Ref.get(bindings).pipe(
377
+ Effect.flatMap(
378
+ (state) => Option.fromNullable(state.get(id2)).pipe(
379
+ Option.match({
380
+ onNone: () => Effect.fail(new BindingNotFound({ bindingId: id2 })),
381
+ onSome: Effect.succeed
382
+ })
383
+ )
384
+ )
385
+ ),
386
+ listBindings: (filter = {}) => Ref.get(bindings).pipe(
387
+ Effect.map(
388
+ (state) => [...state.values()].filter(
389
+ (binding) => bindingMatches(binding, filter)
390
+ )
391
+ )
392
+ ),
393
+ putBinding: (binding) => Ref.update(bindings, (state) => {
394
+ const next = new Map(state);
395
+ next.set(binding.id, binding);
396
+ return next;
397
+ }).pipe(Effect.as(binding)),
398
+ removeBinding: (id2) => Ref.modify(bindings, (state) => {
399
+ if (!state.has(id2)) {
400
+ return [false, state];
401
+ }
402
+ const next = new Map(state);
403
+ next.delete(id2);
404
+ return [true, next];
405
+ }).pipe(
406
+ Effect.flatMap(
407
+ (removed) => removed ? Effect.void : Effect.fail(new BindingNotFound({ bindingId: id2 }))
408
+ )
409
+ )
410
+ };
411
+ })
412
+ );
413
+ var memoryEventStore = Layer.effect(
414
+ EventStore,
415
+ Effect.gen(function* () {
416
+ const references = yield* Ref.make(/* @__PURE__ */ new Map());
417
+ const payloads = yield* Ref.make(/* @__PURE__ */ new Map());
418
+ return {
419
+ put: (reference, payload) => Ref.update(references, (state) => {
420
+ const next = new Map(state);
421
+ next.set(reference.id, reference);
422
+ return next;
423
+ }).pipe(
424
+ Effect.andThen(
425
+ Ref.update(payloads, (state) => {
426
+ const next = new Map(state);
427
+ next.set(reference.id, payload);
428
+ return next;
429
+ })
430
+ )
431
+ ),
432
+ getReference: (id2) => Ref.get(references).pipe(
433
+ Effect.flatMap((state) => {
434
+ const reference = state.get(id2);
435
+ return reference ? Effect.succeed(reference) : Effect.fail(new EventNotFound({ eventId: id2 }));
436
+ })
437
+ ),
438
+ getPayload: (id2) => Ref.get(payloads).pipe(
439
+ Effect.flatMap(
440
+ (state) => state.has(id2) ? Effect.succeed(state.get(id2)) : Effect.fail(new EventNotFound({ eventId: id2 }))
441
+ )
442
+ )
443
+ };
444
+ })
445
+ );
446
+
447
+ // src/grants.ts
448
+ import { Context as Context2, Effect as Effect2, Layer as Layer2 } from "effect";
449
+ var GrantResolver = class extends Context2.Tag("@glove-foundry/GrantResolver")() {
450
+ };
451
+ var replyKey = (policy) => policy.mode === "route" ? `route:${policy.routeId}` : policy.mode;
452
+ var grantResolverLive = Layer2.effect(
453
+ GrantResolver,
454
+ Effect2.gen(function* () {
455
+ const topology = yield* TopologyStore;
456
+ return {
457
+ resolve: (request) => Effect2.gen(function* () {
458
+ const bindings = yield* topology.listBindings({
459
+ agentId: request.agentId,
460
+ enabled: true
461
+ });
462
+ if (bindings.length === 0) {
463
+ return yield* Effect2.fail(
464
+ new GrantResolutionError({
465
+ runId: request.runId,
466
+ agentId: request.agentId,
467
+ reason: "agent has no enabled bindings"
468
+ })
469
+ );
470
+ }
471
+ const routes = yield* topology.listRoutes({ enabled: true });
472
+ const routeById = new Map(
473
+ routes.map((route) => [route.id, route])
474
+ );
475
+ const accountIds = /* @__PURE__ */ new Set();
476
+ const capabilities = /* @__PURE__ */ new Set();
477
+ const outboundRouteIds = /* @__PURE__ */ new Set();
478
+ const replyPolicies = /* @__PURE__ */ new Map();
479
+ for (const binding of bindings) {
480
+ if (binding.accountId) accountIds.add(binding.accountId);
481
+ binding.capabilities.forEach(
482
+ (capability) => capabilities.add(capability)
483
+ );
484
+ if (binding.routeId) {
485
+ const route = routeById.get(binding.routeId);
486
+ if (!route) {
487
+ return yield* Effect2.fail(
488
+ new GrantResolutionError({
489
+ runId: request.runId,
490
+ agentId: request.agentId,
491
+ reason: `binding "${binding.id}" references a missing or disabled route`
492
+ })
493
+ );
494
+ }
495
+ if (route.accountId) accountIds.add(route.accountId);
496
+ if (route.direction === "outbound") {
497
+ outboundRouteIds.add(route.id);
498
+ }
499
+ }
500
+ if (binding.reply) {
501
+ replyPolicies.set(replyKey(binding.reply), binding.reply);
502
+ }
503
+ }
504
+ if (replyPolicies.size > 1) {
505
+ return yield* Effect2.fail(
506
+ new GrantResolutionError({
507
+ runId: request.runId,
508
+ agentId: request.agentId,
509
+ reason: "enabled bindings declare conflicting reply policies"
510
+ })
511
+ );
512
+ }
513
+ const explicitReply = replyPolicies.values().next().value;
514
+ const reply = explicitReply ?? (request.originRouteId ? { mode: "origin" } : { mode: "none" });
515
+ if (reply.mode === "route") {
516
+ const route = routeById.get(reply.routeId);
517
+ if (!route || route.direction !== "outbound") {
518
+ return yield* Effect2.fail(
519
+ new GrantResolutionError({
520
+ runId: request.runId,
521
+ agentId: request.agentId,
522
+ reason: `reply route "${reply.routeId}" is missing, disabled, or not outbound`
523
+ })
524
+ );
525
+ }
526
+ outboundRouteIds.add(route.id);
527
+ if (route.accountId) accountIds.add(route.accountId);
528
+ }
529
+ return {
530
+ runId: request.runId,
531
+ agentId: request.agentId,
532
+ accountIds: [...accountIds].sort(),
533
+ outboundRouteIds: [...outboundRouteIds].sort(),
534
+ capabilities: [...capabilities].sort(),
535
+ reply
536
+ };
537
+ }).pipe(
538
+ Effect2.withSpan("foundry.grant.resolve", {
539
+ attributes: {
540
+ "foundry.run.id": request.runId,
541
+ "foundry.agent.id": request.agentId
542
+ }
543
+ })
544
+ )
545
+ };
546
+ })
547
+ );
548
+
549
+ // src/manifest.ts
550
+ import { Effect as Effect3, JSONSchema, Schema as Schema2 } from "effect";
551
+ var JsonSchemaDocument = Schema2.Record({
552
+ key: Schema2.String,
553
+ value: Schema2.Unknown
554
+ });
555
+ var FoundryManifestCapability = Schema2.Struct({
556
+ id: Schema2.String,
557
+ description: Schema2.String,
558
+ account: Schema2.Literal("none", "optional", "required"),
559
+ effect: Schema2.Literal("read", "write")
560
+ });
561
+ var FoundryManifestTransmission = Schema2.Struct({
562
+ id: Schema2.String,
563
+ name: Schema2.String,
564
+ description: Schema2.String,
565
+ shape: Schema2.Literal(
566
+ "capability-only",
567
+ "inbound-only",
568
+ "outbound-only",
569
+ "bidirectional"
570
+ ),
571
+ account: Schema2.optional(
572
+ Schema2.Struct({
573
+ required: Schema2.Boolean,
574
+ metadataSchema: JsonSchemaDocument
575
+ })
576
+ ),
577
+ capabilities: Schema2.Array(FoundryManifestCapability),
578
+ inbound: Schema2.optional(
579
+ Schema2.Struct({
580
+ configSchema: JsonSchemaDocument,
581
+ eventSchema: JsonSchemaDocument
582
+ })
583
+ ),
584
+ outbound: Schema2.optional(
585
+ Schema2.Struct({
586
+ configSchema: JsonSchemaDocument,
587
+ inputSchema: JsonSchemaDocument,
588
+ outputSchema: JsonSchemaDocument
589
+ })
590
+ )
591
+ });
592
+ var FoundryApplicationManifest = Schema2.Struct({
593
+ schemaVersion: Schema2.Literal(2),
594
+ generatedAt: Schema2.String,
595
+ transmissions: Schema2.Array(FoundryManifestTransmission)
596
+ });
597
+ var ManifestCompilationError = class extends Schema2.TaggedError(
598
+ "ManifestCompilationError"
599
+ )("ManifestCompilationError", {
600
+ message: Schema2.String
601
+ }) {
602
+ };
603
+ function shapeOf(definition) {
604
+ if (definition.inbound && definition.outbound) return "bidirectional";
605
+ if (definition.inbound) return "inbound-only";
606
+ if (definition.outbound) return "outbound-only";
607
+ return "capability-only";
608
+ }
609
+ function jsonSchema(schema) {
610
+ return JSONSchema.make(schema);
611
+ }
612
+ function compileApplicationManifest(transmissions) {
613
+ return Effect3.try({
614
+ try: () => {
615
+ const seen = /* @__PURE__ */ new Set();
616
+ const compiled = [...transmissions].sort((left, right) => left.id.localeCompare(right.id)).map((definition) => {
617
+ if (seen.has(definition.id)) {
618
+ throw new Error(`Duplicate integration id "${definition.id}".`);
619
+ }
620
+ seen.add(definition.id);
621
+ return {
622
+ id: definition.id,
623
+ name: definition.name,
624
+ description: definition.description,
625
+ shape: shapeOf(definition),
626
+ ...definition.account ? {
627
+ account: {
628
+ required: definition.account.required,
629
+ metadataSchema: jsonSchema(definition.account.metadata)
630
+ }
631
+ } : {},
632
+ capabilities: [...definition.capabilities ?? []],
633
+ ...definition.inbound ? {
634
+ inbound: {
635
+ configSchema: jsonSchema(definition.inbound.config),
636
+ eventSchema: jsonSchema(definition.inbound.event)
637
+ }
638
+ } : {},
639
+ ...definition.outbound ? {
640
+ outbound: {
641
+ configSchema: jsonSchema(definition.outbound.config),
642
+ inputSchema: jsonSchema(definition.outbound.input),
643
+ outputSchema: jsonSchema(definition.outbound.output)
644
+ }
645
+ } : {}
646
+ };
647
+ });
648
+ return Schema2.decodeUnknownSync(FoundryApplicationManifest)({
649
+ schemaVersion: 2,
650
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
651
+ transmissions: compiled
652
+ });
653
+ },
654
+ catch: (cause) => new ManifestCompilationError({
655
+ message: cause instanceof Error ? cause.message : String(cause)
656
+ })
657
+ }).pipe(
658
+ Effect3.withSpan("foundry.manifest.compile", {
659
+ attributes: { "foundry.transmission.count": transmissions.length }
660
+ })
661
+ );
662
+ }
663
+
664
+ // src/transmission.ts
665
+ function escapeXml(value) {
666
+ return String(value).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
667
+ }
668
+ function stable(value) {
669
+ if (Array.isArray(value)) return value.map(stable);
670
+ if (value && typeof value === "object") {
671
+ return Object.fromEntries(
672
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, stable(item)])
673
+ );
674
+ }
675
+ return value;
676
+ }
677
+ function data(value) {
678
+ return escapeXml(JSON.stringify(stable(value)) ?? "null");
679
+ }
680
+ function serializeInboundTransmissionXml(input) {
681
+ const playbooks = input.playbooks.map((playbook) => {
682
+ const directives = playbook.directives.map(
683
+ (directive) => ` <directive action="${escapeXml(directive.action)}">
684
+ <instruction>${escapeXml(directive.instruction)}</instruction>` + (directive.parameters ? `
685
+ <parameters format="json">${data(directive.parameters)}</parameters>` : "") + `
686
+ </directive>`
687
+ ).join("\n");
688
+ const outbound = (playbook.outbound ?? []).map(
689
+ (target) => ` <outbound route="${escapeXml(target.routeId)}"` + (target.applicationId ? ` application="${escapeXml(target.applicationId)}"` : "") + (target.event ? ` event="${escapeXml(target.event)}"` : "") + (target.accountId ? ` account="${escapeXml(target.accountId)}"` : "") + (target.applicationAccountId ? ` application-account="${escapeXml(target.applicationAccountId)}"` : "") + `>` + (target.instruction ? escapeXml(target.instruction) : "") + `</outbound>`
690
+ ).join("\n");
691
+ return ` <playbook id="${escapeXml(playbook.id)}">
692
+ ${directives}` + (outbound ? `
693
+ ${outbound}` : "") + (playbook.serialization ? `
694
+ <serialization format="json">${data(playbook.serialization)}</serialization>` : "") + `
695
+ </playbook>`;
696
+ }).join("\n");
697
+ return `<transmission direction="inbound" definition="${escapeXml(input.transmissionId)}" route="${escapeXml(input.routeId)}" event="${escapeXml(input.eventName)}" event-id="${escapeXml(input.eventId)}" thread="${escapeXml(input.threadKey)}">
698
+ <payload format="json">${data(input.event)}</payload>
699
+ ${playbooks}
700
+ </transmission>`;
701
+ }
702
+
703
+ // src/observability.ts
704
+ import { randomUUID } from "node:crypto";
705
+ var MemoryObservabilityAdapter = class {
706
+ maxEvents;
707
+ events = [];
708
+ listeners = /* @__PURE__ */ new Set();
709
+ sequence = 0;
710
+ constructor(options) {
711
+ this.maxEvents = options?.maxEvents ?? 1e4;
712
+ if (!Number.isInteger(this.maxEvents) || this.maxEvents < 1) {
713
+ throw new Error("maxEvents must be an integer >= 1");
714
+ }
715
+ }
716
+ append(input) {
717
+ const event = Object.freeze({
718
+ id: randomUUID(),
719
+ sequence: ++this.sequence,
720
+ timestamp: input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
721
+ type: input.type,
722
+ category: input.category,
723
+ ...input.agent ? { agent: input.agent } : {},
724
+ ...input.runId ? { runId: input.runId } : {},
725
+ data: input.data
726
+ });
727
+ this.events.push(event);
728
+ if (this.events.length > this.maxEvents) {
729
+ this.events.splice(0, this.events.length - this.maxEvents);
730
+ }
731
+ for (const listener of this.listeners) listener(event);
732
+ return event;
733
+ }
734
+ list(filter = {}) {
735
+ const matched = this.events.filter((event) => {
736
+ if (filter.after !== void 0 && event.sequence <= filter.after) {
737
+ return false;
738
+ }
739
+ if (filter.agent && event.agent !== filter.agent) return false;
740
+ if (filter.runId && event.runId !== filter.runId) return false;
741
+ if (filter.category && event.category !== filter.category) return false;
742
+ return true;
743
+ });
744
+ const limit = Math.max(1, Math.min(filter.limit ?? 500, 5e3));
745
+ return matched.slice(-limit);
746
+ }
747
+ subscribe(listener) {
748
+ this.listeners.add(listener);
749
+ return () => this.listeners.delete(listener);
750
+ }
751
+ clear() {
752
+ this.events.length = 0;
753
+ }
754
+ };
755
+ function categoryForAgentEvent(type) {
756
+ if (type.startsWith("tool_")) return "tool";
757
+ if (type.startsWith("foundry.installation")) return "application";
758
+ if (type.startsWith("foundry.layer") || type.startsWith("foundry.subscriber")) {
759
+ return "extension";
760
+ }
761
+ if (type.startsWith("glove_memory") || type.startsWith("glove_context")) {
762
+ return "memory";
763
+ }
764
+ if (type.startsWith("mcp") || type.includes("discovermcp")) return "mcp";
765
+ if (type.startsWith("inbox")) return "inbox";
766
+ if (type.startsWith("hook_") || type.startsWith("skill_") || type.startsWith("subagent_")) {
767
+ return "extension";
768
+ }
769
+ if (type === "text_delta" || type.startsWith("model_") || type === "token_consumption") {
770
+ return "model";
771
+ }
772
+ return "system";
773
+ }
774
+ function encodedAgentEvent(message) {
775
+ const start = message.indexOf(FOUNDRY_EVENT_PREFIX);
776
+ if (start < 0) return null;
777
+ const json2 = message.slice(start + FOUNDRY_EVENT_PREFIX.length).trim();
778
+ try {
779
+ const parsed = JSON.parse(json2);
780
+ return typeof parsed.type === "string" ? { type: parsed.type, data: parsed.data, timestamp: parsed.timestamp } : null;
781
+ } catch {
782
+ return null;
783
+ }
784
+ }
785
+ var FoundryObserver = class {
786
+ constructor(adapter, routeForSignalName, onAgentEvent) {
787
+ this.adapter = adapter;
788
+ this.routeForSignalName = routeForSignalName;
789
+ this.onAgentEvent = onAgentEvent;
790
+ }
791
+ logBuffers = /* @__PURE__ */ new Map();
792
+ route(name) {
793
+ return this.routeForSignalName(name);
794
+ }
795
+ runEvent(type, run, data2 = {}) {
796
+ this.adapter.append({
797
+ type,
798
+ category: "run",
799
+ agent: this.route(run.signalName),
800
+ runId: run.id,
801
+ data: data2
802
+ });
803
+ }
804
+ onSignalDiscovered(event) {
805
+ this.adapter.append({
806
+ type: "agent.discovered",
807
+ category: "agent",
808
+ agent: this.route(event.signalName),
809
+ data: { runtime: "foundry-execution", filePath: event.filePath }
810
+ });
811
+ }
812
+ onRunDispatched({ run }) {
813
+ this.runEvent("run.dispatched", run);
814
+ }
815
+ onRunStarted({ run }) {
816
+ this.runEvent("run.started", run);
817
+ }
818
+ onRunCompleted(event) {
819
+ this.flushRunLogs(event.run);
820
+ this.runEvent("run.completed", event.run, { output: event.output });
821
+ }
822
+ onRunTimeout({ run }) {
823
+ this.runEvent("run.timeout", run);
824
+ }
825
+ onRunRetry(event) {
826
+ this.runEvent("run.retry", event.run, {
827
+ attempt: event.attempt,
828
+ maxAttempts: event.maxAttempts
829
+ });
830
+ }
831
+ onRunFailed(event) {
832
+ this.flushRunLogs(event.run);
833
+ this.runEvent("run.failed", event.run, { error: event.error });
834
+ }
835
+ onRunCancelled({ run }) {
836
+ this.flushRunLogs(run);
837
+ this.runEvent("run.cancelled", run);
838
+ }
839
+ onRunSkipped(event) {
840
+ this.runEvent("run.skipped", event.run, { reason: event.reason });
841
+ }
842
+ onRunRescheduled(event) {
843
+ this.runEvent("run.rescheduled", event.run, {
844
+ nextRunAt: event.nextRunAt.toISOString()
845
+ });
846
+ }
847
+ onCompleteError(event) {
848
+ this.runEvent("run.on-complete-error", event.run, { error: event.error });
849
+ }
850
+ onLogOutput(event) {
851
+ const key = `${event.run.id}:${event.level}`;
852
+ const combined = `${this.logBuffers.get(key) ?? ""}${event.message}`;
853
+ const lines = combined.split(/\r?\n/);
854
+ const complete = combined.endsWith("\n") ? lines : lines.slice(0, -1);
855
+ const remainder = combined.endsWith("\n") ? "" : lines.at(-1) ?? "";
856
+ if (remainder) this.logBuffers.set(key, remainder);
857
+ else this.logBuffers.delete(key);
858
+ for (const line of complete) {
859
+ if (line) this.appendLogLine(event.run, event.level, line);
860
+ }
861
+ }
862
+ appendLogLine(run, level, message) {
863
+ const encoded = encodedAgentEvent(message);
864
+ if (encoded) {
865
+ this.onAgentEvent?.({
866
+ route: this.route(run.signalName),
867
+ run,
868
+ type: encoded.type,
869
+ data: encoded.data
870
+ });
871
+ this.adapter.append({
872
+ type: `agent.${encoded.type}`,
873
+ category: categoryForAgentEvent(encoded.type),
874
+ agent: this.route(run.signalName),
875
+ runId: run.id,
876
+ ...encoded.timestamp ? { timestamp: encoded.timestamp } : {},
877
+ data: encoded.data
878
+ });
879
+ return;
880
+ }
881
+ this.adapter.append({
882
+ type: `log.${level}`,
883
+ category: "log",
884
+ agent: this.route(run.signalName),
885
+ runId: run.id,
886
+ data: { message }
887
+ });
888
+ }
889
+ flushRunLogs(run) {
890
+ for (const level of ["stdout", "stderr"]) {
891
+ const key = `${run.id}:${level}`;
892
+ const remainder = this.logBuffers.get(key);
893
+ if (!remainder) continue;
894
+ this.logBuffers.delete(key);
895
+ this.appendLogLine(run, level, remainder);
896
+ }
897
+ }
898
+ };
899
+
900
+ // src/runtime.ts
901
+ import { existsSync } from "node:fs";
902
+ import { fileURLToPath } from "node:url";
903
+ import {
904
+ Effect as Effect5,
905
+ Layer as Layer3,
906
+ ManagedRuntime as EffectManagedRuntime,
907
+ Schema as Schema3
908
+ } from "effect";
909
+ import { EnvStore, MemoryEnvStorage } from "station-env";
910
+ import {
911
+ MemoryAdapter,
912
+ parseInterval,
913
+ SignalRunner
914
+ } from "station-signal";
915
+ import {
916
+ ScheduleMemoryAdapter,
917
+ ScheduleReconciler,
918
+ nextCronOccurrence
919
+ } from "station-schedules";
920
+
921
+ // src/connection-supervisor.ts
922
+ import { Effect as Effect4 } from "effect";
923
+ function safeError(cause) {
924
+ return cause instanceof Error ? cause.message : String(cause);
925
+ }
926
+ function routeSignature(routes) {
927
+ return routes.map((route) => route.id).sort().join("\0");
928
+ }
929
+ var ApplicationConnectionSupervisor = class {
930
+ constructor(options) {
931
+ this.options = options;
932
+ }
933
+ running = /* @__PURE__ */ new Map();
934
+ states = /* @__PURE__ */ new Map();
935
+ list() {
936
+ return [...this.states.values()].map((state) => ({
937
+ ...state,
938
+ routeIds: [...state.routeIds]
939
+ }));
940
+ }
941
+ async reconcile(desired) {
942
+ const wanted = new Map(desired.map((item) => [item.id, item]));
943
+ for (const [id2, running] of this.running) {
944
+ const next = wanted.get(id2);
945
+ if (!next || routeSignature(next.routes) !== routeSignature(running.desired.routes)) {
946
+ await this.stop(id2);
947
+ if (!next) this.states.delete(id2);
948
+ }
949
+ }
950
+ for (const item of desired) {
951
+ if (!this.running.has(item.id)) this.start(item);
952
+ }
953
+ }
954
+ async reconnect(id2) {
955
+ const current = this.running.get(id2)?.desired;
956
+ if (!current) throw new Error(`Application connection "${id2}" was not found.`);
957
+ await this.stop(id2);
958
+ this.start(current);
959
+ }
960
+ async stopAll() {
961
+ await Promise.all([...this.running.keys()].map((id2) => this.stop(id2)));
962
+ }
963
+ start(desired) {
964
+ const controller = new AbortController();
965
+ const task = this.run(desired, controller.signal).finally(() => {
966
+ const current = this.running.get(desired.id);
967
+ if (current?.controller === controller) this.running.delete(desired.id);
968
+ });
969
+ this.running.set(desired.id, { desired, controller, task });
970
+ }
971
+ async stop(id2) {
972
+ const running = this.running.get(id2);
973
+ if (!running) return;
974
+ running.controller.abort();
975
+ await running.task.catch(() => void 0);
976
+ this.running.delete(id2);
977
+ this.update(running.desired, {
978
+ status: "disconnected",
979
+ disconnectedAt: (/* @__PURE__ */ new Date()).toISOString()
980
+ });
981
+ }
982
+ async run(desired, signal) {
983
+ let attempts = 0;
984
+ while (!signal.aborted) {
985
+ attempts += 1;
986
+ this.update(desired, {
987
+ status: attempts === 1 ? "connecting" : "reconnecting",
988
+ attempts
989
+ });
990
+ let ready = false;
991
+ try {
992
+ const program = desired.connection.connect({
993
+ applicationId: desired.application.id,
994
+ connectionId: desired.connection.id,
995
+ definitionId: desired.definitionId,
996
+ workspaceId: desired.workspaceId,
997
+ ...desired.account ? { account: desired.account } : {},
998
+ routes: desired.routes,
999
+ signal,
1000
+ ready: () => Effect4.sync(() => {
1001
+ if (ready) return;
1002
+ ready = true;
1003
+ this.update(desired, {
1004
+ status: "connected",
1005
+ attempts,
1006
+ connectedAt: (/* @__PURE__ */ new Date()).toISOString(),
1007
+ error: ""
1008
+ });
1009
+ }),
1010
+ receive: (input) => Effect4.tryPromise({
1011
+ try: async () => {
1012
+ if (!desired.routes.some((route) => route.id === input.route.id)) {
1013
+ throw new Error(
1014
+ `Connection "${desired.id}" emitted through inactive route "${input.route.id}".`
1015
+ );
1016
+ }
1017
+ this.update(desired, {
1018
+ status: ready ? "connected" : "connecting",
1019
+ attempts,
1020
+ lastEventAt: (/* @__PURE__ */ new Date()).toISOString()
1021
+ });
1022
+ await this.options.receive({
1023
+ routeId: input.route.id,
1024
+ eventId: input.eventId,
1025
+ threadKey: input.threadKey,
1026
+ raw: input.raw
1027
+ });
1028
+ },
1029
+ catch: (cause) => cause
1030
+ }).pipe(Effect4.orDie),
1031
+ ...desired.account && desired.accountSessions ? {
1032
+ withAccountSession: (operation, use) => desired.accountSessions.withSession({
1033
+ accountId: desired.account.id,
1034
+ operation,
1035
+ agentId: `connection:${desired.id}`,
1036
+ conversationId: `connection:${desired.id}`,
1037
+ workspaceId: desired.workspaceId
1038
+ }, use)
1039
+ } : {}
1040
+ });
1041
+ await Effect4.runPromise(program, { signal });
1042
+ if (signal.aborted) break;
1043
+ throw new Error("Provider connection ended.");
1044
+ } catch (cause) {
1045
+ if (signal.aborted) break;
1046
+ this.update(desired, {
1047
+ status: "failed",
1048
+ attempts,
1049
+ disconnectedAt: (/* @__PURE__ */ new Date()).toISOString(),
1050
+ error: safeError(cause)
1051
+ });
1052
+ }
1053
+ const backoffMs = Math.min(3e4, 250 * 2 ** Math.min(attempts - 1, 7));
1054
+ await new Promise((resolve3) => {
1055
+ const timer = setTimeout(resolve3, backoffMs);
1056
+ signal.addEventListener("abort", () => {
1057
+ clearTimeout(timer);
1058
+ resolve3();
1059
+ }, { once: true });
1060
+ });
1061
+ }
1062
+ }
1063
+ update(desired, patch) {
1064
+ const prior = this.states.get(desired.id);
1065
+ const state = Object.freeze({
1066
+ id: desired.id,
1067
+ applicationId: desired.application.id,
1068
+ connectionId: desired.connection.id,
1069
+ definitionId: desired.definitionId,
1070
+ workspaceId: desired.workspaceId,
1071
+ ...desired.account ? { accountId: desired.account.id } : {},
1072
+ routeIds: Object.freeze(desired.routes.map((route) => route.id).sort()),
1073
+ status: patch.status ?? prior?.status ?? "disconnected",
1074
+ attempts: patch.attempts ?? prior?.attempts ?? 0,
1075
+ ...patch.connectedAt ?? prior?.connectedAt ? { connectedAt: patch.connectedAt ?? prior?.connectedAt } : {},
1076
+ ...patch.disconnectedAt ?? prior?.disconnectedAt ? { disconnectedAt: patch.disconnectedAt ?? prior?.disconnectedAt } : {},
1077
+ ...patch.lastEventAt ?? prior?.lastEventAt ? { lastEventAt: patch.lastEventAt ?? prior?.lastEventAt } : {},
1078
+ ...patch.error !== void 0 ? patch.error ? { error: patch.error } : {} : prior?.error ? { error: prior.error } : {}
1079
+ });
1080
+ this.states.set(desired.id, state);
1081
+ this.options.emit({
1082
+ type: `application.connection.${state.status}`,
1083
+ data: state
1084
+ });
1085
+ }
1086
+ };
1087
+
1088
+ // src/registry.ts
1089
+ import { readdir } from "node:fs/promises";
1090
+ import { extname, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
1091
+ import { pathToFileURL } from "node:url";
1092
+ var MODULE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".js", ".mjs"]);
1093
+ var DEFAULT_REGISTRY_DIRECTORIES = Object.freeze({
1094
+ tools: "tools",
1095
+ applications: "applications",
1096
+ mcp: "mcp",
1097
+ memory: "memory",
1098
+ layers: "layers",
1099
+ subscribers: "subscribers"
1100
+ });
1101
+ var KINDS = [
1102
+ {
1103
+ kind: "tool",
1104
+ directory: "tools",
1105
+ suffix: ".tool",
1106
+ brand: FOUNDRY_SHARED_TOOL_BRAND
1107
+ },
1108
+ {
1109
+ kind: "application",
1110
+ directory: "applications",
1111
+ suffix: ".application",
1112
+ brand: FOUNDRY_AGENT_APPLICATION_BRAND
1113
+ },
1114
+ { kind: "mcp", directory: "mcp", suffix: ".mcp", brand: FOUNDRY_MCP_BRAND },
1115
+ {
1116
+ kind: "memory",
1117
+ directory: "memory",
1118
+ suffix: ".memory",
1119
+ brand: FOUNDRY_MEMORY_BRAND
1120
+ }
1121
+ ];
1122
+ function normalizePath(path) {
1123
+ return path.split(sep2).join("/");
1124
+ }
1125
+ async function moduleFiles(directory, suffix) {
1126
+ let entries;
1127
+ try {
1128
+ entries = await readdir(directory, { recursive: true });
1129
+ } catch (cause) {
1130
+ const code = cause.code;
1131
+ if (code === "ENOENT") return [];
1132
+ throw cause;
1133
+ }
1134
+ return entries.filter((entry) => {
1135
+ if (entry.endsWith(".d.ts")) return false;
1136
+ const extension = extname(entry);
1137
+ return MODULE_EXTENSIONS.has(extension) && entry.slice(0, -extension.length).endsWith(suffix);
1138
+ }).map((entry) => resolve2(directory, entry)).sort();
1139
+ }
1140
+ function routeFromModule(directory, filePath, suffix) {
1141
+ const rel = normalizePath(relative2(directory, filePath));
1142
+ const extension = extname(rel);
1143
+ return rel.slice(0, -extension.length - suffix.length);
1144
+ }
1145
+ async function importDefault(filePath, cacheBust) {
1146
+ const url = pathToFileURL(filePath);
1147
+ if (cacheBust) url.searchParams.set("t", String(Date.now()));
1148
+ const imported = await import(url.href);
1149
+ return imported.default;
1150
+ }
1151
+ async function importModule(filePath, cacheBust) {
1152
+ const url = pathToFileURL(filePath);
1153
+ if (cacheBust) url.searchParams.set("t", String(Date.now()));
1154
+ return await import(url.href);
1155
+ }
1156
+ function isToolBody(value) {
1157
+ if (!value || typeof value !== "object") return false;
1158
+ const body = value;
1159
+ return typeof body.description === "string" && typeof body.do === "function" && (body.inputSchema !== void 0 || body.jsonSchema !== void 0);
1160
+ }
1161
+ function assembleToolModule(route, module) {
1162
+ const branded = module.default;
1163
+ if (branded && typeof branded === "object" && branded[FOUNDRY_SHARED_TOOL_BRAND] === true) {
1164
+ return branded;
1165
+ }
1166
+ const body = isToolBody(module.default) ? module.default : isToolBody(module.tool) ? module.tool : isToolBody(module) ? module : null;
1167
+ if (!body) {
1168
+ throw new Error(`Tool route "${route}" must export a Glove tool body as default, as \`tool\`, or as named description/inputSchema/do constants.`);
1169
+ }
1170
+ const name = route.replaceAll("/", "__").replaceAll("-", "_");
1171
+ return defineSharedTool({
1172
+ id: route,
1173
+ description: typeof module.summary === "string" ? module.summary : body.description,
1174
+ tool: { ...body, name }
1175
+ });
1176
+ }
1177
+ function addUnique2(target, values, label) {
1178
+ const ids = new Set(target.map((value) => value.id));
1179
+ for (const value of values) {
1180
+ if (ids.has(value.id)) {
1181
+ throw new Error(`Duplicate Foundry ${label} id "${value.id}".`);
1182
+ }
1183
+ ids.add(value.id);
1184
+ target.push(value);
1185
+ }
1186
+ }
1187
+ async function discoverFoundryRegistry(options) {
1188
+ const directories = {
1189
+ ...DEFAULT_REGISTRY_DIRECTORIES,
1190
+ ...options.directories
1191
+ };
1192
+ const registry = {
1193
+ tools: [...EMPTY_CAPABILITY_REGISTRY.tools],
1194
+ applications: [...EMPTY_CAPABILITY_REGISTRY.applications],
1195
+ mcp: [...EMPTY_CAPABILITY_REGISTRY.mcp],
1196
+ memory: [...EMPTY_CAPABILITY_REGISTRY.memory]
1197
+ };
1198
+ const files = [];
1199
+ const native = {
1200
+ layers: [...EMPTY_NATIVE_REGISTRY.layers],
1201
+ subscribers: [...EMPTY_NATIVE_REGISTRY.subscribers]
1202
+ };
1203
+ const nativeFiles = [];
1204
+ for (const config of options.capabilities === false ? [] : KINDS) {
1205
+ const directory = resolve2(options.rootDir, directories[config.directory]);
1206
+ for (const filePath of await moduleFiles(directory, config.suffix)) {
1207
+ const route = routeFromModule(directory, filePath, config.suffix);
1208
+ const imported = await importModule(filePath, options.cacheBust ?? false);
1209
+ const value = config.kind === "tool" ? assembleToolModule(route, imported) : imported.default;
1210
+ if (!value || typeof value !== "object" || value[config.brand] !== true) {
1211
+ throw new Error(
1212
+ `${normalizePath(relative2(options.rootDir, filePath))} must default-export a Foundry ${config.kind} definition.`
1213
+ );
1214
+ }
1215
+ const definition = value;
1216
+ bindFileIdentity(value, route, config.kind);
1217
+ if ((options.strictFileRoutes ?? true) && definition.id !== route) {
1218
+ throw new Error(
1219
+ `Foundry ${config.kind} route mismatch in ${filePath}: file resolves to "${route}" but declares "${definition.id}".`
1220
+ );
1221
+ }
1222
+ if (config.kind === "tool") {
1223
+ addUnique2(registry.tools, [value], "shared tool");
1224
+ } else if (config.kind === "application") {
1225
+ addUnique2(
1226
+ registry.applications,
1227
+ [value],
1228
+ "agent application"
1229
+ );
1230
+ } else if (config.kind === "mcp") {
1231
+ addUnique2(registry.mcp, [value], "MCP");
1232
+ } else if (config.kind === "memory") {
1233
+ addUnique2(
1234
+ registry.memory,
1235
+ [value],
1236
+ "memory profile"
1237
+ );
1238
+ }
1239
+ files.push({
1240
+ kind: config.kind,
1241
+ id: definition.id,
1242
+ filePath,
1243
+ relativePath: normalizePath(relative2(options.rootDir, filePath))
1244
+ });
1245
+ }
1246
+ }
1247
+ for (const config of options.native === false ? [] : [
1248
+ {
1249
+ kind: "layer",
1250
+ directory: "layers",
1251
+ suffix: ".layer",
1252
+ brand: FOUNDRY_LAYER_BRAND
1253
+ },
1254
+ {
1255
+ kind: "subscriber",
1256
+ directory: "subscribers",
1257
+ suffix: ".subscriber",
1258
+ brand: FOUNDRY_SUBSCRIBER_BRAND
1259
+ }
1260
+ ]) {
1261
+ const directory = resolve2(options.rootDir, directories[config.directory]);
1262
+ for (const filePath of await moduleFiles(directory, config.suffix)) {
1263
+ const route = routeFromModule(directory, filePath, config.suffix);
1264
+ const value = await importDefault(filePath, options.cacheBust ?? false);
1265
+ if (!value || typeof value !== "object" || value[config.brand] !== true) {
1266
+ throw new Error(
1267
+ `${normalizePath(relative2(options.rootDir, filePath))} must default-export a Foundry ${config.kind} definition.`
1268
+ );
1269
+ }
1270
+ const definition = value;
1271
+ bindFileIdentity(value, route, config.kind);
1272
+ if ((options.strictFileRoutes ?? true) && definition.id !== route) {
1273
+ throw new Error(
1274
+ `Foundry ${config.kind} route mismatch in ${filePath}: file resolves to "${route}" but declares "${definition.id}".`
1275
+ );
1276
+ }
1277
+ if (config.kind === "layer") {
1278
+ addUnique2(native.layers, [value], "layer");
1279
+ } else {
1280
+ addUnique2(
1281
+ native.subscribers,
1282
+ [value],
1283
+ "subscriber"
1284
+ );
1285
+ }
1286
+ nativeFiles.push({
1287
+ kind: config.kind,
1288
+ id: definition.id,
1289
+ filePath,
1290
+ relativePath: normalizePath(relative2(options.rootDir, filePath))
1291
+ });
1292
+ }
1293
+ }
1294
+ return {
1295
+ capabilities: Object.freeze({
1296
+ tools: Object.freeze(registry.tools),
1297
+ applications: Object.freeze(registry.applications),
1298
+ mcp: Object.freeze(registry.mcp),
1299
+ memory: Object.freeze(registry.memory)
1300
+ }),
1301
+ native: Object.freeze({
1302
+ layers: Object.freeze(native.layers),
1303
+ subscribers: Object.freeze(native.subscribers)
1304
+ }),
1305
+ files: Object.freeze(files),
1306
+ nativeFiles: Object.freeze(nativeFiles)
1307
+ };
1308
+ }
1309
+
1310
+ // src/runtime.ts
1311
+ var FoundryRuntimeError = class extends Schema3.TaggedError(
1312
+ "FoundryRuntimeError"
1313
+ )("FoundryRuntimeError", {
1314
+ operation: Schema3.String,
1315
+ message: Schema3.String
1316
+ }) {
1317
+ };
1318
+ function parseJson(value) {
1319
+ if (value === void 0) return void 0;
1320
+ try {
1321
+ return JSON.parse(value);
1322
+ } catch {
1323
+ return value;
1324
+ }
1325
+ }
1326
+ function runtimeFailure(operation, cause) {
1327
+ return new FoundryRuntimeError({
1328
+ operation,
1329
+ message: cause instanceof Error ? cause.message : String(cause)
1330
+ });
1331
+ }
1332
+ function promiseEffect(operation, evaluate) {
1333
+ return Effect5.tryPromise({
1334
+ try: evaluate,
1335
+ catch: (cause) => runtimeFailure(operation, cause)
1336
+ }).pipe(Effect5.withSpan(`foundry.${operation}`));
1337
+ }
1338
+ function executionAgentEntrypoint() {
1339
+ const built = fileURLToPath(new URL("./execution-agent.js", import.meta.url));
1340
+ if (existsSync(built)) return built;
1341
+ return fileURLToPath(new URL("./execution-agent.ts", import.meta.url));
1342
+ }
1343
+ var FoundryRuntime = class _FoundryRuntime {
1344
+ rootDir;
1345
+ agents;
1346
+ application;
1347
+ applicationFilePath;
1348
+ registry;
1349
+ manifest;
1350
+ applicationManifest;
1351
+ observability;
1352
+ data;
1353
+ byRoute = /* @__PURE__ */ new Map();
1354
+ routeBySignalName = /* @__PURE__ */ new Map();
1355
+ transmissionById = /* @__PURE__ */ new Map();
1356
+ /** Stable topology view used by synchronous playbook validation. */
1357
+ topologyRoutes = /* @__PURE__ */ new Map();
1358
+ compositionByDefinition = /* @__PURE__ */ new Map();
1359
+ connectionSupervisor;
1360
+ execution;
1361
+ observer;
1362
+ signalRunner;
1363
+ envStore;
1364
+ envProvider;
1365
+ scheduleAdapter;
1366
+ services;
1367
+ runnerLoops = [];
1368
+ materializedActivations = /* @__PURE__ */ new Set();
1369
+ started = false;
1370
+ disposed = false;
1371
+ constructor(options) {
1372
+ this.rootDir = options.rootDir;
1373
+ this.agents = Object.freeze([...options.agents]);
1374
+ this.application = options.application ?? EMPTY_FOUNDRY_APPLICATION;
1375
+ this.applicationFilePath = options.applicationFilePath;
1376
+ for (const route of this.application.routes ?? []) {
1377
+ this.topologyRoutes.set(route.id, route);
1378
+ }
1379
+ const capabilities = { tools: [], applications: [], mcp: [], memory: [] };
1380
+ const native = { layers: [], subscribers: [] };
1381
+ for (const discovered of this.agents) {
1382
+ const composition = discovered.definition.components ?? EMPTY_AGENT_COMPOSITION;
1383
+ this.compositionByDefinition.set(discovered.route, composition);
1384
+ capabilities.tools.push(...composition.capabilities.tools);
1385
+ capabilities.applications.push(...composition.capabilities.applications);
1386
+ capabilities.mcp.push(...composition.capabilities.mcp);
1387
+ capabilities.memory.push(...composition.capabilities.memory);
1388
+ native.layers.push(...composition.native.layers);
1389
+ native.subscribers.push(...composition.native.subscribers);
1390
+ for (const application of composition.capabilities.applications) {
1391
+ for (const transmission of application.transmissions ?? []) {
1392
+ const existing = this.transmissionById.get(transmission.id);
1393
+ if (existing && existing !== transmission) {
1394
+ throw new Error(
1395
+ `Transmission "${transmission.id}" is defined by more than one agent-local application. Share one application definition instead.`
1396
+ );
1397
+ }
1398
+ this.transmissionById.set(transmission.id, transmission);
1399
+ }
1400
+ }
1401
+ }
1402
+ this.registry = Object.freeze({
1403
+ capabilities: Object.freeze({
1404
+ tools: Object.freeze(capabilities.tools),
1405
+ applications: Object.freeze(capabilities.applications),
1406
+ mcp: Object.freeze(capabilities.mcp),
1407
+ memory: Object.freeze(capabilities.memory)
1408
+ }),
1409
+ native: Object.freeze({
1410
+ layers: Object.freeze(native.layers),
1411
+ subscribers: Object.freeze(native.subscribers)
1412
+ }),
1413
+ files: Object.freeze([]),
1414
+ nativeFiles: Object.freeze([])
1415
+ });
1416
+ this.manifest = createManifest(this.agents);
1417
+ this.applicationManifest = Effect5.runSync(
1418
+ compileApplicationManifest([...this.transmissionById.values()])
1419
+ );
1420
+ this.observability = options.observability ?? new MemoryObservabilityAdapter({
1421
+ maxEvents: options.config?.observability?.maxEvents
1422
+ });
1423
+ this.data = this.application.data ?? new MemoryFoundryDataAdapter();
1424
+ for (const discovered of this.agents) {
1425
+ this.byRoute.set(discovered.route, discovered);
1426
+ this.routeBySignalName.set(discovered.executionName, discovered.route);
1427
+ }
1428
+ this.observer = new FoundryObserver(
1429
+ this.observability,
1430
+ (name) => this.routeBySignalName.get(name) ?? name,
1431
+ (event) => {
1432
+ if (event.type === FOUNDRY_CORE_COMMAND_EVENT) {
1433
+ void this.executeCoreCommand(event.data, event.run.id).catch((cause) => {
1434
+ this.observability.append({
1435
+ type: "core.command.failed",
1436
+ category: "system",
1437
+ agent: event.route,
1438
+ runId: event.run.id,
1439
+ data: { error: cause instanceof Error ? cause.message : String(cause), command: event.data }
1440
+ });
1441
+ });
1442
+ }
1443
+ }
1444
+ );
1445
+ this.connectionSupervisor = new ApplicationConnectionSupervisor({
1446
+ receive: async (input) => {
1447
+ await this.dispatchInbound(input);
1448
+ },
1449
+ emit: (event) => {
1450
+ this.observability.append({
1451
+ type: event.type,
1452
+ category: "application",
1453
+ data: event.data
1454
+ });
1455
+ }
1456
+ });
1457
+ const configured = options.config?.execution;
1458
+ const defaults = DEFAULT_FOUNDRY_CONFIG.execution;
1459
+ this.execution = {
1460
+ maxConcurrent: configured?.maxConcurrent ?? defaults.maxConcurrent,
1461
+ maxAttempts: configured?.maxAttempts ?? defaults.maxAttempts,
1462
+ retryBackoffMs: configured?.retryBackoffMs ?? defaults.retryBackoffMs,
1463
+ pollIntervalMs: configured?.pollIntervalMs ?? defaults.pollIntervalMs,
1464
+ idlePollIntervalMs: configured?.idlePollIntervalMs ?? defaults.idlePollIntervalMs
1465
+ };
1466
+ this.envStore = new EnvStore(new MemoryEnvStorage());
1467
+ this.envProvider = {
1468
+ resolveFor: async (target) => {
1469
+ const resolvedEnvironment = await this.envStore.resolveFor(target);
1470
+ if (target.kind === "signal") {
1471
+ const route = routeFromInternalAgentName(target.name);
1472
+ resolvedEnvironment[FOUNDRY_AGENT_ROUTE_ENV] = route;
1473
+ const discovered = this.byRoute.get(route);
1474
+ if (discovered) {
1475
+ resolvedEnvironment[FOUNDRY_AGENT_FILE_ENV] = discovered.filePath;
1476
+ }
1477
+ }
1478
+ if (this.applicationFilePath) {
1479
+ resolvedEnvironment[FOUNDRY_APPLICATION_ENV] = this.applicationFilePath;
1480
+ }
1481
+ return resolvedEnvironment;
1482
+ }
1483
+ };
1484
+ this.scheduleAdapter = new ScheduleMemoryAdapter();
1485
+ const signalAdapter = new MemoryAdapter();
1486
+ const signalScheduleReconciler = new ScheduleReconciler({
1487
+ adapter: this.scheduleAdapter,
1488
+ kinds: ["signal"],
1489
+ triggerFn: (schedule, scheduledFor) => this.signalRunner.triggerSignal(
1490
+ schedule.target,
1491
+ schedule.input,
1492
+ { id: schedule.id, scheduledFor }
1493
+ ),
1494
+ hasPendingOrRunning: (schedule) => this.signalRunner.hasPendingOrRunningForSignal(schedule.target),
1495
+ parseInterval,
1496
+ onError: (error, schedule) => this.observability.append({
1497
+ type: "schedule.error",
1498
+ category: "activation",
1499
+ data: { scheduleId: schedule?.id, error: error.message }
1500
+ })
1501
+ });
1502
+ this.signalRunner = new SignalRunner({
1503
+ adapter: signalAdapter,
1504
+ pollIntervalMs: this.execution.pollIntervalMs,
1505
+ idlePollIntervalMs: this.execution.idlePollIntervalMs,
1506
+ maxConcurrent: this.execution.maxConcurrent,
1507
+ maxAttempts: this.execution.maxAttempts,
1508
+ retryBackoffMs: this.execution.retryBackoffMs,
1509
+ subscribers: [this.observer],
1510
+ scheduleReconciler: signalScheduleReconciler,
1511
+ envProvider: this.envProvider,
1512
+ stationId: "foundry-local",
1513
+ failUnknownSignals: true
1514
+ });
1515
+ for (const discovered of this.agents) {
1516
+ this.signalRunner.registerSignal(
1517
+ this.executionSignal(discovered),
1518
+ executionAgentEntrypoint()
1519
+ );
1520
+ }
1521
+ const topologyLayer = memoryTopologyStore;
1522
+ const defaultServices = Layer3.mergeAll(
1523
+ memoryAccountDirectory(this.application.accounts ?? []),
1524
+ topologyLayer,
1525
+ memoryEventStore
1526
+ );
1527
+ const serviceBase = this.application.services ?? defaultServices;
1528
+ const resolver = grantResolverLive.pipe(Layer3.provide(serviceBase));
1529
+ this.services = EffectManagedRuntime.make(
1530
+ Layer3.merge(serviceBase, resolver)
1531
+ );
1532
+ }
1533
+ static async discover(options) {
1534
+ const agents = await discoverAgents({
1535
+ agentsDir: options.agentsDir,
1536
+ strictFileRoutes: options.config?.strictFileRoutes
1537
+ });
1538
+ const registry = await discoverFoundryRegistry({
1539
+ rootDir: options.rootDir,
1540
+ capabilities: false,
1541
+ native: false,
1542
+ strictFileRoutes: options.config?.strictFileRoutes
1543
+ });
1544
+ return new _FoundryRuntime({
1545
+ rootDir: options.rootDir,
1546
+ agents,
1547
+ application: options.application,
1548
+ applicationFilePath: options.applicationFilePath,
1549
+ registry,
1550
+ config: options.config,
1551
+ observability: options.observability
1552
+ });
1553
+ }
1554
+ startEffect() {
1555
+ return promiseEffect("runtime.start", async () => {
1556
+ if (this.started) throw new Error("Foundry runtime is already started.");
1557
+ if (this.disposed) {
1558
+ throw new Error("Foundry runtime has been stopped and cannot be restarted.");
1559
+ }
1560
+ if (this.agents.length === 0) {
1561
+ throw new Error("Foundry did not discover any agents.");
1562
+ }
1563
+ this.started = true;
1564
+ try {
1565
+ await this.seedTopology();
1566
+ for (const instance of await this.listAgentInstances()) {
1567
+ if (!this.byRoute.has(instance.definitionId)) {
1568
+ throw new Error(
1569
+ `Agent instance "${instance.id}" references unknown definition "${instance.definitionId}".`
1570
+ );
1571
+ }
1572
+ for (const installation of instance.installations) {
1573
+ this.assertRegisteredInstallation(instance.definitionId, installation);
1574
+ }
1575
+ this.validatePlaybooks(
1576
+ instance.definitionId,
1577
+ instance.playbooks,
1578
+ instance.installations
1579
+ );
1580
+ }
1581
+ for (const subscription of await this.listPlaybookSubscriptions()) {
1582
+ await this.validatePlaybookSubscription(subscription);
1583
+ }
1584
+ await this.signalRunner.initialize();
1585
+ await this.reconstructActivations();
1586
+ this.runInBackground("signal", this.signalRunner.start());
1587
+ await this.reconcileApplicationConnections();
1588
+ } catch (cause) {
1589
+ try {
1590
+ await this.stop();
1591
+ } catch {
1592
+ }
1593
+ throw cause;
1594
+ }
1595
+ });
1596
+ }
1597
+ start() {
1598
+ return Effect5.runPromise(this.startEffect());
1599
+ }
1600
+ stopEffect() {
1601
+ return promiseEffect("runtime.stop", async () => {
1602
+ if (this.disposed) return;
1603
+ if (!this.started) {
1604
+ await this.services.dispose();
1605
+ this.disposed = true;
1606
+ return;
1607
+ }
1608
+ await this.connectionSupervisor.stopAll();
1609
+ this.observability.append({ type: "runtime.stop.signals", category: "system", data: {} });
1610
+ await this.signalRunner.stop({ graceful: true, timeoutMs: 1e4 });
1611
+ this.observability.append({ type: "runtime.stop.loops", category: "system", data: {} });
1612
+ await this.settleRunnerLoops(1e3);
1613
+ this.runnerLoops.length = 0;
1614
+ await this.envStore.close();
1615
+ await this.scheduleAdapter.close?.();
1616
+ await this.services.dispose();
1617
+ this.started = false;
1618
+ this.disposed = true;
1619
+ this.observability.append({ type: "runtime.stopped", category: "system", data: {} });
1620
+ });
1621
+ }
1622
+ stop() {
1623
+ return Effect5.runPromise(this.stopEffect());
1624
+ }
1625
+ requestEffect(route, request) {
1626
+ return promiseEffect("agent.request", async () => {
1627
+ if (!this.started) throw new Error("Foundry runtime is not started.");
1628
+ const discovered = this.byRoute.get(route);
1629
+ if (!discovered) throw new Error(`Foundry agent "${route}" was not found.`);
1630
+ const agent = await Effect5.runPromise(this.data.getAgent(request.agentId));
1631
+ if (!agent) throw new Error(`Foundry agent instance "${request.agentId}" was not found.`);
1632
+ if (agent.definitionId !== route) {
1633
+ throw new Error(`Agent instance "${request.agentId}" uses definition "${agent.definitionId}", not "${route}".`);
1634
+ }
1635
+ const conversation = await Effect5.runPromise(this.data.getConversation(request.conversationId));
1636
+ if (!conversation || conversation.agentId !== agent.id) {
1637
+ throw new Error(`Conversation "${request.conversationId}" does not belong to agent instance "${agent.id}".`);
1638
+ }
1639
+ if (request.workspaceId !== agent.workspaceId || request.workspaceId !== conversation.workspaceId) {
1640
+ throw new Error("The request, agent instance, and conversation must share a workspace.");
1641
+ }
1642
+ const runId = await this.signalRunner.triggerSignal(
1643
+ discovered.executionName,
1644
+ await this.executionEnvelope(request)
1645
+ );
1646
+ const run = await this.signalRunner.getRun(runId);
1647
+ if (!run) throw new Error(`Foundry failed to create run "${runId}".`);
1648
+ return this.toFoundryRun(run);
1649
+ }).pipe(
1650
+ Effect5.withSpan("foundry.agent.request", {
1651
+ attributes: { "foundry.agent.id": route }
1652
+ })
1653
+ );
1654
+ }
1655
+ request(route, request) {
1656
+ return Effect5.runPromise(this.requestEffect(route, request));
1657
+ }
1658
+ async createAgent(definitionId, options = {}) {
1659
+ if (!this.byRoute.has(definitionId)) {
1660
+ throw new Error(`Foundry agent definition "${definitionId}" was not found.`);
1661
+ }
1662
+ const agent = createAgentInstance(definitionId, options);
1663
+ for (const installation of agent.installations) {
1664
+ this.assertRegisteredInstallation(definitionId, installation);
1665
+ }
1666
+ this.validatePlaybooks(definitionId, agent.playbooks, agent.installations);
1667
+ if (await Effect5.runPromise(this.data.getAgent(agent.id))) {
1668
+ throw new Error(`Foundry agent instance "${agent.id}" already exists.`);
1669
+ }
1670
+ await Effect5.runPromise(this.data.putAgent(agent));
1671
+ this.observability.append({ type: "agent.instance.created", category: "agent", agent: definitionId, data: agent });
1672
+ if (this.started) await this.reconcileApplicationConnections();
1673
+ return agent;
1674
+ }
1675
+ async createConversation(agentId, options = {}) {
1676
+ const agent = await Effect5.runPromise(this.data.getAgent(agentId));
1677
+ if (!agent) throw new Error(`Foundry agent instance "${agentId}" was not found.`);
1678
+ const conversation = createConversation(agent, options);
1679
+ if (await Effect5.runPromise(this.data.getConversation(conversation.id))) {
1680
+ throw new Error(`Foundry conversation "${conversation.id}" already exists.`);
1681
+ }
1682
+ await Effect5.runPromise(this.data.putConversation(conversation));
1683
+ this.observability.append({ type: "conversation.created", category: "agent", agent: agent.definitionId, data: conversation });
1684
+ return conversation;
1685
+ }
1686
+ listAgentInstances(definitionId) {
1687
+ return Effect5.runPromise(this.data.listAgents(definitionId));
1688
+ }
1689
+ async setAgentPlaybooks(agentId, playbooks) {
1690
+ const agent = await this.configureAgent(agentId, { playbooks });
1691
+ this.observability.append({
1692
+ type: "agent.playbooks.updated",
1693
+ category: "agent",
1694
+ agent: agent.definitionId,
1695
+ data: { agentId, playbookIds: agent.playbooks.map((playbook) => playbook.id) }
1696
+ });
1697
+ return agent;
1698
+ }
1699
+ listPlaybookSubscriptions(workspaceId) {
1700
+ return Effect5.runPromise(this.data.listPlaybookSubscriptions(workspaceId));
1701
+ }
1702
+ /** List persisted future activations created by schedules or sleeping runs. */
1703
+ listActivations(workspaceId) {
1704
+ return Effect5.runPromise(this.data.listActivations(workspaceId));
1705
+ }
1706
+ async putPlaybookSubscription(input) {
1707
+ const firstTarget = input.targets[0];
1708
+ const subscription = firstTarget && "definitionId" in firstTarget ? reconstructPlaybookSubscription(input) : definePlaybookSubscription(input);
1709
+ await this.validatePlaybookSubscription(subscription);
1710
+ await Effect5.runPromise(this.data.putPlaybookSubscription(subscription));
1711
+ this.observability.append({
1712
+ type: "playbook.subscription.updated",
1713
+ category: "application",
1714
+ data: {
1715
+ subscriptionId: subscription.id,
1716
+ playbookId: subscription.playbook.id,
1717
+ targetDefinitions: subscription.targets.map((target) => target.definitionId)
1718
+ }
1719
+ });
1720
+ if (this.started) await this.reconcileApplicationConnections();
1721
+ return subscription;
1722
+ }
1723
+ async deletePlaybookSubscription(id2) {
1724
+ const removed = await Effect5.runPromise(this.data.deletePlaybookSubscription(id2));
1725
+ if (removed) {
1726
+ this.observability.append({
1727
+ type: "playbook.subscription.deleted",
1728
+ category: "application",
1729
+ data: { subscriptionId: id2 }
1730
+ });
1731
+ if (this.started) await this.reconcileApplicationConnections();
1732
+ }
1733
+ return removed;
1734
+ }
1735
+ listApplicationConnections() {
1736
+ return this.connectionSupervisor.list();
1737
+ }
1738
+ reconnectApplicationConnection(id2) {
1739
+ return this.connectionSupervisor.reconnect(id2);
1740
+ }
1741
+ /** Atomically replace the persisted, frontend-editable instance configuration. */
1742
+ async configureAgent(agentId, options) {
1743
+ const current = await Effect5.runPromise(this.data.getAgent(agentId));
1744
+ if (!current) throw new Error(`Foundry agent instance "${agentId}" was not found.`);
1745
+ const installations = options.installations ? normalizeAgentInstallations(options.installations) : current.installations;
1746
+ for (const installation of installations) {
1747
+ this.assertRegisteredInstallation(current.definitionId, installation);
1748
+ }
1749
+ const playbooks = options.playbooks ? Object.freeze(options.playbooks.map(reconstructPlaybook)) : current.playbooks;
1750
+ this.validatePlaybooks(current.definitionId, playbooks, installations);
1751
+ const agent = Object.freeze({
1752
+ ...current,
1753
+ ...options.context ? { context: Object.freeze(structuredClone(options.context)) } : {},
1754
+ installations,
1755
+ playbooks,
1756
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1757
+ });
1758
+ await Effect5.runPromise(this.data.putAgent(agent));
1759
+ this.observability.append({
1760
+ type: "agent.instance.configured",
1761
+ category: "agent",
1762
+ agent: current.definitionId,
1763
+ data: {
1764
+ agentId,
1765
+ installations: installations.map(installationKey),
1766
+ playbookIds: playbooks.map((playbook) => playbook.id)
1767
+ }
1768
+ });
1769
+ if (this.started) await this.reconcileApplicationConnections();
1770
+ return agent;
1771
+ }
1772
+ listConversations(agentId) {
1773
+ return Effect5.runPromise(this.data.listConversations(agentId));
1774
+ }
1775
+ listWorkspaceEntries(workspaceId) {
1776
+ return Effect5.runPromise(this.data.listWorkspaceEntries(workspaceId));
1777
+ }
1778
+ async putWorkspaceEntry(workspaceId, key, value) {
1779
+ const entry = { workspaceId, key, value, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1780
+ await Effect5.runPromise(this.data.putWorkspaceEntry(entry));
1781
+ return entry;
1782
+ }
1783
+ listSharedInbox(workspaceId) {
1784
+ return Effect5.runPromise(this.data.listInboxItems(workspaceId));
1785
+ }
1786
+ async postSharedInbox(input) {
1787
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1788
+ const item = { ...input, id: id("inbox"), createdAt: now, updatedAt: now };
1789
+ await Effect5.runPromise(this.data.putInboxItem(item));
1790
+ return item;
1791
+ }
1792
+ async updateSharedInbox(workspaceId, itemId, status) {
1793
+ const current = (await this.listSharedInbox(workspaceId)).find((item2) => item2.id === itemId);
1794
+ if (!current) throw new Error(`Shared inbox item "${itemId}" was not found in workspace "${workspaceId}".`);
1795
+ const item = { ...current, status, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1796
+ await Effect5.runPromise(this.data.putInboxItem(item));
1797
+ return item;
1798
+ }
1799
+ listTasks(workspaceId) {
1800
+ return Effect5.runPromise(this.data.listTasks(workspaceId));
1801
+ }
1802
+ async createTask(input) {
1803
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1804
+ const task = { ...input, id: id("task"), createdAt: now, updatedAt: now };
1805
+ await Effect5.runPromise(this.data.putTask(task));
1806
+ return task;
1807
+ }
1808
+ async updateTask(workspaceId, taskId, status) {
1809
+ const current = (await this.listTasks(workspaceId)).find((task2) => task2.id === taskId);
1810
+ if (!current) throw new Error(`Task "${taskId}" was not found in workspace "${workspaceId}".`);
1811
+ const task = { ...current, status, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1812
+ await Effect5.runPromise(this.data.putTask(task));
1813
+ return task;
1814
+ }
1815
+ listDataEnvironment(scope) {
1816
+ return Effect5.runPromise(this.data.listEnvironment(scope));
1817
+ }
1818
+ async send(agentId, conversationId, message, options = {}) {
1819
+ const agent = await Effect5.runPromise(this.data.getAgent(agentId));
1820
+ if (!agent) throw new Error(`Foundry agent instance "${agentId}" was not found.`);
1821
+ return this.request(agent.definitionId, {
1822
+ agentId,
1823
+ conversationId,
1824
+ workspaceId: agent.workspaceId,
1825
+ message,
1826
+ ...options.payload !== void 0 ? { payload: options.payload } : {},
1827
+ ...options.context ? { context: options.context } : {},
1828
+ source: { kind: "direct" }
1829
+ });
1830
+ }
1831
+ async getRun(runId) {
1832
+ const run = await this.signalRunner.getRun(runId);
1833
+ return run ? this.toFoundryRun(run) : null;
1834
+ }
1835
+ async listRuns(route) {
1836
+ const signalName = route ? this.byRoute.get(route)?.executionName : void 0;
1837
+ if (route && !signalName) return [];
1838
+ const runs = await this.signalRunner.listAllRuns({ signalName });
1839
+ return runs.map((run) => this.toFoundryRun(run));
1840
+ }
1841
+ async waitForRun(runId, options) {
1842
+ const run = await this.signalRunner.waitForRun(runId, options);
1843
+ return run ? this.toFoundryRun(run) : null;
1844
+ }
1845
+ cancel(runId) {
1846
+ return this.signalRunner.cancel(runId);
1847
+ }
1848
+ capabilityManifest(definitionId) {
1849
+ if (!this.compositionByDefinition.has(definitionId)) {
1850
+ throw new Error(`Foundry agent definition "${definitionId}" was not found.`);
1851
+ }
1852
+ const capabilities = this.compositionByDefinition.get(definitionId).capabilities;
1853
+ const fileByKey = new Map(
1854
+ this.registry.files.map((file) => [`${file.kind}:${file.id}`, file])
1855
+ );
1856
+ const map = (kind, values, ownership) => values.map((value) => ({
1857
+ id: value.id,
1858
+ kind,
1859
+ description: value.description ?? "",
1860
+ ownership,
1861
+ file: fileByKey.get(`${kind}:${value.id}`)?.relativePath
1862
+ }));
1863
+ return {
1864
+ tools: map("tool", capabilities.tools, "instance"),
1865
+ applications: map(
1866
+ "application",
1867
+ capabilities.applications,
1868
+ "instance"
1869
+ ),
1870
+ mcp: map("mcp", capabilities.mcp, "instance"),
1871
+ memory: map("memory", capabilities.memory, "definition")
1872
+ };
1873
+ }
1874
+ nativeManifest(definitionId) {
1875
+ if (!this.compositionByDefinition.has(definitionId)) {
1876
+ throw new Error(`Foundry agent definition "${definitionId}" was not found.`);
1877
+ }
1878
+ const native = this.compositionByDefinition.get(definitionId).native;
1879
+ const fileByKey = new Map(
1880
+ this.registry.nativeFiles.map((file) => [`${file.kind}:${file.id}`, file])
1881
+ );
1882
+ const map = (kind, values) => values.map((value) => ({
1883
+ id: value.id,
1884
+ kind,
1885
+ description: value.description,
1886
+ file: fileByKey.get(`${kind}:${value.id}`)?.relativePath
1887
+ }));
1888
+ return {
1889
+ layers: map("layer", native.layers),
1890
+ subscribers: map("subscriber", native.subscribers)
1891
+ };
1892
+ }
1893
+ async listInstallations(agentId) {
1894
+ const agent = await Effect5.runPromise(this.data.getAgent(agentId));
1895
+ if (!agent) {
1896
+ throw new Error(`Foundry agent instance "${agentId}" was not found.`);
1897
+ }
1898
+ return agent.installations;
1899
+ }
1900
+ async installCapability(agentId, installation) {
1901
+ const agent = await Effect5.runPromise(this.data.getAgent(agentId));
1902
+ if (!agent) {
1903
+ throw new Error(`Foundry agent instance "${agentId}" was not found.`);
1904
+ }
1905
+ this.assertRegisteredInstallation(agent.definitionId, installation);
1906
+ const installations = normalizeAgentInstallations([
1907
+ ...agent.installations,
1908
+ installation
1909
+ ]);
1910
+ const updated = Object.freeze({
1911
+ ...agent,
1912
+ installations,
1913
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1914
+ });
1915
+ await Effect5.runPromise(this.data.putAgent(updated));
1916
+ this.observability.append({
1917
+ type: "installation.installed",
1918
+ category: "application",
1919
+ agent: agentId,
1920
+ data: installation
1921
+ });
1922
+ if (this.started) await this.reconcileApplicationConnections();
1923
+ return updated;
1924
+ }
1925
+ async uninstallCapability(agentId, installation) {
1926
+ const agent = await Effect5.runPromise(this.data.getAgent(agentId));
1927
+ if (!agent) throw new Error(`Foundry agent instance "${agentId}" was not found.`);
1928
+ const installations = normalizeAgentInstallations(
1929
+ agent.installations.filter(
1930
+ (candidate) => installationKey(candidate) !== installationKey(installation)
1931
+ )
1932
+ );
1933
+ this.validatePlaybooks(agent.definitionId, agent.playbooks, installations);
1934
+ const updated = Object.freeze({
1935
+ ...agent,
1936
+ installations,
1937
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1938
+ });
1939
+ await Effect5.runPromise(this.data.putAgent(updated));
1940
+ this.observability.append({
1941
+ type: "installation.uninstalled",
1942
+ category: "application",
1943
+ agent: agentId,
1944
+ data: installation
1945
+ });
1946
+ if (this.started) await this.reconcileApplicationConnections();
1947
+ return updated;
1948
+ }
1949
+ listAccounts() {
1950
+ return this.services.runPromise(
1951
+ Effect5.gen(function* () {
1952
+ const accounts = yield* (yield* AccountDirectory).list();
1953
+ return accounts.map(
1954
+ ({ accessRef: _accessRef, ...account }) => account
1955
+ );
1956
+ })
1957
+ );
1958
+ }
1959
+ listRoutes() {
1960
+ return this.services.runPromise(
1961
+ Effect5.gen(function* () {
1962
+ return yield* (yield* TopologyStore).listRoutes();
1963
+ })
1964
+ );
1965
+ }
1966
+ async putRoute(route) {
1967
+ await this.validateRoute(route);
1968
+ const stored = await this.services.runPromise(
1969
+ Effect5.gen(function* () {
1970
+ return yield* (yield* TopologyStore).putRoute(route);
1971
+ })
1972
+ );
1973
+ this.topologyRoutes.set(stored.id, stored);
1974
+ return stored;
1975
+ }
1976
+ async removeRoute(id2) {
1977
+ await this.services.runPromise(
1978
+ Effect5.gen(function* () {
1979
+ return yield* (yield* TopologyStore).removeRoute(id2);
1980
+ })
1981
+ );
1982
+ this.topologyRoutes.delete(id2);
1983
+ }
1984
+ listBindings() {
1985
+ return this.services.runPromise(
1986
+ Effect5.gen(function* () {
1987
+ return yield* (yield* TopologyStore).listBindings();
1988
+ })
1989
+ );
1990
+ }
1991
+ async putBinding(binding) {
1992
+ await this.validateBinding(binding);
1993
+ return this.services.runPromise(
1994
+ Effect5.gen(function* () {
1995
+ return yield* (yield* TopologyStore).putBinding(binding);
1996
+ })
1997
+ );
1998
+ }
1999
+ removeBinding(id2) {
2000
+ return this.services.runPromise(
2001
+ Effect5.gen(function* () {
2002
+ return yield* (yield* TopologyStore).removeBinding(id2);
2003
+ })
2004
+ );
2005
+ }
2006
+ resolveGrant(request) {
2007
+ return this.services.runPromise(
2008
+ Effect5.gen(function* () {
2009
+ return yield* (yield* GrantResolver).resolve(request);
2010
+ })
2011
+ );
2012
+ }
2013
+ putEvent(reference, payload) {
2014
+ return this.services.runPromise(
2015
+ Effect5.gen(function* () {
2016
+ return yield* (yield* EventStore).put(reference, payload);
2017
+ })
2018
+ );
2019
+ }
2020
+ async dispatchInbound(input) {
2021
+ const route = (await this.listRoutes()).find((item) => item.id === input.routeId);
2022
+ if (!route || route.direction !== "inbound" || !route.enabled) {
2023
+ throw new Error(`Inbound route "${input.routeId}" is missing or disabled.`);
2024
+ }
2025
+ const claim = `${route.id}:${input.eventId}`;
2026
+ const prior = await Effect5.runPromise(this.data.getInboundDelivery(claim));
2027
+ if (prior?.status === "completed") {
2028
+ return this.runsFromIds(prior.runIds);
2029
+ }
2030
+ const acquired = await Effect5.runPromise(this.data.claimInboundDelivery(claim));
2031
+ if (!acquired) return this.waitForInboundDelivery(claim);
2032
+ try {
2033
+ const integration = this.transmissionById.get(route.transmissionId);
2034
+ if (!integration?.inbound) throw new Error(`Transmission "${route.transmissionId}" has no inbound contract.`);
2035
+ const account = route.accountId ? await this.services.runPromise(Effect5.gen(function* () {
2036
+ return yield* (yield* AccountDirectory).get(route.accountId);
2037
+ })) : void 0;
2038
+ const ingressContext = { route, ...account ? { account } : {} };
2039
+ if (integration.inbound.adapter) {
2040
+ const authenticated = await Effect5.runPromise(integration.inbound.adapter.authenticate(input.raw, ingressContext));
2041
+ if (!authenticated) throw new Error(`Inbound event for route "${route.id}" was not authenticated.`);
2042
+ }
2043
+ const event = integration.inbound.adapter ? await Effect5.runPromise(integration.inbound.adapter.normalize(input.raw, ingressContext)) : await Schema3.decodeUnknownPromise(integration.inbound.event)(input.raw);
2044
+ const inferredEventName = event && typeof event === "object" ? ["type", "kind", "event"].map((key) => event[key]).find((value) => typeof value === "string" && value.length > 0) : void 0;
2045
+ const classifiedEvent = integration.inbound.classify ? await Effect5.runPromise(integration.inbound.classify(event, ingressContext)) : void 0;
2046
+ if (classifiedEvent && !integration.events?.some((candidate) => candidate.id === classifiedEvent.id)) {
2047
+ throw new Error(
2048
+ `Transmission "${integration.id}" classified an event it does not declare. Add the imported event definition to its events array.`
2049
+ );
2050
+ }
2051
+ const eventName = classifiedEvent?.id ?? inferredEventName ?? "event";
2052
+ const instances = await this.listAgentInstances();
2053
+ const runs = [];
2054
+ const deliveries = [];
2055
+ const matchedByAgent = /* @__PURE__ */ new Map();
2056
+ for (const agent of instances) {
2057
+ const installedApplications = new Set(
2058
+ agent.installations.filter((installation) => installation.kind === "application").map((installation) => installation.id)
2059
+ );
2060
+ const hasInstalledOwner = (this.compositionByDefinition.get(agent.definitionId)?.capabilities.applications ?? []).some(
2061
+ (application) => installedApplications.has(application.id) && application.transmissions?.some(
2062
+ (candidate) => candidate.id === integration.id && candidate.inbound
2063
+ )
2064
+ );
2065
+ if (!hasInstalledOwner) continue;
2066
+ const matched = [];
2067
+ for (const playbook of agent.playbooks) {
2068
+ if (await this.playbookMatches(
2069
+ playbook,
2070
+ integration,
2071
+ route,
2072
+ eventName,
2073
+ event,
2074
+ ingressContext
2075
+ )) matched.push(playbook);
2076
+ }
2077
+ if (matched.length === 0) continue;
2078
+ matchedByAgent.set(agent.id, { agent, playbooks: matched });
2079
+ }
2080
+ for (const subscription of await this.listPlaybookSubscriptions()) {
2081
+ if (!subscription.enabled || subscription.workspaceId.length === 0) continue;
2082
+ if (!await this.playbookMatches(
2083
+ subscription.playbook,
2084
+ integration,
2085
+ route,
2086
+ eventName,
2087
+ event,
2088
+ ingressContext
2089
+ )) continue;
2090
+ for (const [targetIndex, target] of subscription.targets.entries()) {
2091
+ const resolved = await this.resolveSubscriptionTarget(
2092
+ subscription,
2093
+ target,
2094
+ targetIndex,
2095
+ {
2096
+ route,
2097
+ eventId: input.eventId,
2098
+ eventName,
2099
+ threadKey: input.threadKey,
2100
+ event
2101
+ }
2102
+ );
2103
+ for (const agent of resolved) {
2104
+ const prior2 = matchedByAgent.get(agent.id);
2105
+ if (prior2) {
2106
+ if (!prior2.playbooks.some((playbook) => playbook.id === subscription.playbook.id)) {
2107
+ prior2.playbooks.push(subscription.playbook);
2108
+ }
2109
+ } else {
2110
+ matchedByAgent.set(agent.id, {
2111
+ agent,
2112
+ playbooks: [subscription.playbook]
2113
+ });
2114
+ }
2115
+ }
2116
+ }
2117
+ }
2118
+ for (const { agent, playbooks: matched } of matchedByAgent.values()) {
2119
+ const conversationId = `transmission:${route.id}:${agent.id}:${input.threadKey}`;
2120
+ let conversation = await Effect5.runPromise(this.data.getConversation(conversationId));
2121
+ if (!conversation) {
2122
+ conversation = await this.createConversation(agent.id, {
2123
+ id: conversationId,
2124
+ context: { routeId: route.id, threadKey: input.threadKey }
2125
+ });
2126
+ }
2127
+ const serializationContext = {
2128
+ ...ingressContext,
2129
+ eventId: input.eventId,
2130
+ eventName,
2131
+ threadKey: input.threadKey,
2132
+ playbooks: matched
2133
+ };
2134
+ const eventMessage = integration.inbound.serialize ? await Effect5.runPromise(integration.inbound.serialize(event, serializationContext)) : serializeInboundTransmissionXml({
2135
+ transmissionId: integration.id,
2136
+ routeId: route.id,
2137
+ eventId: input.eventId,
2138
+ eventName,
2139
+ threadKey: input.threadKey,
2140
+ event,
2141
+ playbooks: matched
2142
+ });
2143
+ const run = await this.request(agent.definitionId, {
2144
+ agentId: agent.id,
2145
+ conversationId: conversation.id,
2146
+ workspaceId: agent.workspaceId,
2147
+ message: eventMessage,
2148
+ payload: event,
2149
+ context: {
2150
+ playbookIds: matched.map((playbook) => playbook.id),
2151
+ outbound: matched.flatMap((playbook) => playbook.outbound ?? [])
2152
+ },
2153
+ source: {
2154
+ kind: "transmission",
2155
+ id: route.id,
2156
+ provider: route.transmissionId,
2157
+ eventId: input.eventId,
2158
+ threadKey: input.threadKey
2159
+ }
2160
+ });
2161
+ runs.push(run);
2162
+ deliveries.push({
2163
+ agentId: agent.id,
2164
+ playbookIds: matched.map((playbook) => playbook.id),
2165
+ runId: run.id
2166
+ });
2167
+ }
2168
+ await Effect5.runPromise(
2169
+ this.data.completeInboundDelivery(claim, runs.map((run) => run.id))
2170
+ );
2171
+ this.observability.append({
2172
+ type: "transmission.dispatched",
2173
+ category: "application",
2174
+ data: { routeId: route.id, eventId: input.eventId, eventName, deliveries }
2175
+ });
2176
+ return runs;
2177
+ } catch (cause) {
2178
+ await Effect5.runPromise(this.data.releaseInboundDelivery(claim));
2179
+ throw cause;
2180
+ }
2181
+ }
2182
+ async dispatchOutbound(input) {
2183
+ const route = (await this.listRoutes()).find((item) => item.id === input.routeId);
2184
+ if (!route || route.direction !== "outbound" || !route.enabled) {
2185
+ throw new Error(`Outbound route "${input.routeId}" is missing or disabled.`);
2186
+ }
2187
+ if (input.transmissionId && route.transmissionId !== input.transmissionId) {
2188
+ throw new Error(
2189
+ `Outbound route "${route.id}" does not belong to transmission "${input.transmissionId}".`
2190
+ );
2191
+ }
2192
+ if (input.applicationId) {
2193
+ const agent = await Effect5.runPromise(this.data.getAgent(input.agentId));
2194
+ const application = agent ? this.compositionByDefinition.get(agent.definitionId)?.capabilities.applications.find(
2195
+ (candidate) => candidate.id === input.applicationId
2196
+ ) : void 0;
2197
+ if (!application || !agent?.installations.some(
2198
+ (installation) => installation.kind === "application" && installation.id === input.applicationId
2199
+ ) || !application.transmissions?.some(
2200
+ (candidate) => candidate.id === route.transmissionId
2201
+ )) {
2202
+ throw new Error(
2203
+ `Application "${input.applicationId}" does not own outbound transmission "${route.transmissionId}" for agent "${input.agentId}".`
2204
+ );
2205
+ }
2206
+ }
2207
+ const grant = await this.resolveGrant({
2208
+ runId: input.runId,
2209
+ agentId: input.agentId
2210
+ });
2211
+ if (!grant.outboundRouteIds.includes(route.id)) {
2212
+ throw new Error(`Run "${input.runId}" is not authorized for outbound route "${route.id}".`);
2213
+ }
2214
+ const transmission = this.transmissionById.get(route.transmissionId);
2215
+ if (!transmission?.outbound?.adapter) {
2216
+ throw new Error(`Transmission "${route.transmissionId}" has no outbound delivery adapter.`);
2217
+ }
2218
+ const account = route.accountId ? await this.services.runPromise(Effect5.gen(function* () {
2219
+ return yield* (yield* AccountDirectory).get(route.accountId);
2220
+ })) : void 0;
2221
+ const payload = await Schema3.decodeUnknownPromise(transmission.outbound.input)(input.payload);
2222
+ const output = await Effect5.runPromise(transmission.outbound.adapter.deliver(payload, {
2223
+ route,
2224
+ ...account ? { account } : {},
2225
+ grant
2226
+ }));
2227
+ const validated = await Schema3.decodeUnknownPromise(transmission.outbound.output)(output);
2228
+ this.observability.append({
2229
+ type: "transmission.outbound.delivered",
2230
+ category: "application",
2231
+ runId: input.runId,
2232
+ data: {
2233
+ ...input.commandId ? { commandId: input.commandId } : {},
2234
+ ...input.applicationId ? { applicationId: input.applicationId } : {},
2235
+ ...input.transmissionId ? { transmissionId: input.transmissionId } : {},
2236
+ routeId: route.id,
2237
+ output: validated
2238
+ }
2239
+ });
2240
+ return validated;
2241
+ }
2242
+ async health() {
2243
+ const [execution, environment, activations] = await Promise.all([
2244
+ this.signalRunner.getAdapter().ping(),
2245
+ this.envStore.ping(),
2246
+ this.scheduleAdapter.ping()
2247
+ ]);
2248
+ return {
2249
+ ok: execution && environment && activations,
2250
+ execution,
2251
+ environment,
2252
+ activations,
2253
+ agents: this.agents.length,
2254
+ capabilities: this.registry.files.length,
2255
+ surfaces: this.registry.nativeFiles.length
2256
+ };
2257
+ }
2258
+ async runsFromIds(runIds) {
2259
+ return (await Promise.all(runIds.map((id2) => this.getRun(id2)))).filter(
2260
+ (run) => run !== null
2261
+ );
2262
+ }
2263
+ async waitForInboundDelivery(key) {
2264
+ const deadline = Date.now() + 3e4;
2265
+ while (Date.now() < deadline) {
2266
+ const claim = await Effect5.runPromise(this.data.getInboundDelivery(key));
2267
+ if (!claim) {
2268
+ throw new Error(`Inbound delivery "${key}" was released before completion.`);
2269
+ }
2270
+ if (claim.status === "completed") return this.runsFromIds(claim.runIds);
2271
+ await new Promise((resolveWait) => setTimeout(resolveWait, 25));
2272
+ }
2273
+ throw new Error(`Timed out waiting for inbound delivery "${key}".`);
2274
+ }
2275
+ async validatePlaybookSubscription(subscription) {
2276
+ for (const target of subscription.targets) {
2277
+ if (!this.byRoute.has(target.definitionId)) {
2278
+ throw new Error(
2279
+ `Playbook subscription "${subscription.id}" targets unknown agent definition "${target.definitionId}".`
2280
+ );
2281
+ }
2282
+ for (const installation of target.installations) {
2283
+ this.assertRegisteredInstallation(target.definitionId, installation);
2284
+ }
2285
+ if (target.provisioning.mode === "existing") {
2286
+ for (const agentId of target.provisioning.agentIds) {
2287
+ const agent = await Effect5.runPromise(this.data.getAgent(agentId));
2288
+ if (!agent || agent.definitionId !== target.definitionId) {
2289
+ throw new Error(
2290
+ `Playbook subscription "${subscription.id}" references missing or incompatible agent instance "${agentId}".`
2291
+ );
2292
+ }
2293
+ this.validatePlaybooks(
2294
+ agent.definitionId,
2295
+ [subscription.playbook],
2296
+ agent.installations
2297
+ );
2298
+ }
2299
+ } else {
2300
+ this.validatePlaybooks(
2301
+ target.definitionId,
2302
+ [subscription.playbook],
2303
+ target.installations
2304
+ );
2305
+ }
2306
+ }
2307
+ }
2308
+ async playbookMatches(playbook, transmission, route, eventName, event, ingressContext) {
2309
+ if (playbook.enabled === false || playbook.transmissionId !== transmission.id) return false;
2310
+ if (playbook.match?.routeIds && !playbook.match.routeIds.includes(route.id)) return false;
2311
+ if (playbook.match?.event && playbook.match.event !== eventName) return false;
2312
+ const predicateRef = playbook.match?.predicate;
2313
+ if (!predicateRef) return true;
2314
+ const predicate = transmissionPredicate(transmission, predicateRef.name);
2315
+ if (!predicate) {
2316
+ throw new Error(
2317
+ `Playbook "${playbook.id}" references unknown predicate "${predicateRef.name}" on transmission "${transmission.id}".`
2318
+ );
2319
+ }
2320
+ return Effect5.runPromise(
2321
+ predicate.match(event, predicateRef.parameters ?? {}, ingressContext)
2322
+ );
2323
+ }
2324
+ async resolveSubscriptionTarget(subscription, target, targetIndex, input) {
2325
+ const policy = target.provisioning;
2326
+ if (policy.mode === "existing") {
2327
+ return Promise.all(policy.agentIds.map(async (agentId) => {
2328
+ const agent = await Effect5.runPromise(this.data.getAgent(agentId));
2329
+ if (!agent) throw new Error(`Subscribed agent instance "${agentId}" was not found.`);
2330
+ return this.attachSubscriptionPlaybook(agent, subscription, target);
2331
+ }));
2332
+ }
2333
+ const seeds = policy.mode === "custom" ? await Effect5.runPromise(
2334
+ this.application.provisioner?.provision(policy.adapter, {
2335
+ subscription,
2336
+ target,
2337
+ route: input.route,
2338
+ eventId: input.eventId,
2339
+ eventName: input.eventName,
2340
+ threadKey: input.threadKey,
2341
+ event: input.event
2342
+ }) ?? Effect5.fail(
2343
+ new Error(
2344
+ `Playbook subscription "${subscription.id}" requires custom provisioner "${policy.adapter}", but the Foundry application does not define one.`
2345
+ )
2346
+ )
2347
+ ) : [{
2348
+ provisioningKey: [
2349
+ "subscription",
2350
+ subscription.workspaceId,
2351
+ subscription.id,
2352
+ String(targetIndex),
2353
+ policy.mode,
2354
+ ...policy.mode === "per-thread" ? [input.route.id, input.threadKey] : policy.mode === "per-event" ? [input.route.id, input.eventId] : [policy.key ?? "default"]
2355
+ ].join(":")
2356
+ }];
2357
+ const agents = [];
2358
+ for (const seed of seeds) {
2359
+ const agent = await Effect5.runPromise(this.data.provisionAgent({
2360
+ definitionId: target.definitionId,
2361
+ provisioningKey: seed.provisioningKey,
2362
+ workspaceId: seed.workspaceId ?? subscription.workspaceId,
2363
+ context: seed.context ?? target.context,
2364
+ installations: seed.installations ?? target.installations,
2365
+ playbooks: seed.playbooks ?? [subscription.playbook],
2366
+ ...seed.id ? { id: seed.id } : {}
2367
+ }));
2368
+ const attached = await this.attachSubscriptionPlaybook(agent, subscription, target);
2369
+ agents.push(attached);
2370
+ this.observability.append({
2371
+ type: "playbook.subscription.agent-resolved",
2372
+ category: "agent",
2373
+ agent: target.definitionId,
2374
+ data: {
2375
+ subscriptionId: subscription.id,
2376
+ agentId: attached.id,
2377
+ provisioningKey: seed.provisioningKey
2378
+ }
2379
+ });
2380
+ }
2381
+ return agents;
2382
+ }
2383
+ async attachSubscriptionPlaybook(agent, subscription, target) {
2384
+ const playbooks = [
2385
+ ...agent.playbooks.filter((playbook) => playbook.id !== subscription.playbook.id),
2386
+ subscription.playbook
2387
+ ];
2388
+ const installations = normalizeAgentInstallations([
2389
+ ...agent.installations,
2390
+ ...target.installations
2391
+ ]);
2392
+ this.validatePlaybooks(agent.definitionId, playbooks, installations);
2393
+ const changed = JSON.stringify(agent.playbooks) !== JSON.stringify(playbooks) || JSON.stringify(agent.installations) !== JSON.stringify(installations);
2394
+ if (!changed) return agent;
2395
+ const updated = Object.freeze({
2396
+ ...agent,
2397
+ playbooks: Object.freeze(playbooks),
2398
+ installations,
2399
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2400
+ });
2401
+ await Effect5.runPromise(this.data.putAgent(updated));
2402
+ return updated;
2403
+ }
2404
+ async reconcileApplicationConnections() {
2405
+ const requirements = [];
2406
+ for (const subscription of await this.listPlaybookSubscriptions()) {
2407
+ if (!subscription.enabled || subscription.playbook.enabled === false) continue;
2408
+ for (const target of subscription.targets) {
2409
+ requirements.push({
2410
+ definitionId: target.definitionId,
2411
+ workspaceId: subscription.workspaceId,
2412
+ playbook: subscription.playbook,
2413
+ installedApplications: new Set(
2414
+ target.installations.filter((installation) => installation.kind === "application").map((installation) => installation.id)
2415
+ )
2416
+ });
2417
+ }
2418
+ }
2419
+ for (const agent of await this.listAgentInstances()) {
2420
+ const installedApplications = new Set(
2421
+ agent.installations.filter((installation) => installation.kind === "application").map((installation) => installation.id)
2422
+ );
2423
+ for (const playbook of agent.playbooks) {
2424
+ if (playbook.enabled === false) continue;
2425
+ requirements.push({
2426
+ definitionId: agent.definitionId,
2427
+ workspaceId: agent.workspaceId,
2428
+ playbook,
2429
+ installedApplications
2430
+ });
2431
+ }
2432
+ }
2433
+ const routes = (await this.listRoutes()).filter(
2434
+ (route) => route.direction === "inbound" && route.enabled
2435
+ );
2436
+ const grouped = /* @__PURE__ */ new Map();
2437
+ for (const requirement of requirements) {
2438
+ const composition = this.compositionByDefinition.get(requirement.definitionId);
2439
+ if (!composition) continue;
2440
+ for (const application of composition.capabilities.applications) {
2441
+ if (requirement.installedApplications && !requirement.installedApplications.has(application.id)) continue;
2442
+ if (!application.transmissions?.some(
2443
+ (transmission) => transmission.id === requirement.playbook.transmissionId
2444
+ )) continue;
2445
+ for (const connection of application.connections ?? []) {
2446
+ if (!connection.transmissions.some(
2447
+ (transmission) => transmission.id === requirement.playbook.transmissionId
2448
+ )) continue;
2449
+ const matchingRoutes = routes.filter(
2450
+ (route) => route.transmissionId === requirement.playbook.transmissionId && (!requirement.playbook.match?.routeIds || requirement.playbook.match.routeIds.includes(route.id))
2451
+ );
2452
+ for (const route of matchingRoutes) {
2453
+ const account = route.accountId ? this.application.accounts?.find((candidate) => candidate.id === route.accountId) : void 0;
2454
+ const id2 = [
2455
+ requirement.definitionId,
2456
+ requirement.workspaceId,
2457
+ application.id,
2458
+ connection.id,
2459
+ account?.id ?? "no-account"
2460
+ ].join(":");
2461
+ const prior = grouped.get(id2);
2462
+ if (prior) {
2463
+ if (!prior.routes.some((candidate) => candidate.id === route.id)) prior.routes.push(route);
2464
+ } else {
2465
+ grouped.set(id2, {
2466
+ id: id2,
2467
+ application,
2468
+ connection,
2469
+ definitionId: requirement.definitionId,
2470
+ workspaceId: requirement.workspaceId,
2471
+ ...account ? { account } : {},
2472
+ routes: [route],
2473
+ accountSessions: this.byRoute.get(requirement.definitionId)?.definition.accountSessions
2474
+ });
2475
+ }
2476
+ }
2477
+ }
2478
+ }
2479
+ }
2480
+ await this.connectionSupervisor.reconcile([...grouped.values()]);
2481
+ }
2482
+ assertRegisteredInstallation(definitionId, installation) {
2483
+ const registry = this.compositionByDefinition.get(definitionId)?.capabilities ?? EMPTY_CAPABILITY_REGISTRY;
2484
+ const values = installation.kind === "tool" ? registry.tools : installation.kind === "application" ? registry.applications : registry.mcp;
2485
+ if (!values.some((value) => value.id === installation.id)) {
2486
+ throw new Error(
2487
+ `Unknown Foundry ${installation.kind} capability "${installation.id}".`
2488
+ );
2489
+ }
2490
+ }
2491
+ validatePlaybooks(definitionId, playbooks, installations) {
2492
+ const routes = this.topologyRoutes;
2493
+ const applicationDefinitions = this.compositionByDefinition.get(definitionId)?.capabilities.applications ?? [];
2494
+ const applications = new Map(
2495
+ applicationDefinitions.map((application) => [application.id, application])
2496
+ );
2497
+ const installedApplications = new Set(
2498
+ installations.filter((installation) => installation.kind === "application").map((installation) => installation.id)
2499
+ );
2500
+ const localTransmissions = new Map(
2501
+ (this.compositionByDefinition.get(definitionId)?.capabilities.applications ?? []).flatMap(
2502
+ (application) => (application.transmissions ?? []).map((transmission) => [
2503
+ transmission.id,
2504
+ transmission
2505
+ ])
2506
+ )
2507
+ );
2508
+ for (const playbook of playbooks) {
2509
+ const transmission = localTransmissions.get(playbook.transmissionId);
2510
+ if (!transmission?.inbound) {
2511
+ throw new Error(
2512
+ `Playbook "${playbook.id}" references transmission "${playbook.transmissionId}" without an inbound definition.`
2513
+ );
2514
+ }
2515
+ const inboundOwners = applicationDefinitions.filter(
2516
+ (application) => application.transmissions?.some(
2517
+ (candidate) => candidate.id === playbook.transmissionId && candidate.inbound
2518
+ )
2519
+ );
2520
+ if (!inboundOwners.some((application) => installedApplications.has(application.id))) {
2521
+ throw new Error(
2522
+ `Playbook "${playbook.id}" requires an installed application that owns inbound transmission "${playbook.transmissionId}".`
2523
+ );
2524
+ }
2525
+ const predicate = playbook.match?.predicate?.name;
2526
+ if (predicate && !transmissionPredicate(transmission, predicate)) {
2527
+ throw new Error(
2528
+ `Playbook "${playbook.id}" references unknown predicate "${predicate}" on transmission "${playbook.transmissionId}".`
2529
+ );
2530
+ }
2531
+ for (const routeId of playbook.match?.routeIds ?? []) {
2532
+ const route = routes.get(routeId);
2533
+ if (!route || route.direction !== "inbound" || route.transmissionId !== playbook.transmissionId) {
2534
+ throw new Error(`Playbook "${playbook.id}" references invalid inbound route "${routeId}".`);
2535
+ }
2536
+ }
2537
+ for (const outbound of playbook.outbound ?? []) {
2538
+ const route = routes.get(outbound.routeId);
2539
+ if (!route || route.direction !== "outbound") {
2540
+ throw new Error(`Playbook "${playbook.id}" references invalid outbound route "${outbound.routeId}".`);
2541
+ }
2542
+ if (outbound.accountId && route.accountId !== outbound.accountId) {
2543
+ throw new Error(
2544
+ `Playbook "${playbook.id}" selects account "${outbound.accountId}" outside outbound route "${outbound.routeId}".`
2545
+ );
2546
+ }
2547
+ if (outbound.applicationId) {
2548
+ const application = applications.get(outbound.applicationId);
2549
+ if (!application) {
2550
+ throw new Error(
2551
+ `Playbook "${playbook.id}" references unknown application "${outbound.applicationId}".`
2552
+ );
2553
+ }
2554
+ if (!installedApplications.has(outbound.applicationId)) {
2555
+ throw new Error(
2556
+ `Playbook "${playbook.id}" references uninstalled application "${outbound.applicationId}".`
2557
+ );
2558
+ }
2559
+ if (!application.transmissions?.some(
2560
+ (candidate) => candidate.id === route.transmissionId && candidate.outbound
2561
+ )) {
2562
+ throw new Error(
2563
+ `Application "${outbound.applicationId}" does not own outbound route "${outbound.routeId}".`
2564
+ );
2565
+ }
2566
+ } else {
2567
+ const hasInstalledOwner = applicationDefinitions.some(
2568
+ (application) => installedApplications.has(application.id) && application.transmissions?.some(
2569
+ (candidate) => candidate.id === route.transmissionId && candidate.outbound
2570
+ )
2571
+ );
2572
+ if (!hasInstalledOwner) {
2573
+ throw new Error(
2574
+ `Playbook "${playbook.id}" outbound route "${outbound.routeId}" has no installed owning application.`
2575
+ );
2576
+ }
2577
+ }
2578
+ }
2579
+ for (const applicationId of playbook.applications ?? []) {
2580
+ if (!applications.has(applicationId)) {
2581
+ throw new Error(`Playbook "${playbook.id}" references unknown application "${applicationId}".`);
2582
+ }
2583
+ if (!installedApplications.has(applicationId)) {
2584
+ throw new Error(`Playbook "${playbook.id}" references uninstalled application "${applicationId}".`);
2585
+ }
2586
+ }
2587
+ }
2588
+ }
2589
+ async validateRoute(route) {
2590
+ const integration = this.transmissionById.get(route.transmissionId);
2591
+ if (!integration) {
2592
+ throw new Error(
2593
+ `Route "${route.id}" references unknown transmission "${route.transmissionId}".`
2594
+ );
2595
+ }
2596
+ const contract = route.direction === "inbound" ? integration.inbound : integration.outbound;
2597
+ if (!contract) {
2598
+ throw new Error(
2599
+ `Integration "${integration.id}" does not support ${route.direction} routes.`
2600
+ );
2601
+ }
2602
+ if (integration.account?.required && !route.accountId) {
2603
+ throw new Error(`Route "${route.id}" requires an account.`);
2604
+ }
2605
+ if (route.accountId) {
2606
+ const account = await this.services.runPromise(
2607
+ Effect5.gen(function* () {
2608
+ return yield* (yield* AccountDirectory).get(route.accountId);
2609
+ })
2610
+ );
2611
+ if (account.transmissionId !== route.transmissionId) {
2612
+ throw new Error(
2613
+ `Route "${route.id}" has an account outside transmission "${route.transmissionId}".`
2614
+ );
2615
+ }
2616
+ }
2617
+ await Schema3.decodeUnknownPromise(contract.config)(route.config);
2618
+ }
2619
+ async validateBinding(binding) {
2620
+ if (!await Effect5.runPromise(this.data.getAgent(binding.agentId))) {
2621
+ throw new Error(
2622
+ `Binding "${binding.id}" references unknown agent instance "${binding.agentId}".`
2623
+ );
2624
+ }
2625
+ const integration = this.transmissionById.get(binding.transmissionId);
2626
+ if (!integration) {
2627
+ throw new Error(
2628
+ `Binding "${binding.id}" references unknown transmission "${binding.transmissionId}".`
2629
+ );
2630
+ }
2631
+ if (integration.account?.required && !binding.accountId) {
2632
+ throw new Error(`Binding "${binding.id}" requires an account.`);
2633
+ }
2634
+ if (binding.accountId) {
2635
+ const account = await this.services.runPromise(
2636
+ Effect5.gen(function* () {
2637
+ return yield* (yield* AccountDirectory).get(binding.accountId);
2638
+ })
2639
+ );
2640
+ if (account.transmissionId !== binding.transmissionId) {
2641
+ throw new Error(
2642
+ `Binding "${binding.id}" has an account outside transmission "${binding.transmissionId}".`
2643
+ );
2644
+ }
2645
+ }
2646
+ const declared = new Set(
2647
+ (integration.capabilities ?? []).map((capability) => capability.id)
2648
+ );
2649
+ const unknown = binding.capabilities.filter(
2650
+ (capability) => !declared.has(capability)
2651
+ );
2652
+ if (unknown.length > 0) {
2653
+ throw new Error(
2654
+ `Binding "${binding.id}" requests undeclared capabilities: ${unknown.join(", ")}.`
2655
+ );
2656
+ }
2657
+ const referencedRoutes = [
2658
+ ...binding.routeId ? [binding.routeId] : [],
2659
+ ...binding.reply?.mode === "route" ? [binding.reply.routeId] : []
2660
+ ];
2661
+ for (const routeId of referencedRoutes) {
2662
+ const route = await this.services.runPromise(
2663
+ Effect5.gen(function* () {
2664
+ return yield* (yield* TopologyStore).getRoute(routeId);
2665
+ })
2666
+ );
2667
+ if (route.transmissionId !== binding.transmissionId) {
2668
+ throw new Error(
2669
+ `Binding "${binding.id}" has a route outside transmission "${binding.transmissionId}".`
2670
+ );
2671
+ }
2672
+ if (binding.reply?.mode === "route" && binding.reply.routeId === routeId) {
2673
+ if (route.direction !== "outbound") {
2674
+ throw new Error(
2675
+ `Binding "${binding.id}" reply route must be outbound.`
2676
+ );
2677
+ }
2678
+ }
2679
+ }
2680
+ }
2681
+ executionSignal(discovered) {
2682
+ return compileAgentDefinition(
2683
+ discovered.definition,
2684
+ discovered.route
2685
+ );
2686
+ }
2687
+ async seedTopology() {
2688
+ for (const route of this.application.routes ?? []) {
2689
+ await this.putRoute(route);
2690
+ }
2691
+ for (const binding of this.application.bindings ?? []) {
2692
+ await this.putBinding(binding);
2693
+ }
2694
+ }
2695
+ runInBackground(kind, loop) {
2696
+ const observed = loop.catch((cause) => {
2697
+ this.observability.append({
2698
+ type: `runtime.${kind}.failed`,
2699
+ category: "system",
2700
+ data: { error: cause instanceof Error ? cause.message : String(cause) }
2701
+ });
2702
+ });
2703
+ this.runnerLoops.push(observed);
2704
+ }
2705
+ async settleRunnerLoops(timeoutMs) {
2706
+ if (this.runnerLoops.length === 0) return;
2707
+ let timeout;
2708
+ const settled = await Promise.race([
2709
+ Promise.allSettled(this.runnerLoops).then(() => true),
2710
+ new Promise((resolveTimeout) => {
2711
+ timeout = setTimeout(() => resolveTimeout(false), timeoutMs);
2712
+ })
2713
+ ]);
2714
+ if (timeout) clearTimeout(timeout);
2715
+ if (!settled) {
2716
+ this.observability.append({
2717
+ type: "runtime.stop.loop-drain-timeout",
2718
+ category: "system",
2719
+ data: { timeoutMs, loops: this.runnerLoops.length }
2720
+ });
2721
+ }
2722
+ }
2723
+ async enqueueCoreRequest(definitionId, request, runAt) {
2724
+ const discovered = this.byRoute.get(definitionId);
2725
+ if (!discovered) throw new Error(`Foundry agent definition "${definitionId}" was not found.`);
2726
+ const agent = await Effect5.runPromise(this.data.getAgent(request.agentId));
2727
+ const conversation = await Effect5.runPromise(this.data.getConversation(request.conversationId));
2728
+ if (!agent || !conversation) throw new Error("Core command references an unknown agent instance or conversation.");
2729
+ const runId = await this.signalRunner.triggerSignal(
2730
+ discovered.executionName,
2731
+ await this.executionEnvelope(request)
2732
+ );
2733
+ if (runAt) {
2734
+ const date = new Date(runAt);
2735
+ if (Number.isNaN(date.getTime())) throw new Error(`Invalid future run date "${runAt}".`);
2736
+ await this.signalRunner.getAdapter().updateRun(runId, { nextRunAt: date });
2737
+ }
2738
+ return runId;
2739
+ }
2740
+ async executeCoreCommand(command, parentRunId) {
2741
+ if (command.type === "transmit") {
2742
+ await this.deliverOutbound(command, parentRunId);
2743
+ return;
2744
+ }
2745
+ if (command.type === "playbook.sync") {
2746
+ await this.syncAgentPlaybooks(command, parentRunId);
2747
+ return;
2748
+ }
2749
+ if (command.type === "schedule.sync") {
2750
+ await this.syncDefinitionSchedules(command, parentRunId);
2751
+ return;
2752
+ }
2753
+ if (command.type === "schedule.update") {
2754
+ await this.updateScheduledActivation(command, parentRunId);
2755
+ return;
2756
+ }
2757
+ if (command.type === "schedule.cancel") {
2758
+ await this.cancelScheduledActivation(command, parentRunId);
2759
+ return;
2760
+ }
2761
+ let agent = command.agentId ? await Effect5.runPromise(this.data.getAgent(command.agentId)) : null;
2762
+ if (!agent) {
2763
+ agent = await this.createAgent(command.definitionId, {
2764
+ ...command.agentId ? { id: command.agentId } : {},
2765
+ workspaceId: command.workspaceId,
2766
+ context: { spawnedByRunId: parentRunId }
2767
+ });
2768
+ }
2769
+ let conversation = command.conversationId ? await Effect5.runPromise(this.data.getConversation(command.conversationId)) : null;
2770
+ if (!conversation) {
2771
+ conversation = await this.createConversation(agent.id, {
2772
+ ...command.conversationId ? { id: command.conversationId } : {},
2773
+ workspaceId: command.workspaceId,
2774
+ context: { spawnedByRunId: parentRunId }
2775
+ });
2776
+ }
2777
+ const request = {
2778
+ agentId: agent.id,
2779
+ conversationId: conversation.id,
2780
+ workspaceId: command.workspaceId,
2781
+ message: command.message,
2782
+ ..."payload" in command && command.payload !== void 0 ? { payload: command.payload } : {},
2783
+ source: {
2784
+ kind: command.type === "sleep" || command.type === "schedule" ? "activation" : command.type,
2785
+ id: command.id
2786
+ }
2787
+ };
2788
+ if (command.type === "schedule" || command.type === "sleep") {
2789
+ const now = /* @__PURE__ */ new Date();
2790
+ const activation = {
2791
+ id: command.id,
2792
+ kind: command.type === "sleep" ? "sleep" : "scheduled",
2793
+ definitionId: command.definitionId,
2794
+ agentId: agent.id,
2795
+ conversationId: conversation.id,
2796
+ workspaceId: command.workspaceId,
2797
+ message: command.message,
2798
+ ...command.type === "schedule" && command.payload !== void 0 ? { payload: command.payload } : {},
2799
+ timing: command.type === "sleep" ? { kind: "at", at: command.wakeAt } : command.timing,
2800
+ origin: "agent-tool",
2801
+ status: "pending",
2802
+ createdByRunId: parentRunId,
2803
+ createdAt: now.toISOString(),
2804
+ updatedAt: now.toISOString()
2805
+ };
2806
+ await Effect5.runPromise(this.data.putActivation(activation));
2807
+ await this.materializeActivation(activation);
2808
+ return;
2809
+ }
2810
+ const runId = await this.enqueueCoreRequest(
2811
+ command.definitionId,
2812
+ request,
2813
+ void 0
2814
+ );
2815
+ this.observability.append({
2816
+ type: "core.command.accepted",
2817
+ category: "system",
2818
+ agent: command.definitionId,
2819
+ runId: parentRunId,
2820
+ data: { commandId: command.id, childRunId: runId, type: command.type }
2821
+ });
2822
+ if (command.type === "background" && command.reconvene) {
2823
+ void this.waitForRun(runId).then(async (run) => {
2824
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2825
+ const item = {
2826
+ id: id("inbox"),
2827
+ workspaceId: command.workspaceId,
2828
+ agentId: command.agentId,
2829
+ conversationId: command.conversationId,
2830
+ topic: "background.reconvene",
2831
+ payload: { commandId: command.id, run },
2832
+ status: "resolved",
2833
+ createdAt: now,
2834
+ updatedAt: now
2835
+ };
2836
+ await Effect5.runPromise(this.data.putInboxItem(item));
2837
+ });
2838
+ }
2839
+ }
2840
+ async syncAgentPlaybooks(command, parentRunId) {
2841
+ const agent = await Effect5.runPromise(this.data.getAgent(command.agentId));
2842
+ if (!agent || agent.definitionId !== command.definitionId || agent.workspaceId !== command.workspaceId) {
2843
+ throw new Error("Playbook synchronization references an invalid agent instance.");
2844
+ }
2845
+ const currentById = new Map(agent.playbooks.map((item) => [item.id, item]));
2846
+ const instancePlaybooks = agent.playbooks.filter(
2847
+ (item) => item.origin !== "agent-definition"
2848
+ );
2849
+ const reconciled = command.playbooks.map((desired) => {
2850
+ const current = currentById.get(desired.id);
2851
+ return current && current.definitionRevision === desired.definitionRevision ? current : desired;
2852
+ });
2853
+ const updated = {
2854
+ ...agent,
2855
+ playbooks: Object.freeze([...instancePlaybooks, ...reconciled]),
2856
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2857
+ };
2858
+ this.validatePlaybooks(command.definitionId, updated.playbooks, updated.installations);
2859
+ await Effect5.runPromise(this.data.putAgent(updated));
2860
+ await this.reconcileApplicationConnections();
2861
+ this.observability.append({
2862
+ type: "agent.playbooks.composed",
2863
+ category: "agent",
2864
+ agent: command.definitionId,
2865
+ runId: parentRunId,
2866
+ data: {
2867
+ agentId: command.agentId,
2868
+ playbookIds: reconciled.map((item) => item.id)
2869
+ }
2870
+ });
2871
+ }
2872
+ assertActivationOwnership(activation, command) {
2873
+ if (activation.kind !== "scheduled" || activation.agentId !== command.agentId || activation.workspaceId !== command.workspaceId) {
2874
+ throw new Error(`Scheduled activation "${activation.id}" is not owned by this agent instance.`);
2875
+ }
2876
+ }
2877
+ backendScheduleId(activationId) {
2878
+ return `activation/${activationId.replaceAll("_", "-")}`;
2879
+ }
2880
+ async disarmActivation(activation) {
2881
+ if (activation.timing.kind === "every" || activation.timing.kind === "cron") {
2882
+ await this.scheduleAdapter.delete(this.backendScheduleId(activation.id));
2883
+ } else if (activation.lastRunId) {
2884
+ await this.signalRunner.cancel(activation.lastRunId);
2885
+ }
2886
+ this.materializedActivations.delete(activation.id);
2887
+ }
2888
+ async syncDefinitionSchedules(command, parentRunId) {
2889
+ const current = (await Effect5.runPromise(this.data.listActivations(command.workspaceId))).filter(
2890
+ (item) => item.agentId === command.agentId && item.kind === "scheduled" && item.origin === "agent-definition"
2891
+ );
2892
+ const desiredIds = new Set(command.schedules.map((item) => item.id));
2893
+ for (const stale of current.filter((item) => !desiredIds.has(item.id))) {
2894
+ await this.disarmActivation(stale);
2895
+ await Effect5.runPromise(this.data.putActivation({
2896
+ ...stale,
2897
+ status: "cancelled",
2898
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2899
+ }));
2900
+ }
2901
+ for (const desired of command.schedules) {
2902
+ const existing = await Effect5.runPromise(this.data.getActivation(desired.id));
2903
+ if (existing) this.assertActivationOwnership(existing, command);
2904
+ if (!desired.enabled) {
2905
+ if (existing && existing.status !== "cancelled") {
2906
+ await this.disarmActivation(existing);
2907
+ await Effect5.runPromise(this.data.putActivation({
2908
+ ...existing,
2909
+ status: "cancelled",
2910
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2911
+ }));
2912
+ }
2913
+ continue;
2914
+ }
2915
+ const unchanged = existing?.definitionRevision === desired.revision;
2916
+ if (unchanged || existing?.status === "cancelled") continue;
2917
+ if (existing) await this.disarmActivation(existing);
2918
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2919
+ const activation = {
2920
+ id: desired.id,
2921
+ kind: "scheduled",
2922
+ definitionId: command.definitionId,
2923
+ agentId: command.agentId,
2924
+ conversationId: command.conversationId,
2925
+ workspaceId: command.workspaceId,
2926
+ message: desired.message,
2927
+ ...desired.payload !== void 0 ? { payload: desired.payload } : {},
2928
+ timing: desired.timing,
2929
+ origin: "agent-definition",
2930
+ scheduleName: desired.name,
2931
+ definitionRevision: desired.revision,
2932
+ status: "pending",
2933
+ createdByRunId: parentRunId,
2934
+ createdAt: existing?.createdAt ?? now,
2935
+ updatedAt: now
2936
+ };
2937
+ await Effect5.runPromise(this.data.putActivation(activation));
2938
+ await this.materializeActivation(activation);
2939
+ }
2940
+ }
2941
+ async updateScheduledActivation(command, parentRunId) {
2942
+ const existing = await Effect5.runPromise(this.data.getActivation(command.activationId));
2943
+ if (!existing) throw new Error(`Scheduled activation "${command.activationId}" was not found.`);
2944
+ this.assertActivationOwnership(existing, command);
2945
+ await this.disarmActivation(existing);
2946
+ const updated = {
2947
+ ...existing,
2948
+ ...command.patch,
2949
+ status: "pending",
2950
+ createdByRunId: parentRunId,
2951
+ lastRunId: void 0,
2952
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2953
+ };
2954
+ await Effect5.runPromise(this.data.putActivation(updated));
2955
+ await this.materializeActivation(updated);
2956
+ this.observability.append({
2957
+ type: "scheduled-action.updated",
2958
+ category: "activation",
2959
+ agent: command.definitionId,
2960
+ runId: parentRunId,
2961
+ data: { commandId: command.id, activationId: updated.id }
2962
+ });
2963
+ }
2964
+ async cancelScheduledActivation(command, parentRunId) {
2965
+ const existing = await Effect5.runPromise(this.data.getActivation(command.activationId));
2966
+ if (!existing) throw new Error(`Scheduled activation "${command.activationId}" was not found.`);
2967
+ this.assertActivationOwnership(existing, command);
2968
+ await this.disarmActivation(existing);
2969
+ await Effect5.runPromise(this.data.putActivation({
2970
+ ...existing,
2971
+ status: "cancelled",
2972
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2973
+ }));
2974
+ this.observability.append({
2975
+ type: "scheduled-action.cancelled",
2976
+ category: "activation",
2977
+ agent: command.definitionId,
2978
+ runId: parentRunId,
2979
+ data: { commandId: command.id, activationId: existing.id }
2980
+ });
2981
+ }
2982
+ async reconstructActivations() {
2983
+ for (const activation of await Effect5.runPromise(this.data.listActivations())) {
2984
+ if (activation.status === "completed" || activation.status === "cancelled") continue;
2985
+ await this.materializeActivation(activation);
2986
+ }
2987
+ }
2988
+ async materializeActivation(activation) {
2989
+ if (this.materializedActivations.has(activation.id)) return;
2990
+ this.materializedActivations.add(activation.id);
2991
+ try {
2992
+ const discovered = this.byRoute.get(activation.definitionId);
2993
+ if (!discovered) {
2994
+ throw new Error(
2995
+ `Activation "${activation.id}" references unknown agent definition "${activation.definitionId}".`
2996
+ );
2997
+ }
2998
+ const storedAgent = await Effect5.runPromise(this.data.getAgent(activation.agentId));
2999
+ const storedConversation = await Effect5.runPromise(
3000
+ this.data.getConversation(activation.conversationId)
3001
+ );
3002
+ if (!storedAgent || storedAgent.definitionId !== activation.definitionId) {
3003
+ throw new Error(`Activation "${activation.id}" references an invalid agent instance.`);
3004
+ }
3005
+ if (!storedConversation || storedConversation.agentId !== activation.agentId) {
3006
+ throw new Error(`Activation "${activation.id}" references an invalid conversation.`);
3007
+ }
3008
+ if (activation.timing.kind === "every" && (!Number.isFinite(activation.timing.intervalMs) || activation.timing.intervalMs <= 0)) {
3009
+ throw new Error(`Activation "${activation.id}" has an invalid recurrence interval.`);
3010
+ }
3011
+ if (activation.timing.kind === "at" && Number.isNaN(new Date(activation.timing.at).getTime())) {
3012
+ throw new Error(`Activation "${activation.id}" has an invalid wake time.`);
3013
+ }
3014
+ const request = {
3015
+ agentId: activation.agentId,
3016
+ conversationId: activation.conversationId,
3017
+ workspaceId: activation.workspaceId,
3018
+ message: activation.message,
3019
+ ...activation.payload !== void 0 ? { payload: activation.payload } : {},
3020
+ source: { kind: "activation", id: activation.id }
3021
+ };
3022
+ if (activation.timing.kind === "every" || activation.timing.kind === "cron") {
3023
+ const now = /* @__PURE__ */ new Date();
3024
+ const schedule = {
3025
+ id: this.backendScheduleId(activation.id),
3026
+ kind: "signal",
3027
+ target: discovered.executionName,
3028
+ ...activation.timing.kind === "every" ? { interval: `${activation.timing.intervalMs}ms` } : { cron: activation.timing.expression, timezone: activation.timing.timezone },
3029
+ overlapPolicy: "skip",
3030
+ misfirePolicy: "fire-once",
3031
+ misfireGraceMs: 6e4,
3032
+ input: await this.executionEnvelope(request),
3033
+ enabled: true,
3034
+ nextRunAt: activation.timing.kind === "every" ? new Date(now.getTime() + activation.timing.intervalMs) : nextCronOccurrence(
3035
+ activation.timing.expression,
3036
+ activation.timing.timezone,
3037
+ now
3038
+ ),
3039
+ createdAt: now,
3040
+ updatedAt: now,
3041
+ createdBy: `core:${activation.createdByRunId}`
3042
+ };
3043
+ await this.scheduleAdapter.add(schedule);
3044
+ await Effect5.runPromise(this.data.putActivation({
3045
+ ...activation,
3046
+ status: "active",
3047
+ updatedAt: now.toISOString()
3048
+ }));
3049
+ this.observability.append({
3050
+ type: "scheduled-action.created",
3051
+ category: "activation",
3052
+ agent: activation.definitionId,
3053
+ runId: activation.createdByRunId,
3054
+ data: { commandId: activation.id, recurring: true, nextRunAt: schedule.nextRunAt }
3055
+ });
3056
+ return;
3057
+ }
3058
+ const runId = await this.enqueueCoreRequest(
3059
+ activation.definitionId,
3060
+ request,
3061
+ activation.timing.at
3062
+ );
3063
+ const active = {
3064
+ ...activation,
3065
+ status: "active",
3066
+ lastRunId: runId,
3067
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3068
+ };
3069
+ await Effect5.runPromise(this.data.putActivation(active));
3070
+ this.observability.append({
3071
+ type: "core.command.accepted",
3072
+ category: "system",
3073
+ agent: activation.definitionId,
3074
+ runId: activation.createdByRunId,
3075
+ data: { commandId: activation.id, childRunId: runId, type: activation.kind }
3076
+ });
3077
+ void this.waitForRun(runId).then(async (run) => {
3078
+ if (!run || run.status !== "completed" && run.status !== "failed" && run.status !== "cancelled") return;
3079
+ await Effect5.runPromise(this.data.putActivation({
3080
+ ...active,
3081
+ status: run.status === "cancelled" ? "cancelled" : "completed",
3082
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3083
+ }));
3084
+ }).catch((cause) => {
3085
+ this.observability.append({
3086
+ type: "activation.persistence.error",
3087
+ category: "activation",
3088
+ agent: activation.definitionId,
3089
+ runId,
3090
+ data: { error: cause instanceof Error ? cause.message : String(cause) }
3091
+ });
3092
+ });
3093
+ } catch (cause) {
3094
+ this.materializedActivations.delete(activation.id);
3095
+ throw cause;
3096
+ }
3097
+ }
3098
+ async deliverOutbound(command, parentRunId) {
3099
+ await this.dispatchOutbound({
3100
+ routeId: command.routeId,
3101
+ agentId: command.agentId,
3102
+ runId: parentRunId,
3103
+ payload: command.payload,
3104
+ commandId: command.id,
3105
+ ...command.applicationId ? { applicationId: command.applicationId } : {},
3106
+ ...command.transmissionId ? { transmissionId: command.transmissionId } : {}
3107
+ });
3108
+ }
3109
+ async executionEnvelope(request) {
3110
+ const agent = await Effect5.runPromise(this.data.getAgent(request.agentId));
3111
+ const conversation = await Effect5.runPromise(this.data.getConversation(request.conversationId));
3112
+ if (!agent || !conversation) throw new Error("Cannot schedule an unknown agent instance or conversation.");
3113
+ const activations = (await Effect5.runPromise(this.data.listActivations(request.workspaceId))).filter((activation) => activation.agentId === request.agentId);
3114
+ return { [FOUNDRY_EXECUTION_MARKER]: true, request, agent, conversation, activations };
3115
+ }
3116
+ toFoundryRun(run) {
3117
+ const storedInput = parseJson(run.input);
3118
+ const input = storedInput && typeof storedInput === "object" && storedInput[FOUNDRY_EXECUTION_MARKER] === true ? storedInput.request : storedInput;
3119
+ const request = input && typeof input === "object" ? input : void 0;
3120
+ return {
3121
+ id: run.id,
3122
+ agent: this.routeBySignalName.get(run.signalName) ?? run.signalName,
3123
+ kind: run.kind,
3124
+ status: run.status,
3125
+ input,
3126
+ ...request?.agentId ? { agentId: request.agentId } : {},
3127
+ ...request?.conversationId ? { conversationId: request.conversationId } : {},
3128
+ ...request?.workspaceId ? { workspaceId: request.workspaceId } : {},
3129
+ ...run.output !== void 0 ? { output: parseJson(run.output) } : {},
3130
+ ...run.error ? { error: run.error } : {},
3131
+ attempts: run.attempts,
3132
+ maxAttempts: run.maxAttempts,
3133
+ timeoutMs: run.timeout,
3134
+ createdAt: run.createdAt.toISOString(),
3135
+ ...run.startedAt ? { startedAt: run.startedAt.toISOString() } : {},
3136
+ ...run.completedAt ? { completedAt: run.completedAt.toISOString() } : {}
3137
+ };
3138
+ }
3139
+ };
3140
+
3141
+ // src/server.ts
3142
+ import {
3143
+ createServer
3144
+ } from "node:http";
3145
+ import { Schema as Schema4 } from "effect";
3146
+
3147
+ // src/dashboard-icons.ts
3148
+ var PHOSPHOR_ICON_PATHS = {
3149
+ overview: "M104,40H56A16,16,0,0,0,40,56v48a16,16,0,0,0,16,16h48a16,16,0,0,0,16-16V56A16,16,0,0,0,104,40Zm0,64H56V56h48v48Zm96-64H152a16,16,0,0,0-16,16v48a16,16,0,0,0,16,16h48a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Zm0,64H152V56h48v48Zm-96,32H56a16,16,0,0,0-16,16v48a16,16,0,0,0,16,16h48a16,16,0,0,0,16-16V152A16,16,0,0,0,104,136Zm0,64H56V152h48v48Zm96-64H152a16,16,0,0,0-16,16v48a16,16,0,0,0,16,16h48a16,16,0,0,0,16-16V152A16,16,0,0,0,200,136Zm0,64H152V152h48v48Z",
3150
+ agent: "M200,48H136V16a8,8,0,0,0-16,0V48H56A32,32,0,0,0,24,80V192a32,32,0,0,0,32,32H200a32,32,0,0,0,32-32V80A32,32,0,0,0,200,48Zm16,144a16,16,0,0,1-16,16H56a16,16,0,0,1-16-16V80A16,16,0,0,1,56,64H200a16,16,0,0,1,16,16Zm-52-56H92a28,28,0,0,0,0,56h72a28,28,0,0,0,0-56Zm-24,16v24H116V152ZM80,164a12,12,0,0,1,12-12h8v24H92A12,12,0,0,1,80,164Zm84,12h-8V152h8a12,12,0,0,1,0,24ZM72,108a12,12,0,1,1,12,12A12,12,0,0,1,72,108Zm88,0a12,12,0,1,1,12,12A12,12,0,0,1,160,108Z",
3151
+ runs: "M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm48.24-94.78-64-40A8,8,0,0,0,100,88v80a8,8,0,0,0,12.24,6.78l64-40a8,8,0,0,0,0-13.56ZM116,153.57V102.43L156.91,128Z",
3152
+ automations: "M232,136.66A104.12,104.12,0,1,1,119.34,24,8,8,0,0,1,120.66,40,88.12,88.12,0,1,0,216,135.34,8,8,0,0,1,232,136.66ZM120,72v56a8,8,0,0,0,8,8h56a8,8,0,0,0,0-16H136V72a8,8,0,0,0-16,0Zm40-24a12,12,0,1,0-12-12A12,12,0,0,0,160,48Zm36,24a12,12,0,1,0-12-12A12,12,0,0,0,196,72Zm24,36a12,12,0,1,0-12-12A12,12,0,0,0,220,108Z",
3153
+ integrations: "M237.66,18.34a8,8,0,0,0-11.32,0l-52.4,52.41-5.37-5.38a32.05,32.05,0,0,0-45.26,0L100,88.69l-6.34-6.35A8,8,0,0,0,82.34,93.66L88.69,100,65.37,123.31a32,32,0,0,0,0,45.26l5.38,5.37-52.41,52.4a8,8,0,0,0,11.32,11.32l52.4-52.41,5.37,5.38a32,32,0,0,0,45.26,0L156,167.31l6.34,6.35a8,8,0,0,0,11.32-11.32L167.31,156l23.32-23.31a32,32,0,0,0,0-45.26l-5.38-5.37,52.41-52.4A8,8,0,0,0,237.66,18.34Zm-116.29,161a16,16,0,0,1-22.62,0L76.69,157.25a16,16,0,0,1,0-22.62L100,111.31,144.69,156Zm57.94-57.94L156,144.69,111.31,100l23.32-23.31a16,16,0,0,1,22.62,0l22.06,22A16,16,0,0,1,179.31,121.37ZM88.57,35A8,8,0,0,1,103.43,29l8,20A8,8,0,0,1,96.57,55ZM24.57,93A8,8,0,0,1,35,88.57l20,8A8,8,0,0,1,49,111.43l-20-8A8,8,0,0,1,24.57,93ZM231.43,163a8,8,0,0,1-10.4,4.46l-20-8A8,8,0,1,1,207,144.57l20,8A8,8,0,0,1,231.43,163Zm-64,58.06A8,8,0,0,1,152.57,227l-8-20A8,8,0,0,1,159.43,201Z",
3154
+ workspaces: "M224,64H154.67L126.93,43.2a16.12,16.12,0,0,0-9.6-3.2H72A16,16,0,0,0,56,56V72H40A16,16,0,0,0,24,88V200a16,16,0,0,0,16,16H192.89A15.13,15.13,0,0,0,208,200.89V184h16.89A15.13,15.13,0,0,0,240,168.89V80A16,16,0,0,0,224,64ZM192,200H40V88H85.33l29.87,22.4A8,8,0,0,0,120,112h72Zm32-32H208V112a16,16,0,0,0-16-16H122.67L94.93,75.2a16.12,16.12,0,0,0-9.6-3.2H72V56h45.33L147.2,78.4A8,8,0,0,0,152,80h72Z",
3155
+ menu: "M224,128a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM40,72H216a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16ZM216,184H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Z",
3156
+ plus: "M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z",
3157
+ close: "M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z",
3158
+ caretRight: "M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z",
3159
+ search: "M229.66,218.34l-50.07-50.06a88.11,88.11,0,1,0-11.31,11.31l50.06,50.07a8,8,0,0,0,11.32-11.32ZM40,112a72,72,0,1,1,72,72A72.08,72.08,0,0,1,40,112Z",
3160
+ definitions: "M229.66,189.66l-32,32a8,8,0,0,1-11.32,0l-32-32a8,8,0,0,1,11.32-11.32L184,196.69V139.31l-56-56-56,56v57.38l18.34-18.35a8,8,0,0,1,11.32,11.32l-32,32a8,8,0,0,1-11.32,0l-32-32a8,8,0,0,1,11.32-11.32L56,196.69V136a8,8,0,0,1,2.34-5.66L120,68.69V24a8,8,0,0,1,16,0V68.69l61.66,61.65A8,8,0,0,1,200,136v60.69l18.34-18.35a8,8,0,0,1,11.32,11.32Z",
3161
+ secure: "M208,40H48A16,16,0,0,0,32,56v56c0,52.72,25.52,84.67,46.93,102.19,23.06,18.86,46,25.26,47,25.53a8,8,0,0,0,4.2,0c1-.27,23.91-6.67,47-25.53C198.48,196.67,224,164.72,224,112V56A16,16,0,0,0,208,40Zm0,72c0,37.07-13.66,67.16-40.6,89.42A129.3,129.3,0,0,1,128,223.62a128.25,128.25,0,0,1-38.92-21.81C61.82,179.51,48,149.3,48,112l0-56,160,0ZM82.34,141.66a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32l-56,56a8,8,0,0,1-11.32,0Z",
3162
+ external: "M200,64V168a8,8,0,0,1-16,0V83.31L69.66,197.66a8,8,0,0,1-11.32-11.32L172.69,72H88a8,8,0,0,1,0-16H192A8,8,0,0,1,200,64Z"
3163
+ };
3164
+ function renderPhosphorIcon(name, className = "icon") {
3165
+ return `<svg class="${className}" data-phosphor="${name}" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="${PHOSPHOR_ICON_PATHS[name]}"/></svg>`;
3166
+ }
3167
+ var GLOVE_HAND_PATH = "M410.08 393.32C410.08 393.447 410.105 393.573 410.153 393.69C410.202 393.807 410.273 393.913 410.362 394.002C410.451 394.092 410.557 394.162 410.673 394.21C410.79 394.257 410.914 394.281 411.04 394.28L422.48 394.22C422.626 394.22 422.766 394.162 422.869 394.059C422.972 393.956 423.03 393.816 423.03 393.67L422.1 203.44C422.099 203.381 422.109 203.323 422.131 203.269C422.153 203.214 422.186 203.164 422.228 203.122C422.27 203.081 422.32 203.047 422.375 203.024C422.431 203.002 422.49 202.99 422.55 202.99L479.35 202.88C479.631 202.88 479.901 202.992 480.1 203.19C480.298 203.389 480.41 203.659 480.41 203.94L480.76 393.79C480.76 393.904 480.805 394.013 480.886 394.094C480.967 394.175 481.076 394.22 481.19 394.22L493.3 394.16C493.449 394.157 493.592 394.096 493.697 393.99C493.801 393.883 493.86 393.739 493.86 393.59L493.45 172.81C493.45 172.593 493.536 172.384 493.69 172.23C493.844 172.076 494.053 171.99 494.27 171.99L546.6 171.73C546.674 171.73 546.745 171.759 546.798 171.812C546.85 171.865 546.88 171.936 546.88 172.01L547.29 394C547.29 394.217 547.376 394.426 547.53 394.58C547.684 394.734 547.893 394.82 548.11 394.82L559.31 394.8C559.543 394.8 559.767 394.706 559.932 394.539C560.097 394.372 560.19 394.146 560.19 393.91L559.84 203.7C559.84 203.498 559.921 203.305 560.066 203.163C560.21 203.02 560.406 202.94 560.61 202.94L617.16 202.83C617.36 202.83 617.552 202.91 617.694 203.052C617.836 203.194 617.917 203.388 617.92 203.59L618.46 497.93C618.459 498.009 618.48 498.087 618.523 498.153C618.565 498.22 618.627 498.273 618.699 498.305C618.772 498.338 618.854 498.349 618.934 498.338C619.013 498.326 619.089 498.292 619.15 498.24L710.7 416.63C710.966 416.396 711.296 416.256 711.64 416.23C712.093 416.197 713.19 416.98 714.93 418.58C726.357 429.047 738.423 439.817 751.13 450.89C752.057 451.697 752.843 452.367 753.49 452.9C753.881 453.221 754.13 453.685 754.183 454.19C754.235 454.695 754.087 455.201 753.77 455.6C748.957 461.66 744.16 467.85 739.38 474.17C730.767 485.563 722.277 496.827 713.91 507.96C704.103 521.013 694.037 534.443 683.71 548.25C682.31 550.123 681.463 551.257 681.17 551.65C651.197 591.923 635.867 612.51 635.18 613.41C633.727 615.303 626.6 624.933 613.8 642.3C612.853 643.587 611.92 644.223 611 644.21C593.94 643.94 576.01 644.48 560.67 644.48C557.17 644.48 507.53 644.427 411.75 644.32C410.983 644.32 410.13 644.353 409.19 644.42C408.676 644.453 408.163 644.342 407.709 644.1C407.255 643.858 406.878 643.494 406.62 643.05C403.013 636.843 400.117 631.393 397.93 626.7C394.94 620.29 392.01 614.85 389 608.75C382.72 595.99 376.747 583.813 371.08 572.22C368.85 567.67 364.77 560.45 362.06 554.23C362.014 554.138 361.99 554.035 361.99 553.93L362 269.83C362 269.637 362.039 269.445 362.115 269.267C362.192 269.09 362.303 268.929 362.444 268.797C362.584 268.664 362.75 268.561 362.932 268.494C363.114 268.428 363.307 268.399 363.5 268.41C378.36 269.18 393.79 268.85 408.23 268.58C408.441 268.575 408.651 268.612 408.847 268.689C409.043 268.766 409.222 268.881 409.373 269.028C409.524 269.176 409.644 269.352 409.726 269.546C409.808 269.74 409.85 269.949 409.85 270.16L410.08 393.32Z";
3168
+ var GLOVE_CUFF_PATH = "M406.91 658.31C406.91 658.22 406.946 658.133 407.01 658.07C407.073 658.006 407.16 657.97 407.25 657.97C462.983 657.857 503.02 657.817 527.36 657.85C559.72 657.9 574.5 657.57 611.92 657.12C612.178 657.117 612.435 657.166 612.674 657.263C612.913 657.36 613.131 657.504 613.315 657.686C613.499 657.869 613.646 658.085 613.746 658.325C613.846 658.564 613.899 658.82 613.9 659.08L614.06 747.46C614.06 747.776 613.946 748.08 613.738 748.318C613.53 748.555 613.243 748.709 612.93 748.75C612.257 748.837 610.613 748.88 608 748.88C574.107 748.9 508.023 748.873 409.75 748.8C407.817 748.8 406.85 747.943 406.85 746.23C406.903 718.95 406.923 689.643 406.91 658.31Z";
3169
+ function renderGloveMark() {
3170
+ return `<svg class="brand-mark" data-brand="glove" viewBox="0 0 1024 1024" fill="none" aria-hidden="true"><path d="${GLOVE_HAND_PATH}" fill="currentColor"/><path d="${GLOVE_CUFF_PATH}" fill="currentColor"/></svg>`;
3171
+ }
3172
+
3173
+ // src/dashboard-script.ts
3174
+ var ICON_PATHS_JSON = JSON.stringify(PHOSPHOR_ICON_PATHS);
3175
+ var DASHBOARD_SCRIPT = String.raw`
3176
+ const ICON_PATHS=${ICON_PATHS_JSON};
3177
+ const state={manifest:null,instances:[],subscriptions:[],connections:[],runs:[],events:[],health:null,transmissions:[],accounts:[],routes:[],bindings:[],activations:[],conversations:{},workspaces:{},filters:{runs:"all"},workspaceTab:"entries",showAllEvents:false};
3178
+ const $=id=>document.getElementById(id);
3179
+ const esc=value=>String(value??"").replace(/[&<>"']/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;"}[c]));
3180
+ const attr=value=>esc(value).replace(/\x60/g,"&#96;");
3181
+ const fmtDate=iso=>iso?new Date(iso).toLocaleString([],{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):"—";
3182
+ const fmtTime=iso=>iso?new Date(iso).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"}):"—";
3183
+ const short=value=>{const text=String(value??"");return text.length>22?text.slice(0,10)+"…"+text.slice(-7):text};
3184
+ const jsonText=value=>JSON.stringify(value,null,2);
3185
+ const count=(n,word)=>n+" "+word+(n===1?"":"s");
3186
+ const icon=(name,className)=>'<svg class="'+(className||"icon")+'" data-phosphor="'+name+'" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="'+ICON_PATHS[name]+'"/></svg>';
3187
+
3188
+ async function api(url,options){
3189
+ const response=await fetch(url,options);let body;
3190
+ try{body=await response.json()}catch{body={error:"The runtime returned an unreadable response."}}
3191
+ if(!response.ok)throw new Error(body.error||("Request failed ("+response.status+")"));return body;
3192
+ }
3193
+ function toast(message){const node=$("toast");node.textContent=message;node.classList.add("show");setTimeout(()=>node.classList.remove("show"),2400)}
3194
+ function route(){const parts=location.pathname.split("/").filter(Boolean).map(decodeURIComponent);return{parts,section:parts[0]||"overview",id:parts.slice(1).join("/")}}
3195
+ function go(path){history.pushState({},"",path);void navigate()}
3196
+ function status(value){return '<span class="status '+attr(value)+'">'+esc(value)+'</span>'}
3197
+ function tags(values){return(values||[]).map(value=>'<span class="tag">'+esc(value)+'</span>').join("")}
3198
+ function empty(title,detail){return'<div class="empty"><strong>'+esc(title)+'</strong>'+esc(detail)+'</div>'}
3199
+ function pageHead(eyebrow,title,detail,actions){return'<div class="page-head"><div><div class="page-rule"></div><div class="eyebrow">'+esc(eyebrow)+'</div><h1>'+esc(title)+'</h1><p>'+esc(detail)+'</p></div>'+(actions?'<div class="actions">'+actions+'</div>':"")+'</div>'}
3200
+ function card(title,body,meta,classes){return'<section class="card '+(classes||"")+'"><div class="card-head"><h2>'+esc(title)+'</h2>'+(meta?'<span class="meta">'+esc(meta)+'</span>':"")+'</div>'+body+'</section>'}
3201
+ function metric(label,value,note,accent){return'<section class="card metric '+(accent?"accent":"")+'"><label>'+esc(label)+'</label><strong>'+esc(value)+'</strong><small>'+esc(note)+'</small></section>'}
3202
+ function dataLinks(){
3203
+ document.querySelectorAll("[data-link]").forEach(node=>node.onclick=event=>{if(event.metaKey||event.ctrlKey||event.shiftKey||event.altKey)return;event.preventDefault();go(node.getAttribute("href"))});
3204
+ document.querySelectorAll("[data-go]").forEach(node=>node.onclick=event=>{if(event.target.closest("a,button"))return;go(node.dataset.go)});
3205
+ }
3206
+ function workspaceIds(){const ids=new Set();state.instances.forEach(x=>ids.add(x.workspaceId));state.subscriptions.forEach(x=>ids.add(x.workspaceId));state.connections.forEach(x=>ids.add(x.workspaceId));state.runs.forEach(x=>{if(x.workspaceId)ids.add(x.workspaceId)});return[...ids].filter(Boolean).sort()}
3207
+ function definition(id){return state.manifest?.agents?.agents?.find(x=>x.id===id)}
3208
+ function eventsFor(runId){return state.events.filter(event=>event.runId===runId)}
3209
+ function isKeyRunEvent(event){
3210
+ const type=event.type;
3211
+ if(/^run\./.test(type)||/^scheduled-action\./.test(type))return true;
3212
+ return /(?:playbooks\.composed|definition\.schedules\.loaded|application\.transmission-tools\.mounted|working-environment\.(?:mounted|snapshot\.saved)|repl\.mounted|subscriber\.mounted|layer\.mounted|installation\.(?:started|completed|failed)|definition\.memory\.mounted|definition\.inboxes\.loaded|tool_use(?:_result)?|model_response_complete)$/.test(type);
3213
+ }
3214
+ function activeRuns(){return state.runs.filter(run=>run.status==="pending"||run.status==="running")}
3215
+ function failureRuns(){return state.runs.filter(run=>run.status==="failed")}
3216
+ function recent(array){return[...array].sort((a,b)=>String(b.createdAt||b.timestamp||b.updatedAt).localeCompare(String(a.createdAt||a.timestamp||a.updatedAt)))}
3217
+ function explainRun(run){
3218
+ if(run.status==="failed")return run.error||"The runtime could not complete this run.";
3219
+ const source=run.input?.source?.kind;
3220
+ if(source==="transmission")return"Started by an inbound transmission.";
3221
+ if(source==="activation")return"Started by a scheduled activation or wake-up.";
3222
+ if(source==="spawn")return"Spawned by another agent run.";
3223
+ if(source==="background")return"Continued as background work.";
3224
+ if(run.status==="completed")return"The agent completed the requested work.";
3225
+ return"The agent is processing a direct message.";
3226
+ }
3227
+ function navState(section){const key=section==="overview"?"overview":section;document.querySelectorAll("[data-nav]").forEach(node=>node.classList.toggle("active",node.dataset.nav===key));$("sidebar").classList.remove("open")}
3228
+ function crumbs(items){$("breadcrumbs").innerHTML=items.map((item,index)=>index===items.length-1?'<span>'+esc(item.label)+'</span>':'<a href="'+attr(item.href)+'" data-link>'+esc(item.label)+'</a><span class="crumb-separator">'+icon("caretRight")+'</span>').join("")}
3229
+
3230
+ function renderOverview(){
3231
+ crumbs([{label:"Overview"}]);
3232
+ const recentRuns=recent(state.runs).slice(0,6);const definitions=state.manifest?.agents?.agents||[];const live=activeRuns();
3233
+ let html=pageHead("Runtime at a glance","What is happening now","Start here. Foundry separates code-defined agents from their runtime instances, then records every invocation as a run.",'<a class="button" href="/agents" data-link>Browse agents</a><button class="button primary" data-new-run>Start a run</button>');
3234
+ html+='<div class="grid cols-4">'+metric("Agent definitions",definitions.length,"Discovered from the agents folder",true)+metric("Runtime instances",state.instances.length,"Persisted agent identities")+metric("Active work",live.length,live.length?"Runs currently pending or running":"No work in flight")+metric("Attention needed",failureRuns().length,"Failed runs in retained history")+'</div>';
3235
+ html+='<div class="grid cols-2" style="margin-top:16px">';
3236
+ const activity=recentRuns.map(run=>'<tr class="clickable" data-go="/runs/'+attr(run.id)+'"><td><span class="primary-cell">'+esc(run.agent)+'</span><span class="secondary">'+esc(short(run.id))+'</span></td><td>'+status(run.status)+'</td><td>'+esc(explainRun(run))+'</td><td>'+esc(fmtDate(run.createdAt))+'</td></tr>').join("");
3237
+ html+=card("Recent runs",'<div class="table-wrap"><table class="table"><thead><tr><th>Agent</th><th>Status</th><th>Why it ran</th><th>Started</th></tr></thead><tbody>'+activity+'</tbody></table></div>'+(activity?"":empty("No runs yet","Start a run to see its progress and trace here.")),count(state.runs.length,"run"),"span-2");
3238
+ const map=definitions.slice(0,6).map(def=>{const instances=state.instances.filter(x=>x.definitionId===def.id);return'<tr class="clickable" data-go="/agents/'+attr(def.id)+'"><td><span class="primary-cell">'+esc(def.id)+'</span><span class="secondary">'+esc(def.description)+'</span></td><td>'+instances.length+'</td><td>'+esc((def.workingEnvironment?"Working environment · ":"")+(def.repl?def.repl+" REPL":"No REPL"))+'</td></tr>'}).join("");
3239
+ html+=card("Agent map",'<div class="table-wrap"><table class="table"><thead><tr><th>Definition</th><th>Instances</th><th>Mounted surface</th></tr></thead><tbody>'+map+'</tbody></table></div>',count(definitions.length,"definition"));
3240
+ const automationCount=state.activations.length+state.subscriptions.length+state.connections.length;
3241
+ html+=card("Background activity",'<div class="card-body">'+(automationCount?'<div class="summary-box"><dl><dt>Scheduled wake-ups</dt><dd>'+state.activations.length+'</dd><dt>Playbook listeners</dt><dd>'+state.subscriptions.length+'</dd><dt>Inbound connections</dt><dd>'+state.connections.length+'</dd></dl><p><a class="link inline-icon" href="/automations" data-link>Inspect automation state '+icon("external")+'</a></p></div>':empty("Nothing is listening yet","Schedules, sleeping runs, playbooks, and app connections appear here."))+'</div>');
3242
+ html+='</div>';$("content").innerHTML=html;document.querySelectorAll("[data-new-run]").forEach(x=>x.onclick=openRunDrawer);dataLinks();
3243
+ }
3244
+
3245
+ function renderAgents(){
3246
+ crumbs([{label:"Agents"}]);const definitions=state.manifest?.agents?.agents||[];
3247
+ let html=pageHead("Code and runtime","Agents","A definition is the code-discovered recipe. An instance is persisted runtime data with its own context, apps, playbooks, and conversations.",'<button class="button primary" data-new-run>Start a run</button>');
3248
+ html+='<div class="callout"><span class="symbol">'+icon("definitions")+'</span><div><b>Definitions and instances are intentionally separate.</b><p>Editing a file changes what can be assembled. Updating an instance changes what one runtime identity actually has installed.</p></div></div>';
3249
+ html+='<div class="definition-grid">'+definitions.map(def=>{const instances=state.instances.filter(x=>x.definitionId===def.id);const features=[def.workingEnvironment?"VFS":null,def.repl?def.repl+" repl":null,def.mesh?"mesh":null].filter(Boolean);return'<a class="definition-card" href="/agents/'+attr(def.id)+'" data-link><span class="definition-icon">'+icon("agent")+'</span><span class="definition-open">'+icon("external")+'</span><h3>'+esc(def.id)+'</h3><p>'+esc(def.description)+'</p><div>'+tags(features)+'</div><div class="definition-meta"><b>'+count(instances.length,"instance")+'</b><span class="muted mono" style="font-size:9px">'+esc(def.file)+'</span></div></a>'}).join("")+'</div>';
3250
+ const rows=recent(state.instances).map(instance=>'<tr class="clickable" data-go="/instances/'+attr(instance.id)+'"><td><span class="primary-cell">'+esc(short(instance.id))+'</span><span class="secondary">'+esc(instance.workspaceId)+'</span></td><td><a class="link" href="/agents/'+attr(instance.definitionId)+'" data-link>'+esc(instance.definitionId)+'</a></td><td>'+instance.installations.length+'</td><td>'+instance.playbooks.length+'</td><td>'+esc(fmtDate(instance.updatedAt))+'</td></tr>').join("");
3251
+ html+=card("Runtime instances",'<div class="table-wrap"><table class="table"><thead><tr><th>Instance</th><th>Definition</th><th>Installed</th><th>Playbooks</th><th>Updated</th></tr></thead><tbody>'+rows+'</tbody></table></div>'+(rows?"":empty("No runtime instances","Start a run or create an instance from a definition.")),count(state.instances.length,"instance"));
3252
+ $("content").innerHTML=html;document.querySelectorAll("[data-new-run]").forEach(x=>x.onclick=openRunDrawer);dataLinks();
3253
+ }
3254
+
3255
+ function renderDefinition(id){
3256
+ const def=definition(id);if(!def)return renderNotFound("Agent definition");
3257
+ crumbs([{label:"Agents",href:"/agents"},{label:id}]);
3258
+ const instances=state.instances.filter(x=>x.definitionId===id);const caps=state.manifest?.definitions?.[id]?.capabilities||{tools:[],applications:[],mcp:[],memory:[]};const surfaces=state.manifest?.definitions?.[id]?.surfaces||{layers:[],subscribers:[]};
3259
+ let html=pageHead("Agent definition",id,def.description,'<button class="button" data-create-instance="'+attr(id)+'">Create instance</button><button class="button primary" data-new-run="'+attr(id)+'">Start a run</button>');
3260
+ html+='<div class="detail-strip"><div><label>File route</label><strong class="mono">'+esc(def.file)+'</strong></div><div><label>Assembly</label><strong>'+esc(def.assembly)+" · "+esc(def.handler)+' handler</strong></div><div><label>Runtime surfaces</label><strong>'+(def.workingEnvironment?"Working environment":"No working environment")+(def.repl?" · "+esc(def.repl)+" REPL":"")+'</strong></div><div><label>Lazy fields</label><strong>'+esc(def.lazy.length?def.lazy.join(", "):"None")+'</strong></div></div>';
3261
+ const rows=instances.map(x=>'<tr class="clickable" data-go="/instances/'+attr(x.id)+'"><td><span class="primary-cell">'+esc(short(x.id))+'</span><span class="secondary">'+esc(x.workspaceId)+'</span></td><td>'+x.installations.length+'</td><td>'+x.playbooks.length+'</td><td>'+esc(fmtDate(x.updatedAt))+'</td></tr>').join("");
3262
+ html+=card("Runtime instances",'<div class="table-wrap"><table class="table"><thead><tr><th>Instance</th><th>Installed</th><th>Playbooks</th><th>Updated</th></tr></thead><tbody>'+rows+'</tbody></table></div>'+(rows?"":empty("No instance exists","Create one now or let a playbook provision one when an inbound event matches.")),count(instances.length,"instance"));
3263
+ html+='<div class="grid cols-2">';
3264
+ const groups=[["Shared tools",caps.tools],["Applications",caps.applications],["MCP",caps.mcp],["Memory",caps.memory]];
3265
+ html+=card("Capability catalogue",'<div class="card-body">'+groups.map(group=>'<div style="margin-bottom:14px"><span class="secondary">'+esc(group[0])+'</span><div class="cap-list">'+(group[1].length?group[1].map(x=>'<span class="cap">'+esc(x.id)+'</span>').join(""):'<span class="muted" style="font-size:11px">None discovered</span>')+'</div></div>').join("")+'</div>',"Available to instances");
3266
+ html+=card("Native composition",'<div class="card-body"><div class="summary-box"><dl><dt>Layers</dt><dd>'+esc((surfaces.layers||[]).map(x=>x.id).join(", ")||"None")+'</dd><dt>Subscribers</dt><dd>'+esc((surfaces.subscribers||[]).map(x=>x.id).join(", ")||"None")+'</dd><dt>Calls</dt><dd>'+esc(def.calls.join(", ")||"None")+'</dd><dt>Subagents</dt><dd>'+esc(def.subagents.join(", ")||"None")+'</dd><dt>Schedules</dt><dd>'+esc(def.schedules.join(", ")||"None")+'</dd><dt>Playbooks</dt><dd>'+esc(def.playbooks.join(", ")||"None")+'</dd></dl></div></div>',"Code-defined");
3267
+ html+='</div>';$("content").innerHTML=html;document.querySelectorAll("[data-new-run]").forEach(x=>x.onclick=()=>openRunDrawer(id));document.querySelectorAll("[data-create-instance]").forEach(x=>x.onclick=()=>void createInstance(id));dataLinks();
3268
+ }
3269
+
3270
+ async function ensureConversations(agentId){if(!state.conversations[agentId])state.conversations[agentId]=await api("/api/conversations?agent="+encodeURIComponent(agentId))}
3271
+ function renderInstance(id){
3272
+ const instance=state.instances.find(x=>x.id===id);if(!instance)return renderNotFound("Agent instance");
3273
+ crumbs([{label:"Agents",href:"/agents"},{label:instance.definitionId,href:"/agents/"+instance.definitionId},{label:short(id)}]);
3274
+ const conversations=state.conversations[id]||[];const runs=recent(state.runs.filter(x=>x.agentId===id));
3275
+ let html=pageHead("Runtime instance",short(id),"Persisted runtime data assembled from "+instance.definitionId+". This identity can be updated without changing the code definition.",'<button class="button primary" data-instance-run>Send message</button>');
3276
+ html+='<div class="detail-strip"><div><label>Definition</label><strong><a class="link" href="/agents/'+attr(instance.definitionId)+'" data-link>'+esc(instance.definitionId)+'</a></strong></div><div><label>Workspace</label><strong><a class="link mono" href="/workspaces/'+attr(instance.workspaceId)+'" data-link>'+esc(instance.workspaceId)+'</a></strong></div><div><label>Conversations</label><strong>'+conversations.length+'</strong></div><div><label>Last updated</label><strong>'+esc(fmtDate(instance.updatedAt))+'</strong></div></div>';
3277
+ html+='<div class="grid cols-2">';
3278
+ const convRows=recent(conversations).map(c=>'<tr><td><span class="primary-cell">'+esc(c.title||"Untitled conversation")+'</span><span class="secondary">'+esc(short(c.id))+'</span></td><td>'+esc(fmtDate(c.updatedAt))+'</td></tr>').join("");
3279
+ html+=card("Conversations",'<div class="table-wrap"><table class="table"><thead><tr><th>Conversation</th><th>Updated</th></tr></thead><tbody>'+convRows+'</tbody></table></div>'+(convRows?"":empty("No conversations","The first message creates a conversation for this instance.")),count(conversations.length,"conversation"));
3280
+ const runRows=runs.slice(0,8).map(run=>'<tr class="clickable" data-go="/runs/'+attr(run.id)+'"><td><span class="primary-cell">'+esc(short(run.id))+'</span><span class="secondary">'+esc(explainRun(run))+'</span></td><td>'+status(run.status)+'</td><td>'+esc(fmtDate(run.createdAt))+'</td></tr>').join("");
3281
+ html+=card("Recent runs",'<div class="table-wrap"><table class="table"><thead><tr><th>Run</th><th>Status</th><th>Started</th></tr></thead><tbody>'+runRows+'</tbody></table></div>'+(runRows?"":empty("No runs","Send this instance a message to begin.")),count(runs.length,"run"));
3282
+ html+=card("Installed capabilities",'<div class="card-body">'+(instance.installations.length?instance.installations.map(x=>'<div class="cap">'+esc(x.kind)+" · "+esc(x.id)+(x.accountId?" · account "+esc(x.accountId):"")+'</div>').join(""):empty("Nothing installed","Applications, MCP servers, and shared tools are instance data."))+'</div>',count(instance.installations.length,"installation"));
3283
+ html+=card("Playbooks and context",'<div class="card-body"><div class="summary-box"><dl><dt>Playbooks</dt><dd>'+esc(instance.playbooks.map(x=>x.name||x.id).join(", ")||"None")+'</dd><dt>Context keys</dt><dd>'+esc(Object.keys(instance.context||{}).join(", ")||"None")+'</dd><dt>Provisioning key</dt><dd class="mono">'+esc(instance.provisioningKey||"Directly provisioned")+'</dd></dl></div><details><summary class="link">View stored instance data</summary><pre class="json">'+esc(jsonText(instance))+'</pre></details></div>',"Persisted data");
3284
+ html+='</div>';$("content").innerHTML=html;document.querySelector("[data-instance-run]").onclick=()=>openRunDrawer(instance.definitionId,id);dataLinks();
3285
+ }
3286
+
3287
+ function renderRuns(){
3288
+ crumbs([{label:"Runs"}]);let runs=recent(state.runs);if(state.filters.runs!=="all")runs=runs.filter(x=>x.status===state.filters.runs);
3289
+ let html=pageHead("Execution history","Runs","Each invocation has one status, one result, and a chronological trace. Open a run to follow assembly, model work, tools, and completion.",'<button class="button primary" data-new-run>Start a run</button>');
3290
+ html+='<div class="filters"><input id="run-search" placeholder="Search agent, run id, or input…"><select id="run-filter"><option value="all">All statuses</option><option value="running">Running</option><option value="completed">Completed</option><option value="failed">Failed</option><option value="cancelled">Cancelled</option></select></div>';
3291
+ const rows=runs.map(run=>'<tr class="clickable run-search-row" data-search="'+attr((run.agent+" "+run.id+" "+jsonText(run.input)).toLowerCase())+'" data-go="/runs/'+attr(run.id)+'"><td><span class="primary-cell">'+esc(run.agent)+'</span><span class="secondary">'+esc(short(run.id))+'</span></td><td>'+status(run.status)+'</td><td>'+esc(run.input?.source?.kind||"direct")+'</td><td>'+run.attempts+" / "+run.maxAttempts+'</td><td>'+esc(fmtDate(run.createdAt))+'</td></tr>').join("");
3292
+ html+=card("All runs",'<div class="table-wrap"><table class="table"><thead><tr><th>Agent / run</th><th>Status</th><th>Source</th><th>Attempts</th><th>Started</th></tr></thead><tbody>'+rows+'</tbody></table></div>'+(rows?"":empty("No matching runs","Change the filter or start a new run.")),count(runs.length,"run"));
3293
+ $("content").innerHTML=html;$("run-filter").value=state.filters.runs;$("run-filter").onchange=e=>{state.filters.runs=e.target.value;renderRuns()};$("run-search").oninput=e=>{const term=e.target.value.toLowerCase();document.querySelectorAll(".run-search-row").forEach(row=>row.classList.toggle("hidden",!row.dataset.search.includes(term)))};document.querySelector("[data-new-run]").onclick=openRunDrawer;dataLinks();
3294
+ }
3295
+
3296
+ function phaseData(run,events){
3297
+ const has=pattern=>events.some(e=>pattern.test(e.type));const time=pattern=>events.find(e=>pattern.test(e.type))?.timestamp;
3298
+ return[
3299
+ {name:"Accepted",detail:"The runtime recorded the invocation and its source.",state:"complete",time:run.createdAt},
3300
+ {name:"Assembled",detail:"Foundry resolved context-dependent tools, memory, apps, layers, and working surfaces.",state:has(/assembly.*complete|agent\.started|run\.started/)?"complete":run.status==="failed"?"error":"active",time:time(/assembly.*complete|run\.started/)},
3301
+ {name:"Agent work",detail:has(/tool_/)?"The model used one or more mounted tools.":"The model processed the request with its assembled context.",state:run.status==="running"?"active":run.status==="failed"?"error":"complete",time:time(/model_|text_delta|tool_/)},
3302
+ {name:run.status==="failed"?"Failed":run.status==="cancelled"?"Cancelled":run.status==="completed"?"Completed":"In progress",detail:explainRun(run),state:run.status==="failed"||run.status==="cancelled"?"error":run.status==="completed"?"complete":"active",time:run.completedAt||time(/run\.(completed|failed|cancelled)/)}
3303
+ ];
3304
+ }
3305
+ function renderRun(id){
3306
+ const run=state.runs.find(x=>x.id===id);if(!run)return renderNotFound("Run");const events=eventsFor(id);
3307
+ const visibleEvents=state.showAllEvents?events:events.filter(isKeyRunEvent);
3308
+ crumbs([{label:"Runs",href:"/runs"},{label:short(id)}]);
3309
+ let html=pageHead("Run detail",run.agent+" · "+short(id),"Follow the high-level phases first. Expand individual events only when you need raw adapter or model evidence.",run.status==="running"||run.status==="pending"?'<button class="button" data-cancel>Cancel run</button>':"");
3310
+ html+='<div class="detail-strip"><div><label>Status</label><strong>'+status(run.status)+'</strong></div><div><label>Source</label><strong>'+esc(run.input?.source?.kind||"direct")+'</strong></div><div><label>Agent instance</label><strong>'+(run.agentId?'<a class="link mono" href="/instances/'+attr(run.agentId)+'" data-link>'+esc(short(run.agentId))+'</a>':"Not recorded")+'</strong></div><div><label>Attempts</label><strong>'+run.attempts+" of "+run.maxAttempts+'</strong></div></div>';
3311
+ html+='<div class="grid cols-3"><section class="card span-2"><div class="card-head"><h2>Run spine</h2><span class="meta">'+count(events.length,"event")+'</span></div><div class="card-body"><div class="trace-note">This shows observable work intent and outcomes. It does not expose private hidden chain-of-thought.</div><div class="run-spine">'+phaseData(run,events).map(p=>'<div class="phase '+p.state+'"><span class="phase-dot"></span><div><strong>'+esc(p.name)+'</strong><p>'+esc(p.detail)+'</p></div><time>'+esc(fmtTime(p.time))+'</time></div>').join("")+'</div></div></section>';
3312
+ html+=card("Result",'<div class="card-body"><div class="summary-box"><dl><dt>Created</dt><dd>'+esc(fmtDate(run.createdAt))+'</dd><dt>Started</dt><dd>'+esc(fmtDate(run.startedAt))+'</dd><dt>Finished</dt><dd>'+esc(fmtDate(run.completedAt))+'</dd><dt>Timeout</dt><dd>'+Math.round(run.timeoutMs/1000)+' seconds</dd></dl></div><details open><summary class="link">Output</summary><pre class="json">'+esc(jsonText(run.output??run.error??null))+'</pre></details><details><summary class="link">Input</summary><pre class="json">'+esc(jsonText(run.input))+'</pre></details></div>',"Recorded outcome");html+='</div>';
3313
+ const eventRows=visibleEvents.map(event=>'<div class="event-row"><button class="event-toggle"><span class="event-time">'+esc(fmtTime(event.timestamp))+'</span><span class="event-category">'+esc(event.category)+'</span><span class="event-type">'+esc(event.type)+'</span><span>⌄</span></button><div class="event-detail"><pre class="json">'+esc(jsonText(event.data))+'</pre></div></div>').join("");
3314
+ const eventControl='<div class="card-body" style="display:flex;align-items:center;gap:12px;padding-top:11px;padding-bottom:11px"><span class="muted" style="font-size:11px">'+(state.showAllEvents?"Showing every retained event, including assembly and process detail.":"Showing the lifecycle events that explain this run. Expand one for raw evidence.")+'</span><button class="button" style="margin-left:auto" data-toggle-events>'+(state.showAllEvents?"Show key events":"Show all "+events.length+" events")+'</button></div>';
3315
+ html+=card("Observable event trace",eventControl+(eventRows||empty("No key events retained","Show all events to inspect runtime logs.")),count(visibleEvents.length,"event"));
3316
+ $("content").innerHTML=html;document.querySelectorAll(".event-toggle").forEach(x=>x.onclick=()=>x.parentElement.classList.toggle("open"));document.querySelector("[data-toggle-events]").onclick=()=>{state.showAllEvents=!state.showAllEvents;renderRun(id)};const cancel=document.querySelector("[data-cancel]");if(cancel)cancel.onclick=()=>void cancelRun(id);dataLinks();
3317
+ }
3318
+
3319
+ function renderAutomations(){
3320
+ crumbs([{label:"Automations"}]);let html=pageHead("Background work","Automations","Schedules and sleeps create future activations. Playbook subscriptions listen for inbound transmissions and can provision one or many agent instances.","");
3321
+ html+='<div class="grid cols-3">'+metric("Scheduled activations",state.activations.length,"Future triggers and sleeping runs",true)+metric("Playbook subscriptions",state.subscriptions.length,"Inbound event policies")+metric("Application connections",state.connections.length,"Long-lived inbound workers")+'</div>';
3322
+ const actRows=recent(state.activations).map(x=>'<tr><td><span class="primary-cell">'+esc(x.kind==="sleep"?"Sleeping run":x.scheduleName||"Scheduled activation")+'</span><span class="secondary">'+esc(short(x.id))+'</span></td><td><a class="link" href="/instances/'+attr(x.agentId)+'" data-link>'+esc(short(x.agentId))+'</a></td><td>'+status(x.status)+'</td><td>'+esc(x.timing?.at||x.timing?.cron||x.timing?.everyMs||"Configured timing")+'</td><td>'+esc(x.origin)+'</td></tr>').join("");
3323
+ html+=card("Schedules and sleeps",'<div class="table-wrap"><table class="table"><thead><tr><th>Activation</th><th>Agent</th><th>Status</th><th>Timing</th><th>Origin</th></tr></thead><tbody>'+actRows+'</tbody></table></div>'+(actRows?"":empty("No future activations","Agents create schedules and sleeps through Foundry utility tools.")),count(state.activations.length,"activation"));
3324
+ const subRows=state.subscriptions.map(x=>'<tr><td><span class="primary-cell">'+esc(x.playbook?.name||x.playbook?.id||x.id)+'</span><span class="secondary">'+esc(short(x.id))+'</span></td><td>'+status(x.enabled?"enabled":"disabled")+'</td><td>'+esc(x.targets.map(t=>t.definitionId+" · "+t.provisioning.mode).join(", "))+'</td><td>'+esc(x.workspaceId)+'</td></tr>').join("");
3325
+ html+=card("Playbook listeners",'<div class="table-wrap"><table class="table"><thead><tr><th>Playbook</th><th>State</th><th>Targets / provisioning</th><th>Workspace</th></tr></thead><tbody>'+subRows+'</tbody></table></div>'+(subRows?"":empty("No playbook subscriptions","Runtime-defined subscriptions appear when a frontend or adapter installs them.")),count(state.subscriptions.length,"subscription"));
3326
+ const connRows=state.connections.map(x=>'<tr><td><span class="primary-cell">'+esc(x.applicationId)+'</span><span class="secondary">'+esc(x.connectionId)+'</span></td><td>'+status(x.status)+'</td><td>'+esc(x.definitionId)+'</td><td>'+x.routeIds.length+'</td><td>'+esc(fmtDate(x.lastEventAt))+'</td></tr>').join("");
3327
+ html+=card("Inbound application workers",'<div class="table-wrap"><table class="table"><thead><tr><th>Application / connection</th><th>Status</th><th>Definition</th><th>Routes</th><th>Last event</th></tr></thead><tbody>'+connRows+'</tbody></table></div>'+(connRows?"":empty("No inbound workers","A connection starts only when an installed app has an active inbound playbook.")),count(state.connections.length,"connection"));
3328
+ $("content").innerHTML=html;dataLinks();
3329
+ }
3330
+
3331
+ function renderIntegrations(){
3332
+ crumbs([{label:"Integrations"}]);let html=pageHead("External boundaries","Integrations","Inspect application transmissions and the runtime data that binds accounts, routes, and agent instances. Credential acquisition and refresh remain adapter-owned.","");
3333
+ html+='<div class="callout"><span class="symbol">'+icon("secure")+'</span><div><b>Foundry stores references, never credential material.</b><p>Accounts expose safe metadata and opaque adapter ownership. Routes and bindings determine what an instance can receive and send.</p></div></div>';
3334
+ const txRows=state.transmissions.map(x=>'<tr><td><span class="primary-cell">'+esc(x.name)+'</span><span class="secondary">'+esc(x.id)+'</span></td><td><span class="tag">'+esc(x.shape)+'</span></td><td>'+x.capabilities.length+'</td><td>'+esc(x.description)+'</td></tr>').join("");
3335
+ html+=card("Transmission catalogue",'<div class="table-wrap"><table class="table"><thead><tr><th>Transmission</th><th>Shape</th><th>Capabilities</th><th>Purpose</th></tr></thead><tbody>'+txRows+'</tbody></table></div>'+(txRows?"":empty("No transmissions discovered","Install an agent-local application definition to add inbound or outbound behavior.")),count(state.transmissions.length,"transmission"));
3336
+ html+='<div class="grid cols-3">';
3337
+ html+=card("Accounts",'<div class="card-body">'+(state.accounts.length?state.accounts.map(x=>'<div class="kv-item"><strong>'+esc(x.label||x.externalAccountId)+'</strong><small>'+esc(x.transmissionId)+" · "+esc(short(x.id))+'</small></div>').join(""):empty("No accounts","Account references are supplied by the application adapter."))+'</div>',count(state.accounts.length,"account"));
3338
+ html+=card("Routes",'<div class="card-body">'+(state.routes.length?state.routes.map(x=>'<div class="kv-item"><strong>'+esc(x.id)+'</strong><small>'+esc(x.direction)+" · "+esc(x.transmissionId)+'</small><div>'+status(x.enabled?"enabled":"disabled")+'</div></div>').join(""):empty("No routes","Routes are runtime data for transmission paths."))+'</div>',count(state.routes.length,"route"));
3339
+ html+=card("Agent bindings",'<div class="card-body">'+(state.bindings.length?state.bindings.map(x=>'<div class="kv-item"><strong>'+esc(short(x.agentId))+'</strong><small>'+esc(x.transmissionId)+" · "+x.capabilities.length+' capabilities</small><div>'+status(x.enabled?"enabled":"disabled")+'</div></div>').join(""):empty("No bindings","Bindings grant an instance access to routes and capabilities."))+'</div>',count(state.bindings.length,"binding"));
3340
+ html+='</div>';$("content").innerHTML=html;dataLinks();
3341
+ }
3342
+
3343
+ async function ensureWorkspace(id){if(!id)return;const values=await Promise.all(["entries","inbox","tasks","environment"].map(surface=>api("/api/workspaces/"+encodeURIComponent(id)+"/"+surface)));state.workspaces[id]={entries:values[0],inbox:values[1],tasks:values[2],environment:values[3]}}
3344
+ function renderWorkspaces(id){
3345
+ const ids=workspaceIds();if(!id&&ids.length){go("/workspaces/"+encodeURIComponent(ids[0]));return}
3346
+ crumbs(id?[{label:"Workspaces",href:"/workspaces"},{label:id}]:[{label:"Workspaces"}]);
3347
+ if(!id){$("content").innerHTML=pageHead("Shared data","Workspaces","Workspaces hold shared entries, an inbox, tasks, and safe environment values across agent instances.","")+empty("No workspaces yet","Create an agent instance with a workspace id to begin.");return}
3348
+ const data=state.workspaces[id]||{entries:[],inbox:[],tasks:[],environment:[]};const localInstances=state.instances.filter(x=>x.workspaceId===id);
3349
+ let html=pageHead("Shared data","Workspace · "+id,"Inspect collaboration state shared by this workspace. Secret environment values are intentionally never exposed.",'<select class="button" id="workspace-select">'+ids.map(x=>'<option value="'+attr(x)+'" '+(x===id?"selected":"")+'>'+esc(x)+'</option>').join("")+'</select>');
3350
+ html+='<div class="grid cols-4">'+metric("Agent instances",localInstances.length,"Runtime identities in this workspace",true)+metric("Shared entries",data.entries.length,"Documents and structured values")+metric("Inbox items",data.inbox.length,"Cross-agent and external handoffs")+metric("Open tasks",data.tasks.filter(x=>x.status==="open"||x.status==="in-progress").length,"Work still requiring action")+'</div>';
3351
+ html+='<div class="workspace-tabs"><button data-tab="entries">Entries</button><button data-tab="inbox">Inbox</button><button data-tab="tasks">Tasks</button><button data-tab="environment">Environment</button></div><div id="workspace-panel"></div>';
3352
+ $("content").innerHTML=html;$("workspace-select").onchange=e=>go("/workspaces/"+encodeURIComponent(e.target.value));document.querySelectorAll("[data-tab]").forEach(x=>x.onclick=()=>{state.workspaceTab=x.dataset.tab;renderWorkspacePanel(data)});renderWorkspacePanel(data);dataLinks();
3353
+ }
3354
+ function renderWorkspacePanel(data){
3355
+ document.querySelectorAll("[data-tab]").forEach(x=>x.classList.toggle("active",x.dataset.tab===state.workspaceTab));const values=data[state.workspaceTab]||[];let body="";
3356
+ if(state.workspaceTab==="entries")body=values.length?'<div class="kv">'+values.map(x=>'<div class="kv-item"><strong>'+esc(x.key)+'</strong><small>Updated '+esc(fmtDate(x.updatedAt))+'</small><div class="kv-value">'+esc(typeof x.value==="string"?x.value:jsonText(x.value))+'</div></div>').join("")+'</div>':empty("No shared entries","Agents can place documents and structured data into this workspace.");
3357
+ if(state.workspaceTab==="inbox")body=values.length?'<div class="table-wrap"><table class="table"><thead><tr><th>Topic</th><th>Agent</th><th>Status</th><th>Updated</th></tr></thead><tbody>'+recent(values).map(x=>'<tr><td><span class="primary-cell">'+esc(x.topic)+'</span><span class="secondary">'+esc(short(x.id))+'</span></td><td>'+esc(short(x.agentId||"shared"))+'</td><td>'+status(x.status)+'</td><td>'+esc(fmtDate(x.updatedAt))+'</td></tr>').join("")+'</tbody></table></div>':empty("Inbox is clear","Shared handoffs and external requests will appear here.");
3358
+ if(state.workspaceTab==="tasks")body=values.length?'<div class="table-wrap"><table class="table"><thead><tr><th>Task</th><th>Owner</th><th>Status</th><th>Updated</th></tr></thead><tbody>'+recent(values).map(x=>'<tr><td><span class="primary-cell">'+esc(x.title)+'</span><span class="secondary">'+esc(x.detail||short(x.id))+'</span></td><td>'+esc(short(x.agentId||"workspace"))+'</td><td>'+status(x.status)+'</td><td>'+esc(fmtDate(x.updatedAt))+'</td></tr>').join("")+'</tbody></table></div>':empty("No tasks","Agents can create shared tasks for work that spans conversations.");
3359
+ if(state.workspaceTab==="environment")body='<div class="callout"><span class="symbol">'+icon("secure")+'</span><div><b>Only safe values are visible.</b><p>Credential material remains inside user-owned adapters and is never returned by this endpoint.</p></div></div>'+(values.length?'<div class="kv">'+values.map(x=>'<div class="kv-item"><strong>'+esc(x.key)+'</strong><small>'+esc(x.scope)+'</small><div class="kv-value">'+esc(typeof x.value==="string"?x.value:jsonText(x.value))+'</div></div>').join("")+'</div>':empty("No public environment values","Mount an environment adapter to expose non-secret context."));
3360
+ $("workspace-panel").innerHTML=card(state.workspaceTab[0].toUpperCase()+state.workspaceTab.slice(1),'<div class="card-body">'+body+'</div>',count(values.length,"item"));
3361
+ }
3362
+
3363
+ function renderNotFound(label){crumbs([{label:"Not found"}]);$("content").innerHTML=pageHead("Inspector",label+" not found","The requested runtime record does not exist or is no longer retained.",'<a class="button" href="/" data-link>Return to overview</a>');dataLinks()}
3364
+ async function loadBase(){
3365
+ const endpoints=["/api/manifest","/api/agent-instances","/api/playbook-subscriptions","/api/application-connections","/api/runs","/api/events","/health","/api/transmissions","/api/accounts","/api/routes","/api/bindings","/api/activations"];
3366
+ const results=await Promise.all(endpoints.map(url=>api(url)));[state.manifest,state.instances,state.subscriptions,state.connections,state.runs,state.events,state.health,state.transmissions,state.accounts,state.routes,state.bindings,state.activations]=results;renderHealth();
3367
+ }
3368
+ async function refreshActivity(){
3369
+ try{const endpoints=["/api/agent-instances","/api/playbook-subscriptions","/api/application-connections","/api/runs","/api/events","/api/activations","/health"];const values=await Promise.all(endpoints.map(url=>api(url)));[state.instances,state.subscriptions,state.connections,state.runs,state.events,state.activations,state.health]=values;renderHealth();await navigate(false)}catch{$("event-state").textContent="Refresh paused"}
3370
+ }
3371
+ function renderHealth(){const ok=Boolean(state.health?.ok);$("runtime-state").classList.toggle("bad",!ok);$("runtime-state").querySelector("span").textContent=ok?"Runtime healthy":"Runtime needs attention"}
3372
+ async function navigate(load=true){
3373
+ const current=route();navState(current.section);
3374
+ try{
3375
+ if(load&&current.section==="instances"&&current.id)await ensureConversations(current.id);
3376
+ if(load&&current.section==="workspaces"&&current.id)await ensureWorkspace(current.id);
3377
+ if(current.section==="overview")renderOverview();else if(current.section==="agents"&&!current.id)renderAgents();else if(current.section==="agents")renderDefinition(current.id);else if(current.section==="instances")renderInstance(current.id);else if(current.section==="runs"&&!current.id)renderRuns();else if(current.section==="runs")renderRun(current.id);else if(current.section==="automations")renderAutomations();else if(current.section==="integrations")renderIntegrations();else if(current.section==="workspaces")renderWorkspaces(current.id);else renderNotFound("Page");
3378
+ }catch(error){$("content").innerHTML='<div class="form-error">'+esc(error.message)+'</div>';console.error(error)}
3379
+ }
3380
+
3381
+ function openRunDrawer(definitionId,instanceId){
3382
+ const definitions=state.manifest?.agents?.agents||[];$("run-definition").innerHTML=definitions.map(x=>'<option value="'+attr(x.id)+'">'+esc(x.id)+'</option>').join("");
3383
+ if(typeof definitionId==="string"&&definition(definitionId))$("run-definition").value=definitionId;updateInstanceOptions(instanceId);$("run-message").value="";$("run-form-error").classList.add("hidden");$("drawer").classList.add("open");setTimeout(()=>$("run-message").focus(),20);
3384
+ }
3385
+ function closeRunDrawer(){$("drawer").classList.remove("open")}
3386
+ function updateInstanceOptions(selected){const definitionId=$("run-definition").value;const instances=state.instances.filter(x=>x.definitionId===definitionId);$("run-instance").innerHTML='<option value="">Create a new instance</option>'+instances.map(x=>'<option value="'+attr(x.id)+'">'+esc(short(x.id))+" · "+esc(x.workspaceId)+'</option>').join("");if(selected&&instances.some(x=>x.id===selected))$("run-instance").value=selected}
3387
+ async function createInstance(definitionId){try{const workspace=workspaceIds()[0]||"foundry-dashboard";const instance=await api("/api/agent-instances",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({definitionId,workspaceId:workspace})});state.instances=[...state.instances,instance];toast("Instance created");go("/instances/"+encodeURIComponent(instance.id))}catch(error){toast(error.message)}}
3388
+ async function startRun(event){
3389
+ event.preventDefault();const errorNode=$("run-form-error");errorNode.classList.add("hidden");
3390
+ try{
3391
+ const definitionId=$("run-definition").value;let instance=state.instances.find(x=>x.id===$("run-instance").value);
3392
+ if(!instance){instance=await api("/api/agent-instances",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({definitionId,workspaceId:workspaceIds()[0]||"foundry-dashboard"})});state.instances=[...state.instances,instance]}
3393
+ await ensureConversations(instance.id);let conversation=state.conversations[instance.id][0];
3394
+ if(!conversation){conversation=await api("/api/conversations",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({agentId:instance.id,title:"Foundry inspector"})});state.conversations[instance.id]=[conversation]}
3395
+ const run=await api("/api/conversations/"+encodeURIComponent(conversation.id)+"/messages",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({agentId:instance.id,message:$("run-message").value})});state.runs=[...state.runs,run];closeRunDrawer();toast("Run accepted");go("/runs/"+encodeURIComponent(run.id));
3396
+ }catch(error){errorNode.textContent=error.message;errorNode.classList.remove("hidden")}
3397
+ }
3398
+ async function cancelRun(id){try{await api("/api/runs/"+encodeURIComponent(id)+"/cancel",{method:"POST"});toast("Cancellation requested");await refreshActivity()}catch(error){toast(error.message)}}
3399
+
3400
+ function searchItems(){
3401
+ const items=[{label:"Overview",detail:"Runtime health and recent activity",path:"/"},{label:"Agents",detail:"Definitions and runtime instances",path:"/agents"},{label:"Runs",detail:"Execution history and traces",path:"/runs"},{label:"Automations",detail:"Schedules, sleeps, playbooks, and connections",path:"/automations"},{label:"Integrations",detail:"Transmissions, routes, accounts, and bindings",path:"/integrations"},{label:"Workspaces",detail:"Shared entries, inbox, tasks, and environment",path:"/workspaces"}];
3402
+ (state.manifest?.agents?.agents||[]).forEach(x=>items.push({label:x.id,detail:"Agent definition · "+x.description,path:"/agents/"+x.id}));state.instances.forEach(x=>items.push({label:short(x.id),detail:"Agent instance · "+x.definitionId,path:"/instances/"+x.id}));recent(state.runs).slice(0,100).forEach(x=>items.push({label:short(x.id),detail:"Run · "+x.agent+" · "+x.status,path:"/runs/"+x.id}));return items;
3403
+ }
3404
+ function renderSearch(){const term=$("search-input").value.trim().toLowerCase();const matches=searchItems().filter(x=>!term||(x.label+" "+x.detail).toLowerCase().includes(term)).slice(0,12);$("search-results").innerHTML=matches.map(x=>'<a class="search-result" href="'+attr(x.path)+'" data-search-link><strong>'+esc(x.label)+'</strong><small>'+esc(x.detail)+'</small></a>').join("")||empty("No matches","Try an agent id, instance id, run id, or page name.");document.querySelectorAll("[data-search-link]").forEach(x=>x.onclick=e=>{e.preventDefault();closeSearch();go(x.getAttribute("href"))})}
3405
+ function openSearch(){$("search-modal").classList.add("open");$("search-input").value="";renderSearch();setTimeout(()=>$("search-input").focus(),20)}function closeSearch(){$("search-modal").classList.remove("open")}
3406
+
3407
+ $("new-run").onclick=()=>openRunDrawer();$("close-drawer").onclick=closeRunDrawer;$("cancel-run").onclick=closeRunDrawer;$("drawer").onclick=e=>{if(e.target===$("drawer"))closeRunDrawer()};$("run-definition").onchange=()=>updateInstanceOptions();$("run-form").onsubmit=startRun;$("open-search").onclick=openSearch;$("search-input").oninput=renderSearch;$("search-modal").onclick=e=>{if(e.target===$("search-modal"))closeSearch()};$("mobile-toggle").onclick=()=>$("sidebar").classList.toggle("open");window.onpopstate=()=>void navigate();document.addEventListener("keydown",e=>{if((e.metaKey||e.ctrlKey)&&e.key.toLowerCase()==="k"){e.preventDefault();openSearch()}if(e.key==="Escape"){closeSearch();closeRunDrawer()}});
3408
+ loadBase().then(async()=>{await navigate();const stream=new EventSource("/api/events");stream.onopen=()=>{$("event-state").textContent="Live updates"};stream.onmessage=()=>void refreshActivity();stream.onerror=()=>{$("event-state").textContent="Reconnecting…"};setInterval(()=>void refreshActivity(),10000)}).catch(error=>{$("content").innerHTML='<div class="form-error"><strong>Could not open Foundry.</strong><br>'+esc(error.message)+'</div>';$("runtime-state").classList.add("bad");$("runtime-state").querySelector("span").textContent="Runtime unavailable"});
3409
+ `;
3410
+
3411
+ // src/dashboard-styles.ts
3412
+ var DASHBOARD_STYLES = String.raw`
3413
+ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,300;0,400;0,500;0,600;1,400&family=JetBrains+Mono:wght@400;500&display=swap');
3414
+ :root{
3415
+ --bg:#080B0A;--bg-elevated:#0F1613;--bg-surface:#141D18;--bg-surface-2:#19241F;
3416
+ --border:#243029;--border-subtle:#18211C;--border-strong:#32453B;
3417
+ --text-primary:#E7EEEA;--text-secondary:#93A399;--text-tertiary:#5C6F64;
3418
+ --accent:#9ED4B8;--accent-soft:#7BBFA0;--accent-strong:#BEE7D0;
3419
+ --accent-dim:rgba(158,212,184,.08);--accent-glow:rgba(158,212,184,.045);--accent-line:rgba(158,212,184,.16);
3420
+ --interface:#E4B879;--network:#83B3E2;--deploy:#B8A7E8;--media:#E29BA8;
3421
+ --success:#8CC494;--warn:#E4B879;--danger:#E29BA8;
3422
+ --sans:'DM Sans',-apple-system,BlinkMacSystemFont,sans-serif;--mono:'JetBrains Mono',monospace;
3423
+ --sidebar:248px;--radius:10px;--radius-lg:14px;--shadow:0 24px 64px rgba(0,0,0,.34);
3424
+ font-family:var(--sans);color:var(--text-primary);background:var(--bg);color-scheme:dark;
3425
+ }
3426
+ *{box-sizing:border-box}html,body{margin:0;min-height:100%;background:var(--bg)}html{scroll-behavior:smooth}body{font-family:var(--sans);color:var(--text-primary);-webkit-font-smoothing:antialiased;overflow-x:hidden}
3427
+ body:before{content:"";position:fixed;inset:0 0 auto 0;height:100vh;background:radial-gradient(60% 40% at 55% -5%,rgba(158,212,184,.06),transparent 70%);pointer-events:none;z-index:0}
3428
+ button,input,textarea,select{font:inherit;color:inherit}button,a{touch-action:manipulation}button:focus-visible,a:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible{outline:2px solid var(--accent-soft);outline-offset:2px;border-radius:5px}
3429
+ ::selection{background:rgba(158,212,184,.22);color:var(--text-primary)}.mono{font-family:var(--mono)}.muted{color:var(--text-secondary)}.hidden{display:none!important}
3430
+ .icon{display:block;width:1em;height:1em;flex:0 0 auto;fill:currentColor}.icon-button{padding:8px;aspect-ratio:1}.icon-button .icon{width:17px;height:17px}
3431
+ .app{min-height:100vh;position:relative;z-index:1}.sidebar{position:fixed;inset:0 auto 0 0;width:var(--sidebar);background:rgba(8,11,10,.92);backdrop-filter:blur(20px) saturate(140%);border-right:1px solid var(--border-subtle);z-index:20;display:flex;flex-direction:column}
3432
+ .brand{height:72px;display:flex;align-items:center;gap:11px;padding:0 20px;border-bottom:1px solid var(--border-subtle);color:inherit;text-decoration:none}.brand-mark{width:26px;height:26px;color:var(--accent);filter:drop-shadow(0 3px 12px rgba(158,212,184,.2));transition:transform .25s ease}.brand:hover .brand-mark{transform:rotate(-7deg) scale(1.05)}.brand strong{display:block;font-size:14px;font-weight:600;letter-spacing:-.015em}.brand small{display:block;color:var(--text-tertiary);font:9px/1.4 var(--mono);text-transform:uppercase;letter-spacing:.13em;margin-top:3px}
3433
+ .nav{padding:20px 12px}.nav-label{padding:0 11px 9px;color:var(--text-tertiary);font:9px var(--mono);text-transform:uppercase;letter-spacing:.15em}.nav a{position:relative;color:var(--text-secondary);text-decoration:none;display:flex;align-items:center;gap:11px;padding:9px 11px;border-radius:7px;margin:2px 0;font-size:13px;transition:color .18s,background .18s}.nav a:hover{background:var(--accent-glow);color:var(--text-primary)}.nav a.active{background:var(--accent-dim);color:var(--accent)}.nav a.active:before{content:"";position:absolute;left:-12px;width:2px;height:22px;background:var(--accent)}.nav-icon{width:18px;height:18px;color:var(--text-tertiary);display:grid;place-items:center}.nav-icon svg{width:17px;height:17px;fill:currentColor}.nav a.active .nav-icon{color:var(--accent)}
3434
+ .sidebar-foot{margin-top:auto;padding:18px;border-top:1px solid var(--border-subtle)}.runtime-state{display:flex;align-items:center;gap:9px;font-size:11px;color:var(--text-secondary)}.runtime-state i{width:7px;height:7px;border-radius:50%;background:var(--success);box-shadow:0 0 8px rgba(140,196,148,.55)}.runtime-state.bad i{background:var(--danger)}.shortcut{margin-top:12px;width:100%;border:1px solid var(--border);background:var(--bg-elevated);color:var(--text-secondary);border-radius:7px;padding:8px 10px;text-align:left;font-size:11px;cursor:pointer;display:flex;align-items:center;justify-content:space-between}.shortcut:hover{border-color:var(--border-strong);color:var(--text-primary)}.shortcut-label{display:flex;align-items:center;gap:7px}.shortcut-label .icon{width:14px;height:14px}.shortcut kbd{color:var(--text-tertiary);font:9px var(--mono);border:0;background:transparent;padding:0}
3435
+ .main{margin-left:var(--sidebar);min-height:100vh}.topbar{height:72px;background:rgba(8,11,10,.72);backdrop-filter:blur(20px) saturate(140%);border-bottom:1px solid var(--border-subtle);display:flex;align-items:center;padding:0 32px;position:sticky;top:0;z-index:10}.breadcrumbs{display:flex;align-items:center;gap:8px;font:11px var(--mono);color:var(--text-tertiary)}.breadcrumbs a{color:var(--text-tertiary);text-decoration:none}.breadcrumbs a:hover{color:var(--accent)}.crumb-separator{display:grid;place-items:center;color:var(--text-tertiary)}.crumb-separator .icon{width:10px;height:10px}.top-actions{margin-left:auto;display:flex;align-items:center;gap:9px}.live-chip{display:flex;align-items:center;gap:7px;border:1px solid var(--border);background:var(--bg-elevated);padding:8px 10px;border-radius:7px;font:9px var(--mono);text-transform:uppercase;letter-spacing:.07em;color:var(--text-secondary)}.live-chip i{width:6px;height:6px;border-radius:50%;background:var(--success);box-shadow:0 0 8px rgba(140,196,148,.45)}
3436
+ .button{border:1px solid var(--border);background:var(--bg-elevated);color:var(--text-secondary);border-radius:7px;padding:9px 13px;font-weight:500;font-size:12px;cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:7px;transition:border-color .18s,background .18s,color .18s,transform .18s}.button>.icon{width:15px;height:15px}.button:hover{border-color:var(--border-strong);background:var(--bg-surface);color:var(--text-primary)}.button.primary{border-color:var(--accent);background:var(--accent);color:var(--bg);font-weight:600;box-shadow:0 2px 18px rgba(158,212,184,.12)}.button.primary:hover{background:var(--accent-strong);transform:translateY(-1px)}.mobile-toggle{display:none}.content{max-width:1480px;margin:0 auto;padding:36px 36px 72px;position:relative}
3437
+ .content:before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;background-image:linear-gradient(var(--border-subtle) 1px,transparent 1px),linear-gradient(90deg,var(--border-subtle) 1px,transparent 1px);background-size:56px 56px;mask-image:linear-gradient(to bottom,rgba(0,0,0,.25),transparent 300px);opacity:.34}
3438
+ .page-head{display:flex;align-items:flex-end;gap:24px;margin-bottom:28px}.eyebrow{display:flex;align-items:center;gap:8px;font:10px var(--mono);color:var(--accent-soft);text-transform:uppercase;letter-spacing:.14em;margin-bottom:12px}.eyebrow:before{content:"";width:18px;height:1px;background:var(--accent-line)}.page-head h1{font-size:34px;font-weight:400;line-height:1.08;letter-spacing:-.035em;margin:0}.page-head p{margin:9px 0 0;color:var(--text-secondary);max-width:690px;font-size:14px;font-weight:300;line-height:1.6}.page-head .actions{margin-left:auto;display:flex;gap:8px;align-items:center}.page-rule{width:42px;height:2px;background:var(--accent);margin-bottom:16px}
3439
+ .grid{display:grid;gap:14px}.cols-4{grid-template-columns:repeat(4,1fr)}.cols-3{grid-template-columns:repeat(3,1fr)}.cols-2{grid-template-columns:repeat(2,1fr)}.span-2{grid-column:span 2}.card{background:rgba(15,22,19,.92);border:1px solid var(--border-subtle);border-radius:var(--radius);min-width:0;margin-bottom:14px;overflow:hidden}.card-head{padding:14px 16px;border-bottom:1px solid var(--border-subtle);display:flex;align-items:center;gap:12px;background:rgba(20,29,24,.3)}.card-head h2{font-size:13px;font-weight:500;margin:0}.card-head .meta{margin-left:auto;color:var(--text-tertiary);font:9px var(--mono)}.card-body{padding:16px}.metric{padding:18px;min-height:124px;margin-bottom:0}.metric label{display:block;color:var(--text-tertiary);font:9px var(--mono);text-transform:uppercase;letter-spacing:.12em}.metric strong{display:block;font-size:30px;font-weight:400;letter-spacing:-.035em;margin-top:15px}.metric small{display:block;color:var(--text-secondary);font-size:11px;font-weight:300;margin-top:6px}.metric.accent{border-top:2px solid var(--accent)}
3440
+ .table-wrap{overflow:auto}.table{width:100%;border-collapse:collapse;min-width:650px}.table th{padding:11px 15px;text-align:left;color:var(--text-tertiary);font:9px var(--mono);text-transform:uppercase;letter-spacing:.1em;border-bottom:1px solid var(--border-subtle);white-space:nowrap}.table td{padding:13px 15px;border-bottom:1px solid var(--border-subtle);font-size:12px;vertical-align:top}.table tr:last-child td{border-bottom:0}.table tbody tr.clickable{cursor:pointer}.table tbody tr.clickable:hover{background:var(--accent-glow)}.primary-cell{font-weight:500;color:var(--text-primary)}.secondary{display:block;color:var(--text-tertiary);font:9px/1.55 var(--mono);margin-top:4px;overflow-wrap:anywhere}
3441
+ .status{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:4px 8px;background:var(--bg-surface-2);color:var(--text-secondary);font:8px var(--mono);text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;border:1px solid var(--border)}.status:before{content:"";width:5px;height:5px;border-radius:50%;background:var(--text-tertiary)}.status.completed,.status.connected,.status.active,.status.enabled,.status.resolved{background:rgba(140,196,148,.08);color:var(--success);border-color:rgba(140,196,148,.18)}.status.completed:before,.status.connected:before,.status.active:before,.status.enabled:before,.status.resolved:before{background:var(--success)}.status.running,.status.pending,.status.connecting,.status.reconnecting,.status.in-progress{background:rgba(131,179,226,.08);color:var(--network);border-color:rgba(131,179,226,.18)}.status.running:before,.status.pending:before,.status.connecting:before,.status.reconnecting:before,.status.in-progress:before{background:var(--network)}.status.failed,.status.cancelled,.status.disconnected,.status.dismissed{background:rgba(226,155,168,.08);color:var(--danger);border-color:rgba(226,155,168,.18)}.status.failed:before,.status.cancelled:before,.status.disconnected:before,.status.dismissed:before{background:var(--danger)}
3442
+ .tag{display:inline-block;border:1px solid var(--border);border-radius:4px;padding:3px 6px;font:8px var(--mono);color:var(--text-secondary);margin:2px 4px 2px 0;background:var(--bg-surface)}.link{color:var(--accent);text-decoration:none;font-weight:500}.link:hover{text-decoration:underline;text-decoration-color:var(--accent-line)}.inline-icon{display:inline-flex;align-items:center;gap:5px}.inline-icon .icon{width:12px;height:12px}.empty{padding:38px 22px;text-align:center;color:var(--text-secondary);font-size:12px;line-height:1.6}.empty strong{display:block;color:var(--text-primary);font-size:14px;font-weight:500;margin-bottom:5px}.filters{display:flex;gap:8px;margin-bottom:14px}.filters input,.filters select{background:var(--bg-elevated);border:1px solid var(--border);border-radius:7px;padding:10px 12px;font-size:12px}.filters input{min-width:280px}
3443
+ .definition-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;margin-bottom:16px}.definition-card{display:block;color:inherit;text-decoration:none;background:var(--bg-elevated);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);padding:19px;min-height:196px;position:relative;overflow:hidden;transition:transform .2s ease,border-color .2s ease,background .2s}.definition-card:before{content:"";position:absolute;inset:0;opacity:0;background:radial-gradient(110% 80% at 100% 0%,var(--accent-dim),transparent 60%);transition:opacity .2s}.definition-card:hover{transform:translateY(-2px);border-color:var(--accent-line);background:var(--bg-surface)}.definition-card:hover:before{opacity:1}.definition-icon{width:31px;height:31px;display:grid;place-items:center;border:1px solid var(--accent-line);border-radius:8px;color:var(--accent);background:var(--accent-dim);margin-bottom:15px;position:relative}.definition-icon .icon{width:17px;height:17px}.definition-open{position:absolute;right:17px;top:17px;color:var(--text-tertiary);transition:color .18s,transform .18s}.definition-open .icon{width:14px;height:14px}.definition-card:hover .definition-open{color:var(--accent);transform:translate(1px,-1px)}.definition-card h3{margin:0;font-size:17px;font-weight:500;position:relative}.definition-card p{color:var(--text-secondary);font-size:12px;font-weight:300;line-height:1.6;margin:9px 0 18px;position:relative}.definition-meta{position:absolute;left:19px;right:19px;bottom:17px;display:flex;justify-content:space-between;align-items:end}.definition-meta b{font:9px var(--mono);color:var(--accent-soft)}
3444
+ .cap-list{display:flex;flex-wrap:wrap;gap:6px}.cap{border:1px solid var(--border);background:var(--bg-surface);border-radius:5px;padding:7px 9px;font:9px var(--mono);margin-bottom:6px;color:var(--text-secondary)}.detail-strip{display:grid;grid-template-columns:repeat(4,1fr);border:1px solid var(--border-subtle);border-radius:var(--radius);background:var(--bg-elevated);margin-bottom:14px;overflow:hidden}.detail-strip>div{padding:15px;border-right:1px solid var(--border-subtle);min-width:0}.detail-strip>div:last-child{border-right:0}.detail-strip label{display:block;color:var(--text-tertiary);font:8px var(--mono);text-transform:uppercase;letter-spacing:.1em}.detail-strip strong{display:block;margin-top:8px;font-size:12px;font-weight:500;overflow-wrap:anywhere}.json{margin:0;background:#070A09;color:var(--text-secondary);border:1px solid var(--border-subtle);border-radius:7px;padding:14px;font:9px/1.65 var(--mono);white-space:pre-wrap;overflow:auto;max-height:360px}.summary-box{font-size:12px;line-height:1.65;overflow-wrap:anywhere}.summary-box dl{display:grid;grid-template-columns:130px 1fr;gap:9px 14px;margin:0}.summary-box dt{color:var(--text-tertiary);font:8px var(--mono);text-transform:uppercase;letter-spacing:.08em}.summary-box dd{margin:0;color:var(--text-secondary)}
3445
+ .run-spine{position:relative;padding:4px 0}.run-spine:before{content:"";position:absolute;left:19px;top:14px;bottom:14px;width:1px;background:var(--border)}.phase{position:relative;display:grid;grid-template-columns:40px 1fr auto;gap:12px;padding:11px 4px}.phase-dot{width:12px;height:12px;border-radius:50%;background:var(--bg-elevated);border:2px solid var(--text-tertiary);margin:2px 0 0 13px;z-index:1}.phase.complete .phase-dot{border-color:var(--success);box-shadow:0 0 8px rgba(140,196,148,.2)}.phase.active .phase-dot{border-color:var(--network);box-shadow:0 0 0 5px rgba(131,179,226,.09)}.phase.error .phase-dot{border-color:var(--danger)}.phase strong{font-size:12px;font-weight:500}.phase p{margin:4px 0 0;color:var(--text-secondary);font-size:11px;font-weight:300;line-height:1.5}.phase time{font:8px var(--mono);color:var(--text-tertiary)}
3446
+ .event-row{border-top:1px solid var(--border-subtle)}.event-toggle{width:100%;border:0;background:transparent;padding:12px 15px;display:grid;grid-template-columns:82px 110px minmax(0,1fr) auto;gap:11px;text-align:left;align-items:center;cursor:pointer}.event-toggle:hover,.event-row.open .event-toggle{background:var(--accent-glow)}.event-time,.event-category{font:8px var(--mono);color:var(--text-tertiary)}.event-type{font:9px var(--mono);overflow:hidden;text-overflow:ellipsis}.event-detail{display:none;padding:0 15px 14px}.event-row.open .event-detail{display:block}.trace-note{border-left:2px solid var(--accent);padding:10px 12px;background:var(--accent-dim);font-size:11px;line-height:1.5;color:var(--text-secondary);margin-bottom:14px}
3447
+ .workspace-tabs{display:flex;gap:2px;border-bottom:1px solid var(--border-subtle);margin:20px 0 14px}.workspace-tabs button{border:0;background:transparent;padding:11px 13px;color:var(--text-secondary);font-size:11px;cursor:pointer;border-bottom:2px solid transparent}.workspace-tabs button.active{color:var(--accent);border-color:var(--accent);font-weight:500}.kv{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.kv-item{border:1px solid var(--border-subtle);border-radius:7px;padding:13px;background:var(--bg-surface)}.kv-item strong{font:10px var(--mono)}.kv-item small{display:block;color:var(--text-tertiary);margin-top:6px}.kv-value{margin-top:12px;color:var(--text-secondary);font-size:11px;line-height:1.5;overflow-wrap:anywhere}.callout{border:1px solid var(--accent-line);background:var(--accent-glow);border-radius:8px;padding:15px 17px;display:flex;align-items:flex-start;gap:12px;margin-bottom:14px}.callout b{font-size:12px;font-weight:500}.callout p{margin:4px 0 0;color:var(--text-secondary);font-size:11px;line-height:1.5}.callout .symbol{width:31px;height:31px;display:grid;place-items:center;flex:0 0 auto;border:1px solid var(--accent-line);border-radius:7px;color:var(--accent);background:var(--accent-dim)}.callout .symbol .icon{width:16px;height:16px}
3448
+ .drawer-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.62);backdrop-filter:blur(3px);z-index:40;display:none}.drawer-backdrop.open{display:block}.drawer{position:absolute;right:0;top:0;bottom:0;width:min(520px,100%);background:var(--bg-elevated);border-left:1px solid var(--border);box-shadow:-20px 0 60px rgba(0,0,0,.35);padding:26px;overflow:auto}.drawer-head{display:flex;align-items:flex-start;margin-bottom:28px}.drawer h2{margin:0;font-size:23px;font-weight:400;letter-spacing:-.025em}.drawer-head button{margin-left:auto;border:0;background:transparent;color:var(--text-secondary);cursor:pointer}.drawer-head button:hover{color:var(--text-primary);background:var(--bg-surface)}.field{margin-bottom:17px}.field label{display:block;font:8px var(--mono);text-transform:uppercase;letter-spacing:.12em;color:var(--text-tertiary);margin-bottom:7px}.field input,.field textarea,.field select{width:100%;border:1px solid var(--border);background:var(--bg);border-radius:7px;padding:11px 12px;font-size:12px}.field textarea{min-height:120px;resize:vertical}.form-error{background:rgba(226,155,168,.08);color:var(--danger);border:1px solid rgba(226,155,168,.18);border-radius:7px;padding:10px 12px;font-size:11px;margin-bottom:13px}.drawer-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:22px}
3449
+ .search-modal{position:fixed;inset:0;background:rgba(0,0,0,.64);backdrop-filter:blur(4px);z-index:50;display:none;padding:12vh 20px}.search-modal.open{display:block}.search-box{max-width:650px;margin:auto;background:var(--bg-elevated);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow);overflow:hidden}.search-input-wrap{display:flex;align-items:center;gap:12px;padding:0 20px;border-bottom:1px solid var(--border);color:var(--text-tertiary)}.search-input-wrap>.icon{width:19px;height:19px}.search-box input{width:100%;border:0;background:var(--bg-elevated);padding:18px 0;font-size:15px}.search-results{max-height:420px;overflow:auto;padding:8px}.search-result{display:block;padding:11px 12px;border-radius:6px;text-decoration:none;color:inherit}.search-result:hover{background:var(--accent-glow)}.search-result strong{font-size:12px;font-weight:500}.search-result small{display:block;color:var(--text-tertiary);margin-top:3px}.toast{position:fixed;right:24px;bottom:24px;background:var(--accent);color:var(--bg);border-radius:7px;padding:12px 15px;font-size:11px;font-weight:600;box-shadow:var(--shadow);z-index:60;transform:translateY(20px);opacity:0;pointer-events:none;transition:.2s}.toast.show{transform:none;opacity:1}
3450
+ @media(max-width:1100px){.cols-4{grid-template-columns:repeat(2,1fr)}.definition-grid{grid-template-columns:repeat(2,1fr)}.cols-3{grid-template-columns:1fr 1fr}.detail-strip{grid-template-columns:1fr 1fr}.detail-strip>div:nth-child(2){border-right:0}.detail-strip>div:nth-child(-n+2){border-bottom:1px solid var(--border-subtle)}}
3451
+ @media(max-width:760px){:root{--sidebar:0px}.sidebar{transform:translateX(-248px);width:248px;transition:transform .2s ease}.sidebar.open{transform:none}.mobile-toggle{display:inline-flex}.topbar{padding:0 16px}.live-chip{display:none}.content{padding:24px 16px 55px}.page-head{display:block}.page-head .actions{margin:18px 0 0}.cols-4,.cols-3,.cols-2,.definition-grid,.kv{grid-template-columns:1fr}.span-2{grid-column:auto}.detail-strip{display:block}.detail-strip>div{border-right:0;border-bottom:1px solid var(--border-subtle)}.filters{flex-wrap:wrap}.filters input{min-width:100%;width:100%}.event-toggle{grid-template-columns:70px 90px 1fr}.event-toggle span:last-child{display:none}.top-actions .button.primary span{display:none}}
3452
+ @media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
3453
+ `;
3454
+
3455
+ // src/dashboard.ts
3456
+ var NAV_ICONS = {
3457
+ overview: renderPhosphorIcon("overview"),
3458
+ agents: renderPhosphorIcon("agent"),
3459
+ runs: renderPhosphorIcon("runs"),
3460
+ automations: renderPhosphorIcon("automations"),
3461
+ integrations: renderPhosphorIcon("integrations"),
3462
+ workspaces: renderPhosphorIcon("workspaces")
3463
+ };
3464
+ function renderDashboard() {
3465
+ return `<!doctype html>
3466
+ <html lang="en">
3467
+ <head>
3468
+ <meta charset="utf-8" />
3469
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
3470
+ <meta name="color-scheme" content="dark" />
3471
+ <title>Glove Foundry \xB7 Inspector</title>
3472
+ <style>${DASHBOARD_STYLES}</style>
3473
+ </head>
3474
+ <body>
3475
+ <div class="app">
3476
+ <aside class="sidebar" id="sidebar">
3477
+ <a class="brand" href="/" data-link>
3478
+ ${renderGloveMark()}
3479
+ <span><strong>Glove Foundry</strong><small>Runtime inspector</small></span>
3480
+ </a>
3481
+ <nav class="nav">
3482
+ <div class="nav-label">Inspect</div>
3483
+ <a href="/" data-link data-nav="overview"><span class="nav-icon">${NAV_ICONS.overview}</span>Overview</a>
3484
+ <a href="/agents" data-link data-nav="agents"><span class="nav-icon">${NAV_ICONS.agents}</span>Agents</a>
3485
+ <a href="/runs" data-link data-nav="runs"><span class="nav-icon">${NAV_ICONS.runs}</span>Runs</a>
3486
+ <a href="/automations" data-link data-nav="automations"><span class="nav-icon">${NAV_ICONS.automations}</span>Automations</a>
3487
+ <a href="/integrations" data-link data-nav="integrations"><span class="nav-icon">${NAV_ICONS.integrations}</span>Integrations</a>
3488
+ <a href="/workspaces" data-link data-nav="workspaces"><span class="nav-icon">${NAV_ICONS.workspaces}</span>Workspaces</a>
3489
+ </nav>
3490
+ <div class="sidebar-foot">
3491
+ <div class="runtime-state" id="runtime-state"><i></i><span>Connecting to runtime\u2026</span></div>
3492
+ <button class="shortcut" id="open-search"><span class="shortcut-label">${renderPhosphorIcon("search")}Search Foundry</span><kbd>\u2318K</kbd></button>
3493
+ </div>
3494
+ </aside>
3495
+ <section class="main">
3496
+ <header class="topbar">
3497
+ <button class="button icon-button mobile-toggle" id="mobile-toggle" aria-label="Open navigation">${renderPhosphorIcon("menu")}</button>
3498
+ <div class="breadcrumbs" id="breadcrumbs"></div>
3499
+ <div class="top-actions">
3500
+ <span class="live-chip"><i></i><span id="event-state">Live updates</span></span>
3501
+ <button class="button primary" id="new-run">${renderPhosphorIcon("plus")} <span>New run</span></button>
3502
+ </div>
3503
+ </header>
3504
+ <main class="content" id="content">
3505
+ <div class="empty"><strong>Opening the Foundry\u2026</strong>Reading definitions, instances, and runtime activity.</div>
3506
+ </main>
3507
+ </section>
3508
+ </div>
3509
+
3510
+ <div class="drawer-backdrop" id="drawer">
3511
+ <section class="drawer" role="dialog" aria-modal="true" aria-labelledby="drawer-title">
3512
+ <div class="drawer-head">
3513
+ <div><div class="eyebrow">Direct invocation</div><h2 id="drawer-title">Start a run</h2></div>
3514
+ <button class="icon-button" id="close-drawer" aria-label="Close">${renderPhosphorIcon("close")}</button>
3515
+ </div>
3516
+ <div id="run-form-error" class="form-error hidden"></div>
3517
+ <form id="run-form">
3518
+ <div class="field"><label for="run-definition">Agent definition</label><select id="run-definition"></select></div>
3519
+ <div class="field"><label for="run-instance">Runtime instance</label><select id="run-instance"></select><small class="muted">Choose \u201CCreate a new instance\u201D to provision one.</small></div>
3520
+ <div class="field"><label for="run-message">Message</label><textarea id="run-message" required placeholder="What should this agent work on?"></textarea></div>
3521
+ <div class="drawer-actions"><button type="button" class="button" id="cancel-run">Cancel</button><button type="submit" class="button primary">Start run</button></div>
3522
+ </form>
3523
+ </section>
3524
+ </div>
3525
+
3526
+ <div class="search-modal" id="search-modal">
3527
+ <div class="search-box" role="dialog" aria-modal="true">
3528
+ <div class="search-input-wrap">${renderPhosphorIcon("search")}<input id="search-input" aria-label="Search Foundry" placeholder="Search pages, agents, instances, and runs\u2026" autocomplete="off" /></div>
3529
+ <div class="search-results" id="search-results"></div>
3530
+ </div>
3531
+ </div>
3532
+ <div class="toast" id="toast"></div>
3533
+ <script>${DASHBOARD_SCRIPT}</script>
3534
+ </body>
3535
+ </html>`;
3536
+ }
3537
+
3538
+ // src/server.ts
3539
+ var MAX_BODY_BYTES = 1024 * 1024;
3540
+ function isFoundryMessageInput(value) {
3541
+ if (typeof value === "string") return true;
3542
+ if (!Array.isArray(value)) return false;
3543
+ return value.every((part) => {
3544
+ if (!part || typeof part !== "object") return false;
3545
+ const candidate = part;
3546
+ if (!["text", "image", "video", "document"].includes(String(candidate.type))) {
3547
+ return false;
3548
+ }
3549
+ if (candidate.text !== void 0 && typeof candidate.text !== "string") return false;
3550
+ if (candidate.source === void 0) return true;
3551
+ if (!candidate.source || typeof candidate.source !== "object") return false;
3552
+ const source = candidate.source;
3553
+ return (source.type === "base64" || source.type === "url") && typeof source.media_type === "string" && (source.data === void 0 || typeof source.data === "string") && (source.url === void 0 || typeof source.url === "string");
3554
+ });
3555
+ }
3556
+ var RequestError = class extends Error {
3557
+ constructor(status, message) {
3558
+ super(message);
3559
+ this.status = status;
3560
+ }
3561
+ };
3562
+ function json(response, status, value) {
3563
+ response.writeHead(status, {
3564
+ "content-type": "application/json; charset=utf-8",
3565
+ "cache-control": "no-store",
3566
+ "x-content-type-options": "nosniff"
3567
+ });
3568
+ response.end(JSON.stringify(value));
3569
+ }
3570
+ async function readJson(request) {
3571
+ const chunks = [];
3572
+ let size = 0;
3573
+ for await (const chunk of request) {
3574
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
3575
+ size += buffer.length;
3576
+ if (size > MAX_BODY_BYTES) {
3577
+ throw new RequestError(413, "Request body exceeds the 1 MB limit.");
3578
+ }
3579
+ chunks.push(buffer);
3580
+ }
3581
+ if (chunks.length === 0) return {};
3582
+ try {
3583
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
3584
+ } catch {
3585
+ throw new RequestError(400, "Request body must be valid JSON.");
3586
+ }
3587
+ }
3588
+ function eventFilter(url) {
3589
+ const afterValue = url.searchParams.get("after");
3590
+ const limitValue = url.searchParams.get("limit");
3591
+ const after = afterValue === null ? void 0 : Number(afterValue);
3592
+ const limit = limitValue === null ? void 0 : Number(limitValue);
3593
+ return {
3594
+ ...after !== void 0 && Number.isFinite(after) ? { after } : {},
3595
+ ...limit !== void 0 && Number.isFinite(limit) ? { limit } : {},
3596
+ ...url.searchParams.get("agent") ? { agent: url.searchParams.get("agent") } : {},
3597
+ ...url.searchParams.get("runId") ? { runId: url.searchParams.get("runId") } : {},
3598
+ ...url.searchParams.get("category") ? {
3599
+ category: url.searchParams.get(
3600
+ "category"
3601
+ )
3602
+ } : {}
3603
+ };
3604
+ }
3605
+ function matches(event, filter) {
3606
+ if (filter.after !== void 0 && event.sequence <= filter.after) return false;
3607
+ if (filter.agent && event.agent !== filter.agent) return false;
3608
+ if (filter.runId && event.runId !== filter.runId) return false;
3609
+ if (filter.category && event.category !== filter.category) return false;
3610
+ return true;
3611
+ }
3612
+ function agentRoute(pathname) {
3613
+ const prefix = "/api/agents/";
3614
+ const suffix = "/runs";
3615
+ if (!pathname.startsWith(prefix) || !pathname.endsWith(suffix)) return null;
3616
+ const encoded = pathname.slice(prefix.length, -suffix.length);
3617
+ if (!encoded) return null;
3618
+ try {
3619
+ return encoded.split("/").map(decodeURIComponent).join("/");
3620
+ } catch {
3621
+ return null;
3622
+ }
3623
+ }
3624
+ function installationFrom(value) {
3625
+ if (!value || typeof value !== "object") {
3626
+ throw new RequestError(400, "Installation body must be an object.");
3627
+ }
3628
+ const body = value;
3629
+ const kinds = /* @__PURE__ */ new Set([
3630
+ "tool",
3631
+ "application",
3632
+ "mcp"
3633
+ ]);
3634
+ if (typeof body.agentId !== "string" || !body.agentId) {
3635
+ throw new RequestError(400, "agentId is required.");
3636
+ }
3637
+ if (typeof body.kind !== "string" || !kinds.has(body.kind)) {
3638
+ throw new RequestError(400, "kind is invalid.");
3639
+ }
3640
+ if (typeof body.id !== "string" || !body.id) {
3641
+ throw new RequestError(400, "id is required.");
3642
+ }
3643
+ return {
3644
+ agentId: body.agentId,
3645
+ installation: {
3646
+ kind: body.kind,
3647
+ id: body.id,
3648
+ ...body.config !== void 0 ? { config: body.config } : {}
3649
+ }
3650
+ };
3651
+ }
3652
+ function foundryRequestFrom(value) {
3653
+ if (!value || typeof value !== "object") {
3654
+ throw new RequestError(400, "Foundry request body must be an object.");
3655
+ }
3656
+ const body = value;
3657
+ for (const key of ["agentId", "conversationId", "workspaceId", "message"]) {
3658
+ if (typeof body[key] !== "string" || !body[key]) {
3659
+ throw new RequestError(400, `${key} is required.`);
3660
+ }
3661
+ }
3662
+ return body;
3663
+ }
3664
+ var FoundryServer = class {
3665
+ constructor(runtime, options = {}) {
3666
+ this.runtime = runtime;
3667
+ this.options = options;
3668
+ this.server = createServer((request, response) => {
3669
+ void this.handle(request, response);
3670
+ });
3671
+ }
3672
+ server;
3673
+ eventStreams = /* @__PURE__ */ new Set();
3674
+ addressInfo = null;
3675
+ async listen() {
3676
+ const host = this.options.host ?? "127.0.0.1";
3677
+ const port = this.options.port ?? 4141;
3678
+ await new Promise((resolve3, reject) => {
3679
+ this.server.once("error", reject);
3680
+ this.server.listen(port, host, () => {
3681
+ this.server.removeListener("error", reject);
3682
+ resolve3();
3683
+ });
3684
+ });
3685
+ const address = this.server.address();
3686
+ if (!address || typeof address === "string") {
3687
+ throw new Error("Foundry server did not receive a TCP address.");
3688
+ }
3689
+ this.addressInfo = address;
3690
+ return { host, port: address.port, url: `http://${host}:${address.port}` };
3691
+ }
3692
+ async close() {
3693
+ if (!this.server.listening) return;
3694
+ for (const stream of this.eventStreams) stream.end();
3695
+ this.eventStreams.clear();
3696
+ await new Promise(
3697
+ (resolve3, reject) => this.server.close((error) => error ? reject(error) : resolve3())
3698
+ );
3699
+ this.addressInfo = null;
3700
+ }
3701
+ address() {
3702
+ return this.addressInfo;
3703
+ }
3704
+ async handle(request, response) {
3705
+ try {
3706
+ const url = new URL(request.url ?? "/", "http://foundry.local");
3707
+ const method = request.method ?? "GET";
3708
+ if (method === "GET" && !url.pathname.startsWith("/api/") && url.pathname !== "/health") {
3709
+ response.writeHead(200, {
3710
+ "content-type": "text/html; charset=utf-8",
3711
+ "cache-control": "no-store",
3712
+ "content-security-policy": "default-src 'self'; script-src 'unsafe-inline' 'self'; style-src 'unsafe-inline' 'self'; connect-src 'self'",
3713
+ "x-content-type-options": "nosniff",
3714
+ "x-frame-options": "DENY"
3715
+ });
3716
+ response.end(renderDashboard());
3717
+ return;
3718
+ }
3719
+ if (method === "GET" && url.pathname === "/health") {
3720
+ json(response, 200, await this.runtime.health());
3721
+ return;
3722
+ }
3723
+ if (method === "GET" && url.pathname === "/api/manifest") {
3724
+ const definitions = Object.fromEntries(
3725
+ this.runtime.agents.map((agent) => [
3726
+ agent.route,
3727
+ {
3728
+ capabilities: this.runtime.capabilityManifest(agent.route),
3729
+ surfaces: this.runtime.nativeManifest(agent.route)
3730
+ }
3731
+ ])
3732
+ );
3733
+ json(response, 200, {
3734
+ agents: this.runtime.manifest,
3735
+ application: this.runtime.applicationManifest,
3736
+ definitions
3737
+ });
3738
+ return;
3739
+ }
3740
+ if (method === "GET" && url.pathname === "/api/agents") {
3741
+ json(response, 200, this.runtime.manifest.agents);
3742
+ return;
3743
+ }
3744
+ if (url.pathname === "/api/agent-instances" && method === "GET") {
3745
+ json(response, 200, await this.runtime.listAgentInstances(url.searchParams.get("definition") ?? void 0));
3746
+ return;
3747
+ }
3748
+ if (url.pathname === "/api/agent-instances" && method === "POST") {
3749
+ const body = await readJson(request);
3750
+ if (typeof body.definitionId !== "string") throw new RequestError(400, "definitionId is required.");
3751
+ json(response, 201, await this.runtime.createAgent(body.definitionId, {
3752
+ ...typeof body.id === "string" ? { id: body.id } : {},
3753
+ ...typeof body.workspaceId === "string" ? { workspaceId: body.workspaceId } : {},
3754
+ ...body.context && typeof body.context === "object" ? { context: body.context } : {},
3755
+ ...Array.isArray(body.installations) ? { installations: body.installations } : {},
3756
+ ...Array.isArray(body.playbooks) ? { playbooks: body.playbooks } : {}
3757
+ }));
3758
+ return;
3759
+ }
3760
+ const instanceConfiguration = url.pathname.match(/^\/api\/agent-instances\/([^/]+)$/);
3761
+ if (instanceConfiguration && method === "PATCH") {
3762
+ const body = await readJson(request);
3763
+ json(response, 200, await this.runtime.configureAgent(
3764
+ decodeURIComponent(instanceConfiguration[1]),
3765
+ {
3766
+ ...body.context && typeof body.context === "object" ? { context: body.context } : {},
3767
+ ...Array.isArray(body.installations) ? { installations: body.installations } : {},
3768
+ ...Array.isArray(body.playbooks) ? { playbooks: body.playbooks } : {}
3769
+ }
3770
+ ));
3771
+ return;
3772
+ }
3773
+ const instancePlaybooks = url.pathname.match(/^\/api\/agent-instances\/([^/]+)\/playbooks$/);
3774
+ if (instancePlaybooks && method === "PUT") {
3775
+ const body = await readJson(request);
3776
+ if (!Array.isArray(body.playbooks)) throw new RequestError(400, "playbooks must be an array.");
3777
+ json(response, 200, await this.runtime.setAgentPlaybooks(
3778
+ decodeURIComponent(instancePlaybooks[1]),
3779
+ body.playbooks
3780
+ ));
3781
+ return;
3782
+ }
3783
+ if (url.pathname === "/api/playbook-subscriptions" && method === "GET") {
3784
+ json(
3785
+ response,
3786
+ 200,
3787
+ await this.runtime.listPlaybookSubscriptions(
3788
+ url.searchParams.get("workspace") ?? void 0
3789
+ )
3790
+ );
3791
+ return;
3792
+ }
3793
+ if (url.pathname === "/api/activations" && method === "GET") {
3794
+ json(
3795
+ response,
3796
+ 200,
3797
+ await this.runtime.listActivations(
3798
+ url.searchParams.get("workspace") ?? void 0
3799
+ )
3800
+ );
3801
+ return;
3802
+ }
3803
+ if (url.pathname === "/api/playbook-subscriptions" && method === "PUT") {
3804
+ json(
3805
+ response,
3806
+ 200,
3807
+ await this.runtime.putPlaybookSubscription(
3808
+ await readJson(request)
3809
+ )
3810
+ );
3811
+ return;
3812
+ }
3813
+ const subscriptionDelete = url.pathname.match(
3814
+ /^\/api\/playbook-subscriptions\/([^/]+)$/
3815
+ );
3816
+ if (subscriptionDelete && method === "DELETE") {
3817
+ json(response, 200, {
3818
+ removed: await this.runtime.deletePlaybookSubscription(
3819
+ decodeURIComponent(subscriptionDelete[1])
3820
+ )
3821
+ });
3822
+ return;
3823
+ }
3824
+ if (url.pathname === "/api/conversations" && method === "GET") {
3825
+ const agentId = url.searchParams.get("agent");
3826
+ if (!agentId) throw new RequestError(400, "agent query is required.");
3827
+ json(response, 200, await this.runtime.listConversations(agentId));
3828
+ return;
3829
+ }
3830
+ if (url.pathname === "/api/conversations" && method === "POST") {
3831
+ const body = await readJson(request);
3832
+ if (typeof body.agentId !== "string") throw new RequestError(400, "agentId is required.");
3833
+ json(response, 201, await this.runtime.createConversation(body.agentId, {
3834
+ ...typeof body.id === "string" ? { id: body.id } : {},
3835
+ ...typeof body.workspaceId === "string" ? { workspaceId: body.workspaceId } : {},
3836
+ ...typeof body.title === "string" ? { title: body.title } : {},
3837
+ ...body.context && typeof body.context === "object" ? { context: body.context } : {}
3838
+ }));
3839
+ return;
3840
+ }
3841
+ const conversationMessage = url.pathname.match(/^\/api\/conversations\/([^/]+)\/messages$/);
3842
+ if (conversationMessage && method === "POST") {
3843
+ const body = await readJson(request);
3844
+ if (typeof body.agentId !== "string" || !isFoundryMessageInput(body.message)) {
3845
+ throw new RequestError(400, "agentId and message are required.");
3846
+ }
3847
+ json(response, 202, await this.runtime.send(
3848
+ body.agentId,
3849
+ decodeURIComponent(conversationMessage[1]),
3850
+ body.message,
3851
+ {
3852
+ ...body.payload !== void 0 ? { payload: body.payload } : {},
3853
+ ...body.context && typeof body.context === "object" ? { context: body.context } : {}
3854
+ }
3855
+ ));
3856
+ return;
3857
+ }
3858
+ const workspaceItem = url.pathname.match(/^\/api\/workspaces\/([^/]+)\/(inbox|tasks)\/([^/]+)$/);
3859
+ if (workspaceItem && method === "PATCH") {
3860
+ const workspaceId = decodeURIComponent(workspaceItem[1]);
3861
+ const surface = workspaceItem[2];
3862
+ const itemId = decodeURIComponent(workspaceItem[3]);
3863
+ const body = await readJson(request);
3864
+ if (typeof body.status !== "string") throw new RequestError(400, "status is required.");
3865
+ if (surface === "inbox") {
3866
+ if (!["pending", "resolved", "dismissed"].includes(body.status)) {
3867
+ throw new RequestError(400, "Invalid shared inbox status.");
3868
+ }
3869
+ json(response, 200, await this.runtime.updateSharedInbox(
3870
+ workspaceId,
3871
+ itemId,
3872
+ body.status
3873
+ ));
3874
+ return;
3875
+ }
3876
+ if (!["open", "in-progress", "completed", "cancelled"].includes(body.status)) {
3877
+ throw new RequestError(400, "Invalid task status.");
3878
+ }
3879
+ json(response, 200, await this.runtime.updateTask(
3880
+ workspaceId,
3881
+ itemId,
3882
+ body.status
3883
+ ));
3884
+ return;
3885
+ }
3886
+ const workspaceSurface = url.pathname.match(/^\/api\/workspaces\/([^/]+)\/(entries|inbox|tasks|environment)$/);
3887
+ if (workspaceSurface) {
3888
+ const workspaceId = decodeURIComponent(workspaceSurface[1]);
3889
+ const surface = workspaceSurface[2];
3890
+ if (method === "GET" && surface === "entries") {
3891
+ json(response, 200, await this.runtime.listWorkspaceEntries(workspaceId));
3892
+ return;
3893
+ }
3894
+ if (method === "PUT" && surface === "entries") {
3895
+ const body = await readJson(request);
3896
+ if (typeof body.key !== "string") throw new RequestError(400, "key is required.");
3897
+ json(response, 200, await this.runtime.putWorkspaceEntry(workspaceId, body.key, body.value));
3898
+ return;
3899
+ }
3900
+ if (method === "GET" && surface === "inbox") {
3901
+ json(response, 200, await this.runtime.listSharedInbox(workspaceId));
3902
+ return;
3903
+ }
3904
+ if (method === "POST" && surface === "inbox") {
3905
+ const body = await readJson(request);
3906
+ if (typeof body.topic !== "string") throw new RequestError(400, "topic is required.");
3907
+ json(response, 201, await this.runtime.postSharedInbox({
3908
+ workspaceId,
3909
+ ...typeof body.agentId === "string" ? { agentId: body.agentId } : {},
3910
+ ...typeof body.conversationId === "string" ? { conversationId: body.conversationId } : {},
3911
+ topic: body.topic,
3912
+ payload: body.payload,
3913
+ status: "pending"
3914
+ }));
3915
+ return;
3916
+ }
3917
+ if (method === "GET" && surface === "tasks") {
3918
+ json(response, 200, await this.runtime.listTasks(workspaceId));
3919
+ return;
3920
+ }
3921
+ if (method === "POST" && surface === "tasks") {
3922
+ const body = await readJson(request);
3923
+ if (typeof body.title !== "string") throw new RequestError(400, "title is required.");
3924
+ json(response, 201, await this.runtime.createTask({
3925
+ workspaceId,
3926
+ ...typeof body.agentId === "string" ? { agentId: body.agentId } : {},
3927
+ ...typeof body.conversationId === "string" ? { conversationId: body.conversationId } : {},
3928
+ title: body.title,
3929
+ ...typeof body.detail === "string" ? { detail: body.detail } : {},
3930
+ status: "open"
3931
+ }));
3932
+ return;
3933
+ }
3934
+ if (method === "GET" && surface === "environment") {
3935
+ json(response, 200, await this.runtime.listDataEnvironment({
3936
+ workspaceId,
3937
+ ...url.searchParams.get("agent") ? { agentId: url.searchParams.get("agent") } : {},
3938
+ ...url.searchParams.get("conversation") ? { conversationId: url.searchParams.get("conversation") } : {}
3939
+ }));
3940
+ return;
3941
+ }
3942
+ }
3943
+ if (method === "GET" && url.pathname === "/api/capabilities") {
3944
+ const definition = url.searchParams.get("definition");
3945
+ if (!definition) throw new Error("definition is required");
3946
+ json(response, 200, this.runtime.capabilityManifest(definition));
3947
+ return;
3948
+ }
3949
+ if (method === "GET" && url.pathname === "/api/surfaces") {
3950
+ const definition = url.searchParams.get("definition");
3951
+ if (!definition) throw new Error("definition is required");
3952
+ json(response, 200, this.runtime.nativeManifest(definition));
3953
+ return;
3954
+ }
3955
+ if (url.pathname === "/api/installations" && method === "GET") {
3956
+ const agentId = url.searchParams.get("agent");
3957
+ if (!agentId) throw new RequestError(400, "agent query is required.");
3958
+ json(response, 200, await this.runtime.listInstallations(agentId));
3959
+ return;
3960
+ }
3961
+ if (url.pathname === "/api/installations" && method === "PUT") {
3962
+ const parsed = installationFrom(await readJson(request));
3963
+ const agent = await this.runtime.installCapability(
3964
+ parsed.agentId,
3965
+ parsed.installation
3966
+ );
3967
+ json(response, 200, agent);
3968
+ return;
3969
+ }
3970
+ if (url.pathname === "/api/installations" && method === "DELETE") {
3971
+ const parsed = installationFrom(await readJson(request));
3972
+ const agent = await this.runtime.uninstallCapability(
3973
+ parsed.agentId,
3974
+ parsed.installation
3975
+ );
3976
+ json(response, 200, agent);
3977
+ return;
3978
+ }
3979
+ if (method === "GET" && url.pathname === "/api/runs") {
3980
+ json(response, 200, await this.runtime.listRuns(url.searchParams.get("agent") ?? void 0));
3981
+ return;
3982
+ }
3983
+ if (method === "GET" && url.pathname === "/api/transmissions") {
3984
+ json(response, 200, this.runtime.applicationManifest.transmissions);
3985
+ return;
3986
+ }
3987
+ if (method === "GET" && url.pathname === "/api/accounts") {
3988
+ json(response, 200, await this.runtime.listAccounts());
3989
+ return;
3990
+ }
3991
+ if (url.pathname === "/api/routes" && method === "GET") {
3992
+ json(response, 200, await this.runtime.listRoutes());
3993
+ return;
3994
+ }
3995
+ if (url.pathname === "/api/routes" && method === "PUT") {
3996
+ const route2 = Schema4.decodeUnknownSync(Route)(await readJson(request));
3997
+ json(response, 200, await this.runtime.putRoute(route2));
3998
+ return;
3999
+ }
4000
+ const routeDelete = url.pathname.match(/^\/api\/routes\/([^/]+)$/);
4001
+ if (routeDelete && method === "DELETE") {
4002
+ const id2 = Schema4.decodeUnknownSync(RouteId)(
4003
+ decodeURIComponent(routeDelete[1])
4004
+ );
4005
+ await this.runtime.removeRoute(id2);
4006
+ json(response, 200, { removed: true });
4007
+ return;
4008
+ }
4009
+ if (url.pathname === "/api/bindings" && method === "GET") {
4010
+ json(response, 200, await this.runtime.listBindings());
4011
+ return;
4012
+ }
4013
+ if (url.pathname === "/api/bindings" && method === "PUT") {
4014
+ const binding = Schema4.decodeUnknownSync(AgentBinding)(
4015
+ await readJson(request)
4016
+ );
4017
+ json(response, 200, await this.runtime.putBinding(binding));
4018
+ return;
4019
+ }
4020
+ const bindingDelete = url.pathname.match(/^\/api\/bindings\/([^/]+)$/);
4021
+ if (bindingDelete && method === "DELETE") {
4022
+ const id2 = Schema4.decodeUnknownSync(BindingId)(
4023
+ decodeURIComponent(bindingDelete[1])
4024
+ );
4025
+ await this.runtime.removeBinding(id2);
4026
+ json(response, 200, { removed: true });
4027
+ return;
4028
+ }
4029
+ if (url.pathname === "/api/grants/resolve" && method === "POST") {
4030
+ const requestBody = Schema4.decodeUnknownSync(
4031
+ Schema4.Struct({
4032
+ runId: RunId,
4033
+ agentId: AgentId,
4034
+ originRouteId: Schema4.optional(RouteId)
4035
+ })
4036
+ )(await readJson(request));
4037
+ json(response, 200, await this.runtime.resolveGrant(requestBody));
4038
+ return;
4039
+ }
4040
+ if (url.pathname === "/api/application-connections" && method === "GET") {
4041
+ json(response, 200, this.runtime.listApplicationConnections());
4042
+ return;
4043
+ }
4044
+ const connectionReconnect = url.pathname.match(
4045
+ /^\/api\/application-connections\/([^/]+)\/reconnect$/
4046
+ );
4047
+ if (connectionReconnect && method === "POST") {
4048
+ await this.runtime.reconnectApplicationConnection(
4049
+ decodeURIComponent(connectionReconnect[1])
4050
+ );
4051
+ json(response, 200, { ok: true });
4052
+ return;
4053
+ }
4054
+ if (url.pathname === "/api/events" && method === "GET") {
4055
+ const filter = eventFilter(url);
4056
+ if (request.headers.accept?.includes("text/event-stream")) {
4057
+ this.streamEvents(request, response, filter);
4058
+ } else {
4059
+ json(response, 200, this.runtime.observability.list(filter));
4060
+ }
4061
+ return;
4062
+ }
4063
+ const transmissionFire = url.pathname.match(/^\/api\/transmissions\/([^/]+)\/fire$/);
4064
+ if (transmissionFire && method === "POST") {
4065
+ const body = await readJson(request);
4066
+ if (typeof body.eventId !== "string" || typeof body.threadKey !== "string") {
4067
+ throw new RequestError(400, "eventId and threadKey are required.");
4068
+ }
4069
+ json(response, 202, await this.runtime.dispatchInbound({
4070
+ routeId: decodeURIComponent(transmissionFire[1]),
4071
+ eventId: body.eventId,
4072
+ threadKey: body.threadKey,
4073
+ raw: body.raw
4074
+ }));
4075
+ return;
4076
+ }
4077
+ const transmissionDeliver = url.pathname.match(/^\/api\/transmissions\/([^/]+)\/deliver$/);
4078
+ if (transmissionDeliver && method === "POST") {
4079
+ const body = await readJson(request);
4080
+ if (typeof body.agentId !== "string" || typeof body.runId !== "string") {
4081
+ throw new RequestError(400, "agentId and runId are required.");
4082
+ }
4083
+ json(response, 200, await this.runtime.dispatchOutbound({
4084
+ routeId: decodeURIComponent(transmissionDeliver[1]),
4085
+ agentId: body.agentId,
4086
+ runId: body.runId,
4087
+ payload: body.payload
4088
+ }));
4089
+ return;
4090
+ }
4091
+ const route = agentRoute(url.pathname);
4092
+ if (route && method === "POST") {
4093
+ json(response, 202, await this.runtime.request(route, foundryRequestFrom(await readJson(request))));
4094
+ return;
4095
+ }
4096
+ const cancelMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/cancel$/);
4097
+ if (cancelMatch && method === "POST") {
4098
+ json(response, 200, {
4099
+ cancelled: await this.runtime.cancel(decodeURIComponent(cancelMatch[1]))
4100
+ });
4101
+ return;
4102
+ }
4103
+ const eventsMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/events$/);
4104
+ if (eventsMatch && method === "GET") {
4105
+ json(
4106
+ response,
4107
+ 200,
4108
+ this.runtime.observability.list({
4109
+ runId: decodeURIComponent(eventsMatch[1]),
4110
+ limit: 5e3
4111
+ })
4112
+ );
4113
+ return;
4114
+ }
4115
+ const runMatch = url.pathname.match(/^\/api\/runs\/([^/]+)$/);
4116
+ if (runMatch && method === "GET") {
4117
+ const run = await this.runtime.getRun(decodeURIComponent(runMatch[1]));
4118
+ if (!run) throw new RequestError(404, "Foundry run was not found.");
4119
+ json(response, 200, run);
4120
+ return;
4121
+ }
4122
+ throw new RequestError(404, "Foundry route was not found.");
4123
+ } catch (error) {
4124
+ const status = error instanceof RequestError ? error.status : 400;
4125
+ const message = error instanceof Error ? error.message : String(error);
4126
+ if (!response.headersSent) json(response, status, { error: message });
4127
+ else response.end();
4128
+ }
4129
+ }
4130
+ streamEvents(request, response, filter) {
4131
+ response.writeHead(200, {
4132
+ "content-type": "text/event-stream; charset=utf-8",
4133
+ "cache-control": "no-cache, no-transform",
4134
+ connection: "keep-alive",
4135
+ "x-accel-buffering": "no"
4136
+ });
4137
+ this.eventStreams.add(response);
4138
+ response.write(": glove-foundry\n\n");
4139
+ for (const event of this.runtime.observability.list(filter)) {
4140
+ response.write(`id: ${event.sequence}
4141
+ data: ${JSON.stringify(event)}
4142
+
4143
+ `);
4144
+ }
4145
+ const unsubscribe = this.runtime.observability.subscribe((event) => {
4146
+ if (matches(event, filter)) {
4147
+ response.write(`id: ${event.sequence}
4148
+ data: ${JSON.stringify(event)}
4149
+
4150
+ `);
4151
+ }
4152
+ });
4153
+ const heartbeat = setInterval(() => response.write(": heartbeat\n\n"), 15e3);
4154
+ const close = () => {
4155
+ clearInterval(heartbeat);
4156
+ unsubscribe();
4157
+ this.eventStreams.delete(response);
4158
+ };
4159
+ request.once("close", close);
4160
+ response.once("close", close);
4161
+ }
4162
+ };
4163
+
4164
+ export {
4165
+ writeGeneratedTypes,
4166
+ composeAgent,
4167
+ EMPTY_AGENT_COMPOSITION,
4168
+ TransmissionId,
4169
+ AgentDefinitionId,
4170
+ AgentId,
4171
+ AccountId,
4172
+ RouteId,
4173
+ BindingId,
4174
+ EventId,
4175
+ RunId,
4176
+ CapabilityId,
4177
+ AccountReference,
4178
+ AccountSummary,
4179
+ InboundRoute,
4180
+ OutboundRoute,
4181
+ Route,
4182
+ ReplyPolicy,
4183
+ AgentBinding,
4184
+ RunGrant,
4185
+ EventReference,
4186
+ AccountNotFound,
4187
+ RouteNotFound,
4188
+ BindingNotFound,
4189
+ EventNotFound,
4190
+ TopologyConflict,
4191
+ AccountSessionUnavailable,
4192
+ GrantResolutionError,
4193
+ AccountDirectory,
4194
+ TopologyStore,
4195
+ EventStore,
4196
+ memoryAccountDirectory,
4197
+ memoryTopologyStore,
4198
+ memoryEventStore,
4199
+ GrantResolver,
4200
+ grantResolverLive,
4201
+ FoundryManifestCapability,
4202
+ FoundryManifestTransmission,
4203
+ FoundryApplicationManifest,
4204
+ ManifestCompilationError,
4205
+ compileApplicationManifest,
4206
+ serializeInboundTransmissionXml,
4207
+ MemoryObservabilityAdapter,
4208
+ FoundryRuntimeError,
4209
+ FoundryRuntime,
4210
+ FoundryServer
4211
+ };