glove-foundry 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/dist/chunk-5NFLGHOC.js +281 -0
- package/dist/chunk-J7ZBJM54.js +6 -0
- package/dist/{chunk-5LDW2N2E.js → chunk-KBMD4RYU.js} +1980 -3464
- package/dist/chunk-LW5PJ2FN.js +546 -0
- package/dist/{chunk-25BGNRJN.js → chunk-SGF3CFUR.js} +248 -120
- package/dist/chunk-TH5ETCG2.js +730 -0
- package/dist/cli.js +7 -3
- package/dist/{client-D0-pKEZM.d.ts → client-DcsnudMI.d.ts} +9 -3
- package/dist/client.d.ts +3 -1
- package/dist/config.d.ts +1 -0
- package/dist/execution-agent.js +9 -4
- package/dist/execution-daemon.d.ts +2 -0
- package/dist/execution-daemon.js +157 -0
- package/dist/execution-loader.d.ts +6 -0
- package/dist/execution-loader.js +20 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +32 -27
- package/dist/station.d.ts +35 -0
- package/dist/station.js +9 -0
- package/docs/architecture.md +1 -1
- package/docs/building-with-foundry.md +19 -2
- package/docs/execution-release.md +57 -0
- package/docs/execution.md +97 -0
- package/docs/release-verification.md +3 -1
- package/package.json +23 -15
- package/templates/minimal/README.md +1 -1
- package/templates/travel-concierge/README.md +1 -1
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mountFoundryGuidance,
|
|
3
|
+
mountFoundryWorkbench
|
|
4
|
+
} from "./chunk-LW5PJ2FN.js";
|
|
5
|
+
import {
|
|
6
|
+
EMPTY_CAPABILITY_REGISTRY,
|
|
7
|
+
EMPTY_FOUNDRY_APPLICATION,
|
|
8
|
+
FOUNDRY_APPROVAL_DIRECTORY_ENV,
|
|
9
|
+
FOUNDRY_COMPOSED_PLAYBOOK_BRAND,
|
|
10
|
+
FOUNDRY_CORE_COMMAND_EVENT,
|
|
11
|
+
FoundryApprovalDisplayManager,
|
|
12
|
+
MemoryFoundryDataAdapter,
|
|
13
|
+
agentPlaybookId,
|
|
14
|
+
agentScheduleActivationId,
|
|
15
|
+
agentScheduleRevision,
|
|
16
|
+
composedPlaybookRevision,
|
|
17
|
+
createFoundryCoreTools,
|
|
18
|
+
createInstalledApplicationTransmissionTools,
|
|
19
|
+
freezeGloveMessage,
|
|
20
|
+
installRegistry,
|
|
21
|
+
isFoundryApplication,
|
|
22
|
+
isFoundrySchedule,
|
|
23
|
+
isInboxCapableStore,
|
|
24
|
+
materializeComposedPlaybook,
|
|
25
|
+
mountAgentDefinitionMemory,
|
|
26
|
+
mountFoundrySurfaces,
|
|
27
|
+
normalizeScheduleTiming,
|
|
28
|
+
toGloveMessage,
|
|
29
|
+
toGloveRequestInput,
|
|
30
|
+
withFoundryPermissions
|
|
31
|
+
} from "./chunk-KBMD4RYU.js";
|
|
32
|
+
import {
|
|
33
|
+
FOUNDRY_APPLICATION_ENV,
|
|
34
|
+
FOUNDRY_EVENT_PREFIX,
|
|
35
|
+
FOUNDRY_EXECUTION_MARKER,
|
|
36
|
+
defineAgentFromModule,
|
|
37
|
+
internalAgentName,
|
|
38
|
+
resolveResolvable
|
|
39
|
+
} from "./chunk-5NFLGHOC.js";
|
|
40
|
+
|
|
41
|
+
// src/agent-runtime.ts
|
|
42
|
+
import { pathToFileURL } from "node:url";
|
|
43
|
+
import { Glove } from "glove-core";
|
|
44
|
+
import { mountMesh } from "glove-mesh";
|
|
45
|
+
import { Effect } from "effect";
|
|
46
|
+
import { signal } from "station-signal";
|
|
47
|
+
import { z } from "zod";
|
|
48
|
+
var INBOX_ITEMS_SCHEMA = z.array(z.object({
|
|
49
|
+
id: z.string().min(1),
|
|
50
|
+
tag: z.string(),
|
|
51
|
+
request: z.string(),
|
|
52
|
+
response: z.string().nullable(),
|
|
53
|
+
status: z.enum(["pending", "resolved", "consumed"]),
|
|
54
|
+
blocking: z.boolean(),
|
|
55
|
+
created_at: z.string(),
|
|
56
|
+
resolved_at: z.string().nullable()
|
|
57
|
+
}));
|
|
58
|
+
async function hydrateInbox(store, items, emit) {
|
|
59
|
+
if (!isInboxCapableStore(store)) {
|
|
60
|
+
throw new Error("Agent inbox loading requires an inbox-capable Glove StoreAdapter.");
|
|
61
|
+
}
|
|
62
|
+
const loaded = INBOX_ITEMS_SCHEMA.parse(items);
|
|
63
|
+
const existing = new Map((await store.getInboxItems()).map((item) => [item.id, item]));
|
|
64
|
+
let added = 0;
|
|
65
|
+
let updated = 0;
|
|
66
|
+
for (const item of loaded) {
|
|
67
|
+
const current = existing.get(item.id);
|
|
68
|
+
if (!current) {
|
|
69
|
+
await store.addInboxItem({ ...item });
|
|
70
|
+
added++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (current.status !== item.status || current.response !== item.response || current.resolved_at !== item.resolved_at) {
|
|
74
|
+
await store.updateInboxItem(item.id, {
|
|
75
|
+
status: item.status,
|
|
76
|
+
response: item.response,
|
|
77
|
+
resolved_at: item.resolved_at
|
|
78
|
+
});
|
|
79
|
+
updated++;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
emit({
|
|
83
|
+
type: "foundry.definition.inboxes.loaded",
|
|
84
|
+
data: { count: loaded.length, added, updated }
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function extractOutput(result) {
|
|
88
|
+
if (result == null || typeof result !== "object") return result;
|
|
89
|
+
const object = result;
|
|
90
|
+
if (Array.isArray(object.messages)) {
|
|
91
|
+
const last = object.messages.at(-1);
|
|
92
|
+
return last?.text ?? "";
|
|
93
|
+
}
|
|
94
|
+
return "text" in object ? object.text : result;
|
|
95
|
+
}
|
|
96
|
+
function serializable(value) {
|
|
97
|
+
try {
|
|
98
|
+
return JSON.parse(JSON.stringify(value));
|
|
99
|
+
} catch {
|
|
100
|
+
return String(value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function writeAgentEvent(type, data) {
|
|
104
|
+
const line = `${FOUNDRY_EVENT_PREFIX}${JSON.stringify({
|
|
105
|
+
type,
|
|
106
|
+
data: serializable(data),
|
|
107
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
108
|
+
})}
|
|
109
|
+
`;
|
|
110
|
+
return new Promise((resolveWrite, rejectWrite) => {
|
|
111
|
+
process.stdout.write(line, (error) => {
|
|
112
|
+
if (error) rejectWrite(error);
|
|
113
|
+
else resolveWrite();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function executionSubscriber() {
|
|
118
|
+
return {
|
|
119
|
+
async record(eventType, data) {
|
|
120
|
+
await writeAgentEvent(eventType, data);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
async function resolveAgentValue(definition, value, context, field) {
|
|
125
|
+
context.controls.emit({
|
|
126
|
+
type: "foundry.assembly.resolve.start",
|
|
127
|
+
data: { field }
|
|
128
|
+
});
|
|
129
|
+
const resolved = await resolveResolvable(
|
|
130
|
+
typeof value === "function" ? value(definition, context) : value
|
|
131
|
+
);
|
|
132
|
+
context.controls.emit({
|
|
133
|
+
type: "foundry.assembly.resolve.complete",
|
|
134
|
+
data: {
|
|
135
|
+
field,
|
|
136
|
+
count: Array.isArray(resolved) ? resolved.length : void 0
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return resolved;
|
|
140
|
+
}
|
|
141
|
+
var HANDLER_ONLY_MODEL = {
|
|
142
|
+
name: "foundry-handler",
|
|
143
|
+
setSystemPrompt: () => void 0,
|
|
144
|
+
prompt: async () => {
|
|
145
|
+
throw new Error(
|
|
146
|
+
"This is a model-free Foundry agent. Define run/spawn or configure a model."
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
async function loadRuntimeApplication() {
|
|
151
|
+
const path = process.env[FOUNDRY_APPLICATION_ENV];
|
|
152
|
+
if (!path) return EMPTY_FOUNDRY_APPLICATION;
|
|
153
|
+
const imported = await import(pathToFileURL(path).href);
|
|
154
|
+
if (!isFoundryApplication(imported.default)) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`${path} must default-export defineApplication(...) for installation resolution.`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return imported.default;
|
|
160
|
+
}
|
|
161
|
+
async function executeSpawned(spawned, message, signal2) {
|
|
162
|
+
if (spawned && typeof spawned === "object" && "processRequest" in spawned && typeof spawned.processRequest === "function") {
|
|
163
|
+
return extractOutput(
|
|
164
|
+
await spawned.processRequest(toGloveRequestInput(message), signal2)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (spawned && typeof spawned === "object" && "run" in spawned && typeof spawned.run === "function") {
|
|
168
|
+
return spawned.run(
|
|
169
|
+
message,
|
|
170
|
+
signal2
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return spawned;
|
|
174
|
+
}
|
|
175
|
+
async function runDefinition(definition, id, runtimeValue) {
|
|
176
|
+
const envelope = runtimeValue;
|
|
177
|
+
const request = envelope.request;
|
|
178
|
+
const input = request;
|
|
179
|
+
const runId = process.env.STATION_SIGNAL_RUN_ID ?? "unknown";
|
|
180
|
+
const subscriber = executionSubscriber();
|
|
181
|
+
const abortController = new AbortController();
|
|
182
|
+
const onTerminate = () => abortController.abort();
|
|
183
|
+
process.once("SIGTERM", onTerminate);
|
|
184
|
+
let disposeSurfaces;
|
|
185
|
+
const cleanups = [];
|
|
186
|
+
const application = await loadRuntimeApplication();
|
|
187
|
+
const storeFactory = definition.store ?? application.conversationStore;
|
|
188
|
+
const store = storeFactory ? await storeFactory({
|
|
189
|
+
definitionId: id,
|
|
190
|
+
agentId: request.agentId,
|
|
191
|
+
conversationId: request.conversationId,
|
|
192
|
+
workspaceId: request.workspaceId
|
|
193
|
+
}) : null;
|
|
194
|
+
const runtimeStore = store ? withFoundryPermissions(store) : null;
|
|
195
|
+
const history = Object.freeze(
|
|
196
|
+
(runtimeStore ? await runtimeStore.getMessages() : []).map(freezeGloveMessage)
|
|
197
|
+
);
|
|
198
|
+
const message = toGloveMessage(request.message);
|
|
199
|
+
const messages = Object.freeze([...history, message]);
|
|
200
|
+
const controls = {
|
|
201
|
+
signal: abortController.signal,
|
|
202
|
+
commands: [],
|
|
203
|
+
emit: (event) => {
|
|
204
|
+
void writeAgentEvent(event.type, event.data).catch((cause) => {
|
|
205
|
+
process.stderr.write(
|
|
206
|
+
`Foundry failed to forward agent event: ${cause instanceof Error ? cause.message : String(cause)}
|
|
207
|
+
`
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
try {
|
|
213
|
+
const registry = definition.components ?? {
|
|
214
|
+
capabilities: EMPTY_CAPABILITY_REGISTRY,
|
|
215
|
+
native: { layers: Object.freeze([]), subscribers: Object.freeze([]) }
|
|
216
|
+
};
|
|
217
|
+
const installations = envelope.agent.installations;
|
|
218
|
+
const data = application.data ?? new MemoryFoundryDataAdapter();
|
|
219
|
+
const initialAssemblyContext = {
|
|
220
|
+
definitionId: id,
|
|
221
|
+
agentId: request.agentId,
|
|
222
|
+
conversationId: request.conversationId,
|
|
223
|
+
workspaceId: request.workspaceId,
|
|
224
|
+
name: internalAgentName(id),
|
|
225
|
+
runId,
|
|
226
|
+
mode: "agent",
|
|
227
|
+
request,
|
|
228
|
+
agentInstance: envelope.agent,
|
|
229
|
+
conversation: envelope.conversation,
|
|
230
|
+
activations: Object.freeze([...envelope.activations ?? []]),
|
|
231
|
+
data,
|
|
232
|
+
input,
|
|
233
|
+
message,
|
|
234
|
+
messageInput: request.message,
|
|
235
|
+
messageText: message.text,
|
|
236
|
+
history,
|
|
237
|
+
messages,
|
|
238
|
+
installations,
|
|
239
|
+
store: runtimeStore,
|
|
240
|
+
subscriber,
|
|
241
|
+
controls
|
|
242
|
+
};
|
|
243
|
+
const assemblyContext = initialAssemblyContext;
|
|
244
|
+
const resolveOptional = async (field, value, fallback) => value === void 0 ? fallback : resolveAgentValue(
|
|
245
|
+
definition,
|
|
246
|
+
value,
|
|
247
|
+
assemblyContext,
|
|
248
|
+
field
|
|
249
|
+
);
|
|
250
|
+
const [model, systemPrompt, displayManager, compactionLimit, compactionInstructions, maxTurns] = await Promise.all([
|
|
251
|
+
resolveOptional("model", definition.model, HANDLER_ONLY_MODEL),
|
|
252
|
+
resolveOptional("systemPrompt", definition.systemPrompt, ""),
|
|
253
|
+
resolveOptional(
|
|
254
|
+
"displayManager",
|
|
255
|
+
definition.displayManager,
|
|
256
|
+
new FoundryApprovalDisplayManager({
|
|
257
|
+
directory: process.env[FOUNDRY_APPROVAL_DIRECTORY_ENV],
|
|
258
|
+
runId,
|
|
259
|
+
definitionId: id,
|
|
260
|
+
agentId: request.agentId,
|
|
261
|
+
conversationId: request.conversationId,
|
|
262
|
+
workspaceId: request.workspaceId,
|
|
263
|
+
signal: abortController.signal,
|
|
264
|
+
emit: controls.emit
|
|
265
|
+
})
|
|
266
|
+
),
|
|
267
|
+
resolveOptional("compactionLimit", definition.compactionLimit, void 0),
|
|
268
|
+
resolveOptional("compactionInstructions", definition.compactionInstructions, "Preserve goals, decisions, unresolved work, tool results, and pending inbox items."),
|
|
269
|
+
resolveOptional("maxTurns", definition.maxTurns, void 0)
|
|
270
|
+
]);
|
|
271
|
+
const base = new Glove({
|
|
272
|
+
...runtimeStore ? { store: runtimeStore } : {},
|
|
273
|
+
model,
|
|
274
|
+
displayManager,
|
|
275
|
+
systemPrompt,
|
|
276
|
+
serverMode: definition.serverMode ?? true,
|
|
277
|
+
...definition.maxRetries !== void 0 ? { maxRetries: definition.maxRetries } : {},
|
|
278
|
+
...definition.maxConsecutiveErrors !== void 0 ? { maxConsecutiveErrors: definition.maxConsecutiveErrors } : {},
|
|
279
|
+
compaction_config: {
|
|
280
|
+
compaction_instructions: compactionInstructions,
|
|
281
|
+
...maxTurns !== void 0 ? { max_turns: maxTurns } : {},
|
|
282
|
+
...compactionLimit !== void 0 ? { compaction_context_limit: compactionLimit } : {}
|
|
283
|
+
},
|
|
284
|
+
...definition.enableToolResultSummary !== void 0 ? { enableToolResultSummary: definition.enableToolResultSummary } : {}
|
|
285
|
+
}).build();
|
|
286
|
+
base.addSubscriber(subscriber);
|
|
287
|
+
const [tools, hooks, skills, subagents, memory, inboxItems, layers, subscribers, calls, playbooks, schedules, mesh, workingEnvironment, repl] = await Promise.all([
|
|
288
|
+
resolveOptional("tools", definition.tools, []),
|
|
289
|
+
resolveOptional("hooks", definition.hooks, []),
|
|
290
|
+
resolveOptional("skills", definition.skills, []),
|
|
291
|
+
resolveOptional("subagents", definition.subagents, []),
|
|
292
|
+
resolveOptional("memory", definition.memory, []),
|
|
293
|
+
definition.inboxes ? resolveAgentValue(
|
|
294
|
+
definition,
|
|
295
|
+
definition.inboxes,
|
|
296
|
+
assemblyContext,
|
|
297
|
+
"inboxes"
|
|
298
|
+
) : Promise.resolve([]),
|
|
299
|
+
resolveOptional("layers", definition.layers, []),
|
|
300
|
+
resolveOptional("subscribers", definition.subscribers, []),
|
|
301
|
+
resolveOptional("calls", definition.calls, []),
|
|
302
|
+
resolveOptional("playbooks", definition.playbooks, []),
|
|
303
|
+
resolveOptional("schedules", definition.schedules, []),
|
|
304
|
+
resolveOptional("mesh", definition.mesh, void 0),
|
|
305
|
+
resolveOptional("workingEnvironment", definition.workingEnvironment, void 0),
|
|
306
|
+
resolveOptional("repl", definition.repl, void 0)
|
|
307
|
+
]);
|
|
308
|
+
const playbookNames = /* @__PURE__ */ new Set();
|
|
309
|
+
const desiredPlaybooks = definition.playbooks === void 0 ? envelope.agent.playbooks : playbooks.map((playbook) => {
|
|
310
|
+
if (playbook[FOUNDRY_COMPOSED_PLAYBOOK_BRAND] !== true) {
|
|
311
|
+
throw new Error("Agent playbooks must be created at runtime with composePlaybook(...).");
|
|
312
|
+
}
|
|
313
|
+
if (playbookNames.has(playbook.name)) {
|
|
314
|
+
throw new Error(`Duplicate composed playbook name "${playbook.name}".`);
|
|
315
|
+
}
|
|
316
|
+
playbookNames.add(playbook.name);
|
|
317
|
+
const revision = composedPlaybookRevision(playbook);
|
|
318
|
+
return materializeComposedPlaybook(
|
|
319
|
+
playbook,
|
|
320
|
+
agentPlaybookId(id, request.agentId, playbook.name),
|
|
321
|
+
revision
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
if (definition.playbooks !== void 0) {
|
|
325
|
+
const playbookSync = {
|
|
326
|
+
id: `playbook_sync_${runId}`,
|
|
327
|
+
type: "playbook.sync",
|
|
328
|
+
definitionId: id,
|
|
329
|
+
agentId: request.agentId,
|
|
330
|
+
conversationId: request.conversationId,
|
|
331
|
+
workspaceId: request.workspaceId,
|
|
332
|
+
playbooks: desiredPlaybooks
|
|
333
|
+
};
|
|
334
|
+
controls.commands.push(playbookSync);
|
|
335
|
+
controls.emit({ type: FOUNDRY_CORE_COMMAND_EVENT, data: playbookSync });
|
|
336
|
+
controls.emit({
|
|
337
|
+
type: "foundry.definition.playbooks.composed",
|
|
338
|
+
data: { count: desiredPlaybooks.length, names: desiredPlaybooks.map((item) => item.playbookName) }
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
const scheduleNames = /* @__PURE__ */ new Set();
|
|
342
|
+
const desiredSchedules = schedules.map((schedule) => {
|
|
343
|
+
if (!isFoundrySchedule(schedule)) {
|
|
344
|
+
throw new Error("Agent schedules must be created with defineSchedule(...).");
|
|
345
|
+
}
|
|
346
|
+
if (scheduleNames.has(schedule.name)) {
|
|
347
|
+
throw new Error(`Duplicate agent schedule name "${schedule.name}".`);
|
|
348
|
+
}
|
|
349
|
+
scheduleNames.add(schedule.name);
|
|
350
|
+
return {
|
|
351
|
+
id: agentScheduleActivationId(id, request.agentId, schedule.name),
|
|
352
|
+
name: schedule.name,
|
|
353
|
+
revision: agentScheduleRevision(schedule),
|
|
354
|
+
message: schedule.message,
|
|
355
|
+
...schedule.payload !== void 0 ? { payload: schedule.payload } : {},
|
|
356
|
+
timing: normalizeScheduleTiming(schedule.timing),
|
|
357
|
+
enabled: schedule.enabled !== false
|
|
358
|
+
};
|
|
359
|
+
});
|
|
360
|
+
const scheduleSync = {
|
|
361
|
+
id: `schedule_sync_${runId}`,
|
|
362
|
+
type: "schedule.sync",
|
|
363
|
+
definitionId: id,
|
|
364
|
+
agentId: request.agentId,
|
|
365
|
+
conversationId: request.conversationId,
|
|
366
|
+
workspaceId: request.workspaceId,
|
|
367
|
+
schedules: desiredSchedules
|
|
368
|
+
};
|
|
369
|
+
controls.commands.push(scheduleSync);
|
|
370
|
+
controls.emit({ type: FOUNDRY_CORE_COMMAND_EVENT, data: scheduleSync });
|
|
371
|
+
controls.emit({
|
|
372
|
+
type: "foundry.definition.schedules.loaded",
|
|
373
|
+
data: { count: desiredSchedules.length, names: desiredSchedules.map((item) => item.name) }
|
|
374
|
+
});
|
|
375
|
+
const effectiveInstallations = [...installations];
|
|
376
|
+
for (const tool of createFoundryCoreTools(assemblyContext, desiredSchedules)) base.fold(tool);
|
|
377
|
+
const applicationTransmissionTools = createInstalledApplicationTransmissionTools(
|
|
378
|
+
assemblyContext,
|
|
379
|
+
registry.capabilities.applications,
|
|
380
|
+
effectiveInstallations,
|
|
381
|
+
desiredPlaybooks
|
|
382
|
+
);
|
|
383
|
+
for (const tool of applicationTransmissionTools) base.fold(tool);
|
|
384
|
+
if (applicationTransmissionTools.length > 0) {
|
|
385
|
+
controls.emit({
|
|
386
|
+
type: "foundry.application.transmission-tools.mounted",
|
|
387
|
+
data: {
|
|
388
|
+
tools: applicationTransmissionTools.map((tool) => tool.name)
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
for (const tool of tools) base.fold(tool);
|
|
393
|
+
for (const hook of hooks) base.defineHook(hook.name, hook.handler);
|
|
394
|
+
for (const skill of skills) base.defineSkill(skill);
|
|
395
|
+
for (const subagent of subagents) base.defineSubAgent(subagent);
|
|
396
|
+
const workbench = await mountFoundryWorkbench({
|
|
397
|
+
glove: base,
|
|
398
|
+
context: assemblyContext,
|
|
399
|
+
...workingEnvironment ? { workingEnvironment } : {},
|
|
400
|
+
...repl ? { repl } : {},
|
|
401
|
+
deferRepl: Boolean(repl?.programmaticTools)
|
|
402
|
+
});
|
|
403
|
+
cleanups.push(workbench.dispose);
|
|
404
|
+
const callByName = /* @__PURE__ */ new Map();
|
|
405
|
+
const invoke = async (name, callInput) => {
|
|
406
|
+
const call = callByName.get(name);
|
|
407
|
+
if (!call) throw new Error(`Foundry call "${name}" is not available.`);
|
|
408
|
+
const parsed = call.input.parse(callInput);
|
|
409
|
+
const value = await resolveResolvable(call.handler(parsed, callContext));
|
|
410
|
+
return call.output.parse(value);
|
|
411
|
+
};
|
|
412
|
+
const surfaceContext = {
|
|
413
|
+
definitionId: id,
|
|
414
|
+
agentId: request.agentId,
|
|
415
|
+
conversationId: request.conversationId,
|
|
416
|
+
workspaceId: request.workspaceId,
|
|
417
|
+
runId,
|
|
418
|
+
input,
|
|
419
|
+
request,
|
|
420
|
+
message,
|
|
421
|
+
messageInput: request.message,
|
|
422
|
+
messageText: message.text,
|
|
423
|
+
history,
|
|
424
|
+
messages,
|
|
425
|
+
glove: base,
|
|
426
|
+
...workbench.workingEnvironment ? { workingEnvironment: workbench.workingEnvironment, vfs: workbench.vfs } : {},
|
|
427
|
+
...workbench.repl ? { repl: workbench.repl } : {},
|
|
428
|
+
signal: abortController.signal,
|
|
429
|
+
emit: controls.emit
|
|
430
|
+
};
|
|
431
|
+
const guidance = await mountFoundryGuidance(base, assemblyContext, {
|
|
432
|
+
facts: await resolveOptional("facts", definition.facts, void 0),
|
|
433
|
+
goals: await resolveOptional("goals", definition.goals, void 0),
|
|
434
|
+
forms: await resolveOptional("forms", definition.forms, void 0),
|
|
435
|
+
contextProviders: await resolveOptional("contextProviders", definition.contextProviders, [])
|
|
436
|
+
});
|
|
437
|
+
cleanups.push(async () => {
|
|
438
|
+
try {
|
|
439
|
+
await guidance.snapshot();
|
|
440
|
+
} finally {
|
|
441
|
+
guidance.dispose();
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
const callContext = {
|
|
445
|
+
...surfaceContext,
|
|
446
|
+
...guidance.handles,
|
|
447
|
+
installations: effectiveInstallations,
|
|
448
|
+
onCleanup: (cleanup) => {
|
|
449
|
+
cleanups.push(cleanup);
|
|
450
|
+
},
|
|
451
|
+
invoke
|
|
452
|
+
};
|
|
453
|
+
for (const call of calls) {
|
|
454
|
+
if (callByName.has(call.name)) throw new Error(`Duplicate Foundry call "${call.name}".`);
|
|
455
|
+
callByName.set(call.name, call);
|
|
456
|
+
if (call.exposeToAgent ?? true) {
|
|
457
|
+
base.fold({
|
|
458
|
+
name: call.name,
|
|
459
|
+
description: call.description,
|
|
460
|
+
inputSchema: call.input,
|
|
461
|
+
async do(callInput) {
|
|
462
|
+
try {
|
|
463
|
+
return { status: "success", data: await invoke(call.name, callInput) };
|
|
464
|
+
} catch (cause) {
|
|
465
|
+
return {
|
|
466
|
+
status: "error",
|
|
467
|
+
data: null,
|
|
468
|
+
message: cause instanceof Error ? cause.message : String(cause)
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
disposeSurfaces = await mountFoundrySurfaces({
|
|
476
|
+
registry: registry.native,
|
|
477
|
+
layers,
|
|
478
|
+
subscribers,
|
|
479
|
+
context: surfaceContext
|
|
480
|
+
});
|
|
481
|
+
await Effect.runPromise(
|
|
482
|
+
installRegistry({
|
|
483
|
+
registry: registry.capabilities,
|
|
484
|
+
installations: effectiveInstallations,
|
|
485
|
+
context: {
|
|
486
|
+
definitionId: id,
|
|
487
|
+
agentId: request.agentId,
|
|
488
|
+
conversationId: request.conversationId,
|
|
489
|
+
workspaceId: request.workspaceId,
|
|
490
|
+
runId,
|
|
491
|
+
input,
|
|
492
|
+
request,
|
|
493
|
+
message,
|
|
494
|
+
messageInput: request.message,
|
|
495
|
+
messageText: message.text,
|
|
496
|
+
history,
|
|
497
|
+
messages,
|
|
498
|
+
glove: base,
|
|
499
|
+
store: base.store,
|
|
500
|
+
emit: controls.emit
|
|
501
|
+
},
|
|
502
|
+
...definition.mcpAdapter ? { mcpAdapter: definition.mcpAdapter } : {},
|
|
503
|
+
...definition.accountSessions ? { accountSessions: definition.accountSessions } : {}
|
|
504
|
+
})
|
|
505
|
+
);
|
|
506
|
+
await Effect.runPromise(
|
|
507
|
+
mountAgentDefinitionMemory({
|
|
508
|
+
registry: registry.capabilities,
|
|
509
|
+
memory,
|
|
510
|
+
context: {
|
|
511
|
+
definitionId: id,
|
|
512
|
+
agentId: request.agentId,
|
|
513
|
+
conversationId: request.conversationId,
|
|
514
|
+
workspaceId: request.workspaceId,
|
|
515
|
+
runId,
|
|
516
|
+
input,
|
|
517
|
+
request,
|
|
518
|
+
message,
|
|
519
|
+
messageInput: request.message,
|
|
520
|
+
messageText: message.text,
|
|
521
|
+
history,
|
|
522
|
+
messages,
|
|
523
|
+
glove: base,
|
|
524
|
+
store: base.store,
|
|
525
|
+
emit: controls.emit
|
|
526
|
+
}
|
|
527
|
+
})
|
|
528
|
+
);
|
|
529
|
+
if (definition.inboxes) {
|
|
530
|
+
await hydrateInbox(base.store, inboxItems, controls.emit);
|
|
531
|
+
}
|
|
532
|
+
if (mesh) {
|
|
533
|
+
await mountMesh(base, {
|
|
534
|
+
adapter: mesh.adapter,
|
|
535
|
+
identity: {
|
|
536
|
+
id: mesh.identity?.id ?? request.agentId,
|
|
537
|
+
name: mesh.identity?.name ?? request.agentId,
|
|
538
|
+
description: mesh.identity?.description ?? definition.description,
|
|
539
|
+
...mesh.identity?.capabilities ? { capabilities: mesh.identity.capabilities } : {},
|
|
540
|
+
metadata: {
|
|
541
|
+
definitionId: id,
|
|
542
|
+
conversationId: request.conversationId,
|
|
543
|
+
workspaceId: request.workspaceId,
|
|
544
|
+
...mesh.identity?.metadata ?? {}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
cleanups.push(() => mesh.adapter.unregister());
|
|
549
|
+
}
|
|
550
|
+
if (definition.configure) {
|
|
551
|
+
await resolveResolvable(definition.configure(base, callContext));
|
|
552
|
+
}
|
|
553
|
+
await workbench.mountRepl();
|
|
554
|
+
const glove = definition.build ? await resolveResolvable(definition.build(base, assemblyContext)) ?? base : base;
|
|
555
|
+
const runtimeContext = { ...callContext, glove };
|
|
556
|
+
const defaultRun = async (messageInput = assemblyContext.messageInput) => extractOutput(
|
|
557
|
+
await glove.processRequest(
|
|
558
|
+
toGloveRequestInput(messageInput),
|
|
559
|
+
abortController.signal
|
|
560
|
+
)
|
|
561
|
+
);
|
|
562
|
+
const spawn = async (messageInput = assemblyContext.messageInput) => definition.spawn ? executeSpawned(
|
|
563
|
+
await resolveResolvable(definition.spawn(glove, runtimeContext, messageInput)),
|
|
564
|
+
messageInput,
|
|
565
|
+
abortController.signal
|
|
566
|
+
) : defaultRun(messageInput);
|
|
567
|
+
const handlerContext = {
|
|
568
|
+
...runtimeContext,
|
|
569
|
+
defaultRun,
|
|
570
|
+
defaultHandler: defaultRun,
|
|
571
|
+
spawn
|
|
572
|
+
};
|
|
573
|
+
const result = definition.run ? await resolveResolvable(definition.run(glove, handlerContext)) : definition.handler ? await resolveResolvable(definition.handler(handlerContext)) : definition.spawn ? await spawn() : await defaultRun();
|
|
574
|
+
await guidance.snapshot();
|
|
575
|
+
const sleep = [...controls.commands].reverse().find(
|
|
576
|
+
(command) => command.type === "sleep"
|
|
577
|
+
);
|
|
578
|
+
return {
|
|
579
|
+
status: sleep ? "suspended" : "completed",
|
|
580
|
+
value: serializable(result),
|
|
581
|
+
agentId: request.agentId,
|
|
582
|
+
conversationId: request.conversationId,
|
|
583
|
+
workspaceId: request.workspaceId,
|
|
584
|
+
...sleep && sleep.type === "sleep" ? { suspension: { commandId: sleep.id, wakeAt: sleep.wakeAt } } : {}
|
|
585
|
+
};
|
|
586
|
+
} finally {
|
|
587
|
+
const failures = [];
|
|
588
|
+
for (const cleanup of cleanups.reverse()) {
|
|
589
|
+
try {
|
|
590
|
+
await cleanup();
|
|
591
|
+
} catch (cause) {
|
|
592
|
+
failures.push(cause);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
if (disposeSurfaces) await disposeSurfaces();
|
|
597
|
+
} catch (cause) {
|
|
598
|
+
failures.push(cause);
|
|
599
|
+
}
|
|
600
|
+
process.removeListener("SIGTERM", onTerminate);
|
|
601
|
+
if (failures.length > 0) throw new AggregateError(failures, "Foundry agent cleanup failed.");
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function compileAgentDefinition(definition, route) {
|
|
605
|
+
const contentPartSchema = z.object({
|
|
606
|
+
type: z.enum(["text", "image", "video", "document"]),
|
|
607
|
+
text: z.string().optional(),
|
|
608
|
+
name: z.string().optional(),
|
|
609
|
+
source: z.object({
|
|
610
|
+
type: z.enum(["base64", "url"]),
|
|
611
|
+
media_type: z.string(),
|
|
612
|
+
data: z.string().optional(),
|
|
613
|
+
url: z.string().optional()
|
|
614
|
+
}).optional()
|
|
615
|
+
});
|
|
616
|
+
const requestSchema = z.object({
|
|
617
|
+
agentId: z.string().min(1),
|
|
618
|
+
conversationId: z.string().min(1),
|
|
619
|
+
workspaceId: z.string().min(1),
|
|
620
|
+
message: z.union([z.string(), z.array(contentPartSchema)]),
|
|
621
|
+
payload: z.unknown().optional(),
|
|
622
|
+
context: z.record(z.string(), z.unknown()).optional(),
|
|
623
|
+
source: z.object({
|
|
624
|
+
kind: z.enum(["direct", "transmission", "activation", "spawn", "background"]),
|
|
625
|
+
id: z.string().optional(),
|
|
626
|
+
provider: z.string().optional(),
|
|
627
|
+
eventId: z.string().optional(),
|
|
628
|
+
threadKey: z.string().optional()
|
|
629
|
+
}).optional()
|
|
630
|
+
});
|
|
631
|
+
const executionEnvelope = z.object({
|
|
632
|
+
[FOUNDRY_EXECUTION_MARKER]: z.literal(true),
|
|
633
|
+
request: requestSchema,
|
|
634
|
+
agent: z.object({
|
|
635
|
+
id: z.string(),
|
|
636
|
+
definitionId: z.string(),
|
|
637
|
+
workspaceId: z.string(),
|
|
638
|
+
context: z.record(z.string(), z.unknown()),
|
|
639
|
+
installations: z.array(z.object({
|
|
640
|
+
kind: z.enum(["tool", "application", "mcp"]),
|
|
641
|
+
id: z.string(),
|
|
642
|
+
config: z.unknown().optional()
|
|
643
|
+
})),
|
|
644
|
+
playbooks: z.array(z.object({
|
|
645
|
+
id: z.string(),
|
|
646
|
+
transmissionId: z.string(),
|
|
647
|
+
enabled: z.boolean().optional(),
|
|
648
|
+
match: z.object({
|
|
649
|
+
event: z.string().optional(),
|
|
650
|
+
routeIds: z.array(z.string()).optional(),
|
|
651
|
+
predicate: z.object({
|
|
652
|
+
name: z.string(),
|
|
653
|
+
parameters: z.record(z.string(), z.unknown()).optional()
|
|
654
|
+
}).optional()
|
|
655
|
+
}).optional(),
|
|
656
|
+
directives: z.array(z.object({
|
|
657
|
+
action: z.string(),
|
|
658
|
+
instruction: z.string(),
|
|
659
|
+
parameters: z.record(z.string(), z.unknown()).optional()
|
|
660
|
+
})),
|
|
661
|
+
applications: z.array(z.string()).optional(),
|
|
662
|
+
outbound: z.array(z.object({
|
|
663
|
+
routeId: z.string(),
|
|
664
|
+
applicationId: z.string().optional(),
|
|
665
|
+
event: z.string().optional(),
|
|
666
|
+
accountId: z.string().optional(),
|
|
667
|
+
applicationAccountId: z.string().optional(),
|
|
668
|
+
instruction: z.string().optional()
|
|
669
|
+
})).optional(),
|
|
670
|
+
serialization: z.record(z.string(), z.unknown()).optional(),
|
|
671
|
+
origin: z.enum(["agent-definition", "instance"]).optional(),
|
|
672
|
+
playbookName: z.string().optional(),
|
|
673
|
+
definitionRevision: z.string().optional()
|
|
674
|
+
})),
|
|
675
|
+
createdAt: z.string(),
|
|
676
|
+
updatedAt: z.string()
|
|
677
|
+
}),
|
|
678
|
+
conversation: z.object({
|
|
679
|
+
id: z.string(),
|
|
680
|
+
agentId: z.string(),
|
|
681
|
+
workspaceId: z.string(),
|
|
682
|
+
title: z.string().optional(),
|
|
683
|
+
context: z.record(z.string(), z.unknown()),
|
|
684
|
+
createdAt: z.string(),
|
|
685
|
+
updatedAt: z.string()
|
|
686
|
+
}),
|
|
687
|
+
activations: z.array(z.object({
|
|
688
|
+
id: z.string(),
|
|
689
|
+
kind: z.enum(["scheduled", "sleep"]),
|
|
690
|
+
definitionId: z.string(),
|
|
691
|
+
agentId: z.string(),
|
|
692
|
+
conversationId: z.string(),
|
|
693
|
+
workspaceId: z.string(),
|
|
694
|
+
message: z.string(),
|
|
695
|
+
payload: z.unknown().optional(),
|
|
696
|
+
timing: z.union([
|
|
697
|
+
z.object({ kind: z.literal("at"), at: z.string() }),
|
|
698
|
+
z.object({ kind: z.literal("every"), intervalMs: z.number() }),
|
|
699
|
+
z.object({ kind: z.literal("cron"), expression: z.string(), timezone: z.string() })
|
|
700
|
+
]),
|
|
701
|
+
origin: z.enum(["agent-definition", "agent-tool"]),
|
|
702
|
+
scheduleName: z.string().optional(),
|
|
703
|
+
definitionRevision: z.string().optional(),
|
|
704
|
+
status: z.enum(["pending", "active", "paused", "completed", "cancelled"]),
|
|
705
|
+
createdByRunId: z.string(),
|
|
706
|
+
lastRunId: z.string().optional(),
|
|
707
|
+
createdAt: z.string(),
|
|
708
|
+
updatedAt: z.string()
|
|
709
|
+
})).default([])
|
|
710
|
+
});
|
|
711
|
+
let builder = signal(internalAgentName(route)).input(executionEnvelope).output(z.object({
|
|
712
|
+
status: z.enum(["completed", "suspended"]),
|
|
713
|
+
value: z.unknown(),
|
|
714
|
+
agentId: z.string(),
|
|
715
|
+
conversationId: z.string(),
|
|
716
|
+
workspaceId: z.string(),
|
|
717
|
+
suspension: z.object({ commandId: z.string(), wakeAt: z.string() }).optional()
|
|
718
|
+
}));
|
|
719
|
+
const built = builder.run(
|
|
720
|
+
(value) => runDefinition(definition, route, value)
|
|
721
|
+
);
|
|
722
|
+
return built;
|
|
723
|
+
}
|
|
724
|
+
function compileAgentModule(route, module) {
|
|
725
|
+
return compileAgentDefinition(defineAgentFromModule(route, module), route);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export {
|
|
729
|
+
compileAgentModule
|
|
730
|
+
};
|