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